aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--Gemfile5
-rw-r--r--Gemfile.lock39
-rw-r--r--RELEASING_RAILS.rdoc2
-rw-r--r--actionmailer/CHANGELOG.md26
-rw-r--r--actionmailer/README.rdoc2
-rw-r--r--actionmailer/lib/action_mailer.rb1
-rw-r--r--actionmailer/lib/action_mailer/base.rb16
-rw-r--r--actionmailer/lib/action_mailer/inline_preview_interceptor.rb61
-rw-r--r--actionmailer/lib/action_mailer/preview.rb2
-rw-r--r--actionmailer/lib/action_mailer/test_helper.rb2
-rw-r--r--actionmailer/test/delivery_methods_test.rb1
-rw-r--r--actionmailer/test/mail_helper_test.rb14
-rw-r--r--actionmailer/test/message_delivery_test.rb1
-rw-r--r--actionmailer/test/test_helper_test.rb11
-rw-r--r--actionpack/CHANGELOG.md17
-rw-r--r--actionpack/README.rdoc2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb30
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb3
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb13
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb6
-rw-r--r--actionpack/lib/action_controller/template_assertions.rb188
-rw-r--r--actionpack/lib/action_controller/test_case.rb195
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/routes.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb21
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb19
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb6
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb11
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb6
-rw-r--r--actionpack/test/controller/http_token_authentication_test.rb6
-rw-r--r--actionpack/test/controller/redirect_test.rb3
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb17
-rw-r--r--actionpack/test/controller/routing_test.rb8
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb16
-rw-r--r--actionpack/test/dispatch/routing/route_set_test.rb14
-rw-r--r--actionpack/test/journey/route_test.rb8
-rw-r--r--actionpack/test/journey/routes_test.rb2
-rw-r--r--actionview/CHANGELOG.md22
-rw-r--r--actionview/README.rdoc2
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb7
-rw-r--r--actionview/lib/action_view/helpers/atom_feed_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb22
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb63
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb71
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb2
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb2
-rw-r--r--actionview/test/activerecord/form_helper_activerecord_test.rb4
-rw-r--r--actionview/test/fixtures/test/_partial_name_in_local_assigns.erb1
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb8
-rw-r--r--actionview/test/template/atom_feed_helper_test.rb22
-rw-r--r--actionview/test/template/capture_helper_test.rb6
-rw-r--r--actionview/test/template/date_helper_test.rb10
-rw-r--r--actionview/test/template/render_test.rb8
-rw-r--r--actionview/test/template/translation_helper_test.rb29
-rw-r--r--actionview/test/template/url_helper_test.rb10
-rw-r--r--activejob/CHANGELOG.md15
-rw-r--r--activejob/README.md2
-rw-r--r--activejob/Rakefile4
-rw-r--r--activejob/lib/active_job/arguments.rb7
-rw-r--r--activejob/lib/active_job/core.rb5
-rw-r--r--activejob/lib/active_job/enqueuing.rb1
-rw-r--r--activejob/lib/active_job/queue_adapter.rb4
-rw-r--r--activejob/lib/active_job/queue_adapters.rb1
-rw-r--r--activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb8
-rw-r--r--activejob/lib/active_job/queue_adapters/inline_adapter.rb2
-rw-r--r--activejob/lib/active_job/queue_adapters/qu_adapter.rb8
-rw-r--r--activejob/lib/active_job/queue_adapters/que_adapter.rb8
-rw-r--r--activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb4
-rw-r--r--activejob/lib/active_job/queue_adapters/sneakers_adapter.rb2
-rw-r--r--activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb2
-rw-r--r--activejob/lib/active_job/test_helper.rb4
-rw-r--r--activejob/lib/rails/generators/job/job_generator.rb2
-rw-r--r--activejob/test/cases/test_helper_test.rb22
-rw-r--r--activejob/test/helper.rb2
-rw-r--r--activejob/test/integration/queuing_test.rb19
-rw-r--r--activejob/test/support/integration/adapters/que.rb26
-rw-r--r--activejob/test/support/integration/adapters/queue_classic.rb22
-rw-r--r--activejob/test/support/integration/adapters/sidekiq.rb59
-rw-r--r--activejob/test/support/integration/test_case_helpers.rb4
-rw-r--r--activejob/test/support/que/inline.rb6
-rw-r--r--activemodel/CHANGELOG.md16
-rw-r--r--activemodel/README.rdoc2
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb14
-rw-r--r--activemodel/lib/active_model/dirty.rb27
-rw-r--r--activemodel/lib/active_model/errors.rb2
-rw-r--r--activemodel/lib/active_model/serialization.rb2
-rw-r--r--activemodel/lib/active_model/validations.rb1
-rw-r--r--activemodel/test/cases/attribute_assignment_test.rb2
-rw-r--r--activemodel/test/cases/dirty_test.rb13
-rw-r--r--activemodel/test/cases/errors_test.rb6
-rw-r--r--activemodel/test/cases/serialization_test.rb25
-rw-r--r--activerecord/CHANGELOG.md83
-rw-r--r--activerecord/README.rdoc6
-rw-r--r--activerecord/Rakefile2
-rw-r--r--activerecord/lib/active_record/aggregations.rb2
-rw-r--r--activerecord/lib/active_record/associations.rb8
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb10
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb3
-rw-r--r--activerecord/lib/active_record/base.rb9
-rw-r--r--activerecord/lib/active_record/callbacks.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb485
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb34
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb38
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb45
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb163
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb41
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb84
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb44
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/statement_pool.rb34
-rw-r--r--activerecord/lib/active_record/connection_handling.rb2
-rw-r--r--activerecord/lib/active_record/core.rb7
-rw-r--r--activerecord/lib/active_record/enum.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb4
-rw-r--r--activerecord/lib/active_record/inheritance.rb14
-rw-r--r--activerecord/lib/active_record/migration.rb18
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb9
-rw-r--r--activerecord/lib/active_record/persistence.rb28
-rw-r--r--activerecord/lib/active_record/railtie.rb1
-rw-r--r--activerecord/lib/active_record/railties/databases.rake2
-rw-r--r--activerecord/lib/active_record/reflection.rb16
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb30
-rw-r--r--activerecord/lib/active_record/relation/merger.rb1
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb2
-rw-r--r--activerecord/lib/active_record/sanitization.rb4
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb15
-rw-r--r--activerecord/lib/active_record/secure_token.rb2
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb24
-rw-r--r--activerecord/lib/active_record/type/integer.rb9
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb9
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb37
-rw-r--r--activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb1
-rw-r--r--activerecord/test/cases/adapters/mysql/table_options_test.rb42
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb37
-rw-r--r--activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb1
-rw-r--r--activerecord/test/cases/adapters/mysql2/table_options_test.rb42
-rw-r--r--activerecord/test/cases/adapters/postgresql/collation_test.rb53
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb11
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb28
-rw-r--r--activerecord/test/cases/autosave_association_test.rb12
-rw-r--r--activerecord/test/cases/callbacks_test.rb2
-rw-r--r--activerecord/test/cases/connection_adapters/adapter_leasing_test.rb6
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb46
-rw-r--r--activerecord/test/cases/connection_management_test.rb23
-rw-r--r--activerecord/test/cases/connection_pool_test.rb187
-rw-r--r--activerecord/test/cases/disconnected_test.rb4
-rw-r--r--activerecord/test/cases/fixtures_test.rb12
-rw-r--r--activerecord/test/cases/inheritance_test.rb119
-rw-r--r--activerecord/test/cases/locking_test.rb10
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb20
-rw-r--r--activerecord/test/cases/primary_keys_test.rb8
-rw-r--r--activerecord/test/cases/relations_test.rb8
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb20
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb12
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb11
-rw-r--r--activerecord/test/cases/timestamp_test.rb11
-rw-r--r--activerecord/test/cases/type/integer_test.rb2
-rw-r--r--activerecord/test/models/company.rb3
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb11
-rw-r--r--activerecord/test/schema/schema.rb14
-rw-r--r--activesupport/CHANGELOG.md43
-rw-r--r--activesupport/README.rdoc2
-rw-r--r--activesupport/lib/active_support/callbacks.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/inquiry.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/wrap.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/module/aliasing.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/numeric.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/inquiry.rb26
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb74
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb64
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb8
-rw-r--r--activesupport/lib/active_support/json/decoding.rb10
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_currency_converter.rb2
-rw-r--r--activesupport/lib/active_support/rails.rb4
-rw-r--r--activesupport/lib/active_support/rescuable.rb2
-rw-r--r--activesupport/lib/active_support/subscriber.rb4
-rw-r--r--activesupport/lib/active_support/test_case.rb9
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb8
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb13
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb72
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb84
-rw-r--r--activesupport/test/core_ext/object/deep_dup_test.rb6
-rw-r--r--activesupport/test/core_ext/object/try_test.rb76
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb47
-rw-r--r--activesupport/test/time_travel_test.rb1
-rw-r--r--activesupport/test/time_zone_test.rb83
-rw-r--r--activesupport/test/xml_mini_test.rb1
-rw-r--r--guides/bug_report_templates/generic_gem.rb15
-rw-r--r--guides/bug_report_templates/generic_master.rb26
-rw-r--r--guides/source/action_controller_overview.md2
-rw-r--r--guides/source/action_mailer_basics.md7
-rw-r--r--guides/source/action_view_overview.md47
-rw-r--r--guides/source/active_job_basics.md2
-rw-r--r--guides/source/active_record_migrations.md4
-rw-r--r--guides/source/active_record_postgresql.md72
-rw-r--r--guides/source/active_record_querying.md11
-rw-r--r--guides/source/active_record_validations.md29
-rw-r--r--guides/source/active_support_core_extensions.md24
-rw-r--r--guides/source/api_documentation_guidelines.md3
-rw-r--r--guides/source/association_basics.md2
-rw-r--r--guides/source/caching_with_rails.md22
-rw-r--r--guides/source/command_line.md2
-rw-r--r--guides/source/configuring.md13
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md58
-rw-r--r--guides/source/debugging_rails_applications.md87
-rw-r--r--guides/source/engines.md4
-rw-r--r--guides/source/getting_started.md21
-rw-r--r--guides/source/i18n.md165
-rw-r--r--guides/source/initialization.md2
-rw-r--r--guides/source/nested_model_forms.md2
-rw-r--r--guides/source/routing.md2
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.md6
-rw-r--r--guides/source/security.md9
-rw-r--r--guides/source/testing.md50
-rw-r--r--guides/source/upgrading_ruby_on_rails.md18
-rw-r--r--railties/CHANGELOG.md35
-rw-r--r--railties/lib/rails.rb10
-rw-r--r--railties/lib/rails/application.rb5
-rw-r--r--railties/lib/rails/commands/console.rb31
-rw-r--r--railties/lib/rails/commands/console_helper.rb34
-rw-r--r--railties/lib/rails/commands/dbconsole.rb140
-rw-r--r--railties/lib/rails/engine.rb63
-rw-r--r--railties/lib/rails/generators/app_base.rb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb2
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup3
-rw-r--r--railties/lib/rails/generators/rails/controller/controller_generator.rb3
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb4
-rw-r--r--railties/lib/rails/generators/rails/resource/resource_generator.rb1
-rw-r--r--railties/lib/rails/generators/rails/scaffold/templates/scaffold.css27
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb5
-rw-r--r--railties/lib/rails/info_controller.rb2
-rw-r--r--railties/lib/rails/mailers_controller.rb24
-rw-r--r--railties/lib/rails/rack/logger.rb4
-rw-r--r--railties/lib/rails/templates/rails/mailers/email.html.erb15
-rw-r--r--railties/lib/rails/templates/rails/mailers/index.html.erb4
-rw-r--r--railties/lib/rails/templates/rails/mailers/mailer.html.erb2
-rw-r--r--railties/lib/rails/templates/rails/welcome/index.html.erb13
-rw-r--r--railties/lib/rails/test_help.rb6
-rw-r--r--railties/lib/rails/test_unit/sub_test_task.rb126
-rw-r--r--railties/test/application/assets_test.rb12
-rw-r--r--railties/test/application/bin_setup_test.rb54
-rw-r--r--railties/test/application/configuration/base_test.rb37
-rw-r--r--railties/test/application/configuration/custom_test.rb72
-rw-r--r--railties/test/application/configuration_test.rb17
-rw-r--r--railties/test/application/loading_test.rb4
-rw-r--r--railties/test/application/mailer_previews_test.rb248
-rw-r--r--railties/test/application/rake/dbs_test.rb28
-rw-r--r--railties/test/application/rake_test.rb4
-rw-r--r--railties/test/commands/dbconsole_test.rb18
-rw-r--r--railties/test/generators/app_generator_test.rb8
-rw-r--r--railties/test/generators/named_base_test.rb23
-rw-r--r--railties/test/generators/plugin_generator_test.rb2
-rw-r--r--railties/test/generators/shared_generator_tests.rb2
-rw-r--r--railties/test/generators_test.rb6
-rw-r--r--railties/test/isolation/abstract_unit.rb4
-rw-r--r--railties/test/path_generation_test.rb8
-rw-r--r--railties/test/test_info_test.rb60
278 files changed, 4158 insertions, 2020 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 421e7088cb..699b6fd2d1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,7 +8,7 @@ Ruby on Rails is a volunteer effort. We encourage you to pitch in. [Join the tea
*We only accept bug reports and pull requests on GitHub*.
-* If you have a question about how to use Ruby on Rails, please [ask the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk).
+* If you have a question about how to use Ruby on Rails, please [ask it on the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk).
* If you have a change or new feature in mind, please [suggest it on the rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code.
diff --git a/Gemfile b/Gemfile
index ddaaacdc59..78224f60ed 100644
--- a/Gemfile
+++ b/Gemfile
@@ -18,6 +18,7 @@ gem 'arel', github: 'rails/arel', branch: 'master'
gem 'mail', github: 'mikel/mail'
gem 'sprockets', '~> 3.0.0.rc.1'
+gem 'sprockets-rails', github: 'rails/sprockets-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)
@@ -46,7 +47,7 @@ group :job do
gem 'sucker_punch', require: false
gem 'delayed_job', require: false
gem 'queue_classic', require: false, platforms: :ruby
- gem 'sneakers', '0.1.1.pre', require: false
+ gem 'sneakers', require: false
gem 'que', require: false
gem 'backburner', require: false
gem 'qu-rails', github: "bkeepers/qu", branch: "master", require: false
@@ -65,7 +66,7 @@ group :test do
platforms :mri do
gem 'stackprof'
- # gem 'byebug'
+ gem 'byebug'
end
gem 'benchmark-ips'
diff --git a/Gemfile.lock b/Gemfile.lock
index 543cfaf3da..5adbf65045 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -35,6 +35,16 @@ GIT
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
+GIT
+ remote: git://github.com/rails/sprockets-rails.git
+ revision: 85b89c44ad40af3056899808475e6e4bf65c1f5a
+ branch: master
+ specs:
+ sprockets-rails (3.0.0.beta1)
+ actionpack (>= 4.0)
+ activesupport (>= 4.0)
+ sprockets (>= 3.0.0, < 4.0)
+
PATH
remote: .
specs:
@@ -104,8 +114,10 @@ GEM
beaneater (0.3.3)
benchmark-ips (2.1.1)
builder (3.2.2)
- bunny (1.1.9)
+ bunny (1.7.0)
amq-protocol (>= 1.9.2)
+ byebug (4.0.5)
+ columnize (= 0.9.0)
celluloid (0.16.0)
timers (~> 4.0.0)
coffee-rails (4.1.0)
@@ -115,6 +127,7 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.9.0)
+ columnize (0.9.0)
connection_pool (2.1.1)
dalli (2.7.2)
dante (0.1.5)
@@ -169,7 +182,7 @@ GEM
rack (>= 1.0)
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
- rails-dom-testing (1.0.5)
+ rails-dom-testing (1.0.6)
activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6.0)
rails-deprecated_sanitizer (>= 1.0.1)
@@ -211,24 +224,20 @@ GEM
rack (~> 1.4)
rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4)
- sneakers (0.1.1.pre)
- bunny (~> 1.1.3)
- serverengine
+ sneakers (1.0.4)
+ bunny (~> 1.7.0)
+ serverengine (~> 1.5.5)
thor
- thread
- sprockets (3.0.0.rc.1)
+ thread (~> 0.1.7)
+ sprockets (3.0.2)
rack (~> 1.0)
- sprockets-rails (2.2.4)
- actionpack (>= 3.0)
- activesupport (>= 3.0)
- sprockets (>= 2.8, < 4.0)
sqlite3 (1.3.10)
stackprof (0.2.7)
sucker_punch (1.3.2)
celluloid (~> 0.16.0)
thor (0.19.1)
- thread (0.1.5)
- thread_safe (0.3.4)
+ thread (0.1.7)
+ thread_safe (0.3.5)
tilt (1.4.1)
timers (4.0.1)
hitimes
@@ -258,6 +267,7 @@ DEPENDENCIES
backburner
bcrypt (~> 3.1.10)
benchmark-ips
+ byebug
coffee-rails (~> 4.1.0)
dalli (>= 2.2.1)
delayed_job
@@ -287,8 +297,9 @@ DEPENDENCIES
sdoc (~> 0.4.0)
sequel
sidekiq
- sneakers (= 0.1.1.pre)
+ sneakers
sprockets (~> 3.0.0.rc.1)
+ sprockets-rails!
sqlite3 (~> 1.3.6)
stackprof
sucker_punch
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
index 0e09234b82..a020b958e1 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -96,7 +96,7 @@ Run `rake install` to generate the gems and install them locally. Then try
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 realise it is broken.
+then realize it is broken.
=== Release the gem.
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 86ecb3ee88..acdc4c7dc9 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,29 @@
+* `assert_emails` in block form use the given number as expected value.
+ This makes the error message much easier to understand.
+
+ *Yuji Yaginuma*
+
+* Add support for inline images in mailer previews by using an interceptor
+ class to convert cid: urls in image src attributes to data urls.
+
+ *Andrew White*
+
+* Mailer preview now uses `url_for` to fix links to emails for apps running on
+ a subdirectory.
+
+ *Remo Mueller*
+
+* Mailer previews no longer crash when the `mail` method wasn't called
+ (`NullMail`).
+
+ Fixes #19849.
+
+ *Yves Senn*
+
+* Make sure labels and values line up in mailer previews.
+
+ *Yves Senn*
+
* Add `assert_enqueued_emails` and `assert_no_enqueued_emails`.
Example:
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index a4e660d621..e5c2ed8c77 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -146,7 +146,7 @@ The Base class has the full list of configuration options. Here's an example:
The latest version of Action Mailer can be installed with RubyGems:
- % [sudo] gem install actionmailer
+ % gem install actionmailer
Source code can be downloaded as part of the Rails project on GitHub
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 17d8dcc208..291a8c1e34 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -40,6 +40,7 @@ module ActionMailer
autoload :Base
autoload :DeliveryMethods
+ autoload :InlinePreviewInterceptor
autoload :MailHelper
autoload :Preview
autoload :Previews, 'action_mailer/preview'
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 6ddc4c9596..4a5229fe18 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -257,8 +257,8 @@ module ActionMailer
# <tt>ActionMailer::Base</tt> sets the following:
#
# * <tt>mime_version: "1.0"</tt>
- # * <tt>charset: "UTF-8",</tt>
- # * <tt>content_type: "text/plain",</tt>
+ # * <tt>charset: "UTF-8"</tt>
+ # * <tt>content_type: "text/plain"</tt>
# * <tt>parts_order: [ "text/plain", "text/enriched", "text/html" ]</tt>
#
# <tt>parts_order</tt> and <tt>charset</tt> are not actually valid <tt>Mail::Message</tt> header fields,
@@ -286,7 +286,7 @@ module ActionMailer
# end
#
# Note that the proc is evaluated right at the start of the mail message generation, so if you
- # set something in the defaults using a proc, and then set the same thing inside of your
+ # set something in the default using a proc, and then set the same thing inside of your
# mailer method, it will get over written by the mailer method.
#
# It is also possible to set these default options that will be used in all mailers through
@@ -319,8 +319,9 @@ module ActionMailer
# callbacks in the same manner that you would use callbacks in classes that
# inherit from <tt>ActionController::Base</tt>.
#
- # Note that unless you have a specific reason to do so, you should prefer using before_action
- # rather than after_action in your Action Mailer classes so that headers are parsed properly.
+ # Note that unless you have a specific reason to do so, you should prefer
+ # using <tt>before_action</tt> rather than <tt>after_action</tt> in your
+ # Action Mailer classes so that headers are parsed properly.
#
# = Previewing emails
#
@@ -379,8 +380,8 @@ module ActionMailer
# * <tt>:password</tt> - If your mail server requires authentication, set the password in this setting.
# * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the
# authentication type here.
- # This is a symbol and one of <tt>:plain</tt> (will send the password in the clear), <tt>:login</tt> (will
- # send password Base64 encoded) or <tt>:cram_md5</tt> (combines a Challenge/Response mechanism to exchange
+ # This is a symbol and one of <tt>:plain</tt> (will send the password Base64 encoded), <tt>:login</tt> (will
+ # send the password Base64 encoded) or <tt>:cram_md5</tt> (combines a Challenge/Response mechanism to exchange
# information and a cryptographic Message Digest 5 algorithm to hash important information)
# * <tt>:enable_starttls_auto</tt> - Detects if STARTTLS is enabled in your SMTP server and starts
# to use it. Defaults to <tt>true</tt>.
@@ -596,6 +597,7 @@ module ActionMailer
class NullMail #:nodoc:
def body; '' end
+ def header; {} end
def respond_to?(string, include_all=false)
true
diff --git a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
new file mode 100644
index 0000000000..dea5e52f0c
--- /dev/null
+++ b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
@@ -0,0 +1,61 @@
+require 'base64'
+
+module ActionMailer
+ # Implements a mailer preview interceptor that converts image tag src attributes
+ # that use inline cid: style urls to data: style urls so that they are visible
+ # when previewing a HTML email in a web browser.
+ #
+ # This interceptor is enabled by default, to remove it delete it from the
+ # <tt>ActionMailer::Base.preview_interceptors</tt> array:
+ #
+ # ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor)
+ #
+ class InlinePreviewInterceptor
+ PATTERN = /src=(?:"cid:[^"]+"|'cid:[^']+')/i
+
+ include Base64
+
+ def self.previewing_email(message) #:nodoc:
+ new(message).transform!
+ end
+
+ def initialize(message) #:nodoc:
+ @message = message
+ end
+
+ def transform! #:nodoc:
+ return message if html_part.blank?
+
+ html_source.gsub!(PATTERN) do |match|
+ if part = find_part(match[9..-2])
+ %[src="#{data_url(part)}"]
+ else
+ match
+ end
+ end
+
+ message
+ end
+
+ private
+ def message
+ @message
+ end
+
+ def html_part
+ @html_part ||= message.html_part
+ end
+
+ def html_source
+ html_part.body.raw_source
+ end
+
+ def data_url(part)
+ "data:#{part.mime_type};base64,#{strict_encode64(part.body.raw_source)}"
+ end
+
+ def find_part(cid)
+ message.all_parts.find{ |p| p.attachment? && p.cid == cid }
+ end
+ end
+end
diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb
index 44cf6665ba..25ad7ee721 100644
--- a/actionmailer/lib/action_mailer/preview.rb
+++ b/actionmailer/lib/action_mailer/preview.rb
@@ -21,7 +21,7 @@ module ActionMailer
# :nodoc:
mattr_accessor :preview_interceptors, instance_writer: false
- self.preview_interceptors = []
+ self.preview_interceptors = [ActionMailer::InlinePreviewInterceptor]
end
module ClassMethods
diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb
index 524e6e3af1..4d03a616d2 100644
--- a/actionmailer/lib/action_mailer/test_helper.rb
+++ b/actionmailer/lib/action_mailer/test_helper.rb
@@ -34,7 +34,7 @@ module ActionMailer
original_count = ActionMailer::Base.deliveries.size
yield
new_count = ActionMailer::Base.deliveries.size
- assert_equal original_count + number, new_count, "#{number} emails expected, but #{new_count - original_count} were sent"
+ assert_equal number, new_count - original_count, "#{number} emails expected, but #{new_count - original_count} were sent"
else
assert_equal number, ActionMailer::Base.deliveries.size
end
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index 2e4e019bf7..78507ce7dc 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'mail'
class MyCustomDelivery
end
diff --git a/actionmailer/test/mail_helper_test.rb b/actionmailer/test/mail_helper_test.rb
index 24ccaab8df..ff6b25b0c7 100644
--- a/actionmailer/test/mail_helper_test.rb
+++ b/actionmailer/test/mail_helper_test.rb
@@ -59,6 +59,12 @@ The second
end
end
+ def use_cache
+ mail_with_defaults do |format|
+ format.html { render(inline: "<% cache(:foo) do %>Greetings from a cache helper block<% end %>") }
+ end
+ end
+
protected
def mail_with_defaults(&block)
@@ -107,5 +113,11 @@ class MailerHelperTest < ActionMailer::TestCase
TEXT
assert_equal expected.gsub("\n", "\r\n"), mail.body.encoded
end
-end
+ def test_use_cache
+ assert_nothing_raised do
+ mail = HelperMailer.use_cache
+ assert_equal "Greetings from a cache helper block", mail.body.encoded
+ end
+ end
+end
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index e4dd269494..aaa3f218b5 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -2,7 +2,6 @@ require 'abstract_unit'
require 'active_job'
require 'minitest/mock'
require 'mailers/delayed_mailer'
-require 'active_support/core_ext/numeric/time'
class MessageDeliveryTest < ActiveSupport::TestCase
include ActiveJob::TestHelper
diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb
index 089933e245..0a4bc75d3e 100644
--- a/actionmailer/test/test_helper_test.rb
+++ b/actionmailer/test/test_helper_test.rb
@@ -112,6 +112,17 @@ class TestHelperMailerTest < ActionMailer::TestCase
assert_match(/1 .* but 2/, error.message)
end
+ def test_assert_emails_message
+ TestHelperMailer.test.deliver_now
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_emails 2 do
+ TestHelperMailer.test.deliver_now
+ end
+ end
+ assert_match "Expected: 2", error.message
+ assert_match "Actual: 1", error.message
+ end
+
def test_assert_no_emails_failure
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_no_emails do
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index a5e551c78e..c27ef2dc5f 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,10 @@
+* Fix `rake routes` not showing the right format when
+ nesting multiple routes.
+
+ See #18373.
+
+ *Ravil Bayramgalin*
+
* Add ability to override default form builder for a controller.
class AdminController < ApplicationController
@@ -35,11 +42,11 @@
*Kohei Suzuki*
-* Fix handling of empty X_FORWARDED_HOST header in raw_host_with_port
+* Fix handling of empty `X_FORWARDED_HOST` header in `raw_host_with_port`.
- Previously, an empty X_FORWARDED_HOST header would cause
- Actiondispatch::Http:URL.raw_host_with_port to return nil, causing
- Actiondispatch::Http:URL.host to raise a NoMethodError.
+ Previously, an empty `X_FORWARDED_HOST` header would cause
+ `Actiondispatch::Http:URL.raw_host_with_port` to return `nil`, causing
+ `Actiondispatch::Http:URL.host` to raise a `NoMethodError`.
*Adam Forsyth*
@@ -274,7 +281,7 @@
* Ensure `append_info_to_payload` is called even if an exception is raised.
- Fixes an issue where when an exception is raised in the request the additonal
+ Fixes an issue where when an exception is raised in the request the additional
payload data is not available.
See:
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 02a24a7412..44c980b070 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -28,7 +28,7 @@ can be used outside of Rails.
The latest version of Action Pack can be installed with RubyGems:
- % [sudo] gem install actionpack
+ % gem install actionpack
Source code can be downloaded as part of the Rails project on GitHub
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 909ed19a49..32c3c9652f 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -74,16 +74,16 @@ module ActionController
end
end
- def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
- authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
+ def authenticate_or_request_with_http_basic(realm = "Application", message = nil, &login_procedure)
+ authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm, message)
end
def authenticate_with_http_basic(&login_procedure)
HttpAuthentication::Basic.authenticate(request, &login_procedure)
end
- def request_http_basic_authentication(realm = "Application")
- HttpAuthentication::Basic.authentication_request(self, realm)
+ def request_http_basic_authentication(realm = "Application", message = nil)
+ HttpAuthentication::Basic.authentication_request(self, realm, message)
end
end
@@ -117,10 +117,11 @@ module ActionController
"Basic #{::Base64.strict_encode64("#{user_name}:#{password}")}"
end
- def authentication_request(controller, realm)
+ def authentication_request(controller, realm, message)
+ message ||= "HTTP Basic: Access denied.\n"
controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.tr('"'.freeze, "".freeze)}")
controller.status = 401
- controller.response_body = "HTTP Basic: Access denied.\n"
+ controller.response_body = message
end
end
@@ -170,8 +171,8 @@ module ActionController
extend self
module ControllerMethods
- def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure)
- authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm)
+ def authenticate_or_request_with_http_digest(realm = "Application", message = nil, &password_procedure)
+ authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm, message)
end
# Authenticate with HTTP Digest, returns true or false
@@ -401,16 +402,16 @@ module ActionController
extend self
module ControllerMethods
- def authenticate_or_request_with_http_token(realm = "Application", &login_procedure)
- authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm)
+ def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure)
+ authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message)
end
def authenticate_with_http_token(&login_procedure)
Token.authenticate(self, &login_procedure)
end
- def request_http_token_authentication(realm = "Application")
- Token.authentication_request(self, realm)
+ def request_http_token_authentication(realm = "Application", message = nil)
+ Token.authentication_request(self, realm, message)
end
end
@@ -498,9 +499,10 @@ module ActionController
# realm - String realm to use in the header.
#
# Returns nothing.
- def authentication_request(controller, realm)
+ def authentication_request(controller, realm, message = nil)
+ message ||= "HTTP Token: Access denied.\n"
controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"'.freeze, "".freeze)}")
- controller.__send__ :render, :text => "HTTP Token: Access denied.\n", :status => :unauthorized
+ controller.__send__ :render, :text => message, :status => :unauthorized
end
end
end
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 0a04848eba..b7cd2b1a59 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -8,8 +8,7 @@ module ActionController
# POST requests without having to specify any root elements.
#
# This functionality is enabled in +config/initializers/wrap_parameters.rb+
- # and can be customized. If you are upgrading to \Rails 3.1, this file will
- # need to be created for the functionality to be enabled.
+ # and can be customized.
#
# You could also turn it on per controller by setting the format array to
# a non-empty array:
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 663a969f72..356493bde6 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -15,11 +15,11 @@ module ActionController #:nodoc:
# access. When a request reaches your application, \Rails verifies the received
# token with the token in the session. All requests are checked except GET requests
# as these should be idempotent. Keep in mind that all session-oriented requests
- # should be CSRF protected, including Javascript and HTML requests.
+ # should be CSRF protected, including JavaScript and HTML requests.
#
- # Since HTML and Javascript requests are typically made from the browser, we
+ # Since HTML and JavaScript requests are typically made from the browser, we
# need to ensure to verify request authenticity for the web browser. We can
- # use session-oriented authentication for these types requests, by using
+ # use session-oriented authentication for these types of requests, by using
# the `protect_form_forgery` method in our controllers.
#
# GET requests are not protected since they don't have side effects like writing
@@ -40,7 +40,8 @@ module ActionController #:nodoc:
#
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method.
# By default <tt>protect_from_forgery</tt> protects your session with
- # <tt>:null_session</tt> method, which provides an empty session during request
+ # <tt>:null_session</tt> method, which provides an empty session
+ # during request.
#
# We may want to disable CSRF protection for APIs since they are typically
# designed to be state-less. That is, the requestion API client will handle
@@ -96,10 +97,10 @@ module ActionController #:nodoc:
# Valid Options:
#
# * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. Like <tt>only: [ :create, :create_all ]</tt>.
- # * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed proc or method reference.
+ # * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
# * <tt>:prepend</tt> - By default, the verification of the authentication token is added to the front of the
# callback chain. If you need to make the verification depend on other callbacks, like authentication methods
- # (say cookies vs oauth), this might not work for you. Pass <tt>prepend: false</tt> to just add the
+ # (say cookies vs OAuth), this might not work for you. Pass <tt>prepend: false</tt> to just add the
# verification callback in the position of the protect_from_forgery call. This means any callbacks added
# before are run first.
# * <tt>:with</tt> - Set the method to handle unverified request.
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 04401cad7b..af31de1f3a 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -110,9 +110,9 @@ module ActionController #:nodoc:
# This means that, if you have <code>yield :title</code> in your layout
# and you want to use streaming, you would have to render the whole template
# (and eventually trigger all queries) before streaming the title and all
- # assets, which kills the purpose of streaming. For this reason Rails 3.1
- # introduces a new helper called +provide+ that does the same as +content_for+
- # but tells the layout to stop searching for other entries and continue rendering.
+ # assets, which kills the purpose of streaming. For this purpose, you can use
+ # a helper called +provide+ that does the same as +content_for+ but tells the
+ # layout to stop searching for other entries and continue rendering.
#
# For instance, the template above using +provide+ would be:
#
diff --git a/actionpack/lib/action_controller/template_assertions.rb b/actionpack/lib/action_controller/template_assertions.rb
new file mode 100644
index 0000000000..304012d24d
--- /dev/null
+++ b/actionpack/lib/action_controller/template_assertions.rb
@@ -0,0 +1,188 @@
+module ActionController
+ module TemplateAssertions
+ extend ActiveSupport::Concern
+
+ included do
+ setup :setup_subscriptions
+ teardown :teardown_subscriptions
+ end
+
+ RENDER_TEMPLATE_INSTANCE_VARIABLES = %w{partials templates layouts files}.freeze
+
+ def setup_subscriptions
+ RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
+ instance_variable_set("@_#{instance_variable}", Hash.new(0))
+ end
+
+ @_subscribers = []
+
+ @_subscribers << ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
+ path = payload[:layout]
+ if path
+ @_layouts[path] += 1
+ if path =~ /^layouts\/(.*)/
+ @_layouts[$1] += 1
+ end
+ end
+ end
+
+ @_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
+ if virtual_path = payload[:virtual_path]
+ partial = virtual_path =~ /^.*\/_[^\/]*$/
+
+ if partial
+ @_partials[virtual_path] += 1
+ @_partials[virtual_path.split("/").last] += 1
+ end
+
+ @_templates[virtual_path] += 1
+ else
+ path = payload[:identifier]
+ if path
+ @_files[path] += 1
+ @_files[path.split("/").last] += 1
+ end
+ end
+ end
+ end
+
+ def teardown_subscriptions
+ @_subscribers.each do |subscriber|
+ ActiveSupport::Notifications.unsubscribe(subscriber)
+ end
+ end
+
+ def process(*args)
+ reset_template_assertion
+ super
+ end
+
+ def reset_template_assertion
+ RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
+ ivar_name = "@_#{instance_variable}"
+ if instance_variable_defined?(ivar_name)
+ instance_variable_get(ivar_name).clear
+ end
+ end
+ end
+
+ # Asserts that the request was rendered with the appropriate template file or partials.
+ #
+ # # assert that the "new" view template was rendered
+ # assert_template "new"
+ #
+ # # assert that the exact template "admin/posts/new" was rendered
+ # assert_template %r{\Aadmin/posts/new\Z}
+ #
+ # # assert that the layout 'admin' was rendered
+ # assert_template layout: 'admin'
+ # assert_template layout: 'layouts/admin'
+ # assert_template layout: :admin
+ #
+ # # assert that no layout was rendered
+ # assert_template layout: nil
+ # assert_template layout: false
+ #
+ # # assert that the "_customer" partial was rendered twice
+ # assert_template partial: '_customer', count: 2
+ #
+ # # assert that no partials were rendered
+ # assert_template partial: false
+ #
+ # # assert that a file was rendered
+ # assert_template file: "README.rdoc"
+ #
+ # # assert that no file was rendered
+ # assert_template file: nil
+ # assert_template file: false
+ #
+ # In a view test case, you can also assert that specific locals are passed
+ # to partials:
+ #
+ # # assert that the "_customer" partial was rendered with a specific object
+ # assert_template partial: '_customer', locals: { customer: @customer }
+ def assert_template(options = {}, message = nil)
+ # Force body to be read in case the template is being streamed.
+ response.body
+
+ case options
+ when NilClass, Regexp, String, Symbol
+ options = options.to_s if Symbol === options
+ rendered = @_templates
+ msg = message || sprintf("expecting <%s> but rendering with <%s>",
+ options.inspect, rendered.keys)
+ matches_template =
+ case options
+ when String
+ !options.empty? && rendered.any? do |t, num|
+ options_splited = options.split(File::SEPARATOR)
+ t_splited = t.split(File::SEPARATOR)
+ t_splited.last(options_splited.size) == options_splited
+ end
+ when Regexp
+ rendered.any? { |t,num| t.match(options) }
+ when NilClass
+ rendered.blank?
+ end
+ assert matches_template, msg
+ when Hash
+ options.assert_valid_keys(:layout, :partial, :locals, :count, :file)
+
+ if options.key?(:layout)
+ expected_layout = options[:layout]
+ msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
+ expected_layout, @_layouts.keys)
+
+ case expected_layout
+ when String, Symbol
+ assert_includes @_layouts.keys, expected_layout.to_s, msg
+ when Regexp
+ assert(@_layouts.keys.any? {|l| l =~ expected_layout }, msg)
+ when nil, false
+ assert(@_layouts.empty?, msg)
+ else
+ raise ArgumentError, "assert_template only accepts a String, Symbol, Regexp, nil or false for :layout"
+ end
+ end
+
+ if options[:file]
+ assert_includes @_files.keys, options[:file]
+ elsif options.key?(:file)
+ assert @_files.blank?, "expected no files but #{@_files.keys} was rendered"
+ end
+
+ if expected_partial = options[:partial]
+ if expected_locals = options[:locals]
+ if defined?(@_rendered_views)
+ view = expected_partial.to_s.sub(/^_/, '').sub(/\/_(?=[^\/]+\z)/, '/')
+
+ partial_was_not_rendered_msg = "expected %s to be rendered but it was not." % view
+ assert_includes @_rendered_views.rendered_views, view, partial_was_not_rendered_msg
+
+ msg = 'expecting %s to be rendered with %s but was with %s' % [expected_partial,
+ expected_locals,
+ @_rendered_views.locals_for(view)]
+ assert(@_rendered_views.view_rendered?(view, options[:locals]), msg)
+ else
+ warn "the :locals option to #assert_template is only supported in a ActionView::TestCase"
+ end
+ elsif expected_count = options[:count]
+ actual_count = @_partials[expected_partial]
+ msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)",
+ expected_partial, expected_count, actual_count)
+ assert(actual_count == expected_count.to_i, msg)
+ else
+ msg = message || sprintf("expecting partial <%s> but action rendered <%s>",
+ options[:partial], @_partials.keys)
+ assert_includes @_partials, expected_partial, msg
+ end
+ elsif options.key?(:partial)
+ assert @_partials.empty?,
+ "Expected no partials to be rendered"
+ end
+ else
+ raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil"
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 33c24999f9..acff22d565 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -2,197 +2,10 @@ require 'rack/session/abstract/id'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/hash/keys'
-
+require 'action_controller/template_assertions'
require 'rails-dom-testing'
module ActionController
- module TemplateAssertions
- extend ActiveSupport::Concern
-
- included do
- setup :setup_subscriptions
- teardown :teardown_subscriptions
- end
-
- RENDER_TEMPLATE_INSTANCE_VARIABLES = %w{partials templates layouts files}.freeze
-
- def setup_subscriptions
- RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
- instance_variable_set("@_#{instance_variable}", Hash.new(0))
- end
-
- @_subscribers = []
-
- @_subscribers << ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
- path = payload[:layout]
- if path
- @_layouts[path] += 1
- if path =~ /^layouts\/(.*)/
- @_layouts[$1] += 1
- end
- end
- end
-
- @_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
- if virtual_path = payload[:virtual_path]
- partial = virtual_path =~ /^.*\/_[^\/]*$/
-
- if partial
- @_partials[virtual_path] += 1
- @_partials[virtual_path.split("/").last] += 1
- end
-
- @_templates[virtual_path] += 1
- else
- path = payload[:identifier]
- if path
- @_files[path] += 1
- @_files[path.split("/").last] += 1
- end
- end
- end
- end
-
- def teardown_subscriptions
- @_subscribers.each do |subscriber|
- ActiveSupport::Notifications.unsubscribe(subscriber)
- end
- end
-
- def process(*args)
- reset_template_assertion
- super
- end
-
- def reset_template_assertion
- RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
- ivar_name = "@_#{instance_variable}"
- if instance_variable_defined?(ivar_name)
- instance_variable_get(ivar_name).clear
- end
- end
- end
-
- # Asserts that the request was rendered with the appropriate template file or partials.
- #
- # # assert that the "new" view template was rendered
- # assert_template "new"
- #
- # # assert that the exact template "admin/posts/new" was rendered
- # assert_template %r{\Aadmin/posts/new\Z}
- #
- # # assert that the layout 'admin' was rendered
- # assert_template layout: 'admin'
- # assert_template layout: 'layouts/admin'
- # assert_template layout: :admin
- #
- # # assert that no layout was rendered
- # assert_template layout: nil
- # assert_template layout: false
- #
- # # assert that the "_customer" partial was rendered twice
- # assert_template partial: '_customer', count: 2
- #
- # # assert that no partials were rendered
- # assert_template partial: false
- #
- # # assert that a file was rendered
- # assert_template file: "README.rdoc"
- #
- # # assert that no file was rendered
- # assert_template file: nil
- # assert_template file: false
- #
- # In a view test case, you can also assert that specific locals are passed
- # to partials:
- #
- # # assert that the "_customer" partial was rendered with a specific object
- # assert_template partial: '_customer', locals: { customer: @customer }
- def assert_template(options = {}, message = nil)
- # Force body to be read in case the template is being streamed.
- response.body
-
- case options
- when NilClass, Regexp, String, Symbol
- options = options.to_s if Symbol === options
- rendered = @_templates
- msg = message || sprintf("expecting <%s> but rendering with <%s>",
- options.inspect, rendered.keys)
- matches_template =
- case options
- when String
- !options.empty? && rendered.any? do |t, num|
- options_splited = options.split(File::SEPARATOR)
- t_splited = t.split(File::SEPARATOR)
- t_splited.last(options_splited.size) == options_splited
- end
- when Regexp
- rendered.any? { |t,num| t.match(options) }
- when NilClass
- rendered.blank?
- end
- assert matches_template, msg
- when Hash
- options.assert_valid_keys(:layout, :partial, :locals, :count, :file)
-
- if options.key?(:layout)
- expected_layout = options[:layout]
- msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
- expected_layout, @_layouts.keys)
-
- case expected_layout
- when String, Symbol
- assert_includes @_layouts.keys, expected_layout.to_s, msg
- when Regexp
- assert(@_layouts.keys.any? {|l| l =~ expected_layout }, msg)
- when nil, false
- assert(@_layouts.empty?, msg)
- else
- raise ArgumentError, "assert_template only accepts a String, Symbol, Regexp, nil or false for :layout"
- end
- end
-
- if options[:file]
- assert_includes @_files.keys, options[:file]
- elsif options.key?(:file)
- assert @_files.blank?, "expected no files but #{@_files.keys} was rendered"
- end
-
- if expected_partial = options[:partial]
- if expected_locals = options[:locals]
- if defined?(@_rendered_views)
- view = expected_partial.to_s.sub(/^_/, '').sub(/\/_(?=[^\/]+\z)/, '/')
-
- partial_was_not_rendered_msg = "expected %s to be rendered but it was not." % view
- assert_includes @_rendered_views.rendered_views, view, partial_was_not_rendered_msg
-
- msg = 'expecting %s to be rendered with %s but was with %s' % [expected_partial,
- expected_locals,
- @_rendered_views.locals_for(view)]
- assert(@_rendered_views.view_rendered?(view, options[:locals]), msg)
- else
- warn "the :locals option to #assert_template is only supported in a ActionView::TestCase"
- end
- elsif expected_count = options[:count]
- actual_count = @_partials[expected_partial]
- msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)",
- expected_partial, expected_count, actual_count)
- assert(actual_count == expected_count.to_i, msg)
- else
- msg = message || sprintf("expecting partial <%s> but action rendered <%s>",
- options[:partial], @_partials.keys)
- assert_includes @_partials, expected_partial, msg
- end
- elsif options.key?(:partial)
- assert @_partials.empty?,
- "Expected no partials to be rendered"
- end
- else
- raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil"
- end
- end
- end
-
class TestRequest < ActionDispatch::TestRequest #:nodoc:
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
DEFAULT_ENV.delete 'PATH_INFO'
@@ -604,7 +417,7 @@ module ActionController
def process(action, *args)
check_required_ivars
- if kwarg_request?(*args)
+ if kwarg_request?(args)
parameters, session, body, flash, http_method, format, xhr = args[0].values_at(:params, :session, :body, :flash, :method, :format, :xhr)
else
http_method, parameters, session, flash = args
@@ -745,7 +558,7 @@ module ActionController
private
def process_with_kwargs(http_method, action, *args)
- if kwarg_request?(*args)
+ if kwarg_request?(args)
args.first.merge!(method: http_method)
process(action, *args)
else
@@ -757,7 +570,7 @@ module ActionController
end
REQUEST_KWARGS = %i(params session flash method body xhr)
- def kwarg_request?(*args)
+ def kwarg_request?(args)
args[0].respond_to?(:keys) && (
(args[0].key?(:format) && args[0].keys.size == 1) ||
args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 2b851cc28d..3170389b36 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -16,7 +16,7 @@ module ActionDispatch
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
# => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
#
- # env["action_dispatch.parameter_filter"] = lambda do |k,v|
+ # env["action_dispatch.parameter_filter"] = -> (k, v) do
# v.reverse! if k =~ /secret/i
# end
# => reverses the value to all keys matching /secret/i
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index 4d5c18984a..4698ff8cc7 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -36,7 +36,7 @@ module ActionDispatch
def requirements # :nodoc:
# needed for rails `rake routes`
- path.requirements.merge(@defaults).delete_if { |_,v|
+ @defaults.merge(path.requirements).delete_if { |_,v|
/.+?/ == v
}
end
diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb
index a6d1980db2..6325dfa3dd 100644
--- a/actionpack/lib/action_dispatch/journey/routes.rb
+++ b/actionpack/lib/action_dispatch/journey/routes.rb
@@ -16,6 +16,10 @@ module ActionDispatch
@simulator = nil
end
+ def empty?
+ routes.empty?
+ end
+
def length
routes.length
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index b7687ca100..dd1f140051 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -79,6 +79,9 @@ module ActionDispatch
# domain: %w(.example.com .example.org) # Allow the cookie
# # for concrete domain names.
#
+ # * <tt>:tld_length</tt> - When using <tt>:domain => :all</tt>, this option can be used to explicitly
+ # set the TLD length when using a short (<= 3 character) domain that is being interpreted as part of a TLD.
+ # For example, to share cookies between user1.lvh.me and user2.lvh.me, set <tt>:tld_length</tt> to 1.
# * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
# * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
# Default is +false+.
@@ -181,7 +184,7 @@ module ActionDispatch
# to the Message{Encryptor,Verifier} allows us to handle the
# (de)serialization step within the cookie jar, which gives us the
# opportunity to detect and migrate legacy cookies.
- module VerifyAndUpgradeLegacySignedMessage
+ module VerifyAndUpgradeLegacySignedMessage # :nodoc:
def initialize(*args)
super
@legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: ActiveSupport::MessageEncryptor::NullSerializer)
@@ -392,7 +395,7 @@ module ActionDispatch
end
end
- class JsonSerializer
+ class JsonSerializer # :nodoc:
def self.load(value)
ActiveSupport::JSON.decode(value)
end
@@ -402,7 +405,7 @@ module ActionDispatch
end
end
- module SerializedCookieJars
+ module SerializedCookieJars # :nodoc:
MARSHAL_SIGNATURE = "\x04\x08".freeze
protected
@@ -454,12 +457,16 @@ module ActionDispatch
@verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
+ # Returns the value of the cookie by +name+ if it is untampered,
+ # returns +nil+ otherwise or if no such cookie exists.
def [](name)
if signed_message = @parent_jar[name]
deserialize name, verify(signed_message)
end
end
+ # Signs and sets the cookie named +name+. The second argument may be the cookie's
+ # value or a hash of options as documented above.
def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
@@ -482,8 +489,8 @@ module ActionDispatch
# UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
# secrets.secret_token and secrets.secret_key_base are both set. It reads
- # legacy cookies signed with the old dummy key generator and re-saves
- # them using the new key generator to provide a smooth upgrade path.
+ # legacy cookies signed with the old dummy key generator and signs and
+ # re-saves them using the new key generator to provide a smooth upgrade path.
class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
include VerifyAndUpgradeLegacySignedMessage
@@ -511,12 +518,16 @@ module ActionDispatch
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
+ # Returns the value of the cookie by +name+ if it is untampered,
+ # returns +nil+ otherwise or if no such cookie exists.
def [](name)
if encrypted_message = @parent_jar[name]
deserialize name, decrypt_and_verify(encrypted_message)
end
end
+ # Encrypts and sets the cookie named +name+. The second argument may be the cookie's
+ # value or a hash of options as documented above.
def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index 15b5a48535..6c7fba00cb 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -11,9 +11,9 @@ module ActionDispatch
# the response body. This is important for streaming responses such as the
# following:
#
- # self.response_body = lambda { |response, output|
+ # self.response_body = -> (response, output) do
# # code here which refers to application models
- # }
+ # end
#
# Cleanup callbacks will not be called until after the response_body lambda
# is evaluated, ensuring that it can refer to application models and other
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 9a92b690c7..bc5ef1abc9 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -3,15 +3,15 @@ require 'active_support/core_ext/uri'
module ActionDispatch
# This middleware returns a file's contents from disk in the body response.
- # When initialized it can accept an optional 'Cache-Control' header which
+ # When initialized, it can accept an optional 'Cache-Control' header, which
# will be set when a response containing a file's contents is delivered.
#
# This middleware will render the file specified in `env["PATH_INFO"]`
- # where the base path is in the +root+ directory. For example if the +root+
- # is set to `public/` then a request with `env["PATH_INFO"]` of
- # `assets/application.js` will return a response with contents of a file
+ # where the base path is in the +root+ directory. For example, if the +root+
+ # is set to `public/`, then a request with `env["PATH_INFO"]` of
+ # `assets/application.js` will return a response with the contents of a file
# located at `public/assets/application.js` if the file exists. If the file
- # does not exist a 404 "File not Found" response will be returned.
+ # does not exist, a 404 "File not Found" response will be returned.
class FileHandler
def initialize(root, cache_control)
@root = root.chomp('/')
@@ -20,6 +20,13 @@ module ActionDispatch
@file_server = ::Rack::File.new(@root, headers)
end
+
+ # Takes a path to a file. If the file is found, has valid encoding, and has
+ # correct read permissions, the return value is a URI-escaped string
+ # representing the filename. Otherwise, false is returned.
+ #
+ # Used by the `Static` class to check the existence of a valid file
+ # in the server's `public/` directory (see Static#call).
def match?(path)
path = URI.parser.unescape(path)
return false unless path.valid_encoding?
@@ -88,7 +95,7 @@ module ActionDispatch
end
# This middleware will attempt to return the contents of a file's body from
- # disk in the response. If a file is not found on disk, the request will be
+ # disk in the response. If a file is not found on disk, the request will be
# delegated to the application stack. This middleware is commonly initialized
# to serve assets from a server's `public/` directory.
#
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index c513737fc2..48c10a7d4c 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -45,7 +45,7 @@ module ActionDispatch
end
def internal?
- controller.to_s =~ %r{\Arails/(info|mailers|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}\z}
+ controller.to_s =~ %r{\Arails/(info|mailers|welcome)}
end
def engine?
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 49009a45cc..0a444ddffc 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -418,7 +418,7 @@ module ActionDispatch
# A pattern can also point to a +Rack+ endpoint i.e. anything that
# responds to +call+:
#
- # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via: :get
+ # match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get
# match 'photos/:id', to: PhotoRackApp, via: :get
# # Yes, controller actions are just rack endpoints
# match 'photos/:id', to: PhotosController.action(:show), via: :get
@@ -470,7 +470,7 @@ module ActionDispatch
# +call+ or a string representing a controller's action.
#
# match 'path', to: 'controller#action', via: :get
- # match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via: :get
+ # match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
# match 'path', to: RackApp, via: :get
#
# [:on]
@@ -899,7 +899,7 @@ module ActionDispatch
#
# Requests to routes can be constrained based on specific criteria:
#
- # constraints(lambda { |req| req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
+ # constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
# resources :iphones
# end
#
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 9390e2937a..f953429e96 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -2,6 +2,7 @@ require 'stringio'
require 'uri'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/object/try'
+require 'active_support/core_ext/string/strip'
require 'rack/test'
require 'minitest'
@@ -80,7 +81,7 @@ module ActionDispatch
#
# xhr :get, '/feed', params: { since: 201501011400 }
def xml_http_request(request_method, path, *args)
- if kwarg_request?(*args)
+ if kwarg_request?(args)
params, headers, env = args.first.values_at(:params, :headers, :env)
else
params = args[0]
@@ -290,7 +291,7 @@ module ActionDispatch
end
def process_with_kwargs(http_method, path, *args)
- if kwarg_request?(*args)
+ if kwarg_request?(args)
process(http_method, path, *args)
else
non_kwarg_request_warning if args.present?
@@ -299,7 +300,7 @@ module ActionDispatch
end
REQUEST_KWARGS = %i(params headers env xhr)
- def kwarg_request?(*args)
+ def kwarg_request?(args)
args[0].respond_to?(:keys) && args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
end
@@ -583,7 +584,7 @@ module ActionDispatch
# https!(false)
# get "/articles/all"
# assert_response :success
- # assert assigns(:articles)
+ # assert_select 'h1', 'Articles'
# end
# end
#
@@ -622,7 +623,7 @@ module ActionDispatch
# def browses_site
# get "/products/all"
# assert_response :success
- # assert assigns(:products)
+ # assert_select 'h1', 'Products'
# end
# end
#
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index 20962a90cb..10fbee7582 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -36,7 +36,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
if authenticate_with_http_basic { |username, password| username == 'pretty' && password == 'please' }
@logged_in = true
else
- request_http_basic_authentication("SuperSecret")
+ request_http_basic_authentication("SuperSecret", "Authentication Failed\n")
end
end
@@ -104,7 +104,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
get :display
assert_response :unauthorized
- assert_equal "HTTP Basic: Access denied.\n", @response.body
+ assert_equal "Authentication Failed\n", @response.body
assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate']
end
@@ -113,7 +113,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
get :display
assert_response :unauthorized
- assert_equal "HTTP Basic: Access denied.\n", @response.body
+ assert_equal "Authentication Failed\n", @response.body
assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate']
end
diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb
index a758df2ec6..2521f0322c 100644
--- a/actionpack/test/controller/http_token_authentication_test.rb
+++ b/actionpack/test/controller/http_token_authentication_test.rb
@@ -30,7 +30,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
if authenticate_with_http_token { |token, options| token == '"quote" pretty' && options[:algorithm] == 'test' }
@logged_in = true
else
- request_http_token_authentication("SuperSecret")
+ request_http_token_authentication("SuperSecret", "Authentication Failed\n")
end
end
@@ -91,7 +91,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
get :display
assert_response :unauthorized
- assert_equal "HTTP Token: Access denied.\n", @response.body
+ assert_equal "Authentication Failed\n", @response.body
assert_equal 'Token realm="SuperSecret"', @response.headers['WWW-Authenticate']
end
@@ -100,7 +100,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
get :display
assert_response :unauthorized
- assert_equal "HTTP Token: Access denied.\n", @response.body
+ assert_equal "Authentication Failed\n", @response.body
assert_equal 'Token realm="SuperSecret"', @response.headers['WWW-Authenticate']
end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index efd790de63..ef30f1ea0f 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -1,8 +1,5 @@
require 'abstract_unit'
-class WorkshopsController < ActionController::Base
-end
-
class RedirectController < ActionController::Base
# empty method not used anywhere to ensure methods like
# `status` and `location` aren't called on `redirect_to` calls
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 8887f291cf..f8cf79a257 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -12,14 +12,6 @@ module RequestForgeryProtectionActions
render :inline => "<%= button_to('New', '/') %>"
end
- def external_form
- render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => 'external_token') {} %>"
- end
-
- def external_form_without_protection
- render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => false) {} %>"
- end
-
def unsafe
render :text => 'pwn'
end
@@ -28,14 +20,6 @@ module RequestForgeryProtectionActions
render :inline => "<%= csrf_meta_tags %>"
end
- def external_form_for
- render :inline => "<%= form_for(:some_resource, :authenticity_token => 'external_token') {} %>"
- end
-
- def form_for_without_protection
- render :inline => "<%= form_for(:some_resource, :authenticity_token => false ) {} %>"
- end
-
def form_for_remote
render :inline => "<%= form_for(:some_resource, :remote => true ) {} %>"
end
@@ -70,7 +54,6 @@ module RequestForgeryProtectionActions
negotiate_same_origin
end
- def rescue_action(e) raise e end
end
# sample controllers
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 2d08987ca6..9bbfb74e72 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -8,8 +8,6 @@ class MilestonesController < ActionController::Base
alias_method :show, :index
end
-ROUTING = ActionDispatch::Routing
-
# See RFC 3986, section 3.3 for allowed path characters.
class UriReservedCharactersRoutingTest < ActiveSupport::TestCase
include RoutingTestHelpers
@@ -871,7 +869,7 @@ class RouteSetTest < ActiveSupport::TestCase
def default_route_set
@default_route_set ||= begin
- set = ROUTING::RouteSet.new
+ set = ActionDispatch::Routing::RouteSet.new
set.draw do
get '/:controller(/:action(/:id))'
end
@@ -1748,13 +1746,13 @@ class RouteSetTest < ActiveSupport::TestCase
include ActionDispatch::RoutingVerbs
- class TestSet < ROUTING::RouteSet
+ class TestSet < ActionDispatch::Routing::RouteSet
def initialize(block)
@block = block
super()
end
- class Dispatcher < ROUTING::RouteSet::Dispatcher
+ class Dispatcher < ActionDispatch::Routing::RouteSet::Dispatcher
def initialize(defaults, set, block)
super(defaults)
@block = block
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
index 3df022c64b..4047214843 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -313,6 +313,22 @@ module ActionDispatch
assert_equal ["Prefix Verb URI Pattern Controller#Action",
" GET /:controller(/:action) (?-mix:api\\/[^\\/]+)#:action"], output
end
+
+ def test_inspect_routes_shows_resources_route_when_assets_disabled
+ @set = ActionDispatch::Routing::RouteSet.new
+ app = ActiveSupport::OrderedOptions.new
+
+ Rails.stubs(:application).returns(app)
+
+ output = draw do
+ get '/cart', to: 'cart#show'
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " cart GET /cart(.:format) cart#show"
+ ], output
+ end
end
end
end
diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb
index fe52c50336..9327fe12c6 100644
--- a/actionpack/test/dispatch/routing/route_set_test.rb
+++ b/actionpack/test/dispatch/routing/route_set_test.rb
@@ -17,6 +17,16 @@ module ActionDispatch
@set = RouteSet.new
end
+ test "not being empty when route is added" do
+ assert empty?
+
+ draw do
+ get 'foo', to: SimpleApp.new('foo#index')
+ end
+
+ assert_not empty?
+ end
+
test "url helpers are added when route is added" do
draw do
get 'foo', to: SimpleApp.new('foo#index')
@@ -136,6 +146,10 @@ module ActionDispatch
def url_helpers
@set.url_helpers
end
+
+ def empty?
+ @set.empty?
+ end
end
end
end
diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb
index 21d867aca0..0980df06e8 100644
--- a/actionpack/test/journey/route_test.rb
+++ b/actionpack/test/journey/route_test.rb
@@ -25,6 +25,14 @@ module ActionDispatch
end
end
+ def test_path_requirements_override_defaults
+ strexp = Router::Strexp.build(':name', { name: /love/ }, ['/'])
+ path = Path::Pattern.new strexp
+ defaults = { name: 'tender' }
+ route = Route.new('name', nil, path, nil, 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'},
diff --git a/actionpack/test/journey/routes_test.rb b/actionpack/test/journey/routes_test.rb
index b54d961f66..2a1c1c337e 100644
--- a/actionpack/test/journey/routes_test.rb
+++ b/actionpack/test/journey/routes_test.rb
@@ -14,9 +14,11 @@ module ActionDispatch
requirements = { :hello => /world/ }
routes.add_route nil, path, requirements, {:id => nil}, {}
+ assert_not routes.empty?
assert_equal 1, routes.length
routes.clear
+ assert routes.empty?
assert_equal 0, routes.length
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 74dbff80da..201e118971 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,20 @@
+* Do not put partial name to `local_assigns` when rendering without
+ an object or a collection.
+
+ *Henrik Nygren*
+
+* Remove `:rescue_format` option for `translate` helper since it's no longer
+ supported by I18n.
+
+ *Bernard Potocki*
+
+* `translate` should handle `raise` flag correctly in case of both main and default
+ translation is missing.
+
+ Fixes #19967
+
+ *Bernard Potocki*
+
* Load the `default_form_builder` from the controller on initialization, which overrides
the global config if it is present.
@@ -14,8 +31,7 @@
*Adam Prescott*
* `translate` should accept nils as members of the `:default`
- parameter without raising a translation missing error. Fixes a
- regression introduced 362557e.
+ parameter without raising a translation missing error.
Fixes #19419
@@ -109,7 +125,7 @@
*Angelo Capilleri*
-* Allow entries without a link tag in AtomFeedHelper.
+* Allow entries without a link tag in `AtomFeedHelper`.
*Daniel Gomez de Souza*
diff --git a/actionview/README.rdoc b/actionview/README.rdoc
index 5bb62c7562..8b1f85f748 100644
--- a/actionview/README.rdoc
+++ b/actionview/README.rdoc
@@ -9,7 +9,7 @@ used to inline short Ruby snippets inside HTML), and XML Builder.
The latest version of Action View can be installed with RubyGems:
- % [sudo] gem install actionview
+ % gem install actionview
Source code can be downloaded as part of the Rails project on GitHub
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 60fc9ee1a2..e32f8e219e 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -207,6 +207,7 @@ module ActionView
# # => <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
def image_tag(source, options={})
options = options.symbolize_keys
+ check_for_image_tag_errors(options)
src = options[:src] = path_to_image(source)
@@ -325,6 +326,12 @@ module ActionView
[size, size]
end
end
+
+ def check_for_image_tag_errors(options)
+ if options[:size] && (options[:height] || options[:width])
+ raise ArgumentError, "Cannot pass a :size option with a :height or :width option"
+ end
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb
index d8be4e5678..bb1cdd0f8d 100644
--- a/actionview/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb
@@ -16,7 +16,7 @@ module ActionView
# end
#
# app/controllers/posts_controller.rb:
- # class PostsController < ApplicationController::Base
+ # class PostsController < ApplicationController
# # GET /posts.html
# # GET /posts.atom
# def index
@@ -51,7 +51,7 @@ module ActionView
# * <tt>:language</tt>: Defaults to "en-US".
# * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
# * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
- # * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}"
+ # * <tt>:id</tt>: The id for this feed. Defaults to "tag:localhost,2005:/posts", in this case.
# * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
# created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
# 2005 is used (as an "I don't care" value).
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index 0e2a5f90f4..251764a8de 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -75,7 +75,8 @@ module ActionView
# render(topics) => render("topics/topic")
# render(message.topics) => render("topics/topic")
#
- # It's not possible to derive all render calls like that, though. Here are a few examples of things that can't be derived:
+ # It's not possible to derive all render calls like that, though.
+ # Here are a few examples of things that can't be derived:
#
# render group_of_attachments
# render @project.documents.where(published: true).order('created_at')
@@ -97,19 +98,21 @@ module ActionView
# <%# Template Dependency: todolists/todolist %>
# <%= render_sortable_todolists @project.todolists %>
#
- # The pattern used to match these is /# Template Dependency: ([^ ]+)/, so it's important that you type it out just so.
+ # The pattern used to match these is /# Template Dependency: ([^ ]+)/,
+ # so it's important that you type it out just so.
# You can only declare one template dependency per line.
#
# === External dependencies
#
- # If you use a helper method, for example, inside of 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
+ # 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:
#
# <%# Helper Dependency Updated: May 6, 2012 at 6pm %>
# <%= some_helper_method(person) %>
#
- # Now all you'll have to do is change that timestamp when the helper method changes.
+ # Now all you have to do is change that timestamp when the helper method changes.
#
# === Automatic Collection Caching
#
@@ -118,7 +121,7 @@ module ActionView
# <%= render @notifications %>
# <%= render partial: 'notifications/notification', collection: @notifications %>
#
- # If the notifications/_notification partial starts with a cache call like so:
+ # If the notifications/_notification partial starts with a cache call as:
#
# <% cache notification do %>
# <%= notification.name %>
@@ -127,14 +130,15 @@ module ActionView
# The collection can then automatically use any cached renders for that
# template by reading them at once instead of one by one.
#
- # See ActionView::Template::Handlers::ERB.resource_cache_call_pattern for more
- # information on what cache calls make a template eligible for this collection caching.
+ # See ActionView::Template::Handlers::ERB.resource_cache_call_pattern for
+ # more information on what cache calls make a template eligible for this
+ # collection caching.
#
# The automatic cache multi read can be turned off like so:
#
# <%= render @notifications, cache: false %>
def cache(name = {}, options = nil, &block)
- if controller.perform_caching
+ if controller.respond_to?(:perform_caching) && controller.perform_caching
safe_concat(fragment_for(cache_fragment_name(name, options), options, &block))
else
yield
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 7fdeca5ea8..3a9acafaa2 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -1631,7 +1631,7 @@ module ActionView
# target labels for radio_button tags (where the value is used in the ID of the input tag).
#
# ==== Examples
- # label(:post, :title)
+ # label(:title)
# # => <label for="post_title">Title</label>
#
# You can localize your labels based on model and attribute names.
@@ -1644,7 +1644,7 @@ module ActionView
#
# Which then will result in
#
- # label(:post, :body)
+ # label(:body)
# # => <label for="post_body">Write your entire text here</label>
#
# Localization can also be based purely on the translation of the attribute-name
@@ -1655,21 +1655,22 @@ module ActionView
# post:
# cost: "Total cost"
#
- # label(:post, :cost)
+ # label(:cost)
# # => <label for="post_cost">Total cost</label>
#
- # label(:post, :title, "A short title")
+ # label(:title, "A short title")
# # => <label for="post_title">A short title</label>
#
- # label(:post, :title, "A short title", class: "title_label")
+ # label(:title, "A short title", class: "title_label")
# # => <label for="post_title" class="title_label">A short title</label>
#
- # label(:post, :privacy, "Public Post", value: "public")
+ # label(:privacy, "Public Post", value: "public")
# # => <label for="post_privacy_public">Public Post</label>
#
- # label(:post, :terms) do
+ # label(:terms) do
# 'Accept <a href="/terms">Terms</a>.'.html_safe
# end
+ # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
def label(method, text = nil, options = {}, &block)
@template.label(@object_name, method, text, objectify_options(options), &block)
end
@@ -1718,16 +1719,17 @@ module ActionView
# hashes instead of arrays.
#
# # Let's say that @post.validated? is 1:
- # check_box("post", "validated")
+ # check_box("validated")
# # => <input name="post[validated]" type="hidden" value="0" />
# # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
#
# # Let's say that @puppy.gooddog is "no":
- # check_box("puppy", "gooddog", {}, "yes", "no")
+ # check_box("gooddog", {}, "yes", "no")
# # => <input name="puppy[gooddog]" type="hidden" value="no" />
# # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
#
- # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no")
+ # # Let's say that @eula.accepted is "no":
+ # check_box("accepted", { class: 'eula_check' }, "yes", "no")
# # => <input name="eula[accepted]" type="hidden" value="no" />
# # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
@@ -1742,13 +1744,14 @@ module ActionView
# +options+ hash. You may pass HTML options there as well.
#
# # Let's say that @post.category returns "rails":
- # radio_button("post", "category", "rails")
- # radio_button("post", "category", "java")
+ # radio_button("category", "rails")
+ # radio_button("category", "java")
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
# # <input type="radio" id="post_category_java" name="post[category]" value="java" />
#
- # radio_button("user", "receive_newsletter", "yes")
- # radio_button("user", "receive_newsletter", "no")
+ # # Let's say that @user.category returns "no":
+ # radio_button("receive_newsletter", "yes")
+ # radio_button("receive_newsletter", "no")
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
def radio_button(method, tag_value, options = {})
@@ -1761,14 +1764,17 @@ module ActionView
# shown.
#
# ==== Examples
- # hidden_field(:signup, :pass_confirm)
- # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
+ # # Let's say that @signup.pass_confirm returns true:
+ # hidden_field(:pass_confirm)
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="true" />
#
- # hidden_field(:post, :tag_list)
- # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
+ # # Let's say that @post.tag_list returns "blog, ruby":
+ # hidden_field(:tag_list)
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="blog, ruby" />
#
- # hidden_field(:user, :token)
- # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
+ # # Let's say that @user.token returns "abcde":
+ # hidden_field(:token)
+ # # => <input type="hidden" id="user_token" name="user[token]" value="abcde" />
#
def hidden_field(method, options = {})
@emitted_hidden_id = true if method == :id
@@ -1789,19 +1795,24 @@ module ActionView
# * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations.
#
# ==== Examples
- # file_field(:user, :avatar)
+ # # Let's say that @user has avatar:
+ # file_field(:avatar)
# # => <input type="file" id="user_avatar" name="user[avatar]" />
#
- # file_field(:post, :image, :multiple => true)
- # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
+ # # Let's say that @post has image:
+ # file_field(:image, :multiple => true)
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
#
- # file_field(:post, :attached, accept: 'text/html')
+ # # Let's say that @post has attached:
+ # file_field(:attached, accept: 'text/html')
# # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
#
- # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg')
+ # # Let's say that @post has image:
+ # file_field(:image, accept: 'image/png,image/gif,image/jpeg')
# # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
#
- # file_field(:attachment, :file, class: 'file_input')
+ # # Let's say that @attachment has file:
+ # file_field(:file, class: 'file_input')
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
def file_field(method, options = {})
self.multipart = true
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 8a5928477f..d3deee0df3 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -80,7 +80,7 @@ module ActionView
#
# When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
#
- # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }})
+ # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: -> (category) { category.archived? }})
#
# If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
# <select name="post[category_id]" id="post_category_id">
@@ -410,7 +410,7 @@ module ActionView
# * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
# * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
# array of child objects representing the <tt><option></tt> tags.
- # * group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
+ # * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
# string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
# * +option_key_method+ - The name of a method which, when called on a child object of a member of
# +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index 9d7390f1fd..0615bd2e0d 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -7,34 +7,47 @@ module ActionView
module Helpers
module TranslationHelper
include TagHelper
- # Delegates to <tt>I18n#translate</tt> but also performs three additional functions.
+ # Delegates to <tt>I18n#translate</tt> but also performs three additional
+ # functions.
#
- # First, it will ensure that any thrown +MissingTranslation+ messages will be turned
- # into inline spans that:
+ # First, it will ensure that any thrown +MissingTranslation+ messages will
+ # be rendered as inline spans that:
#
- # * have a "translation-missing" class set,
- # * contain the missing key as a title attribute and
- # * a titleized version of the last key segment as a text.
+ # * Have a <tt>translation-missing</tt> class applied
+ # * Contain the missing key as the value of the +title+ attribute
+ # * Have a titleized version of the last key segment as text
#
- # E.g. the value returned for a missing translation key :"blog.post.title" will be
- # <span class="translation_missing" title="translation missing: en.blog.post.title">Title</span>.
- # This way your views will display rather reasonable strings but it will still
- # be easy to spot missing translations.
+ # For example, the value returned for the missing translation key
+ # <tt>"blog.post.title"</tt> will be:
#
- # Second, it'll scope the key by the current partial if the key starts
- # with a period. So if you call <tt>translate(".foo")</tt> from the
- # <tt>people/index.html.erb</tt> template, you'll actually be calling
- # <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
- # to translate many keys within the same partials and gives you a simple framework
- # for scoping them consistently. If you don't prepend the key with a period,
- # nothing is converted.
+ # <span
+ # class="translation_missing"
+ # title="translation missing: en.blog.post.title">Title</span>
#
- # Third, it'll mark the translation as safe HTML if the key has the suffix
- # "_html" or the last element of the key is the word "html". For example,
- # calling translate("footer_html") or translate("footer.html") will return
- # a safe HTML string that won't be escaped by other HTML helper methods. This
- # naming convention helps to identify translations that include HTML tags so that
- # you know what kind of output to expect when you call translate in a template.
+ # This allows for views to display rather reasonable strings while still
+ # giving developers a way to find missing translations.
+ #
+ # If you would prefer missing translations to raise an error, you can
+ # opt out of span-wrapping behavior globally by setting
+ # <tt>ActionView::Base.raise_on_missing_translations = true</tt> or
+ # individually by passing <tt>raise: true</tt> as an option to
+ # <tt>translate</tt>.
+ #
+ # Second, if the key starts with a period <tt>translate</tt> will scope
+ # the key by the current partial. Calling <tt>translate(".foo")</tt> from
+ # the <tt>people/index.html.erb</tt> template is equivalent to calling
+ # <tt>translate("people.index.foo")</tt>. This makes it less
+ # repetitive to translate many keys within the same partial and provides
+ # a convention to scope keys consistently.
+ #
+ # Third, the translation will be marked as <tt>html_safe</tt> if the key
+ # has the suffix "_html" or the last element of the key is "html". Calling
+ # <tt>translate("footer_html")</tt> or <tt>translate("footer.html")</tt>
+ # will return an HTML safe string that won't be escaped by other HTML
+ # helper methods. This naming convention helps to identify translations
+ # that include HTML tags so that you know what kind of output to expect
+ # when you call translate in a template and translators know which keys
+ # they can provide HTML values for.
def translate(key, options = {})
options = options.dup
has_default = options.has_key?(:default)
@@ -47,12 +60,12 @@ module ActionView
# If the user has explicitly decided to NOT raise errors, pass that option to I18n.
# Otherwise, tell I18n to raise an exception, which we rescue further in this method.
# Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default.
- if options[:raise] == false || (options.key?(:rescue_format) && options[:rescue_format].nil?)
+ if options[:raise] == false
raise_error = false
- options[:raise] = false
+ i18n_raise = false
else
- raise_error = options[:raise] || options[:rescue_format] || ActionView::Base.raise_on_missing_translations
- options[:raise] = true
+ raise_error = options[:raise] || ActionView::Base.raise_on_missing_translations
+ i18n_raise = true
end
if html_safe_translation_key?(key)
@@ -62,11 +75,11 @@ module ActionView
html_safe_options[name] = ERB::Util.html_escape(value.to_s)
end
end
- translation = I18n.translate(scope_key_by_partial(key), html_safe_options)
+ translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
translation.respond_to?(:html_safe) ? translation.html_safe : translation
else
- I18n.translate(scope_key_by_partial(key), options)
+ I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise))
end
rescue I18n::MissingTranslationData => e
if remaining_defaults.present?
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index cd151c0189..b751bca31e 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -338,7 +338,7 @@ module ActionView
end
object ||= locals[as]
- locals[as] = object
+ locals[as] = object if @has_object
content = @template.render(view, locals) do |*name|
view._layout_for(*name, &block)
diff --git a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
index b77c884e66..c8268e226e 100644
--- a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
@@ -6,7 +6,7 @@ module ActionView
included do
# Fallback cache store if Action View is used without Rails.
- # Otherwise overriden in Railtie to use Rails.cache.
+ # Otherwise overridden in Railtie to use Rails.cache.
mattr_accessor(:collection_cache) { ActiveSupport::Cache::MemoryStore.new }
end
diff --git a/actionview/test/activerecord/form_helper_activerecord_test.rb b/actionview/test/activerecord/form_helper_activerecord_test.rb
index 0a62f49f35..2769b97445 100644
--- a/actionview/test/activerecord/form_helper_activerecord_test.rb
+++ b/actionview/test/activerecord/form_helper_activerecord_test.rb
@@ -35,10 +35,6 @@ class FormHelperActiveRecordTest < ActionView::TestCase
end
end
- def _routes
- Routes
- end
-
include Routes.url_helpers
def test_nested_fields_for_with_child_index_option_override_on_a_nested_attributes_collection_association
diff --git a/actionview/test/fixtures/test/_partial_name_in_local_assigns.erb b/actionview/test/fixtures/test/_partial_name_in_local_assigns.erb
new file mode 100644
index 0000000000..28ee9f41c5
--- /dev/null
+++ b/actionview/test/fixtures/test/_partial_name_in_local_assigns.erb
@@ -0,0 +1 @@
+<%= local_assigns.has_key?(:partial_name_in_local_assigns) %> \ No newline at end of file
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index 02dce71496..6e6ce20924 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -464,6 +464,14 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal({:size => '16x10'}, options)
end
+ def test_image_tag_raises_an_error_for_competing_size_arguments
+ exception = assert_raise(ArgumentError) do
+ image_tag("gold.png", :height => "100", :width => "200", :size => "45x70")
+ end
+
+ assert_equal("Cannot pass a :size option with a :height or :width option", exception.message)
+ end
+
def test_favicon_link_tag
FaviconLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
diff --git a/actionview/test/template/atom_feed_helper_test.rb b/actionview/test/template/atom_feed_helper_test.rb
index 525d99750d..591cd71404 100644
--- a/actionview/test/template/atom_feed_helper_test.rb
+++ b/actionview/test/template/atom_feed_helper_test.rb
@@ -14,7 +14,7 @@ class ScrollsController < ActionController::Base
FEEDS["defaults"] = <<-EOT
atom_feed(:schema_date => '2008') do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll) do |entry|
@@ -31,7 +31,7 @@ class ScrollsController < ActionController::Base
FEEDS["entry_options"] = <<-EOT
atom_feed do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll, :url => "/otherstuff/" + scroll.to_param.to_s, :updated => Time.utc(2007, 1, scroll.id)) do |entry|
@@ -48,7 +48,7 @@ class ScrollsController < ActionController::Base
FEEDS["entry_type_options"] = <<-EOT
atom_feed(:schema_date => '2008') do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll, :type => 'text/xml') do |entry|
@@ -65,7 +65,7 @@ class ScrollsController < ActionController::Base
FEEDS["entry_url_false_option"] = <<-EOT
atom_feed do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll, :url => false) do |entry|
@@ -82,7 +82,7 @@ class ScrollsController < ActionController::Base
FEEDS["xml_block"] = <<-EOT
atom_feed do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
feed.author do |author|
author.name("DHH")
@@ -100,7 +100,7 @@ class ScrollsController < ActionController::Base
atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app',
'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll) do |entry|
@@ -118,7 +118,7 @@ class ScrollsController < ActionController::Base
FEEDS["feed_with_overridden_ids"] = <<-EOT
atom_feed({:id => 'tag:test.rubyonrails.org,2008:test/'}) do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll, :id => "tag:test.rubyonrails.org,2008:"+scroll.id.to_s) do |entry|
@@ -137,7 +137,7 @@ class ScrollsController < ActionController::Base
atom_feed(:schema_date => '2008',
:instruct => {'xml-stylesheet' => { :href=> 't.css', :type => 'text/css' }}) do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll) do |entry|
@@ -155,7 +155,7 @@ class ScrollsController < ActionController::Base
atom_feed(:schema_date => '2008',
:instruct => {'target1' => [{ :a => '1', :b => '2' }, { :c => '3', :d => '4' }]}) do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll) do |entry|
@@ -172,7 +172,7 @@ class ScrollsController < ActionController::Base
FEEDS["feed_with_xhtml_content"] = <<-'EOT'
atom_feed do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll) do |entry|
@@ -197,7 +197,7 @@ class ScrollsController < ActionController::Base
new_xml = Builder::XmlMarkup.new(:target=>'')
atom_feed(:xml => new_xml) do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll) do |entry|
diff --git a/actionview/test/template/capture_helper_test.rb b/actionview/test/template/capture_helper_test.rb
index f213da5934..1e099d482c 100644
--- a/actionview/test/template/capture_helper_test.rb
+++ b/actionview/test/template/capture_helper_test.rb
@@ -210,10 +210,4 @@ class CaptureHelperTest < ActionView::TestCase
def alt_encoding(output_buffer)
output_buffer.encoding == Encoding::US_ASCII ? Encoding::UTF_8 : Encoding::US_ASCII
end
-
- def view_with_controller
- TestController.new.view_context.tap do |view|
- view.output_buffer = ActionView::OutputBuffer.new
- end
- end
end
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index bfb073680e..9212420ec9 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -130,7 +130,7 @@ class DateHelperTest < ActionView::TestCase
def test_distance_in_words_with_mathn_required
# test we avoid Integer#/ (redefined by mathn)
- require 'mathn'
+ silence_warnings { require "mathn" }
from = Time.utc(2004, 6, 6, 21, 45, 0)
assert_distance_of_time_in_words(from)
end
@@ -3217,12 +3217,4 @@ class DateHelperTest < ActionView::TestCase
expected = '<time datetime="2013-02-20T00:00:00+00:00">20 Feb 00:00</time>'
assert_equal expected, time_tag(time, :format => :short)
end
-
- protected
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
- end
end
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 22665b6844..27bbb9b6c1 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -277,6 +277,14 @@ module RenderTestCases
assert_nil @view.render(:partial => "test/customer", :collection => nil)
end
+ def test_render_partial_without_object_does_not_put_partial_name_to_local_assigns
+ assert_equal 'false', @view.render(partial: 'test/partial_name_in_local_assigns')
+ end
+
+ def test_render_partial_with_nil_object_puts_partial_name_to_local_assigns
+ assert_equal 'true', @view.render(partial: 'test/partial_name_in_local_assigns', object: nil)
+ end
+
def test_render_partial_with_nil_values_in_collection
assert_equal "Hello: davidHello: Anonymous", @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), nil ])
end
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index df096b3c3a..5dc281adb2 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -41,8 +41,8 @@ class TranslationHelperTest < ActiveSupport::TestCase
I18n.backend.reload!
end
- def test_delegates_to_i18n_setting_the_rescue_format_option_to_html
- I18n.expects(:translate).with(:foo, :locale => 'en', :raise=>true).returns("")
+ def test_delegates_setting_to_i18n
+ I18n.expects(:translate).with(:foo, :locale => 'en', :raise => true).returns("")
translate :foo, :locale => 'en'
end
@@ -58,12 +58,6 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert_equal true, translate(:"translations.missing").html_safe?
end
- def test_returns_missing_translation_message_using_nil_as_rescue_format
- expected = 'translation missing: en.translations.missing'
- assert_equal expected, translate(:"translations.missing", :rescue_format => nil)
- assert_equal false, translate(:"translations.missing", :rescue_format => nil).html_safe?
- end
-
def test_raises_missing_translation_message_with_raise_config_option
ActionView::Base.raise_on_missing_translations = true
@@ -96,12 +90,6 @@ class TranslationHelperTest < ActiveSupport::TestCase
I18n.exception_handler = old_exception_handler
end
- def test_i18n_translate_defaults_to_nil_rescue_format
- expected = 'translation missing: en.translations.missing'
- assert_equal expected, I18n.translate(:"translations.missing")
- assert_equal false, I18n.translate(:"translations.missing").html_safe?
- end
-
def test_translation_returning_an_array
expected = %w(foo bar)
assert_equal expected, translate(:"translations.array")
@@ -157,6 +145,19 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert_equal true, translation.html_safe?
end
+ def test_translate_with_missing_default
+ translation = translate(:'translations.missing', :default => :'translations.missing_html')
+ expected = '<span class="translation_missing" title="translation missing: en.translations.missing_html">Missing Html</span>'
+ assert_equal expected, translation
+ assert_equal true, translation.html_safe?
+ end
+
+ def test_translate_with_missing_default_and_raise_option
+ assert_raise(I18n::MissingTranslationData) do
+ translate(:'translations.missing', :default => :'translations.missing_html', :raise => true)
+ end
+ end
+
def test_translate_with_two_defaults_named_html
translation = translate(:'translations.missing', :default => [:'translations.missing_html', :'translations.hello_html'])
assert_equal '<a>Hello World</a>', translation
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index ef4df0407a..0e35c67516 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -379,6 +379,11 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_dom_equal %{<a href="/">Listing</a>}, link_to_if(true, "Listing", url_hash)
end
+ def test_link_to_if_with_block
+ assert_equal "Fallback", link_to_if(false, "Showing", url_hash) { "Fallback" }
+ assert_dom_equal %{<a href="/">Listing</a>}, link_to_if(true, "Listing", url_hash) { "Fallback" }
+ end
+
def request_for_url(url, opts = {})
env = Rack::MockRequest.env_for("http://www.example.com#{url}", opts)
ActionDispatch::Request.new(env)
@@ -479,6 +484,11 @@ class UrlHelperTest < ActiveSupport::TestCase
link_to_unless_current("Listing", "http://www.example.com/")
end
+ def test_link_to_unless_with_block
+ assert_dom_equal %{<a href="/">Showing</a>}, link_to_unless(false, "Showing", url_hash) { "Fallback" }
+ assert_equal "Fallback", link_to_unless(true, "Listing", url_hash) { "Fallback" }
+ end
+
def test_mail_to
assert_dom_equal %{<a href="mailto:david@loudthinking.com">david@loudthinking.com</a>}, mail_to("david@loudthinking.com")
assert_dom_equal %{<a href="mailto:david@loudthinking.com">David Heinemeier Hansson</a>}, mail_to("david@loudthinking.com", "David Heinemeier Hansson")
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 85a437a1dd..aba386c18e 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,4 +1,17 @@
-* A generated job now inherents from `app/jobs/application_job.rb` by default.
+* Allow `DelayedJob`, `Sidekiq`, `qu`, and `que` to report the job id back to
+ `ActiveJob::Base` as `provider_job_id`.
+
+ Fixes #18821.
+
+ *Kevin Deisz* And *Jeroen van Baarsen*
+
+* `assert_enqueued_jobs` and `assert_performed_jobs` in block form use the
+ given number as expected value. This makes the error message much easier to
+ understand.
+
+ *y-yagi*
+
+* A generated job now inherits from `app/jobs/application_job.rb` by default.
*Jeroen van Baarsen*
diff --git a/activejob/README.md b/activejob/README.md
index 5170ebee6e..f9a3183b1a 100644
--- a/activejob/README.md
+++ b/activejob/README.md
@@ -102,7 +102,7 @@ see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.
The latest version of Active Job can be installed with RubyGems:
```
- % [sudo] gem install activejob
+ % gem install activejob
```
Source code can be downloaded as part of the Rails project on GitHub
diff --git a/activejob/Rakefile b/activejob/Rakefile
index 6c7e6aae6e..0e36bb81b3 100644
--- a/activejob/Rakefile
+++ b/activejob/Rakefile
@@ -35,7 +35,7 @@ namespace :test do
t.libs << 'test'
t.test_files = FileList['test/cases/**/*_test.rb']
t.verbose = true
- t.warning = true
+ t.warning = false
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
end
@@ -54,7 +54,7 @@ namespace :test do
t.libs << 'test'
t.test_files = FileList['test/integration/**/*_test.rb']
t.verbose = true
- t.warning = true
+ t.warning = false
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
end
end
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index 622c37098e..cdb37879b6 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -5,6 +5,8 @@ module ActiveJob
#
# Wraps the original exception raised as +original_exception+.
class DeserializationError < StandardError
+ # The original exception that was raised during deserialization of job
+ # arguments.
attr_reader :original_exception
def initialize(e) #:nodoc:
@@ -24,6 +26,7 @@ module ActiveJob
module Arguments
extend self
+ # :nodoc:
TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum ]
# Serializes a set of arguments. Whitelisted types are returned
@@ -43,8 +46,11 @@ module ActiveJob
end
private
+ # :nodoc:
GLOBALID_KEY = '_aj_globalid'.freeze
+ # :nodoc:
SYMBOL_KEYS_KEY = '_aj_symbol_keys'.freeze
+ # :nodoc:
WITH_INDIFFERENT_ACCESS_KEY = '_aj_hash_with_indifferent_access'.freeze
private_constant :GLOBALID_KEY, :SYMBOL_KEYS_KEY, :WITH_INDIFFERENT_ACCESS_KEY
@@ -113,6 +119,7 @@ module ActiveJob
result
end
+ # :nodoc:
RESERVED_KEYS = [
GLOBALID_KEY, GLOBALID_KEY.to_sym,
SYMBOL_KEYS_KEY, SYMBOL_KEYS_KEY.to_sym,
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index ddd7d1361c..0528572cd0 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -1,4 +1,6 @@
module ActiveJob
+ # Provides general behavior that will be included into every Active Job
+ # object that inherits from ActiveJob::Base.
module Core
extend ActiveSupport::Concern
@@ -15,6 +17,9 @@ module ActiveJob
# Queue in which the job will reside.
attr_writer :queue_name
+
+ # ID optionally provided by adapter
+ attr_accessor :provider_job_id
end
# These methods will be included into any Active Job object, adding
diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb
index 430c17e1bf..98d92385dd 100644
--- a/activejob/lib/active_job/enqueuing.rb
+++ b/activejob/lib/active_job/enqueuing.rb
@@ -1,6 +1,7 @@
require 'active_job/arguments'
module ActiveJob
+ # Provides behavior for enqueuing and retrying jobs.
module Enqueuing
extend ActiveSupport::Concern
diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb
index 9c4519432d..054a5d93b4 100644
--- a/activejob/lib/active_job/queue_adapter.rb
+++ b/activejob/lib/active_job/queue_adapter.rb
@@ -4,7 +4,7 @@ require 'active_support/core_ext/string/inflections'
module ActiveJob
# The <tt>ActiveJob::QueueAdapter</tt> module is used to load the
- # correct adapter. The default queue adapter is the :inline queue.
+ # correct adapter. The default queue adapter is the +:inline+ queue.
module QueueAdapter #:nodoc:
extend ActiveSupport::Concern
@@ -20,7 +20,7 @@ module ActiveJob
end
# Specify the backend queue provider. The default queue adapter
- # is the :inline queue. See QueueAdapters for more
+ # is the +:inline+ queue. See QueueAdapters for more
# information.
def queue_adapter=(name_or_adapter_or_class)
self._queue_adapter = interpret_adapter(name_or_adapter_or_class)
diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb
index 8aa85979f6..bd69e525bb 100644
--- a/activejob/lib/active_job/queue_adapters.rb
+++ b/activejob/lib/active_job/queue_adapters.rb
@@ -27,7 +27,6 @@ module ActiveJob
# | Sneakers | Yes | Yes | No | Queue | Queue | No |
# | Sucker Punch | Yes | Yes | No | No | No | No |
# | Active Job Inline | No | Yes | N/A | N/A | N/A | N/A |
- # | Active Job | Yes | Yes | Yes | No | No | No |
#
# ==== Async
#
diff --git a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
index 852a6ee326..ac83da2b9c 100644
--- a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
@@ -14,11 +14,15 @@ module ActiveJob
# Rails.application.config.active_job.queue_adapter = :delayed_job
class DelayedJobAdapter
def enqueue(job) #:nodoc:
- Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name)
+ delayed_job = Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name)
+ job.provider_job_id = delayed_job.id
+ delayed_job
end
def enqueue_at(job, timestamp) #:nodoc:
- Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, run_at: Time.at(timestamp))
+ delayed_job = Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, run_at: Time.at(timestamp))
+ job.provider_job_id = delayed_job.id
+ delayed_job
end
class JobWrapper #:nodoc:
diff --git a/activejob/lib/active_job/queue_adapters/inline_adapter.rb b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
index 1d06324c18..8ad5f4de07 100644
--- a/activejob/lib/active_job/queue_adapters/inline_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
@@ -14,7 +14,7 @@ module ActiveJob
end
def enqueue_at(*) #:nodoc:
- raise NotImplementedError.new("Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/active_job_basics.html")
+ raise NotImplementedError, "Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/active_job_basics.html"
end
end
end
diff --git a/activejob/lib/active_job/queue_adapters/qu_adapter.rb b/activejob/lib/active_job/queue_adapters/qu_adapter.rb
index 94584ef9d8..0e198922fc 100644
--- a/activejob/lib/active_job/queue_adapters/qu_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/qu_adapter.rb
@@ -17,13 +17,17 @@ module ActiveJob
# Rails.application.config.active_job.queue_adapter = :qu
class QuAdapter
def enqueue(job, *args) #:nodoc:
- Qu::Payload.new(klass: JobWrapper, args: [job.serialize]).tap do |payload|
+ qu_job = Qu::Payload.new(klass: JobWrapper, args: [job.serialize]).tap do |payload|
payload.instance_variable_set(:@queue, job.queue_name)
end.push
+
+ # qu_job can be nil depending on the configured backend
+ job.provider_job_id = qu_job.id unless qu_job.nil?
+ qu_job
end
def enqueue_at(job, timestamp, *args) #:nodoc:
- raise NotImplementedError
+ raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html"
end
class JobWrapper < Qu::Job #:nodoc:
diff --git a/activejob/lib/active_job/queue_adapters/que_adapter.rb b/activejob/lib/active_job/queue_adapters/que_adapter.rb
index 84cc2845b0..90947aa98d 100644
--- a/activejob/lib/active_job/queue_adapters/que_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/que_adapter.rb
@@ -16,11 +16,15 @@ module ActiveJob
# Rails.application.config.active_job.queue_adapter = :que
class QueAdapter
def enqueue(job) #:nodoc:
- JobWrapper.enqueue job.serialize, queue: job.queue_name
+ que_job = JobWrapper.enqueue job.serialize
+ job.provider_job_id = que_job.attrs["job_id"]
+ que_job
end
def enqueue_at(job, timestamp) #:nodoc:
- JobWrapper.enqueue job.serialize, queue: job.queue_name, run_at: Time.at(timestamp)
+ que_job = JobWrapper.enqueue job.serialize, run_at: Time.at(timestamp)
+ job.provider_job_id = que_job.attrs["job_id"]
+ que_job
end
class JobWrapper < Que::Job #:nodoc:
diff --git a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
index 743d5ea333..c321776bf5 100644
--- a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
@@ -17,7 +17,7 @@ module ActiveJob
class SidekiqAdapter
def enqueue(job) #:nodoc:
#Sidekiq::Client does not support symbols as keys
- Sidekiq::Client.push \
+ job.provider_job_id = Sidekiq::Client.push \
'class' => JobWrapper,
'wrapped' => job.class.to_s,
'queue' => job.queue_name,
@@ -25,7 +25,7 @@ module ActiveJob
end
def enqueue_at(job, timestamp) #:nodoc:
- Sidekiq::Client.push \
+ job.provider_job_id = Sidekiq::Client.push \
'class' => JobWrapper,
'wrapped' => job.class.to_s,
'queue' => job.queue_name,
diff --git a/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
index f5737487ca..f102c6567e 100644
--- a/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
@@ -28,7 +28,7 @@ module ActiveJob
end
def enqueue_at(job, timestamp) #:nodoc:
- raise NotImplementedError
+ raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html"
end
class JobWrapper #:nodoc:
diff --git a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
index 64c93e8198..c6c35f8ab4 100644
--- a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
@@ -23,7 +23,7 @@ module ActiveJob
end
def enqueue_at(job, timestamp) #:nodoc:
- raise NotImplementedError
+ raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html"
end
class JobWrapper #:nodoc:
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index 4efb4b72d2..9b307e8dc8 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -68,7 +68,7 @@ module ActiveJob
original_count = enqueued_jobs_size(only: only)
yield
new_count = enqueued_jobs_size(only: only)
- assert_equal original_count + number, new_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued"
+ assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued"
else
actual_count = enqueued_jobs_size(only: only)
assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"
@@ -164,7 +164,7 @@ module ActiveJob
original_count = performed_jobs.size
perform_enqueued_jobs(only: only) { yield }
new_count = performed_jobs.size
- assert_equal original_count + number, new_count,
+ assert_equal number, new_count - original_count,
"#{number} jobs expected, but #{new_count - original_count} were performed"
else
performed_jobs_size = performed_jobs.size
diff --git a/activejob/lib/rails/generators/job/job_generator.rb b/activejob/lib/rails/generators/job/job_generator.rb
index 86e4c5266c..2115fb9f71 100644
--- a/activejob/lib/rails/generators/job/job_generator.rb
+++ b/activejob/lib/rails/generators/job/job_generator.rb
@@ -1,6 +1,6 @@
require 'rails/generators/named_base'
-module Rails
+module Rails # :nodoc:
module Generators # :nodoc:
class JobGenerator < Rails::Generators::NamedBase # :nodoc:
desc 'This generator creates an active job file at app/jobs'
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index 19a2820a6e..04c4c446e2 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -31,6 +31,17 @@ class EnqueuedJobsTest < ActiveJob::TestCase
end
end
+ def test_assert_enqueued_jobs_message
+ HelloJob.perform_later('sean')
+ e = assert_raises Minitest::Assertion do
+ assert_enqueued_jobs 2 do
+ HelloJob.perform_later('sean')
+ end
+ end
+ assert_match "Expected: 2", e.message
+ assert_match "Actual: 1", e.message
+ end
+
def test_assert_enqueued_jobs_with_no_block
assert_nothing_raised do
HelloJob.perform_later('rafael')
@@ -230,6 +241,17 @@ class PerformedJobsTest < ActiveJob::TestCase
end
end
+ def test_assert_performed_jobs_message
+ HelloJob.perform_later('sean')
+ e = assert_raises Minitest::Assertion do
+ assert_performed_jobs 2 do
+ HelloJob.perform_later('sean')
+ end
+ end
+ assert_match "Expected: 2", e.message
+ assert_match "Actual: 1", e.message
+ end
+
def test_assert_performed_jobs_with_no_block
assert_nothing_raised do
perform_enqueued_jobs do
diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb
index 72ec2b8904..57907042d9 100644
--- a/activejob/test/helper.rb
+++ b/activejob/test/helper.rb
@@ -14,5 +14,3 @@ else
end
require 'active_support/testing/autorun'
-
-ActiveSupport::TestCase.test_order = :random
diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb
index af19a92118..d345092dee 100644
--- a/activejob/test/integration/queuing_test.rb
+++ b/activejob/test/integration/queuing_test.rb
@@ -11,7 +11,7 @@ class QueuingTest < ActiveSupport::TestCase
end
test 'should not run jobs queued on a non-listening queue' do
- skip if adapter_is?(:inline) || adapter_is?(:sucker_punch)
+ skip if adapter_is?(:inline, :sucker_punch, :que)
old_queue = TestJob.queue_name
begin
@@ -26,8 +26,6 @@ class QueuingTest < ActiveSupport::TestCase
test 'should supply a wrapped class name to Sidekiq' do
skip unless adapter_is?(:sidekiq)
- require 'sidekiq/testing'
-
Sidekiq::Testing.fake! do
::HelloJob.perform_later
hash = ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper.jobs.first
@@ -48,7 +46,7 @@ class QueuingTest < ActiveSupport::TestCase
test 'should run job enqueued in the future at the specified time' do
begin
- TestJob.set(wait: 3.seconds).perform_later @id
+ TestJob.set(wait: 5.seconds).perform_later @id
wait_for_jobs_to_finish_for(2.seconds)
assert_not job_executed
wait_for_jobs_to_finish_for(10.seconds)
@@ -57,4 +55,17 @@ class QueuingTest < ActiveSupport::TestCase
skip
end
end
+
+ test 'should supply a provider_job_id when available for immediate jobs' do
+ skip unless adapter_is?(:delayed_job, :sidekiq, :qu, :que)
+ test_job = TestJob.perform_later @id
+ refute test_job.provider_job_id.nil?, '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)
+ 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'
+ end
end
diff --git a/activejob/test/support/integration/adapters/que.rb b/activejob/test/support/integration/adapters/que.rb
index ba7657a42a..0cd8952a28 100644
--- a/activejob/test/support/integration/adapters/que.rb
+++ b/activejob/test/support/integration/adapters/que.rb
@@ -2,6 +2,15 @@ module QueJobsManager
def setup
require 'sequel'
ActiveJob::Base.queue_adapter = :que
+ Que.mode = :off
+ Que.worker_count = 1
+ end
+
+ def clear_jobs
+ Que.clear!
+ end
+
+ def start_workers
que_url = ENV['QUE_DATABASE_URL'] || 'postgres:///active_jobs_que_int_test'
uri = URI.parse(que_url)
user = uri.user||ENV['USER']
@@ -11,24 +20,17 @@ module QueJobsManager
%x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1}
Que.connection = Sequel.connect(que_url)
Que.migrate!
- Que.mode = :off
- Que.worker_count = 1
- rescue Sequel::DatabaseConnectionError
- puts "Cannot run integration tests for que. To be able to run integration tests for que you need to install and start postgresql.\n"
- exit
- end
-
- def clear_jobs
- Que.clear!
- end
- def start_workers
@thread = Thread.new do
loop do
- Que::Job.work("integration_tests")
+ Que::Job.work
sleep 0.5
end
end
+
+ rescue Sequel::DatabaseConnectionError
+ puts "Cannot run integration tests for que. To be able to run integration tests for que you need to install and start postgresql.\n"
+ exit
end
def stop_workers
diff --git a/activejob/test/support/integration/adapters/queue_classic.rb b/activejob/test/support/integration/adapters/queue_classic.rb
index f522b2711f..29c04bf625 100644
--- a/activejob/test/support/integration/adapters/queue_classic.rb
+++ b/activejob/test/support/integration/adapters/queue_classic.rb
@@ -3,17 +3,7 @@ module QueueClassicJobsManager
ENV['QC_DATABASE_URL'] ||= 'postgres:///active_jobs_qc_int_test'
ENV['QC_RAILS_DATABASE'] = 'false'
ENV['QC_LISTEN_TIME'] = "0.5"
- uri = URI.parse(ENV['QC_DATABASE_URL'])
- user = uri.user||ENV['USER']
- pass = uri.password
- db = uri.path[1..-1]
- %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database if exists "#{db}"' -U #{user} -t template1}
- %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1}
ActiveJob::Base.queue_adapter = :queue_classic
- QC::Setup.create
- rescue PG::ConnectionBad
- puts "Cannot run integration tests for queue_classic. To be able to run integration tests for queue_classic you need to install and start postgresql.\n"
- exit
end
def clear_jobs
@@ -21,12 +11,24 @@ module QueueClassicJobsManager
end
def start_workers
+ uri = URI.parse(ENV['QC_DATABASE_URL'])
+ user = uri.user||ENV['USER']
+ pass = uri.password
+ db = uri.path[1..-1]
+ %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database if exists "#{db}"' -U #{user} -t template1}
+ %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1}
+ QC::Setup.create
+
QC.default_conn_adapter.disconnect
QC.default_conn_adapter = nil
@pid = fork do
worker = QC::Worker.new(q_name: 'integration_tests')
worker.start
end
+
+ rescue PG::ConnectionBad
+ puts "Cannot run integration tests for queue_classic. To be able to run integration tests for queue_classic you need to install and start postgresql.\n"
+ exit
end
def stop_workers
diff --git a/activejob/test/support/integration/adapters/sidekiq.rb b/activejob/test/support/integration/adapters/sidekiq.rb
index dd24b3abf2..4988cdb33f 100644
--- a/activejob/test/support/integration/adapters/sidekiq.rb
+++ b/activejob/test/support/integration/adapters/sidekiq.rb
@@ -1,5 +1,8 @@
require 'sidekiq/api'
+require 'sidekiq/testing'
+Sidekiq::Testing.disable!
+
module SidekiqJobsManager
def setup
@@ -16,28 +19,38 @@ module SidekiqJobsManager
end
def start_workers
- fork do
- logfile = Rails.root.join("log/sidekiq.log").to_s
- pidfile = Rails.root.join("tmp/sidekiq.pid").to_s
- ::Process.daemon(true, true)
- [$stdout, $stderr].each do |io|
- File.open(logfile, 'ab') do |f|
- io.reopen(f)
- end
- io.sync = true
- end
+ continue_read, continue_write = IO.pipe
+ death_read, death_write = IO.pipe
+
+ @pid = fork do
+ continue_read.close
+ death_write.close
+
+ # Celluloid & Sidekiq are not warning-clean :(
+ $VERBOSE = false
+
$stdin.reopen('/dev/null')
+ $stdout.sync = true
+ $stderr.sync = true
+
+ logfile = Rails.root.join("log/sidekiq.log").to_s
Sidekiq::Logging.initialize_logger(logfile)
- File.open(File.expand_path(pidfile), 'w') do |f|
- f.puts ::Process.pid
- end
self_read, self_write = IO.pipe
trap "TERM" do
self_write.puts("TERM")
end
+ Thread.new do
+ begin
+ death_read.read
+ rescue Exception
+ end
+ self_write.puts("TERM")
+ end
+
require 'celluloid'
+ Celluloid.logger = nil
require 'sidekiq/launcher'
sidekiq = Sidekiq::Launcher.new({queues: ["integration_tests"],
environment: "test",
@@ -48,23 +61,29 @@ module SidekiqJobsManager
Sidekiq::Scheduled.const_set :INITIAL_WAIT, 1
begin
sidekiq.run
+ continue_write.puts "started"
while readable_io = IO.select([self_read])
signal = readable_io.first[0].gets.strip
raise Interrupt if signal == "TERM"
end
rescue Interrupt
- sidekiq.stop
- exit(0)
end
+
+ sidekiq.stop
+ exit!
end
- sleep 1
+ continue_write.close
+ death_read.close
+ @worker_lifeline = death_write
+
+ raise "Failed to start worker" unless continue_read.gets == "started\n"
end
def stop_workers
- pidfile = Rails.root.join("tmp/sidekiq.pid").to_s
- Process.kill 'TERM', File.open(pidfile).read.to_i
- FileUtils.rm_f pidfile
- rescue
+ if @pid
+ Process.kill 'TERM', @pid
+ Process.wait @pid
+ end
end
def can_run?
diff --git a/activejob/test/support/integration/test_case_helpers.rb b/activejob/test/support/integration/test_case_helpers.rb
index bed28b2900..7e87ede275 100644
--- a/activejob/test/support/integration/test_case_helpers.rb
+++ b/activejob/test/support/integration/test_case_helpers.rb
@@ -27,8 +27,8 @@ module TestCaseHelpers
jobs_manager.clear_jobs
end
- def adapter_is?(adapter_class_symbol)
- ActiveJob::Base.queue_adapter.class.name.split("::").last.gsub(/Adapter$/, '').underscore == adapter_class_symbol.to_s
+ def adapter_is?(*adapter_class_symbols)
+ adapter_class_symbols.map(&:to_s).include?(ActiveJob::Base.queue_adapter.class.name.split("::").last.gsub(/Adapter$/, '').underscore)
end
def wait_for_jobs_to_finish_for(seconds=60)
diff --git a/activejob/test/support/que/inline.rb b/activejob/test/support/que/inline.rb
index 2e210acb6b..0232da1370 100644
--- a/activejob/test/support/que/inline.rb
+++ b/activejob/test/support/que/inline.rb
@@ -3,7 +3,11 @@ require 'que'
Que::Job.class_eval do
class << self; alias_method :original_enqueue, :enqueue; end
def self.enqueue(*args)
- args.pop if args.last.is_a?(Hash)
+ if args.last.is_a?(Hash)
+ options = args.pop
+ options.delete(:run_at)
+ args << options unless options.empty?
+ end
self.run(*args)
end
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 32a2cb4517..3c1510b31d 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,3 +1,17 @@
+* Ensure `method_missing` is called for methods passed to
+ `ActiveModel::Serialization#serializable_hash` that don't exist.
+
+ *Jay Elaraj*
+
+* 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.
+
+ It makes the dirty-attributes query methods consistent before and after
+ saving.
+
+ *Fernando Tapia Rico*
+
* Deprecate the `:tokenizer` option for `validates_length_of`, in favor of
plain Ruby.
@@ -13,7 +27,7 @@
*Wojciech Wnętrzak*
-* Allow symbol as values for `tokenize` of `LengthValidator`
+* Allow symbol as values for `tokenize` of `LengthValidator`.
*Kensuke Naito*
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index 4920666f27..5c36b1277e 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -242,7 +242,7 @@ behavior out of the box:
The latest version of Active Model can be installed with RubyGems:
- % [sudo] gem install activemodel
+ % gem install activemodel
Source code can be downloaded as part of the Rails project on GitHub
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 96be551264..ff7280f9e5 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -23,7 +23,7 @@ module ActiveModel
# The requirements to implement <tt>ActiveModel::AttributeMethods</tt> are to:
#
# * <tt>include ActiveModel::AttributeMethods</tt> in your class.
- # * Call each of its method you want to add, such as +attribute_method_suffix+
+ # * Call each of its methods you want to add, such as +attribute_method_suffix+
# or +attribute_method_prefix+.
# * Call +define_attribute_methods+ after the other methods are called.
# * Define the various generic +_attribute+ methods that you have declared.
@@ -227,7 +227,7 @@ module ActiveModel
# Declares the attributes that should be prefixed and suffixed by
# ActiveModel::AttributeMethods.
#
- # To use, pass attribute names (as strings or symbols), be sure to declare
+ # To use, pass attribute names (as strings or symbols). Be sure to declare
# +define_attribute_methods+ after you define any prefix, suffix or affix
# methods, or they will not hook in.
#
@@ -239,7 +239,7 @@ module ActiveModel
#
# # Call to define_attribute_methods must appear after the
# # attribute_method_prefix, attribute_method_suffix or
- # # attribute_method_affix declares.
+ # # attribute_method_affix declarations.
# define_attribute_methods :name, :age, :address
#
# private
@@ -255,7 +255,7 @@ module ActiveModel
# Declares an attribute that should be prefixed and suffixed by
# ActiveModel::AttributeMethods.
#
- # To use, pass an attribute name (as string or symbol), be sure to declare
+ # To use, pass an attribute name (as string or symbol). Be sure to declare
# +define_attribute_method+ after you define any prefix, suffix or affix
# method, or they will not hook in.
#
@@ -267,7 +267,7 @@ module ActiveModel
#
# # Call to define_attribute_method must appear after the
# # attribute_method_prefix, attribute_method_suffix or
- # # attribute_method_affix declares.
+ # # attribute_method_affix declarations.
# define_attribute_method :name
#
# private
@@ -363,7 +363,7 @@ module ActiveModel
end
# Define a method `name` in `mod` that dispatches to `send`
- # using the given `extra` args. This fallbacks `define_method`
+ # using the given `extra` args. This falls back on `define_method`
# and `send` if the given names cannot be compiled.
def define_proxy_call(include_private, mod, name, send, *extra) #:nodoc:
defn = if name =~ NAME_COMPILABLE_REGEXP
@@ -419,7 +419,7 @@ module ActiveModel
# returned by <tt>attributes</tt>, as though they were first-class
# methods. So a +Person+ class with a +name+ attribute can for example use
# <tt>Person#name</tt> and <tt>Person#name=</tt> and never directly use
- # the attributes hash -- except for multiple assigns with
+ # the attributes hash -- except for multiple assignments with
# <tt>ActiveRecord::Base#attributes=</tt>.
#
# It's also possible to instantiate related objects, so a <tt>Client</tt>
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index c03e5fac79..0169c20e0b 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -76,9 +76,11 @@ module ActiveModel
#
# Reset the changes:
#
- # person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]}
+ # person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]}
+ # person.name_previously_changed? # => true
+ # person.name_previous_change # => ["Uncle Bob", "Bill"]
# person.reload!
- # person.previous_changes # => {}
+ # person.previous_changes # => {}
#
# Rollback the changes:
#
@@ -100,10 +102,10 @@ module ActiveModel
# person.changes # => {"name" => ["Bill", "Bob"]}
#
# If an attribute is modified in-place then make use of
- # +[attribute_name]_will_change!+ to mark that the attribute is changing.
+ # <tt>[attribute_name]_will_change!</tt> to mark that the attribute is changing.
# Otherwise \Active \Model can't track changes to in-place attributes. Note
# that Active Record can detect in-place modifications automatically. You do
- # not need to call +[attribute_name]_will_change!+ on Active Record models.
+ # not need to call <tt>[attribute_name]_will_change!</tt> on Active Record models.
#
# person.name_will_change!
# person.name_change # => ["Bill", "Bill"]
@@ -115,6 +117,7 @@ module ActiveModel
included do
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
+ attribute_method_suffix '_previously_changed?', '_previous_change'
attribute_method_affix prefix: 'restore_', suffix: '!'
end
@@ -179,6 +182,11 @@ module ActiveModel
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
end
+ # Handles <tt>*_previously_changed?</tt> for +method_missing+.
+ def attribute_previously_changed?(attr, options = {}) #:nodoc:
+ previous_changes_include?(attr)
+ end
+
# Restore all previous data of the provided attributes.
def restore_attributes(attributes = changed)
attributes.each { |attr| restore_attribute! attr }
@@ -192,6 +200,12 @@ module ActiveModel
end
alias attribute_changed_by_setter? changes_include?
+ # Returns +true+ if attr_name were changed before the model was saved,
+ # +false+ otherwise.
+ def previous_changes_include?(attr_name)
+ @previously_changed.include?(attr_name)
+ end
+
# Removes current changes and makes them accessible through +previous_changes+.
def changes_applied # :doc:
@previously_changed = changes
@@ -209,6 +223,11 @@ module ActiveModel
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
end
+ # Handles <tt>*_previous_change</tt> for +method_missing+.
+ def attribute_previous_change(attr)
+ @previously_changed[attr] if attribute_previously_changed?(attr)
+ end
+
# Handles <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
return if attribute_changed?(attr)
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index f843b279ce..287a2559d2 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -452,7 +452,6 @@ module ActiveModel
defaults = []
end
- defaults << options.delete(:message)
defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope)
defaults << :"errors.attributes.#{attribute}.#{type}"
defaults << :"errors.messages.#{type}"
@@ -461,6 +460,7 @@ module ActiveModel
defaults.flatten!
key = defaults.shift
+ defaults = options.delete(:message) if options[:message]
value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
options = {
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index 976f50b13e..c06a64c75f 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -107,7 +107,7 @@ module ActiveModel
hash = {}
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
- Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) }
+ Array(options[:methods]).each { |m| hash[m.to_s] = send(m) }
serializable_add_includes(options) do |association, records, opts|
hash[association.to_s] = if records.respond_to?(:to_ary)
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 74d60327d6..c1019169e1 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -87,6 +87,7 @@ module ActiveModel
validates_with BlockValidator, _merge_attributes(attr_names), &block
end
+ # :nodoc:
VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze
# Adds a validation method or block to the class. This is useful when
diff --git a/activemodel/test/cases/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb
index 3b01644dd1..64a85e01eb 100644
--- a/activemodel/test/cases/attribute_assignment_test.rb
+++ b/activemodel/test/cases/attribute_assignment_test.rb
@@ -58,8 +58,6 @@ class AttributeAssignmentTest < ActiveModel::TestCase
end
test "assign private attribute" do
- rubinius_skip "https://github.com/rubinius/rubinius/issues/3328"
-
model = Model.new
assert_raises(ActiveModel::UnknownAttributeError) do
model.assign_attributes(metadata: { a: 1 })
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index 66ed8a350a..d17a12ad12 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -137,6 +137,19 @@ class DirtyTest < ActiveModel::TestCase
assert_equal [nil, "Jericho Cane"], @model.previous_changes['name']
end
+ test "setting new attributes should not affect previous changes" do
+ @model.name = "Jericho Cane"
+ @model.save
+ @model.name = "DudeFella ManGuy"
+ assert_equal [nil, "Jericho Cane"], @model.name_previous_change
+ end
+
+ test "saving should preserve model's previous changed status" do
+ @model.name = "Jericho Cane"
+ @model.save
+ assert @model.name_previously_changed?
+ end
+
test "previous value is preserved when changed after save" do
assert_equal({}, @model.changed_attributes)
@model.name = "Paul"
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index f781a0017f..d10c20caad 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -149,6 +149,12 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal ["cannot be blank"], person.errors[:name]
end
+ test "add an error message on a specific attribute with a defined type" do
+ person = Person.new
+ person.errors.add(:name, :blank, message: "cannot be blank")
+ assert_equal ["cannot be blank"], person.errors[:name]
+ end
+
test "add an error with a symbol" do
person = Person.new
person.errors.add(:name, :blank)
diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb
index 4ae41aa19c..8d3165cd78 100644
--- a/activemodel/test/cases/serialization_test.rb
+++ b/activemodel/test/cases/serialization_test.rb
@@ -16,6 +16,14 @@ class SerializationTest < ActiveModel::TestCase
instance_values.except("address", "friends")
end
+ def method_missing(method_name, *args)
+ if method_name == :bar
+ 'i_am_bar'
+ else
+ super
+ end
+ end
+
def foo
'i_am_foo'
end
@@ -58,23 +66,22 @@ class SerializationTest < ActiveModel::TestCase
end
def test_method_serializable_hash_should_work_with_methods_option
- expected = {"name"=>"David", "gender"=>"male", "foo"=>"i_am_foo", "email"=>"david@example.com"}
- assert_equal expected, @user.serializable_hash(methods: [:foo])
+ expected = {"name"=>"David", "gender"=>"male", "foo"=>"i_am_foo", "bar"=>"i_am_bar", "email"=>"david@example.com"}
+ assert_equal expected, @user.serializable_hash(methods: [:foo, :bar])
end
def test_method_serializable_hash_should_work_with_only_and_methods
- expected = {"foo"=>"i_am_foo"}
- assert_equal expected, @user.serializable_hash(only: [], methods: [:foo])
+ expected = {"foo"=>"i_am_foo", "bar"=>"i_am_bar"}
+ assert_equal expected, @user.serializable_hash(only: [], methods: [:foo, :bar])
end
def test_method_serializable_hash_should_work_with_except_and_methods
- expected = {"gender"=>"male", "foo"=>"i_am_foo"}
- assert_equal expected, @user.serializable_hash(except: [:name, :email], methods: [:foo])
+ expected = {"gender"=>"male", "foo"=>"i_am_foo", "bar"=>"i_am_bar"}
+ assert_equal expected, @user.serializable_hash(except: [:name, :email], methods: [:foo, :bar])
end
- def test_should_not_call_methods_that_dont_respond
- expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"}
- assert_equal expected, @user.serializable_hash(methods: [:bar])
+ def test_should_raise_NoMethodError_for_non_existing_method
+ assert_raise(NoMethodError) { @user.serializable_hash(methods: [:nada]) }
end
def test_should_use_read_attribute_for_serialization
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 904ac5c26a..5b09101bca 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,74 @@
+* Allow single table inheritance instantiation to work when storing
+ demodulized class names.
+
+ *Alex Robbin*
+
+* Correctly pass MySQL options when using `structure_dump` or
+ `structure_load`.
+
+ Specifically, it fixes an issue when using SSL authentication.
+
+ *Alex Coomans*
+
+* Dump indexes in `create_table` instead of `add_index`.
+
+ If the adapter supports indexes in `create_table`, generated SQL is
+ slightly more efficient.
+
+ *Ryuta Kamizono*
+
+* Correctly dump `:options` on `create_table` for MySQL.
+
+ *Ryuta Kamizono*
+
+* PostgreSQL: `:collation` support for string and text columns.
+
+ Example:
+
+ create_table :foos do |t|
+ t.string :string_en, collation: 'en_US.UTF-8'
+ t.text :text_ja, collation: 'ja_JP.UTF-8'
+ end
+
+ *Ryuta Kamizono*
+
+* Make `unscope` aware of "less than" and "greater than" conditions.
+
+ *TAKAHASHI Kazuaki*
+
+* `find_by` and `find_by!` raise `ArgumentError` when called without
+ arguments.
+
+ *Kohei Suzuki*
+
+* Revert behavior of `db:schema:load` back to loading the full
+ environment. This ensures that initializers are run.
+
+ Fixes #19545.
+
+ *Yves Senn*
+
+* Fix missing index when using `timestamps` with the `index` option.
+
+ The `index` option used with `timestamps` should be passed to both
+ `column` definitions for `created_at` and `updated_at` rather than just
+ the first.
+
+ *Paul Mucur*
+
+* Rename `:class` to `:anonymous_class` in association options.
+
+ Fixes #19659.
+
+ *Andrew White*
+
+* Autosave existing records on a has many through association when the parent
+ is new.
+
+ Fixes #19782.
+
+ *Sean Griffin*
+
* Fixed a bug where uniqueness validations would error on out of range values,
even if an validation should have prevented it from hitting the database.
@@ -21,20 +92,22 @@
*Mehmet Emin İNAÇ*
-* Reduce memory usage from loading types on pg.
+* Reduce memory usage from loading types on PostgreSQL.
Fixes #19578.
*Sean Griffin*
-* Add `config.active_record.warn_on_records_fetched_greater_than` option
+* Add `config.active_record.warn_on_records_fetched_greater_than` option.
When set to an integer, a warning will be logged whenever a result set
- larger than the specified size is returned by a query. Fixes #16463
+ larger than the specified size is returned by a query.
+
+ Fixes #16463.
*Jason Nochlin*
-* Ignore psqlrc when loading database structure.
+* Ignore `.psqlrc` when loading database structure.
*Jason Weathered*
@@ -706,7 +779,7 @@
*Yves Senn*
-* Fix bug with 'ActiveRecord::Type::Numeric' that caused negative values to
+* Fix bug with `ActiveRecord::Type::Numeric` that caused negative values to
be marked as having changed when set to the same negative value.
Closes #18161.
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index f4777919d3..049c5d2b3b 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -31,8 +31,8 @@ which might look like this:
PRIMARY KEY (id)
);
-This would also define the following accessors: `Product#name` and
-`Product#name=(new_name)`.
+This would also define the following accessors: <tt>Product#name</tt> and
+<tt>Product#name=(new_name)</tt>.
* Associations between objects defined by simple class methods.
@@ -188,7 +188,7 @@ Admit the Database:
The latest version of Active Record can be installed with RubyGems:
- % [sudo] gem install activerecord
+ % gem install activerecord
Source code can be downloaded as part of the Rails project on GitHub:
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index f1facac21b..fa53926eb2 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -122,7 +122,7 @@ namespace :db do
# prepare hstore
if %x( createdb --version ).strip.gsub(/(.*)(\d\.\d\.\d)$/, "\\2") < "9.1.0"
- puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html"
+ puts "Please prepare hstore data type. See http://www.postgresql.org/docs/current/static/hstore.html"
end
end
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 3d497a30fb..f7b50cd25a 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -142,7 +142,7 @@ module ActiveRecord
# converted to an instance of value class if necessary.
#
# For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be
- # aggregated using the NetAddr::CIDR value class (http://www.ruby-doc.org/gems/docs/n/netaddr-1.5.0/NetAddr/CIDR.html).
+ # aggregated using the NetAddr::CIDR value class (http://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR).
# The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter.
# New values can be assigned to the value object using either another NetAddr::CIDR object, a string
# or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index c5c2178ee2..a3c30642d0 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -185,7 +185,7 @@ module ActiveRecord
super
end
- # Returns the specified association instance if it responds to :loaded?, nil otherwise.
+ # Returns the specified association instance if it exists, nil otherwise.
def association_instance_get(name)
@association_cache[name]
end
@@ -285,7 +285,7 @@ module ActiveRecord
# end
#
# If your model class is <tt>Project</tt>, the module is
- # named <tt>Project::GeneratedFeatureMethods</tt>. The GeneratedFeatureMethods module is
+ # named <tt>Project::GeneratedAssociationMethods</tt>. The GeneratedAssociationMethods module is
# included in the model class immediately after the (anonymous) generated attributes methods
# module, meaning an association will override the methods for an attribute with the same name.
#
@@ -1722,7 +1722,7 @@ module ActiveRecord
Builder::HasMany.define_callbacks self, middle_reflection
Reflection.add_reflection self, middle_reflection.name, middle_reflection
- middle_reflection.parent_reflection = [name.to_s, habtm_reflection]
+ middle_reflection.parent_reflection = habtm_reflection
include Module.new {
class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -1743,7 +1743,7 @@ module ActiveRecord
end
has_many name, scope, hm_options, &extension
- self._reflections[name.to_s].parent_reflection = [name.to_s, habtm_reflection]
+ self._reflections[name.to_s].parent_reflection = habtm_reflection
end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 88406740d8..ba1b1814d1 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -16,7 +16,7 @@ module ActiveRecord::Associations::Builder
end
self.extensions = []
- VALID_OPTIONS = [:class_name, :class, :foreign_key, :validate] # :nodoc:
+ VALID_OPTIONS = [:class_name, :anonymous_class, :foreign_key, :validate] # :nodoc:
def self.build(model, name, scope, options, &block)
if model.dangerous_attribute_method?(name)
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 97b57a6a55..ffd9c9d6fc 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
@@ -78,7 +78,7 @@ module ActiveRecord::Associations::Builder
join_model.table_name_resolver = habtm
join_model.class_resolver = lhs_model
- join_model.add_left_association :left_side, class: lhs_model
+ join_model.add_left_association :left_side, anonymous_class: lhs_model
join_model.add_right_association association_name, belongs_to_options(options)
join_model
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 29e8a0edc1..cd79266952 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -38,12 +38,10 @@ module ActiveRecord
def insert_record(record, validate = true, raise = false)
ensure_not_nested
- if record.new_record?
- if raise
- record.save!(:validate => validate)
- else
- return unless record.save(:validate => validate)
- end
+ if raise
+ record.save!(:validate => validate)
+ else
+ return unless record.save(:validate => validate)
end
save_through_record(record)
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index cc265e2af6..45fdcaa1cd 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -29,7 +29,8 @@ module ActiveRecord
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
end
- # Re-raise with the ActiveRecord constant in case of an error
+ # Tries to assign given value to given attribute.
+ # In case of an error, re-raises with the ActiveRecord constant.
def _assign_attribute(k, v) # :nodoc:
super
rescue ActiveModel::UnknownAttributeError
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 67490ecd97..9c5b7d937d 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -120,23 +120,22 @@ module ActiveRecord #:nodoc:
# All column values are automatically available through basic accessors on the Active Record
# object, but sometimes you want to specialize this behavior. This can be done by overwriting
# the default accessors (using the same name as the attribute) and calling
- # <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually
- # change things.
+ # +super+ to actually change things.
#
# class Song < ActiveRecord::Base
# # Uses an integer of seconds to hold the length of the song
#
# def length=(minutes)
- # write_attribute(:length, minutes.to_i * 60)
+ # super(minutes.to_i * 60)
# end
#
# def length
- # read_attribute(:length) / 60
+ # super / 60
# end
# end
#
# You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
- # instead of <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
+ # or <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
#
# == Attribute query methods
#
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 2fcba8e309..3027ce928e 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -194,7 +194,7 @@ module ActiveRecord
#
# If the +before_validation+ callback throws +:abort+, the process will be
# aborted and <tt>Base#save</tt> will return +false+. If Base#save! is called it will raise a
- # ActiveRecord::RecordInvalid exception. Nothing will be appended to the errors object.
+ # <tt>ActiveRecord::RecordInvalid</tt> exception. Nothing will be appended to the errors object.
#
# == Canceling callbacks
#
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 8c50f3d1a3..6535121075 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1,7 +1,6 @@
require 'thread'
require 'thread_safe'
require 'monitor'
-require 'set'
module ActiveRecord
# Raised when a connection could not be obtained within the connection
@@ -10,6 +9,12 @@ module ActiveRecord
class ConnectionTimeoutError < ConnectionNotEstablished
end
+ # Raised when a pool was unable to get ahold of all its connections
+ # to perform a "group" action such as +ConnectionPool#disconnect!+
+ # or +ConnectionPool#clear_reloadable_connections!+.
+ class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
+ end
+
module ConnectionAdapters
# Connection pool base class for managing Active Record database
# connections.
@@ -63,6 +68,15 @@ module ActiveRecord
# connection at the end of a thread or a thread dies unexpectedly.
# Regardless of this setting, the Reaper will be invoked before every
# blocking wait. (Default nil, which means don't schedule the Reaper).
+ #
+ #--
+ # Synchronization policy:
+ # * all public methods can be called outside +synchronize+
+ # * access to these i-vars needs to be in +synchronize+:
+ # * @connections
+ # * @now_connecting
+ # * private methods that require being called in a +synchronize+ blocks
+ # are now explicitly documented
class ConnectionPool
# Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
# with which it shares a Monitor. But could be a generic Queue.
@@ -129,17 +143,15 @@ module ActiveRecord
# - ConnectionTimeoutError if +timeout+ is given and no element
# becomes available within +timeout+ seconds,
def poll(timeout = nil)
- synchronize do
- if timeout
- no_wait_poll || wait_poll(timeout)
- else
- no_wait_poll
- end
- end
+ synchronize { internal_poll(timeout) }
end
private
+ def internal_poll(timeout)
+ no_wait_poll || (timeout && wait_poll(timeout))
+ end
+
def synchronize(&block)
@lock.synchronize(&block)
end
@@ -193,6 +205,80 @@ module ActiveRecord
end
end
+ # Adds the ability to turn a basic fair FIFO queue into one
+ # biased to some thread.
+ module BiasableQueue # :nodoc:
+ class BiasedConditionVariable # :nodoc:
+ # semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+,
+ # +signal+ and +wait+ methods are only called while holding a lock
+ def initialize(lock, other_cond, preferred_thread)
+ @real_cond = lock.new_cond
+ @other_cond = other_cond
+ @preferred_thread = preferred_thread
+ @num_waiting_on_real_cond = 0
+ end
+
+ def broadcast
+ broadcast_on_biased
+ @other_cond.broadcast
+ end
+
+ def broadcast_on_biased
+ @num_waiting_on_real_cond = 0
+ @real_cond.broadcast
+ end
+
+ def signal
+ if @num_waiting_on_real_cond > 0
+ @num_waiting_on_real_cond -= 1
+ @real_cond
+ else
+ @other_cond
+ end.signal
+ end
+
+ def wait(timeout)
+ if Thread.current == @preferred_thread
+ @num_waiting_on_real_cond += 1
+ @real_cond
+ else
+ @other_cond
+ end.wait(timeout)
+ end
+ end
+
+ def with_a_bias_for(thread)
+ previous_cond = nil
+ new_cond = nil
+ synchronize do
+ previous_cond = @cond
+ @cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread)
+ end
+ yield
+ ensure
+ synchronize do
+ @cond = previous_cond if previous_cond
+ new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers
+ end
+ end
+ end
+
+ # Connections must be leased while holding the main pool mutex. This is
+ # an internal subclass that also +.leases+ returned connections while
+ # still in queue's critical section (queue synchronizes with the same
+ # +@lock+ as the main pool) so that a returned connection is already
+ # leased and there is no need to re-enter synchronized block.
+ class ConnectionLeasingQueue < Queue # :nodoc:
+ include BiasableQueue
+
+ private
+ def internal_poll(timeout)
+ conn = super
+ conn.lease if conn
+ conn
+ end
+ end
+
# Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
# A reaper instantiated with a nil frequency will never reap the
# connection pool.
@@ -220,7 +306,7 @@ module ActiveRecord
include MonitorMixin
- attr_accessor :automatic_reconnect, :checkout_timeout
+ attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
attr_reader :spec, :connections, :size, :reaper
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
@@ -241,56 +327,75 @@ module ActiveRecord
# default max pool size to 5
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
- # The cache of reserved connections mapped to threads
- @reserved_connections = ThreadSafe::Cache.new(:initial_capacity => @size)
+ # The cache of threads mapped to reserved connections, the sole purpose
+ # of the cache is to speed-up +connection+ method, it is not the authoritative
+ # registry of which thread owns which connection, that is tracked by
+ # +connection.owner+ attr on each +connection+ instance.
+ # The invariant works like this: if there is mapping of +thread => conn+,
+ # then that +thread+ does indeed own that +conn+, however an absence of a such
+ # mapping does not mean that the +thread+ doesn't own the said connection, in
+ # that case +conn.owner+ attr should be consulted.
+ # Access and modification of +@thread_cached_conns+ does not require
+ # synchronization.
+ @thread_cached_conns = ThreadSafe::Cache.new(:initial_capacity => @size)
@connections = []
@automatic_reconnect = true
- @available = Queue.new self
+ # Connection pool allows for concurrent (outside the main `synchronize` section)
+ # establishment of new connections. This variable tracks the number of threads
+ # currently in the process of independently establishing connections to the DB.
+ @now_connecting = 0
+
+ # A boolean toggle that allows/disallows new connections.
+ @new_cons_enabled = true
+
+ @available = ConnectionLeasingQueue.new self
end
# Retrieve the connection associated with the current thread, or call
# #checkout to obtain one if necessary.
#
# #connection can be called any number of times; the connection is
- # held in a hash keyed by the thread id.
+ # held in a cache keyed by a thread.
def connection
- # this is correctly done double-checked locking
- # (ThreadSafe::Cache's lookups have volatile semantics)
- @reserved_connections[current_connection_id] || synchronize do
- @reserved_connections[current_connection_id] ||= checkout
- end
+ @thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout
end
# Is there an open connection that is being used for the current thread?
+ #
+ # This method only works for connections that have been abtained through
+ # #connection or #with_connection methods, connections obtained through
+ # #checkout will not be detected by #active_connection?
def active_connection?
- synchronize do
- @reserved_connections.fetch(current_connection_id) {
- return false
- }.in_use?
- end
+ @thread_cached_conns[connection_cache_key(Thread.current)]
end
# Signal that the thread is finished with the current connection.
# #release_connection releases the connection-thread association
# and returns the connection to the pool.
- def release_connection(with_id = current_connection_id)
- synchronize do
- conn = @reserved_connections.delete(with_id)
- checkin conn if conn
+ #
+ # This method only works for connections that have been obtained through
+ # #connection or #with_connection methods, connections obtained through
+ # #checkout will not be automatically released.
+ def release_connection(owner_thread = Thread.current)
+ if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
+ checkin conn
end
end
- # If a connection already exists yield it to the block. If no connection
+ # If a connection obtained through #connection or #with_connection methods
+ # already exists yield it to the block. If no such connection
# exists checkout a connection, yield it to the block, and checkin the
# connection when finished.
def with_connection
- connection_id = current_connection_id
- fresh_connection = true unless active_connection?
- yield connection
+ unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
+ conn = connection
+ fresh_connection = true
+ end
+ yield conn
ensure
- release_connection(connection_id) if fresh_connection
+ release_connection if fresh_connection
end
# Returns true if a connection has already been opened.
@@ -299,32 +404,81 @@ module ActiveRecord
end
# Disconnects all connections in the pool, and clears the pool.
- def disconnect!
- synchronize do
- @reserved_connections.clear
- @connections.each do |conn|
- checkin conn
- conn.disconnect!
+ #
+ # Raises:
+ # - +ExclusiveConnectionTimeoutError+ if unable to gain ownership of all
+ # connections in the pool within a timeout interval (default duration is
+ # +spec.config[:checkout_timeout] * 2+ seconds).
+ def disconnect(raise_on_acquisition_timeout = true)
+ with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
+ synchronize do
+ @connections.each do |conn|
+ checkin conn
+ conn.disconnect!
+ end
+ @connections = []
+ @available.clear
end
- @connections = []
- @available.clear
end
end
- # Clears the cache which maps classes.
- def clear_reloadable_connections!
- synchronize do
- @reserved_connections.clear
- @connections.each do |conn|
- checkin conn
- conn.disconnect! if conn.requires_reloading?
- end
- @connections.delete_if(&:requires_reloading?)
- @available.clear
- @connections.each do |conn|
- @available.add conn
+ # Disconnects all connections in the pool, and clears the pool.
+ #
+ # The pool first tries to gain ownership of all connections, if unable to
+ # do so within a timeout interval (default duration is
+ # +spec.config[:checkout_timeout] * 2+ seconds), the pool is forcefully
+ # disconneted wihout any regard for other connection owning threads.
+ def disconnect!
+ disconnect(false)
+ end
+
+ # Clears the cache which maps classes and re-connects connections that
+ # require reloading.
+ #
+ # Raises:
+ # - +ExclusiveConnectionTimeoutError+ if unable to gain ownership of all
+ # connections in the pool within a timeout interval (default duration is
+ # +spec.config[:checkout_timeout] * 2+ seconds).
+ def clear_reloadable_connections(raise_on_acquisition_timeout = true)
+ num_new_conns_required = 0
+
+ with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
+ synchronize do
+ @connections.each do |conn|
+ checkin conn
+ conn.disconnect! if conn.requires_reloading?
+ end
+ @connections.delete_if(&:requires_reloading?)
+
+ @available.clear
+
+ if @connections.size < @size
+ # because of the pruning done by this method, we might be running
+ # low on connections, while threads stuck in queue are helpless
+ # (not being able to establish new connections for themselves),
+ # see also more detailed explanation in +remove+
+ num_new_conns_required = num_waiting_in_queue - @connections.size
+ end
+
+ @connections.each do |conn|
+ @available.add conn
+ end
end
end
+
+ bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
+ end
+
+ # Clears the cache which maps classes and re-connects connections that
+ # require reloading.
+ #
+ # The pool first tries to gain ownership of all connections, if unable to
+ # do so within a timeout interval (default duration is
+ # +spec.config[:checkout_timeout] * 2+ seconds), the pool forcefully
+ # clears the cache and reloads connections without any regard for other
+ # connection owning threads.
+ def clear_reloadable_connections!
+ clear_reloadable_connections(false)
end
# Check-out a database connection from the pool, indicating that you want
@@ -341,12 +495,8 @@ module ActiveRecord
#
# Raises:
# - ConnectionTimeoutError: no connection can be obtained from the pool.
- def checkout
- synchronize do
- conn = acquire_connection
- conn.lease
- checkout_and_verify(conn)
- end
+ def checkout(checkout_timeout = @checkout_timeout)
+ checkout_and_verify(acquire_connection(checkout_timeout))
end
# Check-in a database connection back into the pool, indicating that you
@@ -356,14 +506,12 @@ module ActiveRecord
# calling +checkout+ on this pool.
def checkin(conn)
synchronize do
- owner = conn.owner
+ remove_connection_from_thread_cache conn
conn.run_callbacks :checkin do
conn.expire
end
- release conn, owner
-
@available.add conn
end
end
@@ -371,14 +519,32 @@ module ActiveRecord
# Remove a connection from the connection pool. The connection will
# remain open and active but will no longer be managed by this pool.
def remove(conn)
+ needs_new_connection = false
+
synchronize do
+ remove_connection_from_thread_cache conn
+
@connections.delete conn
@available.delete conn
- release conn, conn.owner
-
- @available.add checkout_new_connection if @available.any_waiting?
+ # @available.any_waiting? => true means that prior to removing this
+ # conn, the pool was at its max size (@connections.size == @size)
+ # this would mean that any threads stuck waiting in the queue wouldn't
+ # know they could checkout_new_connection, so let's do it for them.
+ # Because condition-wait loop is encapsulated in the Queue class
+ # (that in turn is oblivious to ConnectionPool implementation), threads
+ # that are "stuck" there are helpless, they have no way of creating
+ # new connections and are completely reliant on us feeding available
+ # connections into the Queue.
+ needs_new_connection = @available.any_waiting?
end
+
+ # This is intentionally done outside of the synchronized section as we
+ # would like not to hold the main mutex while checking out new connections,
+ # thus there is some chance that needs_new_connection information is now
+ # stale, we can live with that (bulk_make_new_connections will make
+ # sure not to exceed the pool's @size limit).
+ bulk_make_new_connections(1) if needs_new_connection
end
# Recover lost connections for the pool. A lost connection can occur if
@@ -403,7 +569,118 @@ module ActiveRecord
end
end
+ def num_waiting_in_queue # :nodoc:
+ @available.num_waiting
+ end
+
private
+ #--
+ # this is unfortunately not concurrent
+ def bulk_make_new_connections(num_new_conns_needed)
+ num_new_conns_needed.times do
+ # try_to_checkout_new_connection will not exceed pool's @size limit
+ if new_conn = try_to_checkout_new_connection
+ # make the new_conn available to the starving threads stuck @available Queue
+ checkin(new_conn)
+ end
+ end
+ end
+
+ #--
+ # From the discussion on Github:
+ # https://github.com/rails/rails/pull/14938#commitcomment-6601951
+ # This hook-in method allows for easier monkey-patching fixes needed by
+ # JRuby users that use Fibers.
+ def connection_cache_key(thread)
+ thread
+ end
+
+ # Take control of all existing connections so a "group" action such as
+ # reload/disconnect can be performed safely. It is no longer enough to
+ # wrap it in +synchronize+ because some pool's actions are allowed
+ # to be performed outside of the main +synchronize+ block.
+ def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
+ with_new_connections_blocked do
+ attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
+ yield
+ end
+ end
+
+ def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
+ collected_conns = synchronize do
+ # account for our own connections
+ @connections.select {|conn| conn.owner == Thread.current}
+ end
+
+ newly_checked_out = []
+ timeout_time = Time.now + (@checkout_timeout * 2)
+
+ @available.with_a_bias_for(Thread.current) do
+ while true
+ synchronize do
+ return if collected_conns.size == @connections.size && @now_connecting == 0
+ remaining_timeout = timeout_time - Time.now
+ remaining_timeout = 0 if remaining_timeout < 0
+ conn = checkout_for_exclusive_access(remaining_timeout)
+ collected_conns << conn
+ newly_checked_out << conn
+ end
+ end
+ end
+ rescue ExclusiveConnectionTimeoutError
+ # `raise_on_acquisition_timeout == false` means we are directed to ignore any
+ # timeouts and are expected to just give up: we've obtained as many connections
+ # as possible, note that in a case like that we don't return any of the
+ # `newly_checked_out` connections.
+
+ if raise_on_acquisition_timeout
+ release_newly_checked_out = true
+ raise
+ end
+ rescue Exception # if something else went wrong
+ # this can't be a "naked" rescue, because we have should return conns
+ # even for non-StandardErrors
+ release_newly_checked_out = true
+ raise
+ ensure
+ if release_newly_checked_out && newly_checked_out
+ # releasing only those conns that were checked out in this method, conns
+ # checked outside this method (before it was called) are not for us to release
+ newly_checked_out.each {|conn| checkin(conn)}
+ end
+ end
+
+ #--
+ # Must be called in a synchronize block.
+ def checkout_for_exclusive_access(checkout_timeout)
+ checkout(checkout_timeout)
+ rescue ConnectionTimeoutError
+ # this block can't be easily moved into attempt_to_checkout_all_existing_connections's
+ # rescue block, because doing so would put it outside of synchronize section, without
+ # being in a critical section thread_report might become inaccurate
+ msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds"
+
+ thread_report = []
+ @connections.each do |conn|
+ unless conn.owner == Thread.current
+ thread_report << "#{conn} is owned by #{conn.owner}"
+ end
+ end
+
+ msg << " (#{thread_report.join(', ')})" if thread_report.any?
+
+ raise ExclusiveConnectionTimeoutError, msg
+ end
+
+ def with_new_connections_blocked
+ previous_value = nil
+ synchronize do
+ previous_value, @new_cons_enabled = @new_cons_enabled, false
+ end
+ yield
+ ensure
+ synchronize { @new_cons_enabled = previous_value }
+ end
# Acquire a connection by one of 1) immediately removing one
# from the queue of available connections, 2) creating a new
@@ -412,40 +689,78 @@ module ActiveRecord
#
# Raises:
# - ConnectionTimeoutError if a connection could not be acquired
- def acquire_connection
- if conn = @available.poll
+ #
+ #--
+ # Implementation detail: the connection returned by +acquire_connection+
+ # will already be "+connection.lease+ -ed" to the current thread.
+ def acquire_connection(checkout_timeout)
+ # NOTE: we rely on `@available.poll` and `try_to_checkout_new_connection` to
+ # `conn.lease` the returned connection (and to do this in a `synchronized`
+ # section), this is not the cleanest implementation, as ideally we would
+ # `synchronize { conn.lease }` in this method, but by leaving it to `@available.poll`
+ # and `try_to_checkout_new_connection` we can piggyback on `synchronize` sections
+ # of the said methods and avoid an additional `synchronize` overhead.
+ if conn = @available.poll || try_to_checkout_new_connection
conn
- elsif @connections.size < @size
- checkout_new_connection
else
reap
- @available.poll(@checkout_timeout)
+ @available.poll(checkout_timeout)
end
end
- def release(conn, owner)
- thread_id = owner.object_id
-
- if @reserved_connections[thread_id] == conn
- @reserved_connections.delete thread_id
- end
+ #--
+ # if owner_thread param is omitted, this must be called in synchronize block
+ def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
+ @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
end
+ alias_method :release, :remove_connection_from_thread_cache
def new_connection
- Base.send(spec.adapter_method, spec.config)
+ Base.send(spec.adapter_method, spec.config).tap do |conn|
+ conn.schema_cache = schema_cache.dup if schema_cache
+ end
+ end
+
+ # If the pool is not at a +@size+ limit, establish new connection. Connecting
+ # to the DB is done outside main synchronized section.
+ #--
+ # Implementation constraint: a newly established connection returned by this
+ # method must be in the +.leased+ state.
+ def try_to_checkout_new_connection
+ # first in synchronized section check if establishing new conns is allowed
+ # and increment @now_connecting, to prevent overstepping this pool's @size
+ # constraint
+ do_checkout = synchronize do
+ if @new_cons_enabled && (@connections.size + @now_connecting) < @size
+ @now_connecting += 1
+ end
+ end
+ if do_checkout
+ begin
+ # if successfully incremented @now_connecting establish new connection
+ # outside of synchronized section
+ conn = checkout_new_connection
+ ensure
+ synchronize do
+ if conn
+ adopt_connection(conn)
+ # returned conn needs to be already leased
+ conn.lease
+ end
+ @now_connecting -= 1
+ end
+ end
+ end
end
- def current_connection_id #:nodoc:
- Base.connection_id ||= Thread.current.object_id
+ def adopt_connection(conn)
+ conn.pool = self
+ @connections << conn
end
def checkout_new_connection
raise ConnectionNotEstablished unless @automatic_reconnect
-
- c = new_connection
- c.pool = self
- @connections << c
- c
+ new_connection
end
def checkout_and_verify(c)
@@ -618,7 +933,9 @@ module ActiveRecord
# A connection was established in an ancestor process that must have
# subsequently forked. We can't reuse the connection, but we can copy
# the specification and establish a new connection with it.
- establish_connection owner, ancestor_pool.spec
+ establish_connection(owner, ancestor_pool.spec).tap do |pool|
+ pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
+ end
else
owner_to_pool[owner.name] = nil
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
index c0a2111571..30b2fca2ca 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
@@ -18,9 +18,9 @@ module ActiveRecord
end
# Returns the maximum allowed length for an index name. This
- # limit is enforced by rails and Is less than or equal to
+ # limit is enforced by \Rails and is less than or equal to
# <tt>index_name_length</tt>. The gap between
- # <tt>index_name_length</tt> is to allow internal rails
+ # <tt>index_name_length</tt> is to allow internal \Rails
# operations to use prefixes in temporary operations.
def allowed_index_name_length
index_name_length
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 42c794c828..38dd9578fe 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -40,8 +40,9 @@ module ActiveRecord
# Returns a single value from a record
def select_value(arel, name = nil, binds = [])
- if result = select_one(arel, name, binds)
- result.values.first
+ arel, binds = binds_from_relation arel, binds
+ if result = select_rows(to_sql(arel, binds), name, binds).first
+ result.first
end
end
@@ -188,7 +189,7 @@ module ActiveRecord
# You should consult the documentation for your database to understand the
# semantics of these different levels:
#
- # * http://www.postgresql.org/docs/9.1/static/transaction-iso.html
+ # * http://www.postgresql.org/docs/current/static/transaction-iso.html
# * https://dev.mysql.com/doc/refman/5.6/en/set-transaction.html
#
# An <tt>ActiveRecord::TransactionIsolationError</tt> will be raised if:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 91c7298983..2c7409b2dc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -123,6 +123,8 @@ module ActiveRecord
'f'
end
+ # Quote date/time values for use in SQL input. Includes microseconds
+ # if the value is a Time responding to usec.
def quoted_date(value)
if value.acts_like?(:time)
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index f754df93b6..18d943f452 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -14,10 +14,6 @@ module ActiveRecord
send m, o
end
- def visit_AddColumn(o)
- "ADD #{accept(o)}"
- end
-
delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql, to: :@conn
private :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql
@@ -25,7 +21,7 @@ module ActiveRecord
def visit_AlterTable(o)
sql = "ALTER TABLE #{quote_table_name(o.name)} "
- sql << o.adds.map { |col| visit_AddColumn col }.join(' ')
+ sql << o.adds.map { |col| accept col }.join(' ')
sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(' ')
sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(' ')
end
@@ -37,6 +33,10 @@ module ActiveRecord
column_sql
end
+ def visit_AddColumnDefinition(o)
+ "ADD #{accept(o.column)}"
+ end
+
def visit_TableDefinition(o)
create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE "
create_sql << "#{quote_table_name(o.name)} "
@@ -70,6 +70,7 @@ module ActiveRecord
column_options[:after] = o.after
column_options[:auto_increment] = o.auto_increment
column_options[:primary_key] = o.primary_key
+ column_options[:collation] = o.collation
column_options
end
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 cb83d0022c..158b773e11 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -1,8 +1,3 @@
-require 'date'
-require 'set'
-require 'bigdecimal'
-require 'bigdecimal/util'
-
module ActiveRecord
module ConnectionAdapters #:nodoc:
# Abstract representation of an index definition on a table. Instances of
@@ -15,13 +10,16 @@ module ActiveRecord
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :sql_type) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type) #:nodoc:
def primary_key?
primary_key || type.to_sym == :primary_key
end
end
+ class AddColumnDefinition < Struct.new(:column) # :nodoc:
+ end
+
class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc:
end
@@ -227,7 +225,7 @@ module ActiveRecord
# The +type+ parameter is normally one of the migrations native types,
# which is one of the following:
# <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
- # <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
+ # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
# <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
# <tt>:binary</tt>, <tt>:boolean</tt>.
#
@@ -359,6 +357,7 @@ module ActiveRecord
def column(name, type, options = {})
name = name.to_s
type = type.to_sym
+ options = options.dup
if @columns_hash[name] && @columns_hash[name].primary_key?
raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
@@ -399,17 +398,12 @@ module ActiveRecord
column(:updated_at, :datetime, options)
end
- # Adds a reference. Optionally adds a +type+ column, if the
- # +:polymorphic+ option is provided. +references+ and +belongs_to+
- # are interchangeable. The reference column will be an +integer+ by default,
- # the +:type+ option can be used to specify a different type. A foreign
- # key will be created if the +:foreign_key+ option is passed.
+ # Adds a reference.
#
# t.references(:user)
- # t.references(:user, type: "string")
- # t.belongs_to(:supplier, polymorphic: true)
+ # t.belongs_to(:supplier, foreign_key: true)
#
- # See SchemaStatements#add_reference
+ # See SchemaStatements#add_reference for details of the options you can use.
def references(*args, **options)
args.each do |col|
ReferenceDefinition.new(col, **options).add_to(self)
@@ -433,6 +427,7 @@ module ActiveRecord
column.after = options[:after]
column.auto_increment = options[:auto_increment]
column.primary_key = type == :primary_key || options[:primary_key]
+ column.collation = options[:collation]
column
end
@@ -475,7 +470,7 @@ module ActiveRecord
def add_column(name, type, options)
name = name.to_s
type = type.to_sym
- @adds << @td.new_column_definition(name, type, options)
+ @adds << AddColumnDefinition.new(@td.new_column_definition(name, type, options))
end
end
@@ -642,15 +637,12 @@ module ActiveRecord
@base.rename_column(name, column_name, new_column_name)
end
- # Adds a reference. Optionally adds a +type+ column, if
- # <tt>:polymorphic</tt> option is provided.
+ # Adds a reference.
#
# t.references(:user)
- # t.references(:user, type: "string")
- # t.belongs_to(:supplier, polymorphic: true)
# t.belongs_to(:supplier, foreign_key: true)
#
- # See SchemaStatements#add_reference
+ # See SchemaStatements#add_reference for details of the options you can use.
def references(*args)
options = args.extract_options!
args.each do |ref_name|
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index 999cb0ec5a..b944a8631c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -27,20 +27,31 @@ module ActiveRecord
spec[:type] = schema_type(column)
spec[:null] = 'false' unless column.null
- limit = column.limit || native_database_types[column.type][:limit]
- spec[:limit] = limit.inspect if limit
- spec[:precision] = column.precision.inspect if column.precision
- spec[:scale] = column.scale.inspect if column.scale
+ if limit = schema_limit(column)
+ spec[:limit] = limit
+ end
+
+ if precision = schema_precision(column)
+ spec[:precision] = precision
+ end
+
+ if scale = schema_scale(column)
+ spec[:scale] = scale
+ end
default = schema_default(column) if column.has_default?
spec[:default] = default unless default.nil?
+ if collation = schema_collation(column)
+ spec[:collation] = collation
+ end
+
spec
end
# Lists the valid migration options
def migration_keys
- [:name, :limit, :precision, :scale, :default, :null]
+ [:name, :limit, :precision, :scale, :default, :null, :collation]
end
private
@@ -49,6 +60,19 @@ module ActiveRecord
column.type.to_s
end
+ def schema_limit(column)
+ limit = column.limit || native_database_types[column.type][:limit]
+ limit.inspect if limit
+ end
+
+ def schema_precision(column)
+ column.precision.inspect if column.precision
+ end
+
+ def schema_scale(column)
+ column.scale.inspect if column.scale
+ end
+
def schema_default(column)
type = lookup_cast_type_from_column(column)
default = type.deserialize(column.default)
@@ -56,6 +80,10 @@ module ActiveRecord
type.type_cast_for_schema(default)
end
end
+
+ def schema_collation(column)
+ column.collation.inspect if column.collation
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index ecb4868c13..ed19819d63 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -14,6 +14,10 @@ module ActiveRecord
{}
end
+ def table_options(table_name)
+ nil
+ end
+
# Truncates a table alias according to the limits of the current adapter.
def table_alias_for(table_name)
table_name[0...table_alias_length].tr('.', '_')
@@ -120,6 +124,8 @@ module ActiveRecord
# [<tt>:id</tt>]
# Whether to automatically add a primary key column. Defaults to true.
# Join tables for +has_and_belongs_to_many+ should set it to false.
+ #
+ # A Symbol can be used to specify the type of the generated primary key column.
# [<tt>:primary_key</tt>]
# The name of the primary key, if one is to be added automatically.
# Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
@@ -163,6 +169,19 @@ module ActiveRecord
# name varchar(80)
# )
#
+ # ====== Change the primary key column type
+ #
+ # create_table(:tags, id: :string) do |t|
+ # t.column :label, :string
+ # end
+ #
+ # generates:
+ #
+ # CREATE TABLE tags (
+ # id varchar PRIMARY KEY,
+ # label varchar
+ # )
+ #
# ====== Do not add a primary key column
#
# create_table(:categories_suppliers, id: false) do |t|
@@ -630,11 +649,21 @@ module ActiveRecord
indexes(table_name).detect { |i| i.name == index_name }
end
- # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
- # The reference column is an +integer+ by default, the <tt>:type</tt> option can be used to specify
- # a different type.
+ # Adds a reference. The reference column is an integer by default,
+ # the <tt>:type</tt> option can be used to specify a different type.
+ # Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
# <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
#
+ # The +options+ hash can include the following keys:
+ # [<tt>:type</tt>]
+ # The reference column type. Defaults to +:integer+.
+ # [<tt>:index</tt>]
+ # Add an appropriate index. Defaults to false.
+ # [<tt>:foreign_key</tt>]
+ # Add an appropriate foreign key. Defaults to false.
+ # [<tt>:polymorphic</tt>]
+ # Wether an additional +_type+ column should be added. Defaults to false.
+ #
# ====== Create a user_id integer column
#
# add_reference(:products, :user)
@@ -643,10 +672,6 @@ module ActiveRecord
#
# add_reference(:products, :user, type: :string)
#
- # ====== Create a supplier_id and supplier_type columns
- #
- # add_belongs_to(:products, :supplier, polymorphic: true)
- #
# ====== Create supplier_id, supplier_type columns and appropriate index
#
# add_reference(:products, :supplier, polymorphic: true, index: true)
@@ -915,13 +940,13 @@ module ActiveRecord
def add_index_options(table_name, column_name, options = {}) #:nodoc:
column_names = Array(column_name)
- index_name = index_name(table_name, column: column_names)
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
- index_type = options[:unique] ? "UNIQUE" : ""
index_type = options[:type].to_s if options.key?(:type)
+ index_type ||= options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
+ index_name ||= index_name(table_name, column: column_names)
max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
if options.key?(:algorithm)
@@ -1034,7 +1059,7 @@ module ActiveRecord
end
end
- def validate_index_length!(table_name, new_name)
+ def validate_index_length!(table_name, new_name) # :nodoc:
if new_name.length > allowed_index_name_length
raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index ae42e8ef8d..6d3a21a3dc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -1,13 +1,9 @@
-require 'date'
-require 'bigdecimal'
-require 'bigdecimal/util'
require 'active_record/type'
require 'active_support/core_ext/benchmark'
require 'active_record/connection_adapters/schema_cache'
require 'active_record/connection_adapters/sql_type_metadata'
require 'active_record/connection_adapters/abstract/schema_dumper'
require 'active_record/connection_adapters/abstract/schema_creation'
-require 'monitor'
require 'arel/collectors/bind'
require 'arel/collectors/sql_string'
@@ -70,7 +66,6 @@ module ActiveRecord
include DatabaseLimits
include QueryCache
include ActiveSupport::Callbacks
- include MonitorMixin
include ColumnDumper
SIMPLE_INT = /\A\d+\z/
@@ -141,12 +136,20 @@ module ActiveRecord
SchemaCreation.new self
end
+ # this method must only be called while holding connection pool's mutex
def lease
- synchronize do
- unless in_use?
- @owner = Thread.current
+ if in_use?
+ msg = 'Cannot lease connection, '
+ if @owner == Thread.current
+ msg << 'it is already leased by the current thread.'
+ else
+ msg << "it is already in use by a different thread: #{@owner}. " <<
+ "Current thread: #{Thread.current}."
end
+ raise ActiveRecordError, msg
end
+
+ @owner = Thread.current
end
def schema_cache=(cache)
@@ -154,6 +157,7 @@ module ActiveRecord
@schema_cache = cache
end
+ # this method must only be called while holding connection pool's mutex
def expire
@owner = nil
end
@@ -387,8 +391,8 @@ module ActiveRecord
end
end
- def new_column(name, default, sql_type_metadata = nil, null = true)
- Column.new(name, default, sql_type_metadata, null)
+ def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil)
+ Column.new(name, default, sql_type_metadata, null, default_function, collation)
end
def lookup_cast_type(sql_type) # :nodoc:
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 76aee452ca..c3206c6045 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,4 +1,3 @@
-require 'arel/visitors/bind_visitor'
require 'active_support/core_ext/string/strip'
module ActiveRecord
@@ -14,7 +13,7 @@ module ActiveRecord
end
class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
- attr_accessor :charset, :collation
+ attr_accessor :charset
end
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
@@ -28,7 +27,6 @@ module ActiveRecord
column.auto_increment = true
end
column.charset = options[:charset]
- column.collation = options[:collation]
column
end
@@ -44,10 +42,6 @@ module ActiveRecord
end
class SchemaCreation < AbstractAdapter::SchemaCreation
- def visit_AddColumn(o)
- add_column_position!(super, column_options(o))
- end
-
private
def visit_DropForeignKey(name)
@@ -67,6 +61,10 @@ module ActiveRecord
create_sql
end
+ def visit_AddColumnDefinition(o)
+ add_column_position!(super, column_options(o.column))
+ end
+
def visit_ChangeColumnDefinition(o)
change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}"
add_column_position!(change_column_sql, column_options(o.column))
@@ -75,7 +73,6 @@ module ActiveRecord
def column_options(o)
column_options = super
column_options[:charset] = o.charset
- column_options[:collation] = o.collation
column_options
end
@@ -99,8 +96,8 @@ module ActiveRecord
end
def index_in_create(table_name, column_name, options)
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
- "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
+ index_name, index_type, index_columns, _, _, index_using = @conn.add_index_options(table_name, column_name, options)
+ "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns}) "
end
end
@@ -124,24 +121,28 @@ module ActiveRecord
spec
end
- def prepare_column_options(column)
- spec = super
- spec.delete(:precision) if /time/ === column.sql_type && column.precision == 0
- spec.delete(:limit) if :boolean === column.type
+ private
+
+ def schema_limit(column)
+ super unless column.type == :boolean
+ end
+
+ def schema_precision(column)
+ super unless /time/ === column.sql_type && column.precision == 0
+ end
+
+ def schema_collation(column)
if column.collation && table_name = column.instance_variable_get(:@table_name)
@collation_cache ||= {}
@collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"]
- spec[:collation] = column.collation.inspect if column.collation != @collation_cache[table_name]
+ column.collation.inspect if column.collation != @collation_cache[table_name]
end
- spec
end
- def migration_keys
- super + [:collation]
- end
+ public
class Column < ConnectionAdapters::Column # :nodoc:
- delegate :strict, :collation, :extra, to: :sql_type_metadata, allow_nil: true
+ delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
def initialize(*)
super
@@ -195,12 +196,11 @@ module ActiveRecord
end
class MysqlTypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
- attr_reader :collation, :extra, :strict
+ attr_reader :extra, :strict
- def initialize(type_metadata, collation: "", extra: "", strict: false)
+ def initialize(type_metadata, extra: "", strict: false)
super(type_metadata)
@type_metadata = type_metadata
- @collation = collation
@extra = extra
@strict = strict
end
@@ -218,7 +218,7 @@ module ActiveRecord
protected
def attributes_for_hash
- [self.class, @type_metadata, collation, extra, strict]
+ [self.class, @type_metadata, extra, strict]
end
end
@@ -342,8 +342,8 @@ module ActiveRecord
raise NotImplementedError
end
- def new_column(field, default, sql_type_metadata = nil, null = true) # :nodoc:
- Column.new(field, default, sql_type_metadata, null)
+ def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
+ Column.new(field, default, sql_type_metadata, null, default_function, collation)
end
# Must return the MySQL error number from the exception, if the exception has an
@@ -566,8 +566,8 @@ module ActiveRecord
each_hash(result).map do |field|
field_name = set_field_encoding(field[:Field])
sql_type = field[:Type]
- type_metadata = fetch_type_metadata(sql_type, field[:Collation], field[:Extra])
- new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES")
+ type_metadata = fetch_type_metadata(sql_type, field[:Extra])
+ new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
end
end
end
@@ -634,7 +634,7 @@ module ActiveRecord
change_column table_name, column_name, column.sql_type, :default => default
end
- def change_column_null(table_name, column_name, null, default = nil)
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
column = column_for(table_name, column_name)
unless null || default.nil?
@@ -654,8 +654,8 @@ module ActiveRecord
end
def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
+ index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
end
def foreign_keys(table_name)
@@ -686,33 +686,25 @@ module ActiveRecord
end
end
+ def table_options(table_name)
+ create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
+
+ # strip create_definitions and partition_options
+ raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
+
+ # strip AUTO_INCREMENT
+ raw_table_options.sub(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
+ end
+
# Maps logical Rails types to MySQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
case type.to_s
when 'binary'
- case limit
- when 0..0xfff; "varbinary(#{limit})"
- when nil; "blob"
- when 0x1000..0xffffffff; "blob(#{limit})"
- else raise(ActiveRecordError, "No binary type has character length #{limit}")
- end
+ binary_to_sql(limit)
when 'integer'
- case limit
- when 1; 'tinyint'
- when 2; 'smallint'
- when 3; 'mediumint'
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
- when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
- end
+ integer_to_sql(limit)
when 'text'
- case limit
- when 0..0xff; 'tinytext'
- when nil, 0x100..0xffff; 'text'
- when 0x10000..0xffffff; 'mediumtext'
- when 0x1000000..0xffffffff; 'longtext'
- else raise(ActiveRecordError, "No text type has character length #{limit}")
- end
+ text_to_sql(limit)
else
super
end
@@ -826,21 +818,8 @@ module ActiveRecord
end
end
- def fetch_type_metadata(sql_type, collation = "", extra = "")
- MysqlTypeMetadata.new(super(sql_type), collation: collation, extra: extra, strict: strict_mode?)
- end
-
- # MySQL is too stupid to create a temporary table for use subquery, so we have
- # to give it some prompting in the form of a subsubquery. Ugh!
- def subquery_for(key, select)
- subsubselect = select.clone
- subsubselect.projections = [key]
-
- subselect = Arel::SelectManager.new(select.engine)
- subselect.project Arel.sql(key.name)
- # Materialized subquery by adding distinct
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
- subselect.from subsubselect.distinct.as('__active_record_temp')
+ def fetch_type_metadata(sql_type, extra = "")
+ MysqlTypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
end
def add_index_length(option_strings, column_names, options = {})
@@ -882,7 +861,7 @@ module ActiveRecord
def add_column_sql(table_name, column_name, type, options = {})
td = create_table_definition(table_name)
cd = td.new_column_definition(column_name, type, options)
- schema_creation.visit_AddColumn cd
+ schema_creation.accept(AddColumnDefinition.new(cd))
end
def change_column_sql(table_name, column_name, type, options = {})
@@ -924,8 +903,9 @@ module ActiveRecord
end
def add_index_sql(table_name, column_name, options = {})
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
+ index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ index_algorithm[0, 0] = ", " if index_algorithm.present?
+ "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
end
def remove_index_sql(table_name, options = {})
@@ -943,6 +923,19 @@ module ActiveRecord
private
+ # MySQL is too stupid to create a temporary table for use subquery, so we have
+ # to give it some prompting in the form of a subsubquery. Ugh!
+ def subquery_for(key, select)
+ subsubselect = select.clone
+ subsubselect.projections = [key]
+
+ subselect = Arel::SelectManager.new(select.engine)
+ subselect.project Arel.sql(key.name)
+ # Materialized subquery by adding distinct
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
+ subselect.from subsubselect.distinct.as('__active_record_temp')
+ end
+
def version
@version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i)
end
@@ -1009,6 +1002,36 @@ module ActiveRecord
TableDefinition.new(native_database_types, name, temporary, options, as)
end
+ def binary_to_sql(limit) # :nodoc:
+ case limit
+ when 0..0xfff; "varbinary(#{limit})"
+ when nil; "blob"
+ when 0x1000..0xffffffff; "blob(#{limit})"
+ else raise(ActiveRecordError, "No binary type has character length #{limit}")
+ end
+ end
+
+ def integer_to_sql(limit) # :nodoc:
+ case limit
+ when 1; 'tinyint'
+ when 2; 'smallint'
+ when 3; 'mediumint'
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
+ when 5..8; 'bigint'
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
+ end
+ end
+
+ def text_to_sql(limit) # :nodoc:
+ case limit
+ when 0..0xff; 'tinytext'
+ when nil, 0x100..0xffff; 'text'
+ when 0x10000..0xffffff; 'mediumtext'
+ when 0x1000000..0xffffffff; 'longtext'
+ else raise(ActiveRecordError, "No text type has character length #{limit}")
+ end
+ end
+
class MysqlString < Type::String # :nodoc:
def serialize(value)
case value
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index f4dda5154e..4b95b0681d 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -12,7 +12,7 @@ module ActiveRecord
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
end
- attr_reader :name, :null, :sql_type_metadata, :default, :default_function
+ attr_reader :name, :null, :sql_type_metadata, :default, :default_function, :collation
delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
@@ -22,12 +22,13 @@ module ActiveRecord
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
# +sql_type_metadata+ is various information about the type of the column
# +null+ determines if this column allows +NULL+ values.
- def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil)
+ def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil)
@name = name
@sql_type_metadata = sql_type_metadata
@null = null
@default = default
@default_function = default_function
+ @collation = collation
@table_name = nil
end
@@ -60,7 +61,7 @@ module ActiveRecord
protected
def attributes_for_hash
- [self.class, name, default, sql_type_metadata, null, default_function]
+ [self.class, name, default, sql_type_metadata, null, default_function, collation]
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 21631be25c..e97e82f056 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,6 +1,6 @@
require 'active_record/connection_adapters/abstract_mysql_adapter'
-gem 'mysql2', '~> 0.3.13'
+gem 'mysql2', '~> 0.3.18'
require 'mysql2'
module ActiveRecord
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 45b935f1d6..2ae462d773 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -5,8 +5,10 @@ require 'active_support/core_ext/hash/keys'
gem 'mysql', '~> 2.9'
require 'mysql'
-class Mysql
+class Mysql # :nodoc: all
class Time
+ # Used for casting DateTime fields to a MySQL friendly Time.
+ # This was documented in 48498da0dfed5239ea1eafb243ce47d7e3ce9e8e
def to_date
Date.new(year, month, day)
end
@@ -69,34 +71,10 @@ module ActiveRecord
ADAPTER_NAME = 'MySQL'.freeze
class StatementPool < ConnectionAdapters::StatementPool
- def initialize(connection, max = 1000)
- super
- @cache = Hash.new { |h,pid| h[pid] = {} }
- end
-
- def each(&block); cache.each(&block); end
- def key?(key); cache.key?(key); end
- def [](key); cache[key]; end
- def length; cache.length; end
- def delete(key); cache.delete(key); end
-
- def []=(sql, key)
- while @max <= cache.size
- cache.shift.last[:stmt].close
- end
- cache[sql] = key
- end
-
- def clear
- cache.each_value do |hash|
- hash[:stmt].close
- end
- cache.clear
- end
-
private
- def cache
- @cache[Process.pid]
+
+ def dealloc(stmt)
+ stmt[:stmt].close
end
end
@@ -414,8 +392,11 @@ module ActiveRecord
# place when an error occurs. To support older MySQL versions, we
# need to close the statement and delete the statement from the
# cache.
- stmt.close
- @statements.delete sql
+ if binds.empty?
+ stmt.close
+ else
+ @statements.delete sql
+ end
raise e
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
index be13ead120..bfa03fa136 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -8,7 +8,8 @@ module ActiveRecord
def serial?
return unless default_function
- %r{\Anextval\('(?<table_name>.+)_#{name}_seq'::regclass\)\z} === default_function
+ table_name = @table_name || '(?<table_name>.+)'
+ %r{\Anextval\('"?#{table_name}_#{name}_seq"?'::regclass\)\z} === default_function
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index b7755c4593..f175730551 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -40,8 +40,7 @@ module ActiveRecord
PGconn.quote_ident(name.to_s)
end
- # Quote date/time values for use in SQL input. Includes microseconds
- # if the value is a Time responding to usec.
+ # Quote date/time values for use in SQL input.
def quoted_date(value) #:nodoc:
if value.year <= 0
bce_year = format("%04d", -value.year + 1)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 168180cfd3..595c635fc0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -8,6 +8,13 @@ module ActiveRecord
o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array)
super
end
+
+ def add_column_options!(sql, options)
+ if options[:collation]
+ sql << " COLLATE \"#{options[:collation]}\""
+ end
+ super
+ end
end
module SchemaStatements
@@ -63,11 +70,7 @@ module ActiveRecord
# Returns the list of all tables in the schema search path or a specified schema.
def tables(name = nil)
- query(<<-SQL, 'SCHEMA').map { |row| row[0] }
- SELECT tablename
- FROM pg_tables
- WHERE schemaname = ANY (current_schemas(false))
- SQL
+ select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA')
end
# Returns true if table exists.
@@ -77,7 +80,7 @@ module ActiveRecord
name = Utils.extract_schema_qualified_name(name.to_s)
return false unless name.identifier
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
SELECT COUNT(*)
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
@@ -93,15 +96,12 @@ module ActiveRecord
# Returns true if schema exists.
def schema_exists?(name)
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
- SELECT COUNT(*)
- FROM pg_namespace
- WHERE nspname = '#{name}'
- SQL
+ select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", 'SCHEMA').to_i > 0
end
+ # Verifies existence of an index with a given name.
def index_name_exists?(table_name, index_name, default)
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
SELECT COUNT(*)
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
@@ -158,55 +158,48 @@ module ActiveRecord
# Returns the list of all column definitions for a table.
def columns(table_name)
# Limit, precision, and scale are all handled by the superclass.
- column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation|
oid = oid.to_i
fmod = fmod.to_i
type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
default_value = extract_value_from_default(default)
default_function = extract_default_function(default_value, default)
- new_column(column_name, default_value, type_metadata, !notnull, default_function)
+ new_column(column_name, default_value, type_metadata, !notnull, default_function, collation)
end
end
- def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil) # :nodoc:
- PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function)
+ def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
+ PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function, collation)
end
# Returns the current database name.
def current_database
- query('select current_database()', 'SCHEMA')[0][0]
+ select_value('select current_database()', 'SCHEMA')
end
# Returns the current schema name.
def current_schema
- query('SELECT current_schema', 'SCHEMA')[0][0]
+ select_value('SELECT current_schema', 'SCHEMA')
end
# Returns the current database encoding format.
def encoding
- query(<<-end_sql, 'SCHEMA')[0][0]
- SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
- WHERE pg_database.datname LIKE '#{current_database}'
- end_sql
+ select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
end
# Returns the current database collation.
def collation
- query(<<-end_sql, 'SCHEMA')[0][0]
- SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
- end_sql
+ select_value("SELECT datcollate FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
end
# Returns the current database ctype.
def ctype
- query(<<-end_sql, 'SCHEMA')[0][0]
- SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
- end_sql
+ select_value("SELECT datctype FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
end
# Returns an array of schema names.
def schema_names
- query(<<-SQL, 'SCHEMA').flatten
+ select_values(<<-SQL, 'SCHEMA')
SELECT nspname
FROM pg_namespace
WHERE nspname !~ '^pg_.*'
@@ -239,12 +232,12 @@ module ActiveRecord
# Returns the active schema search path.
def schema_search_path
- @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
+ @schema_search_path ||= select_value('SHOW search_path', 'SCHEMA')
end
# Returns the current client message level.
def client_min_messages
- query('SHOW client_min_messages', 'SCHEMA')[0][0]
+ select_value('SHOW client_min_messages', 'SCHEMA')
end
# Set the client message level.
@@ -262,10 +255,7 @@ module ActiveRecord
end
def serial_sequence(table, column)
- result = exec_query(<<-eosql, 'SCHEMA')
- SELECT pg_get_serial_sequence('#{table}', '#{column}')
- eosql
- result.rows.first.first
+ select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA')
end
# Sets the sequence of a table's primary key to the specified value.
@@ -276,9 +266,7 @@ module ActiveRecord
if sequence
quoted_sequence = quote_table_name(sequence)
- select_value <<-end_sql, 'SCHEMA'
- SELECT setval('#{quoted_sequence}', #{value})
- end_sql
+ select_value("SELECT setval('#{quoted_sequence}', #{value})", 'SCHEMA')
else
@logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
end
@@ -301,7 +289,7 @@ module ActiveRecord
if pk && sequence
quoted_sequence = quote_table_name(sequence)
- select_value <<-end_sql, 'SCHEMA'
+ select_value(<<-end_sql, 'SCHEMA')
SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
end_sql
end
@@ -363,7 +351,7 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table)
- pks = exec_query(<<-end_sql, 'SCHEMA').rows
+ pks = query(<<-end_sql, 'SCHEMA')
SELECT attr.attname
FROM pg_attribute attr
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
@@ -395,20 +383,20 @@ module ActiveRecord
rename_table_indexes(table_name, new_name)
end
- # Adds a new column to the named table.
- # See TableDefinition#column for details of the options you can use.
- def add_column(table_name, column_name, type, options = {})
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
clear_cache!
super
end
- # Changes the column of a table.
- def change_column(table_name, column_name, type, options = {})
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
clear_cache!
quoted_table_name = quote_table_name(table_name)
quoted_column_name = quote_column_name(column_name)
sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale], options[:array])
sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
+ if options[:collation]
+ sql << " COLLATE \"#{options[:collation]}\""
+ end
if options[:using]
sql << " USING #{options[:using]}"
elsif options[:cast_as]
@@ -437,7 +425,7 @@ module ActiveRecord
end
end
- def change_column_null(table_name, column_name, null, default = nil)
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
clear_cache!
unless null || default.nil?
column = column_for(table_name, column_name)
@@ -447,7 +435,7 @@ module ActiveRecord
end
# Renames a column in a table.
- def rename_column(table_name, column_name, new_column_name)
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
clear_cache!
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
rename_column_indexes(table_name, column_name, new_column_name)
@@ -462,6 +450,8 @@ module ActiveRecord
execute "DROP INDEX #{quote_table_name(index_name)}"
end
+ # Renames an index of a table. Raises error if length of new
+ # index name is greater than allowed limit.
def rename_index(table_name, old_name, new_name)
validate_index_length!(table_name, new_name)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 332ac9d88c..93fa3984e6 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -14,8 +14,6 @@ require "active_record/connection_adapters/postgresql/type_metadata"
require "active_record/connection_adapters/postgresql/utils"
require "active_record/connection_adapters/statement_pool"
-require 'arel/visitors/bind_visitor'
-
require 'ipaddr'
module ActiveRecord
@@ -68,11 +66,11 @@ module ActiveRecord
# defaults to true.
#
# Any further options are used as connection parameters to libpq. See
- # http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the
+ # http://www.postgresql.org/docs/current/static/libpq-connect.html for the
# list of parameters.
#
# In addition, default connection parameters of libpq can be set per environment variables.
- # See http://www.postgresql.org/docs/9.1/static/libpq-envars.html .
+ # See http://www.postgresql.org/docs/current/static/libpq-envars.html .
class PostgreSQLAdapter < AbstractAdapter
ADAPTER_NAME = 'PostgreSQL'.freeze
@@ -211,44 +209,18 @@ module ActiveRecord
def initialize(connection, max)
super
@counter = 0
- @cache = Hash.new { |h,pid| h[pid] = {} }
end
- def each(&block); cache.each(&block); end
- def key?(key); cache.key?(key); end
- def [](key); cache[key]; end
- def length; cache.length; end
-
def next_key
"a#{@counter + 1}"
end
def []=(sql, key)
- while @max <= cache.size
- dealloc(cache.shift.last)
- end
- @counter += 1
- cache[sql] = key
- end
-
- def clear
- cache.each_value do |stmt_key|
- dealloc stmt_key
- end
- cache.clear
- end
-
- def delete(sql_key)
- dealloc cache[sql_key]
- cache.delete sql_key
+ super.tap { @counter += 1 }
end
private
- def cache
- @cache[Process.pid]
- end
-
def dealloc(key)
@connection.query "DEALLOCATE #{key}" if connection_active?
end
@@ -452,7 +424,7 @@ module ActiveRecord
@connection.server_version
end
- # See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
+ # See http://www.postgresql.org/docs/current/static/errcodes-appendix.html
FOREIGN_KEY_VIOLATION = "23503"
UNIQUE_VIOLATION = "23505"
@@ -726,7 +698,7 @@ module ActiveRecord
end
# SET statements from :variables config hash
- # http://www.postgresql.org/docs/8.3/static/sql-set.html
+ # http://www.postgresql.org/docs/current/static/sql-set.html
variables = @config[:variables] || {}
variables.map do |k, v|
if v == ':default' || v == :default
@@ -770,9 +742,11 @@ module ActiveRecord
# - format_type includes the column size constraint, e.g. varchar(50)
# - ::regclass is a function that gives the id for a table name
def column_definitions(table_name) # :nodoc:
- exec_query(<<-end_sql, 'SCHEMA').rows
+ query(<<-end_sql, 'SCHEMA')
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
- pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
+ (SELECT c.collname FROM pg_collation c, pg_type t
+ WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation)
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index 37ff4e4613..981d5d7a3c 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -13,6 +13,14 @@ module ActiveRecord
@tables = {}
end
+ def initialize_dup(other)
+ super
+ @columns = @columns.dup
+ @columns_hash = @columns_hash.dup
+ @primary_keys = @primary_keys.dup
+ @tables = @tables.dup
+ end
+
def primary_keys(table_name)
@primary_keys[table_name] ||= table_exists?(table_name) ? connection.primary_key(table_name) : nil
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 7e184dd510..7faca9cb70 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -1,6 +1,5 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/connection_adapters/statement_pool'
-require 'arel/visitors/bind_visitor'
gem 'sqlite3', '~> 1.3.6'
require 'sqlite3'
@@ -78,37 +77,10 @@ module ActiveRecord
end
class StatementPool < ConnectionAdapters::StatementPool
- def initialize(connection, max)
- super
- @cache = Hash.new { |h,pid| h[pid] = {} }
- end
-
- def each(&block); cache.each(&block); end
- def key?(key); cache.key?(key); end
- def [](key); cache[key]; end
- def length; cache.length; end
-
- def []=(sql, key)
- while @max <= cache.size
- dealloc(cache.shift.last[:stmt])
- end
- cache[sql] = key
- end
-
- def clear
- cache.each_value do |hash|
- dealloc hash[:stmt]
- end
- cache.clear
- end
-
private
- def cache
- @cache[$$]
- end
def dealloc(stmt)
- stmt.close unless stmt.closed?
+ stmt[:stmt].close unless stmt[:stmt].closed?
end
end
@@ -450,7 +422,7 @@ module ActiveRecord
end
end
- def change_column_null(table_name, column_name, null, default = nil)
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
unless null || default.nil?
exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
end
diff --git a/activerecord/lib/active_record/connection_adapters/statement_pool.rb b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
index c6b1bc8b5b..82e9ef3d3d 100644
--- a/activerecord/lib/active_record/connection_adapters/statement_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
@@ -4,35 +4,53 @@ module ActiveRecord
include Enumerable
def initialize(connection, max = 1000)
+ @cache = Hash.new { |h,pid| h[pid] = {} }
@connection = connection
@max = max
end
- def each
- raise NotImplementedError
+ def each(&block)
+ cache.each(&block)
end
def key?(key)
- raise NotImplementedError
+ cache.key?(key)
end
def [](key)
- raise NotImplementedError
+ cache[key]
end
def length
- raise NotImplementedError
+ cache.length
end
- def []=(sql, key)
- raise NotImplementedError
+ def []=(sql, stmt)
+ while @max <= cache.size
+ dealloc(cache.shift.last)
+ end
+ cache[sql] = stmt
end
def clear
- raise NotImplementedError
+ cache.each_value do |stmt|
+ dealloc stmt
+ end
+ cache.clear
end
def delete(key)
+ dealloc cache[key]
+ cache.delete(key)
+ end
+
+ private
+
+ def cache
+ @cache[Process.pid]
+ end
+
+ def dealloc(stmt)
raise NotImplementedError
end
end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 24f5849e45..d6b661ff76 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -88,7 +88,7 @@ module ActiveRecord
end
def connection_id
- ActiveRecord::RuntimeRegistry.connection_id
+ ActiveRecord::RuntimeRegistry.connection_id ||= Thread.current.object_id
end
def connection_id=(connection_id)
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 1ad910c4bc..8a014e682e 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -88,9 +88,10 @@ module ActiveRecord
##
# :singleton-method:
# Specifies which database schemas to dump when calling db:structure:dump.
- # If :schema_search_path (the default), it will dumps any schemas listed in schema_search_path.
- # Use :all to always dumps all schemas regardless of the schema_search_path.
- # A string of comma separated schemas can also be used to pass a custom list of schemas.
+ # If the value is :schema_search_path (the default), any schemas listed in
+ # schema_search_path are dumped. Use :all to dump all schemas regardless
+ # of schema_search_path, or a string of comma separated schemas for a
+ # custom list.
mattr_accessor :dump_schemas, instance_writer: false
self.dump_schemas = :schema_search_path
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index ea88983917..2b99899e42 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -32,7 +32,7 @@ module ActiveRecord
# Conversation.active
# Conversation.archived
#
- # Of course, you can also query them directly if the scopes doesn't fit your
+ # Of course, you can also query them directly if the scopes don't fit your
# needs:
#
# Conversation.where(status: [:active, :archived])
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 98aee77557..0f1759abaa 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -71,9 +71,9 @@ module ActiveRecord
class RecordNotDestroyed < ActiveRecordError
attr_reader :record
- def initialize(record)
+ def initialize(message, record = nil)
@record = record
- super()
+ super(message)
end
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 24098f72dc..e613d157aa 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -198,14 +198,16 @@ module ActiveRecord
def subclass_from_attributes(attrs)
subclass_name = attrs.with_indifferent_access[inheritance_column]
- if subclass_name.present? && subclass_name != self.name
- subclass = subclass_name.safe_constantize
+ if subclass_name.present?
+ subclass = find_sti_class(subclass_name)
- unless descendants.include?(subclass)
- raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}")
- end
+ if subclass.name != self.name
+ unless descendants.include?(subclass)
+ raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}")
+ end
- subclass
+ subclass
+ end
end
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index a83b90a95f..192a456846 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -275,21 +275,6 @@ module ActiveRecord
# The phrase "Updating salaries..." would then be printed, along with the
# benchmark for the block when the block completes.
#
- # == About the schema_migrations table
- #
- # Rails versions 2.0 and prior used to create a table called
- # <tt>schema_info</tt> when using migrations. This table contained the
- # version of the schema as of the last applied migration.
- #
- # Starting with Rails 2.1, the <tt>schema_info</tt> table is
- # (automatically) replaced by the <tt>schema_migrations</tt> table, which
- # contains the version numbers of all the migrations applied.
- #
- # As a result, it is now possible to add migration files that are numbered
- # lower than the current schema version: when migrating up, those
- # never-applied "interleaved" migrations will be automatically applied, and
- # when migrating down, never-applied "interleaved" migrations will be skipped.
- #
# == Timestamped Migrations
#
# By default, Rails generates migrations that look like:
@@ -307,9 +292,8 @@ module ActiveRecord
#
# == Reversible Migrations
#
- # Starting with Rails 3.1, you will be able to define reversible migrations.
# Reversible migrations are migrations that know how to go +down+ for you.
- # You simply supply the +up+ logic, and the Migration system will figure out
+ # You simply supply the +up+ logic, and the Migration system figures out
# how to execute the down commands for you.
#
# To define a reversible migration, define the +change+ method in your
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 084ef397a8..07e0f134aa 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -147,8 +147,8 @@ module ActiveRecord
# has_many :posts
# accepts_nested_attributes_for :posts, reject_if: :reject_posts
#
- # def reject_posts(attributed)
- # attributed['title'].blank?
+ # def reject_posts(attributes)
+ # attributes['title'].blank?
# end
# end
#
@@ -166,6 +166,11 @@ module ActiveRecord
# member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!'
# member.posts.second.title # => '[UPDATED] other post'
#
+ # However, the above applies if the parent model is being updated as well.
+ # For example, If you wanted to create a +member+ named _joe_ and wanted to
+ # update the +posts+ at the same time, that would give an
+ # ActiveRecord::RecordNotFound error.
+ #
# By default the associated records are protected from being destroyed. If
# you want to destroy any of the associated records through the attributes
# hash, you have to enable it first using the <tt>:allow_destroy</tt>
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index a1e1073792..466175690e 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -148,7 +148,7 @@ module ActiveRecord
# Attributes marked as readonly are silently ignored if the record is
# being updated.
def save!(*args)
- create_or_update(*args) || raise(RecordNotSaved.new(nil, self))
+ create_or_update(*args) || raise(RecordNotSaved.new("Failed to save the record", self))
end
# Deletes the record in the database and freezes this instance to
@@ -193,7 +193,7 @@ module ActiveRecord
# and #destroy! raises ActiveRecord::RecordNotDestroyed.
# See ActiveRecord::Callbacks for further details.
def destroy!
- destroy || raise(ActiveRecord::RecordNotDestroyed, self)
+ destroy || raise(RecordNotDestroyed.new("Failed to destroy the record", self))
end
# Returns an instance of the specified +klass+ with the attributes of the
@@ -205,7 +205,9 @@ module ActiveRecord
# instance using the companies/company partial instead of clients/client.
#
# Note: The new instance will share a link to the same attributes as the original class.
- # So any change to the attributes in either instance will affect the other.
+ # Therefore the sti column value will still be the same.
+ # Any change to the attributes on either instance will affect both instances.
+ # If you want to change the sti column as well, use +becomes!+ instead.
def becomes(klass)
became = klass.new
became.instance_variable_set("@attributes", @attributes)
@@ -428,7 +430,7 @@ module ActiveRecord
self
end
- # Saves the record with the updated_at/on attributes set to the current time
+ # Saves the record with the updated_at/on attributes set to the current time
# or the time specified.
# Please note that no validation is performed and only the +after_touch+,
# +after_commit+ and +after_rollback+ callbacks are executed.
@@ -477,11 +479,23 @@ module ActiveRecord
changes[column] = write_attribute(column, time)
end
- changes[self.class.locking_column] = increment_lock if locking_enabled?
-
clear_attribute_changes(changes.keys)
primary_key = self.class.primary_key
- self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
+ scope = self.class.unscoped.where(primary_key => _read_attribute(primary_key))
+
+ if locking_enabled?
+ locking_column = self.class.locking_column
+ scope = scope.where(locking_column => _read_attribute(locking_column))
+ changes[locking_column] = increment_lock
+ end
+
+ result = scope.update_all(changes) == 1
+
+ if !result && locking_enabled?
+ raise ActiveRecord::StaleObjectError.new(self, "touch")
+ end
+
+ result
else
true
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 7e907beec0..5af64b717a 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -93,6 +93,7 @@ module ActiveRecord
cache = Marshal.load File.binread filename
if cache.version == ActiveRecord::Migrator.current_version
self.connection.schema_cache = cache
+ self.connection_pool.schema_cache = cache.dup
else
warn "Ignoring db/schema_cache.dump because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}."
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 2591e7492d..d168786e71 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -240,7 +240,7 @@ db_namespace = namespace :db do
end
desc 'Load a schema.rb file into the database'
- task :load => [:load_config] do
+ task :load => [:environment, :load_config] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA'])
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 4265afc0a5..5360db6a19 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -69,9 +69,11 @@ module ActiveRecord
def reflections
ref = {}
_reflections.each do |name, reflection|
- parent_name, parent_reflection = reflection.parent_reflection
- if parent_name
- ref[parent_name] = parent_reflection
+ parent_reflection = reflection.parent_reflection
+
+ if parent_reflection
+ parent_name = parent_reflection.name
+ ref[parent_name.to_s] = parent_reflection
else
ref[name] = reflection
end
@@ -196,7 +198,7 @@ module ActiveRecord
@scope = scope
@options = options
@active_record = active_record
- @klass = options[:class]
+ @klass = options[:anonymous_class]
@plural_name = active_record.pluralize_table_names ?
name.to_s.pluralize : name.to_s
end
@@ -204,7 +206,7 @@ module ActiveRecord
def autosave=(autosave)
@automatic_inverse_of = false
@options[:autosave] = autosave
- _, parent_reflection = self.parent_reflection
+ parent_reflection = self.parent_reflection
if parent_reflection
parent_reflection.autosave = autosave
end
@@ -272,7 +274,7 @@ module ActiveRecord
end
attr_reader :type, :foreign_type
- attr_accessor :parent_reflection # [:name, Reflection]
+ attr_accessor :parent_reflection # Reflection
def initialize(name, scope, options, active_record)
super
@@ -635,7 +637,7 @@ module ActiveRecord
def initialize(delegate_reflection)
@delegate_reflection = delegate_reflection
- @klass = delegate_reflection.options[:class]
+ @klass = delegate_reflection.options[:anonymous_class]
@source_reflection_name = delegate_reflection.options[:source]
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 6a3a56f1cc..6020aa238f 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -46,7 +46,7 @@ module ActiveRecord
# # returns the first item or returns a new instance (requires you call .save to persist against the database).
#
# Person.where(name: 'Spartacus', rating: 4).first_or_create
- # # returns the first item or creates it and returns it, available since Rails 3.2.1.
+ # # returns the first item or creates it and returns it.
#
# ==== Alternatives for +find+
#
@@ -57,10 +57,10 @@ module ActiveRecord
# # returns a chainable list of instances with only the mentioned fields.
#
# Person.where(name: 'Spartacus', rating: 4).ids
- # # returns an Array of ids, available since Rails 3.2.1.
+ # # returns an Array of ids.
#
# Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
- # # returns an Array of the required fields, available since Rails 3.1.
+ # # returns an Array of the required fields.
def find(*args)
if block_given?
to_a.find(*args) { |*block_args| yield(*block_args) }
@@ -77,16 +77,16 @@ module ActiveRecord
#
# Post.find_by name: 'Spartacus', rating: 4
# Post.find_by "published_at < ?", 2.weeks.ago
- def find_by(*args)
- where(*args).take
+ def find_by(arg, *args)
+ where(arg, *args).take
rescue RangeError
nil
end
# Like <tt>find_by</tt>, except that if no record is found, raises
# an <tt>ActiveRecord::RecordNotFound</tt> error.
- def find_by!(*args)
- where(*args).take!
+ def find_by!(arg, *args)
+ where(arg, *args).take!
rescue RangeError
raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range value"
end
@@ -111,23 +111,11 @@ module ActiveRecord
# Find the first record (or first N records if a parameter is supplied).
# If no order is defined it will order by primary key.
#
- # Person.first # returns the first object fetched by SELECT * FROM people
+ # Person.first # returns the first object fetched by SELECT * FROM people ORDER BY people.id LIMIT 1
# Person.where(["user_name = ?", user_name]).first
# Person.where(["user_name = :u", { u: user_name }]).first
# Person.order("created_on DESC").offset(5).first
- # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
- #
- # ==== Rails 3
- #
- # Person.first # SELECT "people".* FROM "people" LIMIT 1
- #
- # NOTE: Rails 3 may not order this query by the primary key and the order
- # will depend on the database implementation. In order to ensure that behavior,
- # use <tt>User.order(:id).first</tt> instead.
- #
- # ==== Rails 4
- #
- # Person.first # SELECT "people".* FROM "people" ORDER BY "people"."id" ASC LIMIT 1
+ # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3
#
def first(limit = nil)
if limit
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 65b607ff1c..c3054f1fe9 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/hash/keys'
-require "set"
module ActiveRecord
class Relation
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index f9b9e640ec..1f000b3f0f 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -135,7 +135,7 @@ module ActiveRecord
def predicates_except(columns)
predicates.reject do |node|
case node
- when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual
+ when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
columns.include?(subrelation.name.to_s)
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index c7f55ebaa1..c2567311bd 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -11,17 +11,15 @@ module ActiveRecord
protected
- # Accepts an array, hash, or string of SQL conditions and sanitizes
+ # Accepts an array or string of SQL conditions and sanitizes
# them into a valid SQL fragment for a WHERE clause.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
- # { name: "foo'bar", group_id: 4 } returns "name='foo''bar' and group_id='4'"
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
def sanitize_sql_for_conditions(condition, table_name = self.table_name)
return nil if condition.blank?
case condition
when Array; sanitize_sql_array(condition)
- when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
else condition
end
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index eaeaf0321b..c5910fa1ad 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -1,5 +1,4 @@
require 'stringio'
-require 'active_support/core_ext/big_decimal'
module ActiveRecord
# = Active Record Schema Dumper
@@ -131,6 +130,10 @@ HEADER
tbl.print ", id: false"
end
tbl.print ", force: :cascade"
+
+ table_options = @connection.table_options(table)
+ tbl.print ", options: #{table_options.inspect}" unless table_options.blank?
+
tbl.puts " do |t|"
# then dump all non-primary key columns
@@ -168,11 +171,11 @@ HEADER
tbl.puts
end
+ indexes(table, tbl)
+
tbl.puts " end"
tbl.puts
- indexes(table, tbl)
-
tbl.rewind
stream.print tbl.read
rescue => e
@@ -188,8 +191,7 @@ HEADER
if (indexes = @connection.indexes(table)).any?
add_index_statements = indexes.map do |index|
statement_parts = [
- "add_index #{remove_prefix_and_suffix(index.table).inspect}",
- index.columns.inspect,
+ "t.index #{index.columns.inspect}",
"name: #{index.name.inspect}",
]
statement_parts << 'unique: true' if index.unique
@@ -203,11 +205,10 @@ HEADER
statement_parts << "using: #{index.using.inspect}" if index.using
statement_parts << "type: #{index.type.inspect}" if index.type
- " #{statement_parts.join(', ')}"
+ " #{statement_parts.join(', ')}"
end
stream.puts add_index_statements.sort.join("\n")
- stream.puts
end
end
diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb
index a3023a0cb4..ca11853da7 100644
--- a/activerecord/lib/active_record/secure_token.rb
+++ b/activerecord/lib/active_record/secure_token.rb
@@ -13,7 +13,7 @@ module ActiveRecord
#
# user = User.new
# user.save
- # user.token # => "4kUgL2pdQMSCQtjE"
+ # user.token # => "pX27zsMN2ViQKta1bGfLmVJE"
# user.auth_token # => "77TMHrHJFvFDwodq8w7Ev2m7"
# user.regenerate_token # => true
# user.regenerate_auth_token # => true
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index eafbb2c249..bc80275a88 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -130,15 +130,21 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
end
def prepare_command_options(command)
- args = [command]
- args.concat(['--user', configuration['username']]) if configuration['username']
- args << "--password=#{configuration['password']}" if configuration['password']
- args.concat(['--default-character-set', configuration['encoding']]) if configuration['encoding']
- configuration.slice('host', 'port', 'socket').each do |k, v|
- args.concat([ "--#{k}", v.to_s ]) if v
- end
-
- args
+ args = {
+ 'host' => '--host',
+ 'port' => '--port',
+ 'socket' => '--socket',
+ 'username' => '--user',
+ 'password' => '--password',
+ 'encoding' => '--default-character-set',
+ 'sslca' => '--ssl-ca',
+ 'sslcert' => '--ssl-cert',
+ 'sslcapath' => '--ssl-capath',
+ 'sslcipher' => '--ssh-cipher',
+ 'sslkey' => '--ssl-key'
+ }.map { |opt, arg| "#{arg}=#{configuration[opt]}" if configuration[opt] }.compact
+
+ [command, *args]
end
end
end
diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb
index 2a1b04ac7f..c5040c6d3b 100644
--- a/activerecord/lib/active_record/type/integer.rb
+++ b/activerecord/lib/active_record/type/integer.rb
@@ -46,18 +46,21 @@ module ActiveRecord
def ensure_in_range(value)
unless range.cover?(value)
- raise RangeError, "#{value} is out of range for #{self.class} with limit #{limit || DEFAULT_LIMIT}"
+ raise RangeError, "#{value} is out of range for #{self.class} with limit #{_limit}"
end
end
def max_value
- limit = self.limit || DEFAULT_LIMIT
- 1 << (limit * 8 - 1) # 8 bits per byte with one bit for sign
+ 1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign
end
def min_value
-max_value
end
+
+ def _limit
+ self.limit || DEFAULT_LIMIT
+ end
end
end
end
diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
index 7a3c6f5e95..0d57de4d65 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -14,10 +14,9 @@ module ActiveRecord
protected
attr_reader :migration_action, :join_tables
- # sets the default migration template that is being used for the generation of the migration
- # depending on the arguments which would be sent out in the command line, the migration template
- # and the table name instance variables are setup.
-
+ # Sets the default migration template that is being used for the generation of the migration.
+ # Depending on command line arguments, the migration template and the table name instance
+ # variables are set up.
def set_local_assigns!
@migration_template = "migration.rb"
case file_name
@@ -56,6 +55,8 @@ module ActiveRecord
attributes.select { |a| !a.reference? && a.has_index? }
end
+ # A migration file name can only contain underscores (_), lowercase characters,
+ # and numbers 0-9. Any other file name will raise an IllegalMigrationNameError.
def validate_file_name!
unless file_name =~ /^[_a-z0-9]+$/
raise IllegalMigrationNameError.new(file_name)
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index 6577d56240..57eb5d0e18 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -59,6 +59,43 @@ class ActiveSchemaTest < ActiveRecord::TestCase
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
end
+ def test_index_in_create
+ def (ActiveRecord::Base.connection).table_exists?(*); false; end
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB"
+ actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
+ t.index :last_name, type: type
+ end
+ assert_equal expected, actual
+ end
+
+ expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)) ) ENGINE=InnoDB"
+ actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
+ t.index :last_name, length: 10, using: :btree
+ end
+ assert_equal expected, actual
+ end
+
+ def test_index_in_bulk_change
+ def (ActiveRecord::Base.connection).table_exists?(*); true; end
+ def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "ALTER TABLE `people` ADD #{type} INDEX `index_people_on_last_name` (`last_name`)"
+ actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t|
+ t.index :last_name, type: type
+ end
+ assert_equal expected, actual
+ end
+
+ expected = "ALTER TABLE `peaple` ADD INDEX `index_peaple_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY"
+ actual = ActiveRecord::Base.connection.change_table(:peaple, bulk: true) do |t|
+ t.index :last_name, length: 10, using: :btree, algorithm: :copy
+ end
+ assert_equal expected, actual
+ end
+
def test_drop_table
assert_equal "DROP TABLE `people`", drop_table(:people)
end
diff --git a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
index 340fc95503..345122b1ad 100644
--- a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
@@ -1,5 +1,4 @@
require "cases/helper"
-require 'models/person'
class MysqlCaseSensitivityTest < ActiveRecord::TestCase
class CollationTest < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/mysql/table_options_test.rb b/activerecord/test/cases/adapters/mysql/table_options_test.rb
new file mode 100644
index 0000000000..0e5b0e8aec
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/table_options_test.rb
@@ -0,0 +1,42 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class MysqlTableOptionsTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.drop_table "mysql_table_options", if_exists: true
+ end
+
+ test "table options with ENGINE" do
+ @connection.create_table "mysql_table_options", force: true, options: "ENGINE=MyISAM"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ENGINE=MyISAM}, options
+ end
+
+ test "table options with ROW_FORMAT" do
+ @connection.create_table "mysql_table_options", force: true, options: "ROW_FORMAT=REDUNDANT"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ROW_FORMAT=REDUNDANT}, options
+ end
+
+ test "table options with CHARSET" do
+ @connection.create_table "mysql_table_options", force: true, options: "CHARSET=utf8mb4"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{CHARSET=utf8mb4}, options
+ end
+
+ test "table options with COLLATE" do
+ @connection.create_table "mysql_table_options", force: true, options: "COLLATE=utf8mb4_bin"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{COLLATE=utf8mb4_bin}, options
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index e87cd3886a..0ea556d4fa 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -59,6 +59,43 @@ class ActiveSchemaTest < ActiveRecord::TestCase
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
end
+ def test_index_in_create
+ def (ActiveRecord::Base.connection).table_exists?(*); false; end
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB"
+ actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
+ t.index :last_name, type: type
+ end
+ assert_equal expected, actual
+ end
+
+ expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)) ) ENGINE=InnoDB"
+ actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
+ t.index :last_name, length: 10, using: :btree
+ end
+ assert_equal expected, actual
+ end
+
+ def test_index_in_bulk_change
+ def (ActiveRecord::Base.connection).table_exists?(*); true; end
+ def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "ALTER TABLE `people` ADD #{type} INDEX `index_people_on_last_name` (`last_name`)"
+ actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t|
+ t.index :last_name, type: type
+ end
+ assert_equal expected, actual
+ end
+
+ expected = "ALTER TABLE `peaple` ADD INDEX `index_peaple_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY"
+ actual = ActiveRecord::Base.connection.change_table(:peaple, bulk: true) do |t|
+ t.index :last_name, length: 10, using: :btree, algorithm: :copy
+ end
+ assert_equal expected, actual
+ end
+
def test_drop_table
assert_equal "DROP TABLE `people`", drop_table(:people)
end
diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
index 09bebf3071..ccf3d84a44 100644
--- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
@@ -1,5 +1,4 @@
require "cases/helper"
-require 'models/person'
class Mysql2CaseSensitivityTest < ActiveRecord::TestCase
class CollationTest < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/mysql2/table_options_test.rb b/activerecord/test/cases/adapters/mysql2/table_options_test.rb
new file mode 100644
index 0000000000..0e5b0e8aec
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/table_options_test.rb
@@ -0,0 +1,42 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class MysqlTableOptionsTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.drop_table "mysql_table_options", if_exists: true
+ end
+
+ test "table options with ENGINE" do
+ @connection.create_table "mysql_table_options", force: true, options: "ENGINE=MyISAM"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ENGINE=MyISAM}, options
+ end
+
+ test "table options with ROW_FORMAT" do
+ @connection.create_table "mysql_table_options", force: true, options: "ROW_FORMAT=REDUNDANT"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ROW_FORMAT=REDUNDANT}, options
+ end
+
+ test "table options with CHARSET" do
+ @connection.create_table "mysql_table_options", force: true, options: "CHARSET=utf8mb4"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{CHARSET=utf8mb4}, options
+ end
+
+ test "table options with COLLATE" do
+ @connection.create_table "mysql_table_options", force: true, options: "COLLATE=utf8mb4_bin"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{COLLATE=utf8mb4_bin}, options
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/collation_test.rb b/activerecord/test/cases/adapters/postgresql/collation_test.rb
new file mode 100644
index 0000000000..17ef5f304c
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/collation_test.rb
@@ -0,0 +1,53 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class PostgresqlCollationTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :postgresql_collations, force: true do |t|
+ t.string :string_c, collation: 'C'
+ t.text :text_posix, collation: 'POSIX'
+ end
+ end
+
+ def teardown
+ @connection.drop_table :postgresql_collations, if_exists: true
+ end
+
+ test "string column with collation" do
+ column = @connection.columns(:postgresql_collations).find { |c| c.name == 'string_c' }
+ assert_equal :string, column.type
+ assert_equal 'C', column.collation
+ end
+
+ test "text column with collation" do
+ column = @connection.columns(:postgresql_collations).find { |c| c.name == 'text_posix' }
+ assert_equal :text, column.type
+ assert_equal 'POSIX', column.collation
+ end
+
+ test "add column with collation" do
+ @connection.add_column :postgresql_collations, :title, :string, collation: 'C'
+
+ column = @connection.columns(:postgresql_collations).find { |c| c.name == 'title' }
+ assert_equal :string, column.type
+ assert_equal 'C', column.collation
+ end
+
+ test "change column with collation" do
+ @connection.add_column :postgresql_collations, :description, :string
+ @connection.change_column :postgresql_collations, :description, :text, collation: 'POSIX'
+
+ column = @connection.columns(:postgresql_collations).find { |c| c.name == 'description' }
+ assert_equal :text, column.type
+ assert_equal 'POSIX', column.collation
+ end
+
+ test "schema dump includes collation" do
+ output = dump_table_schema("postgresql_collations")
+ assert_match %r{t.string\s+"string_c",\s+collation: "C"$}, output
+ assert_match %r{t.text\s+"text_posix",\s+collation: "POSIX"$}, output
+ 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 95d00ab3a9..ba90c61d65 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -124,9 +124,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
where("id = :inc", :inc => counter)
}
- has_many :comments, :class => comments
+ has_many :comments, :anonymous_class => comments
}
- belongs_to :post, :class => posts, :inverse_of => false
+ belongs_to :post, :anonymous_class => posts, :inverse_of => false
}
assert_equal 0, counter
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 171cfbde44..2c4e2a875c 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -119,9 +119,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
developer_project = Class.new(ActiveRecord::Base) {
self.table_name = 'developers_projects'
- belongs_to :developer, :class => dev
+ belongs_to :developer, :anonymous_class => dev
}
- has_many :developer_projects, :class => developer_project, :foreign_key => 'developer_id'
+ has_many :developer_projects, :anonymous_class => developer_project, :foreign_key => 'developer_id'
}
dev = developer.first
named = Developer.find(dev.id)
@@ -140,13 +140,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
comments = Class.new(ActiveRecord::Base) {
self.table_name = 'comments'
self.inheritance_column = 'not_there'
- belongs_to :post, :class => post
+ belongs_to :post, :anonymous_class => post
default_scope -> {
counter += 1
where("id = :inc", :inc => counter)
}
}
- has_many :comments, :class => comments, :foreign_key => 'post_id'
+ has_many :comments, :anonymous_class => comments, :foreign_key => 'post_id'
}
assert_equal 0, counter
post = posts.first
@@ -2131,11 +2131,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
car = Car.create!
original_child = FailedBulb.create!(car: car)
- assert_raise(ActiveRecord::RecordNotDestroyed) do
+ error = assert_raise(ActiveRecord::RecordNotDestroyed) do
car.failed_bulbs = [FailedBulb.create!]
end
assert_equal [original_child], car.reload.failed_bulbs
+ assert_equal "Failed to destroy the record", error.message
end
test 'updates counter cache when default scope is given' do
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 5f52c65412..190cef55c4 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -84,11 +84,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
subscriber = make_model "Subscriber"
subscriber.primary_key = 'nick'
- subscription.belongs_to :book, class: book
- subscription.belongs_to :subscriber, class: subscriber
+ subscription.belongs_to :book, anonymous_class: book
+ subscription.belongs_to :subscriber, anonymous_class: subscriber
- book.has_many :subscriptions, class: subscription
- book.has_many :subscribers, through: :subscriptions, class: subscriber
+ book.has_many :subscriptions, anonymous_class: subscription
+ book.has_many :subscribers, through: :subscriptions, anonymous_class: subscriber
anonbook = book.first
namebook = Book.find anonbook.id
@@ -154,10 +154,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
lesson_student = make_model 'LessonStudent'
lesson_student.table_name = 'lessons_students'
- lesson_student.belongs_to :lesson, :class => lesson
- lesson_student.belongs_to :student, :class => student
- lesson.has_many :lesson_students, :class => lesson_student
- lesson.has_many :students, :through => :lesson_students, :class => student
+ lesson_student.belongs_to :lesson, :anonymous_class => lesson
+ lesson_student.belongs_to :student, :anonymous_class => student
+ lesson.has_many :lesson_students, :anonymous_class => lesson_student
+ lesson.has_many :students, :through => :lesson_students, :anonymous_class => student
[lesson, lesson_student, student]
end
@@ -1040,14 +1040,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
- def test_save_should_not_raise_exception_when_join_record_has_errors
- repair_validations(Categorization) do
- Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' }
- c = Category.create(:name => 'Fishing', :authors => [Author.first])
- c.save
- end
- end
-
def test_assign_array_to_new_record_builds_join_records
c = Category.new(:name => 'Fishing', :authors => [Author.first])
assert_equal 1, c.categorizations.size
@@ -1072,11 +1064,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
- def test_create_bang_returns_falsy_when_join_record_has_errors
+ def test_save_returns_falsy_when_join_record_has_errors
repair_validations(Categorization) do
Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' }
c = Category.new(:name => 'Fishing', :authors => [Author.first])
- assert !c.save
+ assert_not c.save
end
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 8f0d7bd037..80b5a0004d 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -43,7 +43,7 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
reference = Class.new(ActiveRecord::Base) {
self.table_name = "references"
def self.name; 'Reference'; end
- belongs_to :person, autosave: true, class: person
+ belongs_to :person, autosave: true, anonymous_class: person
}
u = person.create!(first_name: 'cool')
@@ -1278,6 +1278,16 @@ module AutosaveAssociationOnACollectionAssociationTests
assert_equal new_names, @pirate.reload.send(@association_name).map(&:name)
end
+ def test_should_update_children_when_autosave_is_true_and_parent_is_new_but_child_is_not
+ parrot = Parrot.create!(name: "Polly")
+ parrot.name = "Squawky"
+ pirate = Pirate.new(parrots: [parrot], catchphrase: "Arrrr")
+
+ pirate.save!
+
+ assert_equal "Squawky", parrot.reload.name
+ end
+
def test_should_automatically_validate_the_associated_models
@pirate.send(@association_name).each { |child| child.name = '' }
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 3ae4a6eade..73ac30e547 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -451,6 +451,7 @@ class CallbacksTest < ActiveRecord::TestCase
assert !david.save
exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! }
assert_equal exc.record, david
+ assert_equal "Failed to save the record", exc.message
end
david = ImmutableDeveloper.find(1)
@@ -494,6 +495,7 @@ class CallbacksTest < ActiveRecord::TestCase
assert !david.destroy
exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
assert_equal exc.record, david
+ assert_equal "Failed to destroy the record", exc.message
end
assert_not_nil ImmutableDeveloper.find_by_id(1)
diff --git a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
index 662e19f35e..580568c8ac 100644
--- a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
+++ b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
@@ -6,7 +6,7 @@ module ActiveRecord
class Pool < ConnectionPool
def insert_connection_for_test!(c)
synchronize do
- @connections << c
+ adopt_connection(c)
@available.add c
end
end
@@ -24,7 +24,9 @@ module ActiveRecord
def test_lease_twice
assert @adapter.lease, 'should lease adapter'
- assert_not @adapter.lease, 'should not lease adapter'
+ assert_raises(ActiveRecordError) do
+ @adapter.lease
+ end
end
def test_expire_mutates_in_use
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index b72f8ca88c..9b1865e8bb 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -46,6 +46,52 @@ module ActiveRecord
def test_connection_pools
assert_equal([@pool], @handler.connection_pools)
end
+
+ if Process.respond_to?(:fork)
+ def test_connection_pool_per_pid
+ object_id = ActiveRecord::Base.connection.object_id
+
+ rd, wr = IO.pipe
+ rd.binmode
+ wr.binmode
+
+ pid = fork {
+ rd.close
+ wr.write Marshal.dump ActiveRecord::Base.connection.object_id
+ wr.close
+ exit!
+ }
+
+ wr.close
+
+ Process.waitpid pid
+ assert_not_equal object_id, Marshal.load(rd.read)
+ rd.close
+ end
+
+ def test_retrieve_connection_pool_copies_schema_cache_from_ancestor_pool
+ @pool.schema_cache = @pool.connection.schema_cache
+ @pool.schema_cache.add('posts')
+
+ rd, wr = IO.pipe
+ rd.binmode
+ wr.binmode
+
+ pid = fork {
+ rd.close
+ pool = @handler.retrieve_connection_pool(@klass)
+ wr.write Marshal.dump pool.schema_cache.size
+ wr.close
+ exit!
+ }
+
+ wr.close
+
+ Process.waitpid pid
+ assert_equal @pool.schema_cache.size, Marshal.load(rd.read)
+ rd.close
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index bab624b78a..dff6ea0fb0 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -26,29 +26,6 @@ module ActiveRecord
assert ActiveRecord::Base.connection_handler.active_connections?
end
- if Process.respond_to?(:fork)
- def test_connection_pool_per_pid
- object_id = ActiveRecord::Base.connection.object_id
-
- rd, wr = IO.pipe
- rd.binmode
- wr.binmode
-
- pid = fork {
- rd.close
- wr.write Marshal.dump ActiveRecord::Base.connection.object_id
- wr.close
- exit!
- }
-
- wr.close
-
- Process.waitpid pid
- assert_not_equal object_id, Marshal.load(rd.read)
- rd.close
- end
- end
-
def test_app_delegation
manager = ConnectionManagement.new(@app)
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index aa50efc979..c905772193 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -100,7 +100,7 @@ module ActiveRecord
t = Thread.new { @pool.checkout }
# make sure our thread is in the timeout section
- Thread.pass until t.status == "sleep"
+ Thread.pass until @pool.num_waiting_in_queue == 1
connection = cs.first
connection.close
@@ -112,7 +112,7 @@ module ActiveRecord
t = Thread.new { @pool.checkout }
# make sure our thread is in the timeout section
- Thread.pass until t.status == "sleep"
+ Thread.pass until @pool.num_waiting_in_queue == 1
connection = cs.first
@pool.remove connection
@@ -234,7 +234,7 @@ module ActiveRecord
mutex.synchronize { errors << e }
end
}
- Thread.pass until t.status == "sleep"
+ Thread.pass until @pool.num_waiting_in_queue == i
t
end
@@ -271,7 +271,7 @@ module ActiveRecord
mutex.synchronize { errors << e }
end
}
- Thread.pass until t.status == "sleep"
+ Thread.pass until @pool.num_waiting_in_queue == i
t
end
@@ -341,6 +341,185 @@ module ActiveRecord
handler.establish_connection anonymous, nil
}
end
+
+ def test_pool_sets_connection_schema_cache
+ connection = pool.checkout
+ schema_cache = SchemaCache.new connection
+ schema_cache.add(:posts)
+ pool.schema_cache = schema_cache
+
+ pool.with_connection do |conn|
+ assert_not_same pool.schema_cache, conn.schema_cache
+ assert_equal pool.schema_cache.size, conn.schema_cache.size
+ assert_same pool.schema_cache.columns(:posts), conn.schema_cache.columns(:posts)
+ end
+
+ pool.checkin connection
+ end
+
+ def test_concurrent_connection_establishment
+ assert_operator @pool.connections.size, :<=, 1
+
+ all_threads_in_new_connection = ActiveSupport::Concurrency::Latch.new(@pool.size - @pool.connections.size)
+ all_go = ActiveSupport::Concurrency::Latch.new
+
+ @pool.singleton_class.class_eval do
+ define_method(:new_connection) do
+ all_threads_in_new_connection.release
+ all_go.await
+ super()
+ end
+ end
+
+ connecting_threads = []
+ @pool.size.times do
+ connecting_threads << Thread.new { @pool.checkout }
+ end
+
+ begin
+ Timeout.timeout(5) do
+ # the kernel of the whole test is here, everything else is just scaffolding,
+ # this latch will not be released unless conn. pool allows for concurrent
+ # connection creation
+ all_threads_in_new_connection.await
+ end
+ rescue Timeout::Error
+ flunk 'pool unable to establish connections concurrently or implementation has ' <<
+ 'changed, this test then needs to patch a different :new_connection method'
+ ensure
+ # clean up the threads
+ all_go.release
+ connecting_threads.map(&:join)
+ end
+ end
+
+ def test_non_bang_disconnect_and_clear_reloadable_connections_throw_exception_if_threads_dont_return_their_conns
+ @pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout
+ [:disconnect, :clear_reloadable_connections].each do |group_action_method|
+ @pool.with_connection do |connection|
+ assert_raises(ExclusiveConnectionTimeoutError) do
+ Thread.new { @pool.send(group_action_method) }.join
+ end
+ end
+ end
+ end
+
+ def test_disconnect_and_clear_reloadable_connections_attempt_to_wait_for_threads_to_return_their_conns
+ [:disconnect, :disconnect!, :clear_reloadable_connections, :clear_reloadable_connections!].each do |group_action_method|
+ begin
+ thread = timed_join_result = nil
+ @pool.with_connection do |connection|
+ thread = Thread.new { @pool.send(group_action_method) }
+
+ # give the other `thread` some time to get stuck in `group_action_method`
+ timed_join_result = thread.join(0.3)
+ # thread.join # => `nil` means the other thread hasn't finished running and is still waiting for us to
+ # release our connection
+ assert_nil timed_join_result
+
+ # assert that since this is within default timeout our connection hasn't been forcefully taken away from us
+ assert @pool.active_connection?
+ end
+ ensure
+ thread.join if thread && !timed_join_result # clean up the other thread
+ end
+ end
+ end
+
+ def test_bang_versions_of_disconnect_and_clear_reloadable_connections_if_unable_to_aquire_all_connections_proceed_anyway
+ @pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout
+ [:disconnect!, :clear_reloadable_connections!].each do |group_action_method|
+ @pool.with_connection do |connection|
+ Thread.new { @pool.send(group_action_method) }.join
+ # assert connection has been forcefully taken away from us
+ assert_not @pool.active_connection?
+ end
+ end
+ end
+
+ def test_disconnect_and_clear_reloadable_connections_are_able_to_preempt_other_waiting_threads
+ with_single_connection_pool do |pool|
+ [:disconnect, :disconnect!, :clear_reloadable_connections, :clear_reloadable_connections!].each do |group_action_method|
+ conn = pool.connection # drain the only available connection
+ second_thread_done = ActiveSupport::Concurrency::Latch.new
+
+ # create a first_thread and let it get into the FIFO queue first
+ first_thread = Thread.new do
+ pool.with_connection { second_thread_done.await }
+ end
+
+ # wait for first_thread to get in queue
+ Thread.pass until pool.num_waiting_in_queue == 1
+
+ # create a different, later thread, that will attempt to do a "group action",
+ # but because of the group action semantics it should be able to preempt the
+ # first_thread when a connection is made available
+ second_thread = Thread.new do
+ pool.send(group_action_method)
+ second_thread_done.release
+ end
+
+ # wait for second_thread to get in queue
+ Thread.pass until pool.num_waiting_in_queue == 2
+
+ # return the only available connection
+ pool.checkin(conn)
+
+ # if the second_thread is not able to preempt the first_thread,
+ # they will temporarily (until either of them timeouts with ConnectionTimeoutError)
+ # deadlock and a join(2) timeout will be reached
+ failed = true unless second_thread.join(2)
+
+ #--- post test clean up start
+ second_thread_done.release if failed
+
+ # after `pool.disconnect()` the first thread will be left stuck in queue, no need to wait for
+ # it to timeout with ConnectionTimeoutError
+ if (group_action_method == :disconnect || group_action_method == :disconnect!) && pool.num_waiting_in_queue > 0
+ pool.with_connection {} # create a new connection in case there are threads still stuck in a queue
+ end
+
+ first_thread.join
+ second_thread.join
+ #--- post test clean up end
+
+ flunk "#{group_action_method} is not able to preempt other waiting threads" if failed
+ end
+ end
+ end
+
+ def test_clear_reloadable_connections_creates_new_connections_for_waiting_threads_if_necessary
+ with_single_connection_pool do |pool|
+ conn = pool.connection # drain the only available connection
+ def conn.requires_reloading? # make sure it gets removed from the pool by clear_reloadable_connections
+ true
+ end
+
+ stuck_thread = Thread.new do
+ pool.with_connection {}
+ end
+
+ # wait for stuck_thread to get in queue
+ Thread.pass until pool.num_waiting_in_queue == 1
+
+ pool.clear_reloadable_connections
+
+ unless stuck_thread.join(2)
+ flunk 'clear_reloadable_connections must not let other connection waiting threads get stuck in queue'
+ end
+
+ assert_equal 0, pool.num_waiting_in_queue
+ end
+ end
+
+ private
+ def with_single_connection_pool
+ one_conn_spec = ActiveRecord::Base.connection_pool.spec.dup
+ one_conn_spec.config[:pool] = 1 # this is safe to do, because .dupped ConnectionSpecification also auto-dups its config
+ yield(pool = ConnectionPool.new(one_conn_spec))
+ ensure
+ pool.disconnect! if pool
+ end
end
end
end
diff --git a/activerecord/test/cases/disconnected_test.rb b/activerecord/test/cases/disconnected_test.rb
index 55f0e51717..c25089a420 100644
--- a/activerecord/test/cases/disconnected_test.rb
+++ b/activerecord/test/cases/disconnected_test.rb
@@ -21,7 +21,9 @@ class TestDisconnectedAdapter < ActiveRecord::TestCase
@connection.execute "SELECT count(*) from products"
@connection.disconnect!
assert_raises(ActiveRecord::StatementInvalid) do
- @connection.execute "SELECT count(*) from products"
+ silence_warnings do
+ @connection.execute "SELECT count(*) from products"
+ end
end
end
end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index f8acdcb51e..97ba178b4d 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -279,10 +279,10 @@ class HasManyThroughFixture < ActiveSupport::TestCase
treasure = make_model "Treasure"
pt.table_name = "parrots_treasures"
- pt.belongs_to :parrot, :class => parrot
- pt.belongs_to :treasure, :class => treasure
+ pt.belongs_to :parrot, :anonymous_class => parrot
+ pt.belongs_to :treasure, :anonymous_class => treasure
- parrot.has_many :parrot_treasures, :class => pt
+ parrot.has_many :parrot_treasures, :anonymous_class => pt
parrot.has_many :treasures, :through => :parrot_treasures
parrots = File.join FIXTURES_ROOT, 'parrots'
@@ -297,10 +297,10 @@ class HasManyThroughFixture < ActiveSupport::TestCase
parrot = make_model "Parrot"
treasure = make_model "Treasure"
- pt.belongs_to :parrot, :class => parrot
- pt.belongs_to :treasure, :class => treasure
+ pt.belongs_to :parrot, :anonymous_class => parrot
+ pt.belongs_to :treasure, :anonymous_class => treasure
- parrot.has_many :parrot_treasures, :class => pt
+ parrot.has_many :parrot_treasures, :anonymous_class => pt
parrot.has_many :treasures, :through => :parrot_treasures
parrots = File.join FIXTURES_ROOT, 'parrots'
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 3268555cb8..f67d85603a 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -7,15 +7,32 @@ require 'models/subscriber'
require 'models/vegetables'
require 'models/shop'
+module InheritanceTestHelper
+ def with_store_full_sti_class(&block)
+ assign_store_full_sti_class true, &block
+ end
+
+ def without_store_full_sti_class(&block)
+ assign_store_full_sti_class false, &block
+ end
+
+ def assign_store_full_sti_class(flag)
+ old_store_full_sti_class = ActiveRecord::Base.store_full_sti_class
+ ActiveRecord::Base.store_full_sti_class = flag
+ yield
+ ensure
+ ActiveRecord::Base.store_full_sti_class = old_store_full_sti_class
+ end
+end
+
class InheritanceTest < ActiveRecord::TestCase
+ include InheritanceTestHelper
fixtures :companies, :projects, :subscribers, :accounts, :vegetables
def test_class_with_store_full_sti_class_returns_full_name
- old = ActiveRecord::Base.store_full_sti_class
- ActiveRecord::Base.store_full_sti_class = true
- assert_equal 'Namespaced::Company', Namespaced::Company.sti_name
- ensure
- ActiveRecord::Base.store_full_sti_class = old
+ with_store_full_sti_class do
+ assert_equal 'Namespaced::Company', Namespaced::Company.sti_name
+ end
end
def test_class_with_blank_sti_name
@@ -33,39 +50,31 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_class_without_store_full_sti_class_returns_demodulized_name
- old = ActiveRecord::Base.store_full_sti_class
- ActiveRecord::Base.store_full_sti_class = false
- assert_equal 'Company', Namespaced::Company.sti_name
- ensure
- ActiveRecord::Base.store_full_sti_class = old
+ without_store_full_sti_class do
+ assert_equal 'Company', Namespaced::Company.sti_name
+ end
end
def test_should_store_demodulized_class_name_with_store_full_sti_class_option_disabled
- old = ActiveRecord::Base.store_full_sti_class
- ActiveRecord::Base.store_full_sti_class = false
- item = Namespaced::Company.new
- assert_equal 'Company', item[:type]
- ensure
- ActiveRecord::Base.store_full_sti_class = old
+ without_store_full_sti_class do
+ item = Namespaced::Company.new
+ assert_equal 'Company', item[:type]
+ end
end
def test_should_store_full_class_name_with_store_full_sti_class_option_enabled
- old = ActiveRecord::Base.store_full_sti_class
- ActiveRecord::Base.store_full_sti_class = true
- item = Namespaced::Company.new
- assert_equal 'Namespaced::Company', item[:type]
- ensure
- ActiveRecord::Base.store_full_sti_class = old
+ with_store_full_sti_class do
+ item = Namespaced::Company.new
+ assert_equal 'Namespaced::Company', item[:type]
+ end
end
def test_different_namespace_subclass_should_load_correctly_with_store_full_sti_class_option
- old = ActiveRecord::Base.store_full_sti_class
- ActiveRecord::Base.store_full_sti_class = true
- item = Namespaced::Company.create :name => "Wolverine 2"
- assert_not_nil Company.find(item.id)
- assert_not_nil Namespaced::Company.find(item.id)
- ensure
- ActiveRecord::Base.store_full_sti_class = old
+ with_store_full_sti_class do
+ item = Namespaced::Company.create name: "Wolverine 2"
+ assert_not_nil Company.find(item.id)
+ assert_not_nil Namespaced::Company.find(item.id)
+ end
end
def test_company_descends_from_active_record
@@ -204,10 +213,28 @@ class InheritanceTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'Account') }
end
+ def test_new_with_unrelated_namespaced_type
+ without_store_full_sti_class do
+ e = assert_raises ActiveRecord::SubclassNotFound do
+ Namespaced::Company.new(type: 'Firm')
+ end
+
+ assert_equal "Invalid single-table inheritance type: Namespaced::Firm is not a subclass of Namespaced::Company", e.message
+ end
+ end
+
+
def test_new_with_complex_inheritance
assert_nothing_raised { Client.new(type: 'VerySpecialClient') }
end
+ def test_new_without_storing_full_sti_class
+ without_store_full_sti_class do
+ item = Company.new(type: 'SpecialCo')
+ assert_instance_of Company::SpecialCo, item
+ end
+ end
+
def test_new_with_autoload_paths
path = File.expand_path('../../models/autoloadable', __FILE__)
ActiveSupport::Dependencies.autoload_paths << path
@@ -331,6 +358,7 @@ class InheritanceTest < ActiveRecord::TestCase
end
class InheritanceComputeTypeTest < ActiveRecord::TestCase
+ include InheritanceTestHelper
fixtures :companies
def setup
@@ -344,27 +372,26 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
end
def test_instantiation_doesnt_try_to_require_corresponding_file
- ActiveRecord::Base.store_full_sti_class = false
- foo = Firm.first.clone
- foo.type = 'FirmOnTheFly'
- foo.save!
+ without_store_full_sti_class do
+ foo = Firm.first.clone
+ foo.type = 'FirmOnTheFly'
+ foo.save!
- # Should fail without FirmOnTheFly in the type condition.
- assert_raise(ActiveRecord::RecordNotFound) { Firm.find(foo.id) }
+ # Should fail without FirmOnTheFly in the type condition.
+ assert_raise(ActiveRecord::RecordNotFound) { Firm.find(foo.id) }
- # Nest FirmOnTheFly in the test case where Dependencies won't see it.
- self.class.const_set :FirmOnTheFly, Class.new(Firm)
- assert_raise(ActiveRecord::SubclassNotFound) { Firm.find(foo.id) }
+ # Nest FirmOnTheFly in the test case where Dependencies won't see it.
+ self.class.const_set :FirmOnTheFly, Class.new(Firm)
+ assert_raise(ActiveRecord::SubclassNotFound) { Firm.find(foo.id) }
- # Nest FirmOnTheFly in Firm where Dependencies will see it.
- # This is analogous to nesting models in a migration.
- Firm.const_set :FirmOnTheFly, Class.new(Firm)
+ # Nest FirmOnTheFly in Firm where Dependencies will see it.
+ # This is analogous to nesting models in a migration.
+ Firm.const_set :FirmOnTheFly, Class.new(Firm)
- # And instantiate will find the existing constant rather than trying
- # to require firm_on_the_fly.
- assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find(foo.id) }
- ensure
- ActiveRecord::Base.store_full_sti_class = true
+ # And instantiate will find the existing constant rather than trying
+ # to require firm_on_the_fly.
+ assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find(foo.id) }
+ end
end
def test_sti_type_from_attributes_disabled_in_non_sti_class
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 9e4998a946..dbdcc84b7d 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -177,6 +177,16 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 1, p1.lock_version
end
+ def test_touch_stale_object
+ person = Person.create!(first_name: 'Mehmet Emin')
+ stale_person = Person.find(person.id)
+ person.update_attribute(:gender, 'M')
+
+ assert_raises(ActiveRecord::StaleObjectError) do
+ stale_person.touch
+ end
+ end
+
def test_lock_column_name_existing
t1 = LegacyThing.find(1)
t2 = LegacyThing.find(1)
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index 87348d0f90..1594f99852 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -130,4 +130,24 @@ module ActiveRecord
end
end
end
+else
+class ReferencesWithoutForeignKeySupportTest < ActiveRecord::TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table(:testing_parents, force: true)
+ end
+
+ teardown do
+ @connection.drop_table("testings", if_exists: true)
+ @connection.drop_table("testing_parents", if_exists: true)
+ end
+
+ test "ignores foreign keys defined with the table" do
+ @connection.create_table :testings do |t|
+ t.references :testing_parent, foreign_key: true
+ end
+
+ assert_includes @connection.tables, "testings"
+ end
+end
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 83be9a75d8..b8433f0bba 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -175,6 +175,14 @@ class PrimaryKeysTest < ActiveRecord::TestCase
dashboard = Dashboard.first
assert_equal '2', dashboard.id
end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_serial_with_quoted_sequence_name
+ column = MixedCaseMonkey.columns_hash[MixedCaseMonkey.primary_key]
+ assert_equal "nextval('\"mixed_case_monkeys_monkeyID_seq\"'::regclass)", column.default_function
+ assert column.serial?
+ end
+ end
end
class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 0cf44388fa..b8e2041b6d 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1657,6 +1657,10 @@ class RelationTest < ActiveRecord::TestCase
assert_sql(/^((?!ORDER).)*$/) { Post.all.find_by(author_id: 2) }
end
+ test "find_by requires at least one argument" do
+ assert_raises(ArgumentError) { Post.all.find_by }
+ end
+
test "find_by! with hash conditions returns the first matching record" do
assert_equal posts(:eager_other), Post.order(:id).find_by!(author_id: 2)
end
@@ -1679,6 +1683,10 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ test "find_by! requires at least one argument" do
+ assert_raises(ArgumentError) { Post.all.find_by! }
+ end
+
test "loaded relations cannot be mutated by multi value methods" do
relation = Post.all
relation.to_a
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 63612e33af..e6f0fe6f75 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -168,24 +168,24 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
def test_schema_dumps_index_columns_in_right_order
- index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip
+ index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_index/).first.strip
if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
- assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition
else
- assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition
end
end
def test_schema_dumps_partial_indices
- index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip
+ index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_partial_index/).first.strip
if current_adapter?(:PostgreSQLAdapter)
- assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition
+ assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition
elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter)
- assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
+ assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index?
- assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition
+ assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition
else
- assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index"', index_definition
+ assert_equal 't.index ["firm_id", "type"], name: "company_partial_index"', index_definition
end
end
@@ -232,8 +232,8 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dumps_index_type
output = standard_dump
- assert_match %r{add_index "key_tests", \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output
- assert_match %r{add_index "key_tests", \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output
+ assert_match %r{t\.index \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output
+ assert_match %r{t\.index \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output
end
end
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 4137b20c4a..0dbc60940e 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -153,6 +153,18 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected_7, received_7
end
+ def test_unscope_comparison_where_clauses
+ # unscoped for WHERE (`developers`.`id` <= 2)
+ expected = Developer.order('salary DESC').collect(&:name)
+ received = DeveloperOrderedBySalary.where(id: -Float::INFINITY..2).unscope(where: :id).collect { |dev| dev.name }
+ assert_equal expected, received
+
+ # unscoped for WHERE (`developers`.`id` < 2)
+ expected = Developer.order('salary DESC').collect(&:name)
+ received = DeveloperOrderedBySalary.where(id: -Float::INFINITY...2).unscope(where: :id).collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
def test_unscope_multiple_where_clauses
expected = Developer.order('salary DESC').collect(&:name)
received = DeveloperOrderedBySalary.where(name: 'Jamis').where(id: 1).unscope(where: [:name, :id]).collect(&:name)
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index f58535f044..8d69741a4a 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -283,12 +283,21 @@ module ActiveRecord
def test_structure_dump_with_port_number
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--port", "10000", "--result-file", filename, "--no-data", "test-db").returns(true)
+ Kernel.expects(:system).with("mysqldump", "--port=10000", "--result-file", filename, "--no-data", "test-db").returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(
@configuration.merge('port' => 10000),
filename)
end
+
+ def test_structure_dump_with_ssl
+ filename = "awesome-file.sql"
+ Kernel.expects(:system).with("mysqldump", "--ssl-ca=ca.crt", "--result-file", filename, "--no-data", "test-db").returns(true)
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(
+ @configuration.merge("sslca" => "ca.crt"),
+ filename)
+ end
end
class MySQLStructureLoadTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 7c89b4b9e8..5dab32995c 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -446,6 +446,17 @@ class TimestampTest < ActiveRecord::TestCase
toy = Toy.first
assert_equal [:created_at, :updated_at], toy.send(:all_timestamp_attributes_in_model)
end
+
+ def test_index_is_created_for_both_timestamps
+ ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
+ t.timestamps(:foos, null: true, index: true)
+ end
+
+ indexes = ActiveRecord::Base.connection.indexes('foos')
+ assert_equal ['created_at', 'updated_at'], indexes.flat_map(&:columns).sort
+ ensure
+ ActiveRecord::Base.connection.drop_table(:foos)
+ end
end
class TimestampsWithoutTransactionTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb
index 84fb05dd8e..0dcdbd0667 100644
--- a/activerecord/test/cases/type/integer_test.rb
+++ b/activerecord/test/cases/type/integer_test.rb
@@ -21,7 +21,7 @@ module ActiveRecord
type = Type::Integer.new
assert_nil type.cast([1,2])
assert_nil type.cast({1 => 2})
- assert_nil type.cast((1..2))
+ assert_nil type.cast(1..2)
end
test "casting ActiveRecord models" do
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 6961f8fd6f..67936e8e5d 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -26,6 +26,9 @@ class Company < AbstractCompany
def private_method
"I am Jack's innermost fears and aspirations"
end
+
+ class SpecialCo < Company
+ end
end
module Namespaced
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 008503bc24..872fa595b4 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,5 +1,16 @@
ActiveRecord::Schema.define do
+ enable_extension!('uuid-ossp', ActiveRecord::Base.connection)
+
+ create_table :uuid_parents, id: :uuid, force: true do |t|
+ t.string :name
+ end
+
+ create_table :uuid_children, id: :uuid, force: true do |t|
+ t.string :name
+ t.uuid :uuid_parent_id
+ end
+
%w(postgresql_times postgresql_oids defaults postgresql_timestamp_with_zones
postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name|
drop_table table_name, if_exists: true
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 7b42f8a4a5..66f8f1611d 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -6,20 +6,6 @@ ActiveRecord::Schema.define do
end
end
- #put adapter specific setup here
- case adapter_name
- when "PostgreSQL"
- enable_extension!('uuid-ossp', ActiveRecord::Base.connection)
- create_table :uuid_parents, id: :uuid, force: true do |t|
- t.string :name
- end
- create_table :uuid_children, id: :uuid, force: true do |t|
- t.string :name
- t.uuid :uuid_parent_id
- end
- end
-
-
# ------------------------------------------------------------------- #
# #
# Please keep these create table statements in alphabetical order #
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 3ad2392365..d6fa651e39 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,27 @@
+* Patch `Delegator` to work with `#try`.
+
+ Fixes #5790.
+
+ *Nate Smith*
+
+* Add `Integer#positive?` and `Integer#negative?` query methods
+ in the vein of `Fixnum#zero?`.
+
+ This makes it nicer to do things like `bunch_of_numbers.select(&:positive?)`.
+
+ *DHH*
+
+* Encoding `ActiveSupport::TimeWithZone` to YAML now preserves the timezone information.
+
+ Fixes #9183.
+
+ *Andrew White*
+
+* Added `ActiveSupport::TimeZone#strptime` to allow parsing times as if
+ from a given timezone.
+
+ *Paul A Jungwirth*
+
* `ActiveSupport::Callbacks#skip_callback` now raises an `ArgumentError` if
an unrecognized callback is removed.
@@ -23,7 +47,8 @@
*George Claghorn*
-* Deprecate `alias_method_chain` in favour of `Module#prepend` introduced in Ruby 2.0
+* Deprecate `alias_method_chain` in favour of `Module#prepend` introduced in
+ Ruby 2.0.
*Kir Shatrov*
@@ -32,12 +57,12 @@
*Todd Bealmear*
-* Fixed a problem where String#truncate_words would get stuck with a complex
+* Fixed a problem where `String#truncate_words` would get stuck with a complex
string.
*Henrik Nygren*
-* Fixed a roundtrip problem with AS::SafeBuffer where primitive-like strings
+* Fixed a roundtrip problem with `AS::SafeBuffer` where primitive-like strings
will be dumped as primitives:
Before:
@@ -84,7 +109,7 @@
*Ian Ker-Seymer*
-* Duplicate frozen array when assigning it to a HashWithIndifferentAccess so
+* Duplicate frozen array when assigning it to a `HashWithIndifferentAccess` so
that it doesn't raise a `RuntimeError` when calling `map!` on it in `convert_value`.
Fixes #18550.
@@ -113,7 +138,7 @@
* Add `#on_weekend?`, `#next_weekday`, `#prev_weekday` methods to `Date`,
`Time`, and `DateTime`.
- `#on_weekend?` returns true if the receiving date/time falls on a Saturday
+ `#on_weekend?` returns `true` if the receiving date/time falls on a Saturday
or Sunday.
`#next_weekday` returns a new date/time representing the next day that does
@@ -167,13 +192,13 @@
`Callbacks::CallbackChain.halt_and_display_warning_on_return_false`, will
either not work at all or display a deprecation warning.
-* Add Callbacks::CallbackChain.halt_and_display_warning_on_return_false
+* Add `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`
Setting `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`
- to true will let an app support the deprecated way of halting callback
+ to `true` will let an app support the deprecated way of halting callback
chains by returning `false`.
- Setting the value to false will tell the app to ignore any `false` value
+ Setting the value to `false` will tell the app to ignore any `false` value
returned by callbacks, and only halt the chain upon `throw(:abort)`.
The value can also be set with the Rails configuration option
@@ -186,7 +211,7 @@
*claudiob*
-* Changes arguments and default value of CallbackChain's :terminator option
+* Changes arguments and default value of CallbackChain's `:terminator` option
Chains of callbacks defined without an explicit `:terminator` option will
now be halted as soon as a `before_` callback throws `:abort`.
diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc
index a6424a353a..cd72f53821 100644
--- a/activesupport/README.rdoc
+++ b/activesupport/README.rdoc
@@ -10,7 +10,7 @@ outside of Rails.
The latest version of Active Support can be installed with RubyGems:
- % [sudo] gem install activesupport
+ % gem install activesupport
Source code can be downloaded as part of the Rails project on GitHub:
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 814fd288cf..e8ab3a7db5 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -638,7 +638,7 @@ module ActiveSupport
# set_callback :save, :after, :after_meth, if: :condition
# set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
#
- # The second arguments indicates whether the callback is to be run +:before+,
+ # The second argument indicates whether the callback is to be run +:before+,
# +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
# means the first example above can also be written as:
#
diff --git a/activesupport/lib/active_support/core_ext/array/inquiry.rb b/activesupport/lib/active_support/core_ext/array/inquiry.rb
index de623c466c..e8f44cc378 100644
--- a/activesupport/lib/active_support/core_ext/array/inquiry.rb
+++ b/activesupport/lib/active_support/core_ext/array/inquiry.rb
@@ -1,3 +1,5 @@
+require 'active_support/array_inquirer'
+
class Array
# Wraps the array in an +ArrayInquirer+ object, which gives a friendlier way
# to check its string-like contents.
diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb
index 152eb02218..b611d34c27 100644
--- a/activesupport/lib/active_support/core_ext/array/wrap.rb
+++ b/activesupport/lib/active_support/core_ext/array/wrap.rb
@@ -3,7 +3,7 @@ class Array
#
# Specifically:
#
- # * If the argument is +nil+ an empty list is returned.
+ # * If the argument is +nil+ an empty array is returned.
# * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned.
# * Otherwise, returns an array with the argument as its single element.
#
@@ -15,12 +15,13 @@ class Array
#
# * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt>
# moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns
- # +nil+ right away.
+ # an array with the argument as its single element right away.
# * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt>
# raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
- # * It does not call +to_a+ on the argument, but returns an empty array if argument is +nil+.
+ # * It does not call +to_a+ on the argument, if the argument does not respond to +to_ary+
+ # it returns an array with the argument as its single element.
#
- # The second point is easily explained with some enumerables:
+ # The last point is easily explained with some enumerables:
#
# Array(foo: :bar) # => [[:foo, :bar]]
# Array.wrap(foo: :bar) # => [{:foo=>:bar}]
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
index 9525c10112..01153606c9 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -125,9 +125,21 @@ module DateAndTime
alias :at_beginning_of_year :beginning_of_year
# Returns a new date/time representing the given day in the next week.
+ #
+ # today = Date.today # => Thu, 07 May 2015
+ # today.next_week # => Mon, 11 May 2015
+ #
# The +given_day_in_next_week+ defaults to the beginning of the week
# which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+
- # when set. +DateTime+ objects have their time set to 0:00 unless +same_time+ is true.
+ # when set.
+ #
+ # today = Date.today # => Thu, 07 May 2015
+ # today.next_week(:friday) # => Fri, 15 May 2015
+ #
+ # +DateTime+ objects have their time set to 0:00 unless +same_time+ is true.
+ #
+ # now = Time.current # => Thu, 07 May 2015 13:31:16 UTC +00:00
+ # now.next_week # => Mon, 11 May 2015 00:00:00 UTC +00:00
def next_week(given_day_in_next_week = Date.beginning_of_week, same_time: false)
result = first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week)))
same_time ? copy_time_to(result) : result
diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb
index a4c40b25ff..b6934b9c54 100644
--- a/activesupport/lib/active_support/core_ext/module/aliasing.rb
+++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb
@@ -1,4 +1,7 @@
class Module
+ # NOTE: This method is deprecated. Please use <tt>Module#prepend</tt> that
+ # comes with Ruby 2.0 or newer instead.
+ #
# Encapsulates the common pattern of:
#
# alias_method :foo_without_feature, :foo
diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb
index a6bc0624be..bcdc3eace2 100644
--- a/activesupport/lib/active_support/core_ext/numeric.rb
+++ b/activesupport/lib/active_support/core_ext/numeric.rb
@@ -1,3 +1,4 @@
require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
+require 'active_support/core_ext/numeric/inquiry'
require 'active_support/core_ext/numeric/conversions'
diff --git a/activesupport/lib/active_support/core_ext/numeric/inquiry.rb b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb
new file mode 100644
index 0000000000..7e7ac1b0b2
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb
@@ -0,0 +1,26 @@
+unless 1.respond_to?(:positive?) # TODO: Remove this file when we drop support to ruby < 2.3
+class Numeric
+ # Returns true if the number is positive.
+ #
+ # 1.positive? # => true
+ # 0.positive? # => false
+ # -1.positive? # => false
+ def positive?
+ self > 0
+ end
+
+ # Returns true if the number is negative.
+ #
+ # -1.negative? # => true
+ # 0.negative? # => false
+ # 1.negative? # => false
+ def negative?
+ self < 0
+ end
+end
+
+class Complex
+ undef :positive?
+ undef :negative?
+end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
index 0191d2e973..ad5b2af161 100644
--- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -40,6 +40,7 @@ class Hash
# dup[:a][:c] # => "c"
def deep_dup
each_with_object(dup) do |(key, value), hash|
+ hash.delete(key)
hash[key.deep_dup] = value.deep_dup
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index e0f70b9caa..69be6c4abc 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -1,4 +1,34 @@
+require 'delegate'
+
+module ActiveSupport
+ module Tryable #:nodoc:
+ def try(*a, &b)
+ try!(*a, &b) if a.empty? || respond_to?(a.first)
+ end
+
+ def try!(*a, &b)
+ if a.empty? && block_given?
+ if b.arity.zero?
+ instance_eval(&b)
+ else
+ yield self
+ end
+ else
+ public_send(*a, &b)
+ end
+ end
+ end
+end
+
class Object
+ include ActiveSupport::Tryable
+
+ ##
+ # :method: try
+ #
+ # :call-seq:
+ # try(*a, &b)
+ #
# Invokes the public method whose name goes as first argument just like
# +public_send+ does, except that if the receiver does not respond to it the
# call returns +nil+ rather than raising an exception.
@@ -56,30 +86,40 @@ class Object
#
# Please also note that +try+ is defined on +Object+. Therefore, it won't work
# with instances of classes that do not have +Object+ among their ancestors,
- # like direct subclasses of +BasicObject+. For example, using +try+ with
- # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on
- # the delegator itself.
- def try(*a, &b)
- try!(*a, &b) if a.empty? || respond_to?(a.first)
- end
+ # like direct subclasses of +BasicObject+.
+ ##
+ # :method: try!
+ #
+ # :call-seq:
+ # try!(*a, &b)
+ #
# Same as #try, but raises a NoMethodError exception if the receiver is
# not +nil+ and does not implement the tried method.
#
# "a".try!(:upcase) # => "A"
# nil.try!(:upcase) # => nil
# 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum
- def try!(*a, &b)
- if a.empty? && block_given?
- if b.arity.zero?
- instance_eval(&b)
- else
- yield self
- end
- else
- public_send(*a, &b)
- end
- end
+end
+
+class Delegator
+ include ActiveSupport::Tryable
+
+ ##
+ # :method: try
+ #
+ # :call-seq:
+ # try(a*, &b)
+ #
+ # See Object#try
+
+ ##
+ # :method: try!
+ #
+ # :call-seq:
+ # try!(a*, &b)
+ #
+ # See Object#try!
end
class NilClass
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index a03a66b96b..dfdb8034e5 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -25,15 +25,17 @@ module ActiveSupport
end
end
- # This DeprecatedObjectProxy transforms object to deprecated object.
+ # DeprecatedObjectProxy transforms an object into a deprecated one. It
+ # takes an object, a deprecation message and optionally a deprecator. The
+ # deprecator defaults to +ActiveSupport::Deprecator+ if none is specified.
#
- # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!")
- # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!", deprecator_instance)
+ # deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, "This object is now deprecated")
+ # # => #<Object:0x007fb9b34c34b0>
#
- # When someone executes any method except +inspect+ on proxy object this will
- # trigger +warn+ method on +deprecator_instance+.
- #
- # Default deprecator is <tt>ActiveSupport::Deprecation</tt>
+ # deprecated_object.to_s
+ # DEPRECATION WARNING: This object is now deprecated.
+ # (Backtrace)
+ # # => "#<Object:0x007fb9b34c34b0>"
class DeprecatedObjectProxy < DeprecationProxy
def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance)
@object = object
@@ -51,13 +53,16 @@ module ActiveSupport
end
end
- # This DeprecatedInstanceVariableProxy transforms instance variable to
- # deprecated instance variable.
+ # DeprecatedInstanceVariableProxy transforms an instance variable into a
+ # deprecated one. It takes an instance of a class, a method on that class
+ # and an instance variable. It optionally takes a deprecator as the last
+ # argument. The deprecator defaults to +ActiveSupport::Deprecator+ if none
+ # is specified.
#
# class Example
- # def initialize(deprecator)
- # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator)
- # @_request = :a_request
+ # def initialize
+ # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request)
+ # @_request = :special_request
# end
#
# def request
@@ -69,12 +74,17 @@ module ActiveSupport
# end
# end
#
- # When someone execute any method on @request variable this will trigger
- # +warn+ method on +deprecator_instance+ and will fetch <tt>@_request</tt>
- # variable via +request+ method and execute the same method on non-proxy
- # instance variable.
+ # example = Example.new
+ # # => #<Example:0x007fb9b31090b8 @_request=:special_request, @request=:special_request>
+ #
+ # example.old_request.to_s
+ # # => DEPRECATION WARNING: @request is deprecated! Call request.to_s instead of
+ # @request.to_s
+ # (Bactrace information…)
+ # "special_request"
#
- # Default deprecator is <tt>ActiveSupport::Deprecation</tt>.
+ # example.request.to_s
+ # # => "special_request"
class DeprecatedInstanceVariableProxy < DeprecationProxy
def initialize(instance, method, var = "@#{method}", deprecator = ActiveSupport::Deprecation.instance)
@instance = instance
@@ -93,15 +103,23 @@ module ActiveSupport
end
end
- # This DeprecatedConstantProxy transforms constant to deprecated constant.
+ # DeprecatedConstantProxy transforms a constant into a deprecated one. It
+ # takes the names of an old (deprecated) constant and of a new contstant
+ # (both in string form) and optionally a deprecator. The deprecator defaults
+ # to +ActiveSupport::Deprecator+ if none is specified. The deprecated constant
+ # now returns the value of the new one.
+ #
+ # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
#
- # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST')
- # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST', deprecator_instance)
+ # (In a later update, the orignal implementation of `PLANETS` has been removed.)
#
- # When someone use old constant this will trigger +warn+ method on
- # +deprecator_instance+.
+ # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
+ # PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006')
#
- # Default deprecator is <tt>ActiveSupport::Deprecation</tt>.
+ # PLANETS.map { |planet| planet.capitalize }
+ # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
+ # (Bactrace information…)
+ # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
class DeprecatedConstantProxy < DeprecationProxy
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance)
@old_const = old_const
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index a08c655d69..bde70d772d 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -231,8 +231,8 @@ module ActiveSupport
# Tries to find a constant with the name specified in the argument string.
#
- # 'Module'.constantize # => Module
- # 'Test::Unit'.constantize # => Test::Unit
+ # 'Module'.constantize # => Module
+ # 'Foo::Bar'.constantize # => Foo::Bar
#
# The name is assumed to be the one of a top-level constant, no matter
# whether it starts with "::" or not. No lexical context is taken into
@@ -280,8 +280,8 @@ module ActiveSupport
# Tries to find a constant with the name specified in the argument string.
#
- # safe_constantize('Module') # => Module
- # safe_constantize('Test::Unit') # => Test::Unit
+ # safe_constantize('Module') # => Module
+ # safe_constantize('Foo::Bar') # => Foo::Bar
#
# The name is assumed to be the one of a top-level constant, no matter
# whether it starts with "::" or not. No lexical context is taken into
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index 35548f3f56..2932954f03 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -9,20 +9,14 @@ module ActiveSupport
module JSON
# matches YAML-formatted dates
DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?))$/
-
+
class << self
# Parses a JSON string (JavaScript Object Notation) into a hash.
# See http://www.json.org for more info.
#
# ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
# => {"team" => "rails", "players" => "36"}
- def decode(json, options = {})
- if options.present?
- raise ArgumentError, "In Rails 4.1, ActiveSupport::JSON.decode no longer " \
- "accepts an options hash for MultiJSON. MultiJSON reached its end of life " \
- "and has been removed."
- end
-
+ def decode(json)
data = ::JSON.parse(json, quirks_mode: true)
if ActiveSupport.parse_json_times
diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
index cd5a2b3cbb..7986eb50f0 100644
--- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
@@ -23,7 +23,7 @@ module ActiveSupport
end
def absolute_value(number)
- number.respond_to?("abs") ? number.abs : number.sub(/\A-/, '')
+ number.respond_to?(:abs) ? number.abs : number.sub(/\A-/, '')
end
def options
diff --git a/activesupport/lib/active_support/rails.rb b/activesupport/lib/active_support/rails.rb
index b05c3ff126..c8e3a4bf53 100644
--- a/activesupport/lib/active_support/rails.rb
+++ b/activesupport/lib/active_support/rails.rb
@@ -1,8 +1,8 @@
# This is private interface.
#
# Rails components cherry pick from Active Support as needed, but there are a
-# few features that are used for sure some way or another and it is not worth
-# to put individual requires absolutely everywhere. Think blank? for example.
+# few features that are used for sure in some way or another and it is not worth
+# putting individual requires absolutely everywhere. Think blank? for example.
#
# This file is loaded by every Rails component except Active Support itself,
# but it does not belong to the Rails public interface. It is internal to
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index 67aac32742..fcf5553061 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -68,7 +68,7 @@ module ActiveSupport
raise ArgumentError, "#{klass} is neither an Exception nor a String"
end
- # put the new handler at the end because the list is read in reverse
+ # Put the new handler at the end because the list is read in reverse.
self.rescue_handlers += [[key, options[:with]]]
end
end
diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
index 8db423f0e9..1cd4b807ad 100644
--- a/activesupport/lib/active_support/subscriber.rb
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -5,7 +5,7 @@ module ActiveSupport
# ActiveSupport::Notifications. The subscriber dispatches notifications to
# a registered object based on its given namespace.
#
- # An example would be Active Record subscriber responsible for collecting
+ # An example would be an Active Record subscriber responsible for collecting
# statistics about queries:
#
# module ActiveRecord
@@ -61,7 +61,7 @@ module ActiveSupport
pattern = "#{event}.#{namespace}"
- # don't add multiple subscribers (eg. if methods are redefined)
+ # Don't add multiple subscribers (eg. if methods are redefined).
return if subscriber.patterns.include?(pattern)
subscriber.patterns << pattern
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 24b8f4b9f9..d9a668c0ea 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -36,14 +36,7 @@ module ActiveSupport
# Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+.
# Defaults to +:random+.
def test_order
- test_order = ActiveSupport.test_order
-
- if test_order.nil?
- test_order = :random
- self.test_order = test_order
- end
-
- test_order
+ ActiveSupport.test_order ||= :random
end
end
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index 247df7423b..1de0a19998 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -69,17 +69,17 @@ module ActiveSupport
else
Tempfile.open("isolation") do |tmpfile|
env = {
- ISOLATION_TEST: self.class.name,
- ISOLATION_OUTPUT: tmpfile.path
+ 'ISOLATION_TEST' => self.class.name,
+ 'ISOLATION_OUTPUT' => tmpfile.path
}
load_paths = $-I.map {|p| "-I\"#{File.expand_path(p)}\"" }.join(" ")
orig_args = ORIG_ARGV.join(" ")
test_opts = "-n#{self.class.name}##{self.name}"
- command = "#{Gem.ruby} #{load_paths} #{$0} #{orig_args} #{test_opts}"
+ command = "#{Gem.ruby} #{load_paths} #{$0} '#{orig_args}' #{test_opts}"
# IO.popen lets us pass env in a cross-platform way
- child = IO.popen([env, command])
+ child = IO.popen(env, command)
begin
Process.wait(child.pid)
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index c28de4e21c..b0d7f3299f 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -169,12 +169,13 @@ module ActiveSupport
end
end
- def encode_with(coder)
- if coder.respond_to?(:represent_object)
- coder.represent_object(nil, utc)
- else
- coder.represent_scalar(nil, utc.strftime("%Y-%m-%d %H:%M:%S.%9NZ"))
- end
+ def init_with(coder) #:nodoc:
+ initialize(coder['utc'], coder['zone'], coder['time'])
+ end
+
+ def encode_with(coder) #:nodoc:
+ coder.tag = '!ruby/object:ActiveSupport::TimeWithZone'
+ coder.map = { 'utc' => utc, 'zone' => time_zone, 'time' => time }
end
# Returns a string of the object's date and time in the format used by
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index da39f0d245..2699a064d7 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -348,24 +348,31 @@ module ActiveSupport
#
# Time.zone.parse('Mar 2000') # => Wed, 01 Mar 2000 00:00:00 HST -10:00
def parse(str, now=now())
- parts = Date._parse(str, false)
- return if parts.empty?
-
- time = Time.new(
- parts.fetch(:year, now.year),
- parts.fetch(:mon, now.month),
- parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day),
- parts.fetch(:hour, 0),
- parts.fetch(:min, 0),
- parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
- parts.fetch(:offset, 0)
- )
-
- if parts[:offset]
- TimeWithZone.new(time.utc, self)
- else
- TimeWithZone.new(nil, self, time)
- end
+ parts_to_time(Date._parse(str, false), now)
+ end
+
+ # Parses +str+ according to +format+ and returns an ActiveSupport::TimeWithZone.
+ #
+ # Assumes that +str+ is a time in the time zone +self+,
+ # unless +format+ includes an explicit time zone.
+ # (This is the same behavior as +parse+.)
+ # In either case, the returned TimeWithZone has the timezone of +self+.
+ #
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.strptime('1999-12-31 14:00:00', '%Y-%m-%d %H:%M:%S') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ #
+ # If upper components are missing from the string, they are supplied from
+ # TimeZone#now:
+ #
+ # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ # Time.zone.strptime('22:30:00', '%H:%M:%S') # => Fri, 31 Dec 1999 22:30:00 HST -10:00
+ #
+ # However, if the date component is not provided, but any other upper
+ # components are supplied, then the day of the month defaults to 1:
+ #
+ # Time.zone.strptime('Mar 2000', '%b %Y') # => Wed, 01 Mar 2000 00:00:00 HST -10:00
+ def strptime(str, format, now=now())
+ parts_to_time(DateTime._strptime(str, format), now)
end
# Returns an ActiveSupport::TimeWithZone instance representing the current
@@ -421,7 +428,36 @@ module ActiveSupport
tzinfo.periods_for_local(time)
end
+ def init_with(coder) #:nodoc:
+ initialize(coder['name'])
+ end
+
+ def encode_with(coder) #:nodoc:
+ coder.tag ="!ruby/object:#{self.class}"
+ coder.map = { 'name' => tzinfo.name }
+ end
+
private
+ def parts_to_time(parts, now)
+ return if parts.empty?
+
+ time = Time.new(
+ parts.fetch(:year, now.year),
+ parts.fetch(:mon, now.month),
+ parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day),
+ parts.fetch(:hour, 0),
+ parts.fetch(:min, 0),
+ parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset, 0)
+ )
+
+ if parts[:offset]
+ TimeWithZone.new(time.utc, self)
+ else
+ TimeWithZone.new(nil, self, time)
+ end
+ end
+
def time_now
Time.now
end
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index b82448458d..2d8796179e 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -389,4 +389,88 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
def test_in_milliseconds
assert_equal 10_000, 10.seconds.in_milliseconds
end
+
+ # TODO: Remove positive and negative tests when we drop support to ruby < 2.3
+ b = 2**64
+ b *= b until Bignum === b
+
+ T_ZERO = b.coerce(0).first
+ T_ONE = b.coerce(1).first
+ T_MONE = b.coerce(-1).first
+
+ def test_positive
+ assert_predicate(1, :positive?)
+ assert_not_predicate(0, :positive?)
+ assert_not_predicate(-1, :positive?)
+ assert_predicate(+1.0, :positive?)
+ assert_not_predicate(+0.0, :positive?)
+ assert_not_predicate(-0.0, :positive?)
+ assert_not_predicate(-1.0, :positive?)
+ assert_predicate(+(0.0.next_float), :positive?)
+ assert_not_predicate(-(0.0.next_float), :positive?)
+ assert_predicate(Float::INFINITY, :positive?)
+ assert_not_predicate(-Float::INFINITY, :positive?)
+ assert_not_predicate(Float::NAN, :positive?)
+
+ a = Class.new(Numeric) do
+ def >(x); true; end
+ end.new
+ assert_predicate(a, :positive?)
+
+ a = Class.new(Numeric) do
+ def >(x); false; end
+ end.new
+ assert_not_predicate(a, :positive?)
+
+ assert_predicate(1/2r, :positive?)
+ assert_not_predicate(-1/2r, :positive?)
+
+ assert_predicate(T_ONE, :positive?)
+ assert_not_predicate(T_MONE, :positive?)
+ assert_not_predicate(T_ZERO, :positive?)
+
+ e = assert_raises(NoMethodError) do
+ Complex(1).positive?
+ end
+
+ assert_match(/positive\?/, e.message)
+ end
+
+ def test_negative
+ assert_predicate(-1, :negative?)
+ assert_not_predicate(0, :negative?)
+ assert_not_predicate(1, :negative?)
+ assert_predicate(-1.0, :negative?)
+ assert_not_predicate(-0.0, :negative?)
+ assert_not_predicate(+0.0, :negative?)
+ assert_not_predicate(+1.0, :negative?)
+ assert_predicate(-(0.0.next_float), :negative?)
+ assert_not_predicate(+(0.0.next_float), :negative?)
+ assert_predicate(-Float::INFINITY, :negative?)
+ assert_not_predicate(Float::INFINITY, :negative?)
+ assert_not_predicate(Float::NAN, :negative?)
+
+ a = Class.new(Numeric) do
+ def <(x); true; end
+ end.new
+ assert_predicate(a, :negative?)
+
+ a = Class.new(Numeric) do
+ def <(x); false; end
+ end.new
+ assert_not_predicate(a, :negative?)
+
+ assert_predicate(-1/2r, :negative?)
+ assert_not_predicate(1/2r, :negative?)
+
+ assert_not_predicate(T_ONE, :negative?)
+ assert_predicate(T_MONE, :negative?)
+ assert_not_predicate(T_ZERO, :negative?)
+
+ e = assert_raises(NoMethodError) do
+ Complex(1).negative?
+ end
+
+ assert_match(/negative\?/, e.message)
+ end
end
diff --git a/activesupport/test/core_ext/object/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb
index 91d558dbb5..791b5e7172 100644
--- a/activesupport/test/core_ext/object/deep_dup_test.rb
+++ b/activesupport/test/core_ext/object/deep_dup_test.rb
@@ -50,4 +50,10 @@ class DeepDupTest < ActiveSupport::TestCase
assert dup.instance_variable_defined?(:@a)
end
+ def test_deep_dup_with_hash_class_key
+ hash = { Fixnum => 1 }
+ dup = hash.deep_dup
+ assert_equal 1, dup.keys.length
+ end
+
end
diff --git a/activesupport/test/core_ext/object/try_test.rb b/activesupport/test/core_ext/object/try_test.rb
index 89438675c1..5ea0f0eca6 100644
--- a/activesupport/test/core_ext/object/try_test.rb
+++ b/activesupport/test/core_ext/object/try_test.rb
@@ -77,9 +77,9 @@ class ObjectTryTest < ActiveSupport::TestCase
klass = Class.new do
private
- def private_method
- 'private method'
- end
+ def private_method
+ 'private method'
+ end
end
assert_raise(NoMethodError) { klass.new.try!(:private_method) }
@@ -89,11 +89,75 @@ class ObjectTryTest < ActiveSupport::TestCase
klass = Class.new do
private
- def private_method
- 'private method'
- end
+ def private_method
+ 'private method'
+ end
end
assert_nil klass.new.try(:private_method)
end
+
+ class Decorator < SimpleDelegator
+ def delegator_method
+ 'delegator method'
+ end
+
+ def reverse
+ 'overridden reverse'
+ end
+
+ private
+
+ def private_delegator_method
+ 'private delegator method'
+ end
+ end
+
+ def test_try_with_method_on_delegator
+ assert_equal 'delegator method', Decorator.new(@string).try(:delegator_method)
+ end
+
+ def test_try_with_method_on_delegator_target
+ assert_equal 5, Decorator.new(@string).size
+ end
+
+ def test_try_with_overriden_method_on_delegator
+ assert_equal 'overridden reverse', Decorator.new(@string).reverse
+ end
+
+ def test_try_with_private_method_on_delegator
+ assert_nil Decorator.new(@string).try(:private_delegator_method)
+ end
+
+ def test_try_with_private_method_on_delegator_bang
+ assert_raise(NoMethodError) do
+ Decorator.new(@string).try!(:private_delegator_method)
+ end
+ end
+
+ def test_try_with_private_method_on_delegator_target
+ klass = Class.new do
+ private
+
+ def private_method
+ 'private method'
+ end
+ end
+
+ assert_nil Decorator.new(klass.new).try(:private_method)
+ end
+
+ def test_try_with_private_method_on_delegator_target_bang
+ klass = Class.new do
+ private
+
+ def private_method
+ 'private method'
+ end
+ end
+
+ assert_raise(NoMethodError) do
+ Decorator.new(klass.new).try!(:private_method)
+ end
+ end
end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 92c233d567..79d78c02cd 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'active_support/time'
require 'time_zone_test_helpers'
+require 'active_support/core_ext/string/strip'
class TimeWithZoneTest < ActiveSupport::TestCase
include TimeZoneTestHelpers
@@ -123,11 +124,53 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_to_yaml
- assert_match(/^--- 2000-01-01 00:00:00(\.0+)?\s*Z\n/, @twz.to_yaml)
+ yaml = <<-EOF.strip_heredoc
+ --- !ruby/object:ActiveSupport::TimeWithZone
+ utc: 2000-01-01 00:00:00.000000000 Z
+ zone: !ruby/object:ActiveSupport::TimeZone
+ name: America/New_York
+ time: 1999-12-31 19:00:00.000000000 Z
+ EOF
+
+ assert_equal(yaml, @twz.to_yaml)
end
def test_ruby_to_yaml
- assert_match(/---\s*\n:twz: 2000-01-01 00:00:00(\.0+)?\s*Z\n/, {:twz => @twz}.to_yaml)
+ yaml = <<-EOF.strip_heredoc
+ ---
+ twz: !ruby/object:ActiveSupport::TimeWithZone
+ utc: 2000-01-01 00:00:00.000000000 Z
+ zone: !ruby/object:ActiveSupport::TimeZone
+ name: America/New_York
+ time: 1999-12-31 19:00:00.000000000 Z
+ EOF
+
+ assert_equal(yaml, { 'twz' => @twz }.to_yaml)
+ end
+
+ def test_yaml_load
+ yaml = <<-EOF.strip_heredoc
+ --- !ruby/object:ActiveSupport::TimeWithZone
+ utc: 2000-01-01 00:00:00.000000000 Z
+ zone: !ruby/object:ActiveSupport::TimeZone
+ name: America/New_York
+ time: 1999-12-31 19:00:00.000000000 Z
+ EOF
+
+ assert_equal(@twz, YAML.load(yaml))
+ end
+
+ def test_ruby_yaml_load
+ yaml = <<-EOF.strip_heredoc
+ ---
+ twz: !ruby/object:ActiveSupport::TimeWithZone
+ utc: 2000-01-01 00:00:00.000000000 Z
+ zone: !ruby/object:ActiveSupport::TimeZone
+ name: America/New_York
+ time: 1999-12-31 19:00:00.000000000 Z
+ EOF
+
+ assert_equal({ 'twz' => @twz }, YAML.load(yaml))
end
def test_httpdate
diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb
index 676a143692..869bc09991 100644
--- a/activesupport/test/time_travel_test.rb
+++ b/activesupport/test/time_travel_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'active_support/core_ext/date'
require 'active_support/core_ext/date_time'
require 'active_support/core_ext/numeric/time'
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 7888b9919b..5e0474f449 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -311,6 +311,80 @@ class TimeZoneTest < ActiveSupport::TestCase
end
end
+ def test_strptime
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ twz = zone.strptime('1999-12-31 12:00:00', '%Y-%m-%d %H:%M:%S')
+ assert_equal Time.utc(1999,12,31,17), twz
+ assert_equal Time.utc(1999,12,31,12), twz.time
+ assert_equal Time.utc(1999,12,31,17), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_strptime_with_nondefault_time_zone
+ with_tz_default ActiveSupport::TimeZone['Pacific Time (US & Canada)'] do
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ twz = zone.strptime('1999-12-31 12:00:00', '%Y-%m-%d %H:%M:%S')
+ assert_equal Time.utc(1999,12,31,17), twz
+ assert_equal Time.utc(1999,12,31,12), twz.time
+ assert_equal Time.utc(1999,12,31,17), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+ end
+
+ def test_strptime_with_explicit_time_zone_as_abbrev
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ twz = zone.strptime('1999-12-31 12:00:00 PST', '%Y-%m-%d %H:%M:%S %Z')
+ assert_equal Time.utc(1999,12,31,20), twz
+ assert_equal Time.utc(1999,12,31,15), twz.time
+ assert_equal Time.utc(1999,12,31,20), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_strptime_with_explicit_time_zone_as_h_offset
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ twz = zone.strptime('1999-12-31 12:00:00 -08', '%Y-%m-%d %H:%M:%S %:::z')
+ assert_equal Time.utc(1999,12,31,20), twz
+ assert_equal Time.utc(1999,12,31,15), twz.time
+ assert_equal Time.utc(1999,12,31,20), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_strptime_with_explicit_time_zone_as_hm_offset
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ twz = zone.strptime('1999-12-31 12:00:00 -08:00', '%Y-%m-%d %H:%M:%S %:z')
+ assert_equal Time.utc(1999,12,31,20), twz
+ assert_equal Time.utc(1999,12,31,15), twz.time
+ assert_equal Time.utc(1999,12,31,20), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_strptime_with_explicit_time_zone_as_hms_offset
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ twz = zone.strptime('1999-12-31 12:00:00 -08:00:00', '%Y-%m-%d %H:%M:%S %::z')
+ assert_equal Time.utc(1999,12,31,20), twz
+ assert_equal Time.utc(1999,12,31,15), twz.time
+ assert_equal Time.utc(1999,12,31,20), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_strptime_with_almost_explicit_time_zone
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ twz = zone.strptime('1999-12-31 12:00:00 %Z', '%Y-%m-%d %H:%M:%S %%Z')
+ assert_equal Time.utc(1999,12,31,17), twz
+ assert_equal Time.utc(1999,12,31,12), twz.time
+ assert_equal Time.utc(1999,12,31,17), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_strptime_with_day_omitted
+ with_env_tz 'US/Eastern' do
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ assert_equal Time.local(2000, 2, 1), zone.strptime('Feb', '%b', Time.local(2000, 1, 1))
+ assert_equal Time.local(2005, 2, 1), zone.strptime('Feb 2005', '%b %Y', Time.local(2000, 1, 1))
+ assert_equal Time.local(2005, 2, 2), zone.strptime('2 Feb 2005', '%e %b %Y', Time.local(2000, 1, 1))
+ end
+ end
+
def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize
tzinfo = TZInfo::Timezone.get('America/New_York')
zone = ActiveSupport::TimeZone.create(tzinfo.name, nil, tzinfo)
@@ -413,4 +487,13 @@ class TimeZoneTest < ActiveSupport::TestCase
assert ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Hawaii"])
assert !ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Kuala Lumpur"])
end
+
+ def test_to_yaml
+ assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n", ActiveSupport::TimeZone["Hawaii"].to_yaml)
+ assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Europe/London\n", ActiveSupport::TimeZone["Europe/London"].to_yaml)
+ end
+
+ def test_yaml_load
+ assert_equal(ActiveSupport::TimeZone["Pacific/Honolulu"], YAML.load("--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n"))
+ end
end
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index bcd6997b06..0e4e7427d2 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -1,7 +1,6 @@
require 'abstract_unit'
require 'active_support/xml_mini'
require 'active_support/builder'
-require 'active_support/core_ext/array'
require 'active_support/core_ext/hash'
require 'active_support/core_ext/big_decimal'
diff --git a/guides/bug_report_templates/generic_gem.rb b/guides/bug_report_templates/generic_gem.rb
new file mode 100644
index 0000000000..3e3f8593d4
--- /dev/null
+++ b/guides/bug_report_templates/generic_gem.rb
@@ -0,0 +1,15 @@
+# Activate the gems you are reporting the issue against.
+gem 'activesupport', '4.2.0'
+require 'active_support'
+require 'active_support/core_ext/object/blank'
+require 'minitest/autorun'
+
+# Ensure backward compatibility with Minitest 4
+Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
+
+class BugTest < Minitest::Test
+ def test_stuff
+ assert "zomg".present?
+ refute "".present?
+ end
+end
diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb
new file mode 100644
index 0000000000..d930482d4e
--- /dev/null
+++ b/guides/bug_report_templates/generic_master.rb
@@ -0,0 +1,26 @@
+unless File.exist?('Gemfile')
+ File.write('Gemfile', <<-GEMFILE)
+ source 'https://rubygems.org'
+ gem 'rails', github: 'rails/rails'
+ gem 'arel', github: 'rails/arel'
+ GEMFILE
+
+ system 'bundle'
+end
+
+require 'bundler'
+Bundler.setup(:default)
+
+require 'active_support'
+require 'active_support/core_ext/object/blank'
+require 'minitest/autorun'
+
+# Ensure backward compatibility with Minitest 4
+Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
+
+class BugTest < Minitest::Test
+ def test_stuff
+ assert "zomg".present?
+ refute "".present?
+ end
+end
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index fab0e20aba..7d95d4792e 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -749,7 +749,7 @@ class ApplicationController < ActionController::Base
end
```
-Note that the filter in this case uses `send` because the `logged_in?` method is private and the filter is not run in the scope of the controller. This is not the recommended way to implement this particular filter, but in more simple cases it might be useful.
+Note that the filter in this case uses `send` because the `logged_in?` method is private and the filter does not run in the scope of the controller. This is not the recommended way to implement this particular filter, but in more simple cases it might be useful.
The second way is to use a class (actually, any object that responds to the right methods will do) to handle the filtering. This is useful in cases that are more complex and cannot be implemented in a readable and reusable way using the two other methods. As an example, you could rewrite the login filter again to use a class:
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 73b240ff2c..92a4081233 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -503,7 +503,7 @@ You will need to use:
By using the full URL, your links will now work in your emails.
-#### generating URLs with `url_for`
+#### Generating URLs with `url_for`
`url_for` generate full URL by default in templates.
@@ -517,7 +517,7 @@ If you did not configure the `:host` option globally make sure to pass it to
action: 'greeting') %>
```
-#### generating URLs with named routes
+#### Generating URLs with Named Routes
Email clients have no web context and so paths have no base URL to form complete
web addresses. Thus, you should always use the "_url" variant of named route
@@ -530,6 +530,9 @@ url helper.
<%= user_url(@user, host: 'example.com') %>
```
+NOTE: non-`GET` links require [jQuery UJS](https://github.com/rails/jquery-ujs)
+and won't work in mailer templates. They will result in normal `GET` requests.
+
### Sending Multipart Emails
Action Mailer will automatically send multipart emails if you have different
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 44c02165db..88c1345f27 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -356,39 +356,34 @@ Supposing we use the same `_box` partial from above, this would produce the same
View Paths
----------
-When rendering the view for a request, the controller needs to resolve where to find each of the directories are located.
+When rendering a response, the controller needs to resolve where the different
+views are located. By default it only looks inside the `app/views` directory.
-We are able to modify the order these locations are resolved by using `prepend_view_path` and `append_view_path`.
-
-This allows us to add new paths to the beginning or end of the list used to resolve these paths.
+We can add other locations and give them a certain precedence when resolving
+paths using the `prepend_view_path` and `append_view_path` methods.
### Prepend view path
-This can be helpful for example, when we want to prepend a different directory for subdomains.
+This can be helpful for example, when we want to put views inside a different
+directory for subdomains.
We can do this by using:
-```prepend_view_path "app/views/#{request.subdomain}"```
-
-Then our list becomes something like:
-
-```
-[
- ~/rails_app/app/views/<subdomain>,
- ~/rails_app/app/views,
- # ...
-]
+```ruby
+prepend_view_path "app/views/#{request.subdomain}"
```
-This will put the subdomain path at the beginning of the list.
+Then Action View will look first in this directory when resolving views.
### Append view path
Similarly, we can append paths:
-```append_view_path "app/views/direct"```.
+```ruby
+append_view_path "app/views/direct"
+```
-This will add ```app/views/direct``` and the end of lookup paths for views.
+This will add `app/views/direct` to the end of the lookup paths.
Overview of helpers provided by Action View
-------------------------------------------
@@ -498,7 +493,7 @@ Returns a stylesheet link tag for the sources specified as arguments. If you don
stylesheet_link_tag "application" # => <link href="/assets/application.css" media="screen" rel="stylesheet" />
```
-You can also include all styles in the stylesheet directory using :all as the source:
+You can also include all styles in the stylesheet directory using `:all` as the source:
```ruby
stylesheet_link_tag :all
@@ -513,7 +508,7 @@ stylesheet_link_tag :all, cache: true
#### stylesheet_path
-Computes the path to a stylesheet asset in the `app/assets/stylesheets` directory. If the source filename has no extension, .css will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path.
+Computes the path to a stylesheet asset in the `app/assets/stylesheets` directory. If the source filename has no extension, `.css` will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path.
```ruby
stylesheet_path "application" # => /assets/application.css
@@ -557,7 +552,7 @@ end
```ruby
atom_feed do |feed|
feed.title("Articles Index")
- feed.updated((@articles.first.created_at))
+ feed.updated(@articles.first.created_at)
@articles.each do |article|
feed.entry(article) do |entry|
@@ -814,9 +809,9 @@ third:
Form helpers are designed to make working with models much easier compared to using just standard HTML elements by providing a set of methods for creating forms based on your models. This helper generates the HTML for forms, providing a method for each sort of input (e.g., text, password, select, and so on). When the form is submitted (i.e., when the user hits the submit button or form.submit is called via JavaScript), the form inputs will be bundled into the params object and passed back to the controller.
-There are two types of form helpers: those that specifically work with model attributes and those that don't. This helper deals with those that work with model attributes; to see an example of form helpers that don't work with model attributes, check the ActionView::Helpers::FormTagHelper documentation.
+There are two types of form helpers: those that specifically work with model attributes and those that don't. This helper deals with those that work with model attributes; to see an example of form helpers that don't work with model attributes, check the `ActionView::Helpers::FormTagHelper` documentation.
-The core method of this helper, form_for, gives you the ability to create a form for a model instance; for example, let's say that you have a model Person and want to create a new instance of it:
+The core method of this helper, `form_for`, gives you the ability to create a form for a model instance; for example, let's say that you have a model Person and want to create a new instance of it:
```html+erb
# Note: a @person variable will have been created in the controller (e.g. @person = Person.new)
@@ -858,7 +853,7 @@ check_box("article", "validated")
#### fields_for
-Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes fields_for suitable for specifying additional model objects in the same form:
+Creates a scope around a specific model object like `form_for`, but doesn't create the form tags themselves. This makes `fields_for` suitable for specifying additional model objects in the same form:
```html+erb
<%= form_for @person, url: { action: "update" } do |person_form| %>
@@ -1152,7 +1147,7 @@ Returns a string of option tags that have been compiled by iterating over the `c
# options_from_collection_for_select(collection, value_method, text_method, selected = nil)
```
-For example, imagine a loop iterating over each person in @project.people to generate an input tag:
+For example, imagine a loop iterating over each person in `@project.people` to generate an input tag:
```ruby
options_from_collection_for_select(@project.people, "id", "name")
@@ -1453,7 +1448,7 @@ This sanitize helper will HTML encode all tags and strip all attributes that are
sanitize @article.body
```
-If either the :attributes or :tags options are passed, only the mentioned tags and attributes are allowed and nothing else.
+If either the `:attributes` or `:tags` options are passed, only the mentioned tags and attributes are allowed and nothing else.
```ruby
sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style)
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index 7aeff2ceab..29d0c32b09 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -11,7 +11,7 @@ After reading this guide, you will know:
* How to create jobs.
* How to enqueue jobs.
* How to run jobs in the background.
-* How to send emails from your application async.
+* How to send emails from your application asynchronously.
--------------------------------------------------------------------------------
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index 7a994cc5de..80b1bde1c7 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -554,7 +554,7 @@ or write the `up` and `down` methods instead of using the `change` method.
Complex migrations may require processing that Active Record doesn't know how
to reverse. You can use `reversible` to specify what to do when running a
-migration what else to do when reverting it. For example:
+migration and what else to do when reverting it. For example:
```ruby
class ExampleMigration < ActiveRecord::Migration
@@ -606,7 +606,7 @@ schema, and the `down` method of your migration should revert the
transformations done by the `up` method. In other words, the database schema
should be unchanged if you do an `up` followed by a `down`. For example, if you
create a table in the `up` method, you should drop it in the `down` method. It
-is wise to reverse the transformations in precisely the reverse order they were
+is wise to perform the transformations in precisely the reverse order they were
made in the `up` method. The example in the `reversible` section is equivalent to:
```ruby
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index 4d9c1776f4..dcc523eb0f 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -29,8 +29,8 @@ that are supported by the PostgreSQL adapter.
### Bytea
-* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-binary.html)
-* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-binarystring.html)
+* [type definition](http://www.postgresql.org/docs/current/static/datatype-binary.html)
+* [functions and operators](http://www.postgresql.org/docs/current/static/functions-binarystring.html)
```ruby
# db/migrate/20140207133952_create_documents.rb
@@ -49,8 +49,8 @@ Document.create payload: data
### Array
-* [type definition](http://www.postgresql.org/docs/9.3/static/arrays.html)
-* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-array.html)
+* [type definition](http://www.postgresql.org/docs/current/static/arrays.html)
+* [functions and operators](http://www.postgresql.org/docs/current/static/functions-array.html)
```ruby
# db/migrate/20140207133952_create_books.rb
@@ -83,7 +83,7 @@ Book.where("array_length(ratings, 1) >= 3")
### Hstore
-* [type definition](http://www.postgresql.org/docs/9.3/static/hstore.html)
+* [type definition](http://www.postgresql.org/docs/current/static/hstore.html)
NOTE: you need to enable the `hstore` extension to use hstore.
@@ -112,8 +112,8 @@ profile.save!
### JSON
-* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-json.html)
-* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-json.html)
+* [type definition](http://www.postgresql.org/docs/current/static/datatype-json.html)
+* [functions and operators](http://www.postgresql.org/docs/current/static/functions-json.html)
```ruby
# db/migrate/20131220144913_create_events.rb
@@ -138,10 +138,10 @@ Event.where("payload->>'kind' = ?", "user_renamed")
### Range Types
-* [type definition](http://www.postgresql.org/docs/9.3/static/rangetypes.html)
-* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-range.html)
+* [type definition](http://www.postgresql.org/docs/current/static/rangetypes.html)
+* [functions and operators](http://www.postgresql.org/docs/current/static/functions-range.html)
-This type is mapped to Ruby [`Range`](http://www.ruby-doc.org/core-2.1.1/Range.html) objects.
+This type is mapped to Ruby [`Range`](http://www.ruby-doc.org/core-2.2.2/Range.html) objects.
```ruby
# db/migrate/20130923065404_create_events.rb
@@ -173,7 +173,7 @@ event.ends_at # => Thu, 13 Feb 2014
### Composite Types
-* [type definition](http://www.postgresql.org/docs/9.3/static/rowtypes.html)
+* [type definition](http://www.postgresql.org/docs/current/static/rowtypes.html)
Currently there is no special support for composite types. They are mapped to
normal text columns:
@@ -213,7 +213,7 @@ contact.save!
### Enumerated Types
-* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-enum.html)
+* [type definition](http://www.postgresql.org/docs/current/static/datatype-enum.html)
Currently there is no special support for enumerated types. They are mapped as
normal text columns:
@@ -242,9 +242,12 @@ article.save!
### UUID
-* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-uuid.html)
-* [generator functions](http://www.postgresql.org/docs/9.3/static/uuid-ossp.html)
+* [type definition](http://www.postgresql.org/docs/current/static/datatype-uuid.html)
+* [pgcrypto generator function](http://www.postgresql.org/docs/current/static/pgcrypto.html#AEN159361)
+* [uuid-ossp generator functions](http://www.postgresql.org/docs/current/static/uuid-ossp.html)
+NOTE: you need to enable the `pgcrypto` (only PostgreSQL >= 9.4) or `uuid-ossp`
+extension to use uuid.
```ruby
# db/migrate/20131220144913_create_revisions.rb
@@ -263,10 +266,32 @@ revision = Revision.first
revision.identifier # => "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
```
+You can use `uuid` type to define references in migrations
+
+```ruby
+# db/migrate/20150418012400_create_blog.rb
+create_table :posts, id: :uuid
+
+create_table :comments, id: :uuid do |t|
+ # t.belongs_to :post, type: :uuid
+ t.references :post, type: :uuid
+end
+
+# app/models/post.rb
+class Post < ActiveRecord::Base
+ has_many :comments
+end
+
+# app/models/comment.rb
+class Comment < ActiveRecord::Base
+ belongs_to :post
+end
+```
+
### Bit String Types
-* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-bit.html)
-* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-bitstring.html)
+* [type definition](http://www.postgresql.org/docs/current/static/datatype-bit.html)
+* [functions and operators](http://www.postgresql.org/docs/current/static/functions-bitstring.html)
```ruby
# db/migrate/20131220144913_create_users.rb
@@ -289,10 +314,10 @@ user.save!
### Network Address Types
-* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-net-types.html)
+* [type definition](http://www.postgresql.org/docs/current/static/datatype-net-types.html)
The types `inet` and `cidr` are mapped to Ruby
-[`IPAddr`](http://www.ruby-doc.org/stdlib-2.1.1/libdoc/ipaddr/rdoc/IPAddr.html)
+[`IPAddr`](http://www.ruby-doc.org/stdlib-2.2.2/libdoc/ipaddr/rdoc/IPAddr.html)
objects. The `macaddr` type is mapped to normal text.
```ruby
@@ -324,7 +349,7 @@ macbook.address
### Geometric Types
-* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-geometric.html)
+* [type definition](http://www.postgresql.org/docs/current/static/datatype-geometric.html)
All geometric types, with the exception of `points` are mapped to normal text.
A point is casted to an array containing `x` and `y` coordinates.
@@ -333,12 +358,13 @@ A point is casted to an array containing `x` and `y` coordinates.
UUID Primary Keys
-----------------
-NOTE: you need to enable the `uuid-ossp` extension to generate UUIDs.
+NOTE: you need to enable the `pgcrypto` (only PostgreSQL >= 9.4) or `uuid-ossp`
+extension to generate random UUIDs.
```ruby
# db/migrate/20131220144913_create_devices.rb
-enable_extension 'uuid-ossp' unless extension_enabled?('uuid-ossp')
-create_table :devices, id: :uuid, default: 'uuid_generate_v4()' do |t|
+enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')
+create_table :devices, id: :uuid, default: 'gen_random_uuid()' do |t|
t.string :kind
end
@@ -378,7 +404,7 @@ Document.where("to_tsvector('english', title || ' ' || body) @@ to_tsquery(?)",
Database Views
--------------
-* [view creation](http://www.postgresql.org/docs/9.3/static/sql-createview.html)
+* [view creation](http://www.postgresql.org/docs/current/static/sql-createview.html)
Imagine you need to work with a legacy database containing the following table:
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index de976acd01..e3cfabb327 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -317,7 +317,7 @@ end
The `find_each` method accepts most of the options allowed by the regular `find` method, except for `:order` and `:limit`, which are reserved for internal use by `find_each`.
-Two additional options, `:batch_size` and `:begin_at`, are available as well.
+Three additional options, `:batch_size`, `:begin_at` and `:end_at`, are available as well.
**`:batch_size`**
@@ -348,7 +348,7 @@ Another example would be if you wanted multiple workers handling the same proces
Similar to the `:begin_at` option, `:end_at` allows you to configure the last ID of the sequence whenever the highest ID is not the one you need.
This would be useful, for example, if you wanted to run a batch process, using a subset of records based on `:begin_at` and `:end_at`
-For example, to send newsletters only to users with the primary key starting from 2000 up to 10000 and to retrieve them in batches of 1000:
+For example, to send newsletters only to users with the primary key starting from 2000 up to 10000 and to retrieve them in batches of 5000:
```ruby
User.find_each(begin_at: 2000, end_at: 10000, batch_size: 5000) do |user|
@@ -898,7 +898,7 @@ For example:
Item.transaction do
i = Item.lock.first
i.name = 'Jones'
- i.save
+ i.save!
end
```
@@ -1787,8 +1787,9 @@ EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `articles` ON `articles`.`
under MySQL.
-Active Record performs a pretty printing that emulates the one of the database
-shells. So, the same query running with the PostgreSQL adapter would yield instead
+Active Record performs a pretty printing that emulates that of the
+corresponding database shell. So, the same query running with the
+PostgreSQL adapter would yield instead
```
EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "articles" ON "articles"."user_id" = "users"."id" WHERE "users"."id" = 1
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index d251c5c0b1..a89f865200 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -47,7 +47,7 @@ built-in helpers for common needs, and allows you to create your own validation
methods as well.
There are several other ways to validate data before it is saved into your
-database, including native database constraints, client-side validations,
+database, including native database constraints, client-side validations and
controller-level validations. Here's a summary of the pros and cons:
* Database constraints and/or stored procedures make the validation mechanisms
@@ -122,7 +122,7 @@ database only if the object is valid:
* `update!`
The bang versions (e.g. `save!`) raise an exception if the record is invalid.
-The non-bang versions don't, `save` and `update` return `false`,
+The non-bang versions don't: `save` and `update` return `false`, and
`create` just returns the object.
### Skipping Validations
@@ -143,7 +143,7 @@ database regardless of its validity. They should be used with caution.
* `update_counters`
Note that `save` also has the ability to skip validations if passed `validate:
-false` as argument. This technique should be used with caution.
+false` as an argument. This technique should be used with caution.
* `save(validate: false)`
@@ -272,7 +272,7 @@ available helpers.
This method validates that a checkbox on the user interface was checked when a
form was submitted. This is typically used when the user needs to agree to your
-application's terms of service, confirm reading some text, or any similar
+application's terms of service, confirm that some text is read, or any similar
concept. This validation is very specific to web applications and this
'acceptance' does not need to be recorded anywhere in your database (if you
don't have a field for it, the helper will just create a virtual attribute).
@@ -283,6 +283,7 @@ class Person < ActiveRecord::Base
end
```
+This check is performed only if `terms_of_service` is not `nil`.
The default error message for this helper is _"must be accepted"_.
It can receive an `:accept` option, which determines the value that will be
@@ -338,7 +339,7 @@ In your view template you could use something like
This check is performed only if `email_confirmation` is not `nil`. To require
confirmation, make sure to add a presence check for the confirmation attribute
-(we'll take a look at `presence` later on this guide):
+(we'll take a look at `presence` later on in this guide):
```ruby
class Person < ActiveRecord::Base
@@ -499,9 +500,9 @@ constraints to acceptable values:
default error message for this option is _"must be equal to %{count}"_.
* `:less_than` - Specifies the value must be less than the supplied value. The
default error message for this option is _"must be less than %{count}"_.
-* `:less_than_or_equal_to` - Specifies the value must be less than or equal the
- supplied value. The default error message for this option is _"must be less
- than or equal to %{count}"_.
+* `:less_than_or_equal_to` - Specifies the value must be less than or equal to
+ the supplied value. The default error message for this option is _"must be
+ less than or equal to %{count}"_.
* `:odd` - Specifies the value must be an odd number if set to true. The
default error message for this option is _"must be odd"_.
* `:even` - Specifies the value must be an even number if set to true. The
@@ -617,7 +618,7 @@ end
The validation happens by performing an SQL query into the model's table,
searching for an existing record with the same value in that attribute.
-There is a `:scope` option that you can use to specify other attributes that
+There is a `:scope` option that you can use to specify one or more attributes that
are used to limit the uniqueness check:
```ruby
@@ -626,7 +627,7 @@ class Holiday < ActiveRecord::Base
message: "should happen once per year" }
end
```
-Should you wish to create a database constraint to prevent possible violations of a uniqueness validation using the `:scope` option, you must create a unique index on both columns in your database. See [the MySQL manual](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html) for more details about multiple column indexes or [the PostgreSQL manual](http://www.postgresql.org/docs/9.4/static/ddl-constraints.html) for examples of unique constraints that refer to a group of columns.
+Should you wish to create a database constraint to prevent possible violations of a uniqueness validation using the `:scope` option, you must create a unique index on both columns in your database. See [the MySQL manual](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html) for more details about multiple column indexes or [the PostgreSQL manual](http://www.postgresql.org/docs/current/static/ddl-constraints.html) for examples of unique constraints that refer to a group of columns.
There is also a `:case_sensitive` option that you can use to define whether the
uniqueness constraint will be case sensitive or not. This option defaults to
@@ -813,7 +814,7 @@ end
Person.new.valid? # => ActiveModel::StrictValidationFailed: Name can't be blank
```
-There is also an ability to pass custom exception to `:strict` option.
+There is also the ability to pass a custom exception to the `:strict` option.
```ruby
class Person < ActiveRecord::Base
@@ -877,7 +878,7 @@ end
### Grouping Conditional validations
-Sometimes it is useful to have multiple validations use one condition, it can
+Sometimes it is useful to have multiple validations use one condition. It can
be easily achieved using `with_options`.
```ruby
@@ -889,8 +890,8 @@ class User < ActiveRecord::Base
end
```
-All validations inside of `with_options` block will have automatically passed
-the condition `if: :is_admin?`
+All validations inside of the `with_options` block will have automatically
+passed the condition `if: :is_admin?`
### Combining Validation Conditions
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index ff60f95a2c..047999d4cf 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -2188,7 +2188,7 @@ The method `without` returns a copy of an enumerable with the specified elements
removed:
```ruby
-people.without("Aaron", "Todd")
+["David", "Rafael", "Aaron", "Todd"].without("Aaron", "Todd") # => ["David", "Rafael"]
```
NOTE: Defined in `active_support/core_ext/enumerable.rb`.
@@ -2201,14 +2201,14 @@ Extensions to `Array`
Active Support augments the API of arrays to ease certain ways of accessing them. For example, `to` returns the subarray of elements up to the one at the passed index:
```ruby
-%w(a b c d).to(2) # => %w(a b c)
+%w(a b c d).to(2) # => ["a", "b", "c"]
[].to(7) # => []
```
Similarly, `from` returns the tail from the element at the passed index to the end. If the index is greater than the length of the array, it returns an empty array.
```ruby
-%w(a b c d).from(2) # => %w(c d)
+%w(a b c d).from(2) # => ["c", "d"]
%w(a b c d).from(10) # => []
[].from(0) # => []
```
@@ -2216,7 +2216,7 @@ Similarly, `from` returns the tail from the element at the passed index to the e
The methods `second`, `third`, `fourth`, and `fifth` return the corresponding element (`first` is built-in). Thanks to social wisdom and positive constructiveness all around, `forty_two` is also available.
```ruby
-%w(a b c d).third # => c
+%w(a b c d).third # => "c"
%w(a b c d).fifth # => nil
```
@@ -2229,7 +2229,7 @@ NOTE: Defined in `active_support/core_ext/array/access.rb`.
This method is an alias of `Array#unshift`.
```ruby
-%w(a b c d).prepend('e') # => %w(e a b c d)
+%w(a b c d).prepend('e') # => ["e", "a", "b", "c", "d"]
[].prepend(10) # => [10]
```
@@ -2240,8 +2240,8 @@ NOTE: Defined in `active_support/core_ext/array/prepend_and_append.rb`.
This method is an alias of `Array#<<`.
```ruby
-%w(a b c d).append('e') # => %w(a b c d e)
-[].append([1,2]) # => [[1,2]]
+%w(a b c d).append('e') # => ["a", "b", "c", "d", "e"]
+[].append([1,2]) # => [[1, 2]]
```
NOTE: Defined in `active_support/core_ext/array/prepend_and_append.rb`.
@@ -2428,7 +2428,7 @@ The method `Array.wrap` wraps its argument in an array unless it is already an a
Specifically:
-* If the argument is `nil` an empty list is returned.
+* If the argument is `nil` an empty array is returned.
* Otherwise, if the argument responds to `to_ary` it is invoked, and if the value of `to_ary` is not `nil`, it is returned.
* Otherwise, an array with the argument as its single element is returned.
@@ -2440,9 +2440,9 @@ Array.wrap(0) # => [0]
This method is similar in purpose to `Kernel#Array`, but there are some differences:
-* If the argument responds to `to_ary` the method is invoked. `Kernel#Array` moves on to try `to_a` if the returned value is `nil`, but `Array.wrap` returns `nil` right away.
+* If the argument responds to `to_ary` the method is invoked. `Kernel#Array` moves on to try `to_a` if the returned value is `nil`, but `Array.wrap` returns an array with the argument as its single element right away.
* If the returned value from `to_ary` is neither `nil` nor an `Array` object, `Kernel#Array` raises an exception, while `Array.wrap` does not, it just returns the value.
-* It does not call `to_a` on the argument, though special-cases `nil` to return an empty array.
+* It does not call `to_a` on the argument, if the argument does not respond to +to_ary+ it returns an array with the argument as its single element.
The last point is particularly worth comparing for some enumerables:
@@ -2465,7 +2465,7 @@ NOTE: Defined in `active_support/core_ext/array/wrap.rb`.
### Duplicating
-The method `Array.deep_dup` duplicates itself and all objects inside
+The method `Array#deep_dup` duplicates itself and all objects inside
recursively with Active Support method `Object#deep_dup`. It works like `Array#map` with sending `deep_dup` method to each object inside.
```ruby
@@ -2687,7 +2687,7 @@ NOTE: Defined in `active_support/core_ext/hash/deep_merge.rb`.
### Deep duplicating
-The method `Hash.deep_dup` duplicates itself and all keys and values
+The method `Hash#deep_dup` duplicates itself and all keys and values
inside recursively with Active Support method `Object#deep_dup`. It works like `Enumerator#each_with_object` with sending `deep_dup` method to each pair inside.
```ruby
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index b385bdbe83..46c9013087 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -16,7 +16,8 @@ RDoc
----
The [Rails API documentation](http://api.rubyonrails.org) is generated with
-[RDoc](http://docs.seattlerb.org/rdoc/).
+[RDoc](http://docs.seattlerb.org/rdoc/). To generate it, make sure you are
+in the rails root directory, run `bundle install` and execute:
```bash
bundle exec rake rdoc
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index abac54d22d..412cfd198a 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -1668,7 +1668,7 @@ You can use any of the standard [querying methods](active_record_querying.html)
* `order`
* `readonly`
* `select`
-* `uniq`
+* `distinct`
##### `where`
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 716beb9178..782406659d 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -22,7 +22,7 @@ fragment caching. Rails provides by default fragment caching. In order to use
page and action caching, you will need to add `actionpack-page_caching` and
`actionpack-action_caching` to your Gemfile.
-To start playing with caching you'll want to ensure that `config.action_controller.perform_caching` is set to `true`, if you're running in development mode. This flag is normally set in the corresponding `config/environments/*.rb` and caching is disabled by default for development and test, and enabled for production.
+To start playing with caching you'll want to ensure that `config.action_controller.perform_caching` is set to `true` if you're running in development mode. This flag is normally set in the corresponding `config/environments/*.rb` and caching is disabled by default for development and test, and enabled for production.
```ruby
config.action_controller.perform_caching = true
@@ -144,7 +144,7 @@ It's called "Russian Doll Caching" because it nests multiple fragments. The adva
### 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.
+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.
The most efficient way to implement low-level caching is using the `Rails.cache.fetch` method. This method does both reading and writing to the cache. When passed only a single argument, the key is fetched and value from the cache is returned. If a block is passed, the result of the block will be cached to the given key and the result is returned.
@@ -160,7 +160,7 @@ class Product < ActiveRecord::Base
end
```
-NOTE: Notice that in this example we used `cache_key` method, so the resulting cache-key will be something like `products/233-20140225082222765838000/competing_price`. `cache_key` generates a string based on the model’s `id` and `updated_at` attributes. This is a common convention and has the benefit of invalidating the cache whenever the product is updated. In general, when you use low-level caching for instance level information, you need to generate a cache key.
+NOTE: Notice that in this example we used the `cache_key` method, so the resulting cache-key will be something like `products/233-20140225082222765838000/competing_price`. `cache_key` generates a string based on the model’s `id` and `updated_at` attributes. This is a common convention and has the benefit of invalidating the cache whenever the product is updated. In general, when you use low-level caching for instance level information, you need to generate a cache key.
### SQL Caching
@@ -219,7 +219,7 @@ There are some common options used by all cache implementations. These can be pa
* `:compress` - This option can be used to indicate that compression should be used in the cache. This can be useful for transferring large cache entries over a slow network.
-* `:compress_threshold` - This options is used in conjunction with the `:compress` option to indicate a threshold under which cache entries should not be compressed. This defaults to 16 kilobytes.
+* `:compress_threshold` - This option is used in conjunction with the `:compress` option to indicate a threshold under which cache entries should not be compressed. This defaults to 16 kilobytes.
* `:expires_in` - This option sets an expiration time in seconds for the cache entry when it will be automatically removed from the cache.
@@ -227,7 +227,7 @@ There are some common options used by all cache implementations. These can be pa
### ActiveSupport::Cache::MemoryStore
-This cache store keeps entries in memory in the same Ruby process. The cache store has a bounded size specified by the `:size` options to the initializer (default is 32Mb). When the cache exceeds the allotted size, a cleanup will occur and the least recently used entries will be removed.
+This cache store keeps entries in memory in the same Ruby process. The cache store has a bounded size specified by the `:size` option to the initializer (default is 32Mb). When the cache exceeds the allotted size, a cleanup will occur and the least recently used entries will be removed.
```ruby
config.cache_store = :memory_store, { size: 64.megabytes }
@@ -243,7 +243,7 @@ This cache store uses the file system to store entries. The path to the director
config.cache_store = :file_store, "/path/to/cache/directory"
```
-With this cache store, multiple server processes on the same host can share a cache. Servers processes running on different hosts could share a cache by using a shared file system, but that set up would not be ideal and is not recommended. The cache store is appropriate for low to medium traffic sites that are served off one or two hosts.
+With this cache store, multiple server processes on the same host can share a cache. Server processes running on different hosts could share a cache by using a shared file system, but that set up would not be ideal and is not recommended. The cache store is appropriate for low to medium traffic sites that are served off one or two hosts.
Note that the cache will grow until the disk is full unless you periodically clear out old entries.
@@ -255,7 +255,7 @@ This cache store uses Danga's `memcached` server to provide a centralized cache
When initializing the cache, you need to specify the addresses for all memcached servers in your cluster. If none is specified, it will assume memcached is running on the local host on the default port, but this is not an ideal set up for larger sites.
-The `write` and `fetch` methods on this cache accept two additional options that take advantage of features specific to memcached. You can specify `:raw` to send a value directly to the server with no serialization. The value must be a string or number. You can use memcached direct operation like `increment` and `decrement` only on raw values. You can also specify `:unless_exist` if you don't want memcached to overwrite an existing entry.
+The `write` and `fetch` methods on this cache accept two additional options that take advantage of features specific to memcached. You can specify `:raw` to send a value directly to the server with no serialization. The value must be a string or number. You can use memcached direct operations like `increment` and `decrement` only on raw values. You can also specify `:unless_exist` if you don't want memcached to overwrite an existing entry.
```ruby
config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
@@ -293,7 +293,7 @@ For more information about Ehcache for JRuby and Rails, see [http://ehcache.org/
### 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.
+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.
```ruby
config.cache_store = :null_store
@@ -303,7 +303,7 @@ config.cache_store = :null_store
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, simple set the cache store to a new instance of the class.
+To use a custom cache store, simply set the cache store to a new instance of the class.
```ruby
config.cache_store = MyCacheStore.new
@@ -311,7 +311,7 @@ config.cache_store = MyCacheStore.new
### Cache Keys
-The keys used in a cache can be any object that responds to either `:cache_key` or to `: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.
@@ -353,7 +353,7 @@ class ProductsController < ApplicationController
end
```
-Instead of an options hash, you can also simply pass in a model, Rails will use the `updated_at` and `cache_key` methods for setting `last_modified` and `etag`:
+Instead of an options hash, you can also simply pass in a model. Rails will use the `updated_at` and `cache_key` methods for setting `last_modified` and `etag`:
```ruby
class ProductsController < ApplicationController
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 8f9102611d..315d8c14b6 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -423,7 +423,7 @@ If you want to clear `public/assets` completely, you can use `rake assets:clobbe
The most common tasks of the `db:` Rake namespace are `migrate` and `create`, and it will pay off to try out all of the migration rake tasks (`up`, `down`, `redo`, `reset`). `rake db:version` is useful when troubleshooting, telling you the current version of the database.
-More information about migrations can be found in the [Migrations](migrations.html) guide.
+More information about migrations can be found in the [Migrations](active_record_migrations.html) guide.
### `notes`
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index aa66376d5d..31b937c925 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -114,7 +114,7 @@ numbers. New applications filter out passwords by adding the following `config.f
defaults to `:debug` for all environments. The available log levels are: `:debug`,
`:info`, `:warn`, `:error`, `:fatal`, and `:unknown`.
-* `config.log_tags` accepts a list of methods that the `request` object responds to. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications.
+* `config.log_tags` accepts a list of: methods that the `request` object responds to, a `Proc` that accepts the `request` object, or something that responds to `to_s`. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications.
* `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::Logger`.
@@ -187,13 +187,14 @@ The full set of methods that can be used in this block are as follows:
* `helper` defines whether or not to generate helpers. Defaults to `true`.
* `integration_tool` defines which integration tool to use. Defaults to `nil`.
* `javascripts` turns on the hook for JavaScript files in generators. Used in Rails for when the `scaffold` generator is run. Defaults to `true`.
-* `javascript_engine` configures the engine to be used (for eg. coffee) when generating assets. Defaults to `nil`.
+* `javascript_engine` configures the engine to be used (for eg. coffee) when generating assets. Defaults to `:js`.
* `orm` defines which orm to use. Defaults to `false` and will use Active Record by default.
* `resource_controller` defines which generator to use for generating a controller when using `rails generate resource`. Defaults to `:controller`.
+* `resource_route` defines whether inject resource route definition in routes or not. Defaults to `true`.
* `scaffold_controller` different from `resource_controller`, defines which generator to use for generating a _scaffolded_ controller when using `rails generate scaffold`. Defaults to `:scaffold_controller`.
* `stylesheets` turns on the hook for stylesheets in generators. Used in Rails for when the `scaffold` generator is run, but this hook can be used in other generates as well. Defaults to `true`.
* `stylesheet_engine` configures the stylesheet engine (for eg. sass) to be used when generating assets. Defaults to `:css`.
-* `test_framework` defines which test framework to use. Defaults to `false` and will use Test::Unit by default.
+* `test_framework` defines which test framework to use. Defaults to `false` and will use Minitest by default.
* `template_engine` defines which template engine to use, such as ERB or Haml. Defaults to `:erb`.
### Configuring Middleware
@@ -524,9 +525,9 @@ There are a few configuration options available in Active Support:
* `config.active_support.bare` enables or disables the loading of `active_support/all` when booting Rails. Defaults to `nil`, which means `active_support/all` is loaded.
-* `config.active_support.test_order` sets the order that test cases are executed. Possible values are `:sorted` and `:random`. Currently defaults to `:sorted`. In Rails 5.0, the default will be changed to `:random` instead.
+* `config.active_support.test_order` sets the order that test cases are executed. Possible values are `:random` and `:sorted`. This option is set to `:random` in `config/environments/test.rb` in newly-generated applications. If you have an application that does not specify a `test_order`, it will default to `:sorted`, *until* Rails 5.0, when the default will become `:random`.
-* `config.active_support.escape_html_entities_in_json` enables or disables the escaping of HTML entities in JSON serialization. Defaults to `false`.
+* `config.active_support.escape_html_entities_in_json` enables or disables the escaping of HTML entities in JSON serialization. Defaults to `true`.
* `config.active_support.use_standard_json_time_format` enables or disables serializing dates to ISO 8601 format. Defaults to `true`.
@@ -1126,7 +1127,7 @@ Search Engines Indexing
Sometimes, you may want to prevent some pages of your application to be visible
on search sites like Google, Bing, Yahoo or Duck Duck Go. The robots that index
-these sites will first analyse the `http://your-site.com/robots.txt` file to
+these sites will first analyze the `http://your-site.com/robots.txt` file to
know which pages it is allowed to index.
Rails creates this file for you inside the `/public` folder. By default, it allows
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index aeb5bc7f11..39a9826f21 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -32,15 +32,19 @@ Your issue report should contain a title and a clear description of the issue at
Then, don't get your hopes up! Unless you have a "Code Red, Mission Critical, the World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating an issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with an "I'm having this problem too" comment.
-### Create a Self-Contained gist for Active Record and Action Controller Issues
+### Create an Executable Test Case
-If you are filing a bug report, please use
-[Active Record template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb) or
-[Action Controller template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_gem.rb)
-if the bug is found in a published gem, and
-[Active Record template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb) or
-[Action Controller template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_master.rb)
-if the bug happens in the master branch.
+Having a way to reproduce your issue will be very helpful for others to help confirm, investigate and ultimately fix your issue. You can do this by providing an executable test case. To make this process easier, we have prepared several bug report templates for you to use as a starting point:
+
+* Template for Active Record (models, database) issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb)
+* Template for Action Pack (controllers, routing) issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_master.rb)
+* Generic template for other issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/generic_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/generic_master.rb)
+
+These templates include the boilerplate code to set up a test case against either a released version of Rails (`*_gem.rb`) or edge Rails (`*_master.rb`).
+
+Simply copy the content of the appropriate template into a `.rb` file and make the necessary changes to demonstrate the issue. You can execute it by running `ruby the_file.rb` in your terminal. If all goes well, you should see your test case failing.
+
+You can then share your executable test case as a [gist](https://gist.github.com), or simply paste the content into the issue description.
### Special Treatment for Security Issues
@@ -399,21 +403,27 @@ When you're happy with the code on your computer, you need to commit the changes
$ git commit -a
```
-At this point, your editor should be fired up and you can write a message for this commit. Well formatted and descriptive commit messages are extremely helpful for the others, especially when figuring out why given change was made, so please take the time to write it.
+This should fire up your editor to write a commit message. When you have
+finished, save and close to continue.
+
+A well-formatted and descriptive commit message is very helpful to others for
+understanding why the change was made, so please take the time to write it.
-Good commit message should be formatted according to the following example:
+A good commit message looks like this:
```
Short summary (ideally 50 characters or less)
-More detailed description, if necessary. It should be wrapped to 72
-characters. Try to be as descriptive as you can; even if you think that the
-commit content is obvious, it may not be obvious to others. Add any description
-that is already present in relevant issues - it should not be necessary to visit
-a webpage to check the history.
+More detailed description, if necessary. It should be wrapped to
+72 characters. Try to be as descriptive as you can. Even if you
+think that the commit content is obvious, it may not be obvious
+to others. Add any description that is already present in the
+relevant issues; it should not be necessary to visit a webpage
+to check the history.
+
+The description section can have multiple paragraphs.
-The description section can have multiple paragraphs. Code examples can be
-embedded by indenting them with 4 spaces:
+Code examples can be embedded by indenting them with 4 spaces:
class ArticlesController
def index
@@ -423,14 +433,15 @@ embedded by indenting them with 4 spaces:
You can also add bullet points:
-- you can use dashes or asterisks
+- make a bullet point by starting a line with either a dash (-)
+ or an asterisk (*)
-- also, try to indent next line of a point for readability, if it's too
- long to fit in 72 characters
+- wrap lines at 72 characters, and indent any additional lines
+ with 2 spaces for readability
```
TIP. Please squash your commits into a single commit when appropriate. This
-simplifies future cherry picks and also keeps the git log clean.
+simplifies future cherry picks and keeps the git log clean.
### Update Your Branch
@@ -521,7 +532,7 @@ pull request". The Rails core team will be notified about your submission.
Most pull requests will go through a few iterations before they get merged.
Different contributors will sometimes have different opinions, and often
-patches will need revised before they can get merged.
+patches will need to be revised before they can get merged.
Some contributors to Rails have email notifications from GitHub turned on, but
others do not. Furthermore, (almost) everyone who works on Rails is a
@@ -568,8 +579,7 @@ following:
```bash
$ git fetch upstream
$ git checkout my_pull_request
-$ git rebase upstream/master
-$ git rebase -i
+$ git rebase -i upstream/master
< Choose 'squash' for all of your commits except the first one. >
< Edit the commit message to make sense, and describe all your changes. >
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index c6863f68e6..a3c16c7660 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -17,7 +17,7 @@ After reading this guide, you will know:
View Helpers for Debugging
--------------------------
-One common task is to inspect the contents of a variable. In Rails, you can do this with three methods:
+One common task is to inspect the contents of a variable. Rails provides three different ways to do this:
* `debug`
* `to_yaml`
@@ -54,7 +54,7 @@ Title: Rails debugging guide
### `to_yaml`
-Displaying an instance variable, or any other object or method, in YAML format can be achieved this way:
+Alternatively, calling `to_yaml` on any object converts it to YAML. You can pass this converted object into the `simple_format` helper method to format the output. This is how `debug` does its magic.
```html+erb
<%= simple_format @article.to_yaml %>
@@ -64,9 +64,7 @@ Displaying an instance variable, or any other object or method, in YAML format c
</p>
```
-The `to_yaml` method converts the method to YAML format leaving it more readable, and then the `simple_format` helper is used to render each line as in the console. This is how `debug` method does its magic.
-
-As a result of this, you will have something like this in your view:
+The above code will render something like this:
```yaml
--- !ruby/object Article
@@ -94,7 +92,7 @@ Another useful method for displaying object values is `inspect`, especially when
</p>
```
-Will be rendered as follows:
+Will render:
```
[1, 2, 3, 4, 5]
@@ -109,9 +107,9 @@ It can also be useful to save information to log files at runtime. Rails maintai
### What is the Logger?
-Rails makes use of the `ActiveSupport::Logger` class to write log information. You can also substitute another logger such as `Log4r` if you wish.
+Rails makes use of the `ActiveSupport::Logger` class to write log information. Other loggers, such as `Log4r`, may also be substituted.
-You can specify an alternative logger in your `environment.rb` or any environment file:
+You can specify an alternative logger in `environment.rb` or any other environment file, for example:
```ruby
Rails.logger = Logger.new(STDOUT)
@@ -130,7 +128,7 @@ TIP: By default, each log is created under `Rails.root/log/` and the log file is
### Log Levels
When something is logged, it's printed into the corresponding log if the log
-level of the message is equal or higher than the configured log level. If you
+level of the message is equal to or higher than the configured log level. If you
want to know the current log level, you can call the `Rails.logger.level`
method.
@@ -143,7 +141,7 @@ config.log_level = :warn # In any environment initializer, or
Rails.logger.level = 0 # at any time
```
-This is useful when you want to log under development or staging, but you don't want to flood your production log with unnecessary information.
+This is useful when you want to log under development or staging without flooding your production log with unnecessary information.
TIP: The default Rails log level is `debug` in all environments.
@@ -207,7 +205,7 @@ Adding extra logging like this makes it easy to search for unexpected or unusual
When running multi-user, multi-account applications, it's often useful
to be able to filter the logs using some custom rules. `TaggedLogging`
-in Active Support helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications.
+in Active Support helps you do exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications.
```ruby
logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
@@ -217,34 +215,33 @@ logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs "
```
### Impact of Logs on Performance
-Logging will always have a small impact on performance of your rails app,
- particularly when logging to disk. However, there are a few subtleties:
+Logging will always have a small impact on the performance of your Rails app,
+ particularly when logging to disk. Additionally, there are a few subtleties:
Using the `:debug` level will have a greater performance penalty than `:fatal`,
as a far greater number of strings are being evaluated and written to the
log output (e.g. disk).
-Another potential pitfall is that if you have many calls to `Logger` like this
- in your code:
+Another potential pitfall is too many calls to `Logger` in your code:
```ruby
logger.debug "Person attributes hash: #{@person.attributes.inspect}"
```
-In the above example, There will be a performance impact even if the allowed
+In the above example, there will be a performance impact even if the allowed
output level doesn't include debug. The reason is that Ruby has to evaluate
these strings, which includes instantiating the somewhat heavy `String` object
-and interpolating the variables, and which takes time.
+and interpolating the variables.
Therefore, it's recommended to pass blocks to the logger methods, as these are
-only evaluated if the output level is the same or included in the allowed level
+only evaluated if the output level is the same as — or included in — the allowed level
(i.e. lazy loading). The same code rewritten would be:
```ruby
logger.debug {"Person attributes hash: #{@person.attributes.inspect}"}
```
-The contents of the block, and therefore the string interpolation, is only
-evaluated if debug is enabled. This performance savings is only really
+The contents of the block, and therefore the string interpolation, are only
+evaluated if debug is enabled. This performance savings are only really
noticeable with large amounts of logging, but it's a good practice to employ.
Debugging with the `byebug` gem
@@ -258,8 +255,7 @@ is your best companion.
The debugger can also help you if you want to learn about the Rails source code
but don't know where to start. Just debug any request to your application and
-use this guide to learn how to move from the code you have written deeper into
-Rails code.
+use this guide to learn how to move from the code you have written into the underlying Rails code.
### Setup
@@ -290,7 +286,7 @@ As soon as your application calls the `byebug` method, the debugger will be
started in a debugger shell inside the terminal window where you launched your
application server, and you will be placed at the debugger's prompt `(byebug)`.
Before the prompt, the code around the line that is about to be run will be
-displayed and the current line will be marked by '=>'. Like this:
+displayed and the current line will be marked by '=>', like this:
```
[1, 10] in /PathTo/project/app/controllers/articles_controller.rb
@@ -321,7 +317,7 @@ For example:
=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option)
=> Ctrl-C to shutdown server
[2014-04-11 13:11:47] INFO WEBrick 1.3.1
-[2014-04-11 13:11:47] INFO ruby 2.1.1 (2014-02-24) [i686-linux]
+[2014-04-11 13:11:47] INFO ruby 2.2.2 (2015-04-13) [i686-linux]
[2014-04-11 13:11:47] INFO WEBrick::HTTPServer#start: pid=6370 port=3000
@@ -344,7 +340,7 @@ Processing by ArticlesController#index as HTML
(byebug)
```
-Now it's time to explore and dig into your application. A good place to start is
+Now it's time to explore your application. A good place to start is
by asking the debugger for help. Type: `help`
```
@@ -365,9 +361,9 @@ 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, so you can also use `l` for the `list` command, for example.
+commands. For example, you can use `l` for the `list` command.
-To see the previous ten lines you should type `list-` (or `l-`)
+To see the previous ten lines you should type `list-` (or `l-`).
```
(byebug) l-
@@ -386,7 +382,7 @@ To see the previous ten lines you should type `list-` (or `l-`)
```
-This way you can move inside the file, being able to see the code above and over
+This way you can move inside the file and see the code above
the line where you added the `byebug` call. Finally, to see where you are in
the code again you can type `list=`
@@ -416,8 +412,7 @@ contexts as you go through the different parts of the stack.
The debugger creates a context when a stopping point or an event is reached. The
context has information about the suspended program which enables the debugger
to inspect the frame stack, evaluate variables from the perspective of the
-debugged program, and contains information about the place where the debugged
-program is stopped.
+debugged program, and know the place where the debugged program is stopped.
At any time you can call the `backtrace` command (or its alias `where`) to print
the backtrace of the application. This can be very helpful to know how you got
@@ -481,9 +476,8 @@ character and the number indicates the current thread of execution.
* `thread resume _n_` resumes thread _n_.
* `thread switch _n_` switches the current thread context to _n_.
-This command is very helpful, among other occasions, when you are debugging
-concurrent threads and need to verify that there are no race conditions in your
-code.
+This command is very helpful when you are debugging concurrent threads and need
+to verify that there are no race conditions in your code.
### Inspecting Variables
@@ -545,11 +539,11 @@ Now `@articles` is included in the instance variables, because the line defining
was executed.
TIP: You can also step into **irb** mode with the command `irb` (of course!).
-This way an irb session will be started within the context you invoked it. But
+This will start an irb session within the context you invoked it. But
be warned: this is an experimental feature.
The `var` method is the most convenient way to show variables and their values.
-Let's let `byebug` help us with it.
+Let's have `byebug` help us with it.
```
(byebug) help var
@@ -561,7 +555,7 @@ v[ar] l[ocal] show local variables
```
This is a great way to inspect the values of the current context variables. For
-example, to check that we have no local variables currently defined.
+example, to check that we have no local variables currently defined:
```
(byebug) var local
@@ -592,14 +586,14 @@ tracking the values of a variable while the execution goes on.
1: @articles = nil
```
-The variables inside the displaying list will be printed with their values after
+The variables inside the displayed list will be printed with their values after
you move in the stack. To stop displaying a variable use `undisplay _n_` where
_n_ is the variable number (1 in the last example).
### Step by Step
Now you should know where you are in the running trace and be able to print the
-available variables. But lets continue and move on with the application
+available variables. But let's continue and move on with the application
execution.
Use `step` (abbreviated `s`) to continue running your program until the next
@@ -656,8 +650,8 @@ Next went up a frame because previous frame finished
(byebug)
```
-If we use `step` in the same situation, we will literally go the next ruby
-instruction to be executed. In this case, the activesupport's `week` method.
+If we use `step` in the same situation, we will literally go to the next Ruby
+instruction to be executed. In this case, Active Support's `week` method.
```
(byebug) step
@@ -677,8 +671,7 @@ instruction to be executed. In this case, the activesupport's `week` method.
(byebug)
```
-This is one of the best ways to find bugs in your code, or perhaps in Ruby on
-Rails.
+This is one of the best ways to find bugs in your code.
### Breakpoints
@@ -814,7 +807,7 @@ controller. The console would be rendered next to your HTML content.
### Console
-Inside any controller action or view, you can then invoke the console by
+Inside any controller action or view, you can invoke the console by
calling the `console` method.
For example, in a controller:
@@ -840,7 +833,7 @@ This will render a console inside your view. You don't need to care about the
location of the `console` call; it won't be rendered on the spot of its
invocation but next to your HTML content.
-The console executes pure Ruby code. You can define and instantiate
+The console executes pure Ruby code: You can define and instantiate
custom classes, create new models and inspect variables.
NOTE: Only one console can be rendered per request. Otherwise `web-console`
@@ -865,7 +858,7 @@ to use it in production.
Debugging Memory Leaks
----------------------
-A Ruby application (on Rails or not), can leak memory - either in the Ruby code
+A Ruby application (on Rails or not), can leak memory — either in the Ruby code
or at the C code level.
In this section, you will learn how to find and fix such leaks by using tool
@@ -896,7 +889,7 @@ footnotes that give request information and link back to your source via
TextMate.
* [Query Trace](https://github.com/ruckus/active-record-query-trace/tree/master) Adds query
origin tracing to your logs.
-* [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin
+* [Query Reviewer](https://github.com/nesquena/query_reviewer) This Rails plugin
not only runs "EXPLAIN" before each of your select queries in development, but
provides a small DIV in the rendered output of each page with the summary of
warnings for each query that it analyzed.
@@ -908,7 +901,7 @@ standard Rails error page with a new one containing more contextual information,
like source code and variable inspection.
* [RailsPanel](https://github.com/dejan/rails_panel) Chrome extension for Rails
development that will end your tailing of development.log. Have all information
-about your Rails app requests in the browser - in the Developer Tools panel.
+about your Rails app requests in the browser — in the Developer Tools panel.
Provides insight to db/rendering/total times, parameter list, rendered views and
more.
diff --git a/guides/source/engines.md b/guides/source/engines.md
index bcb0ee7d5d..a89ed1984f 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -582,7 +582,7 @@ the comments, however, is not quite right yet. If you were to create a comment
right now, you would see this error:
```
-Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder],
+Missing partial blorgh/comments/_comment with {:handlers=>[:erb, :builder],
:formats=>[:html], :locale=>[:en, :en]}. Searched in: *
"/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views" *
"/Users/ryan/Sites/side_projects/blorgh/app/views"
@@ -591,7 +591,7 @@ Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder],
The engine is unable to find the partial required for rendering the comments.
Rails looks first in the application's (`test/dummy`) `app/views` directory and
then in the engine's `app/views` directory. When it can't find it, it will throw
-this error. The engine knows to look for `blorgh/comments/comment` because the
+this error. The engine knows to look for `blorgh/comments/_comment` because the
model object it is receiving is from the `Blorgh::Comment` class.
This partial will be responsible for rendering just the comment text, for now.
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 7a23f0802e..5ef376531d 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -23,7 +23,7 @@ application from scratch. It does not assume that you have any prior experience
with Rails. However, to get the most out of it, you need to have some
prerequisites installed:
-* The [Ruby](https://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer.
+* The [Ruby](https://www.ruby-lang.org/en/downloads) language version 2.2.2 or newer.
* The [RubyGems](https://rubygems.org) packaging system, which is installed with Ruby
versions 1.9 and later. To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org).
* A working installation of the [SQLite3 Database](https://www.sqlite.org).
@@ -97,7 +97,7 @@ For more installation methods for most Operating Systems take a look at
```bash
$ ruby -v
-ruby 2.0.0p353
+ruby 2.2.2p95
```
Many popular UNIX-like OSes ship with an acceptable version of SQLite3.
@@ -377,7 +377,7 @@ edit_article GET /articles/:id/edit(.:format) articles#edit
In the next section, you will add the ability to create new articles in your
application and be able to view them. This is the "C" and the "R" from CRUD:
-creation and reading. The form for doing this will look like this:
+create and read. The form for doing this will look like this:
![The new article form](images/getting_started/new_article.png)
@@ -836,7 +836,7 @@ class ArticlesController < ApplicationController
def new
end
- # snipped for brevity
+ # snippet for brevity
```
A couple of things to note. We use `Article.find` to find the article we're
@@ -892,7 +892,7 @@ class ArticlesController < ApplicationController
def new
end
- # snipped for brevity
+ # snippet for brevity
```
And then finally, add the view for this action, located at
@@ -1240,10 +1240,7 @@ article we want to show the form back to the user.
We reuse the `article_params` method that we defined earlier for the create
action.
-TIP: You don't need to pass all attributes to `update`. For
-example, if you'd call `@article.update(title: 'A new title')`
-Rails would only update the `title` attribute, leaving all other
-attributes untouched.
+TIP: It is not necessary to pass all the attributes to `update`. For example, if `@article.update(title: 'A new title')` were called, Rails would only update the `title` attribute, leaving all other attributes untouched.
Finally, we want to show a link to the `edit` action in the list of all the
articles, so let's add that now to `app/views/articles/index.html.erb` to make
@@ -1491,7 +1488,7 @@ appear.
![Confirm Dialog](images/getting_started/confirm_dialog.png)
TIP: Learn more about jQuery Unobtrusive Adapter (jQuery UJS) on
-[Working With Javascript in Rails](working_with_javascript_in_rails.html) guide.
+[Working With JavaScript in Rails](working_with_javascript_in_rails.html) guide.
Congratulations, you can now create, show, list, update and destroy
articles.
@@ -2003,7 +2000,7 @@ class ArticlesController < ApplicationController
@articles = Article.all
end
- # snipped for brevity
+ # snippet for brevity
```
We also want to allow only authenticated users to delete comments, so in the
@@ -2019,7 +2016,7 @@ class CommentsController < ApplicationController
# ...
end
- # snipped for brevity
+ # snippet for brevity
```
Now if you try to create a new article, you will be greeted with a basic HTTP
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 348c60a9d8..51eaf4ba5a 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -40,7 +40,7 @@ Internationalization is a complex problem. Natural languages differ in so many w
* providing support for English and similar languages out of the box
* making it easy to customize and extend everything for other languages
-As part of this solution, **every static string in the Rails framework** - e.g. Active Record validation messages, time and date formats - **has been internationalized**, so _localization_ of a Rails application means "over-riding" these defaults.
+As part of this solution, **every static string in the Rails framework** - e.g. Active Record validation messages, time and date formats - **has been internationalized**. _Localization_ of a Rails application means defining translated values for these strings in desired languages.
### The Overall Architecture of the Library
@@ -84,13 +84,13 @@ So, let's internationalize a simple Rails application from the ground up in the
Setup the Rails Application for Internationalization
----------------------------------------------------
-There are just a few simple steps to get up and running with I18n support for your application.
+There are a few steps to get up and running with I18n support for a Rails application.
### Configure the I18n Module
-Following the _convention over configuration_ philosophy, Rails will set up your application with reasonable defaults. If you need different settings, you can overwrite them easily.
+Following the _convention over configuration_ philosophy, Rails I18n provides reasonable default translation strings. When different translation strings are needed, they can be overridden.
-Rails adds all `.rb` and `.yml` files from the `config/locales` directory to your **translations load path**, automatically.
+Rails adds all `.rb` and `.yml` files from the `config/locales` directory to the **translations load path**, automatically.
The default `en.yml` locale in this directory contains a sample pair of translation strings:
@@ -101,15 +101,15 @@ en:
This means, that in the `:en` locale, the key _hello_ will map to the _Hello world_ string. Every string inside Rails is internationalized in this way, see for instance Active Model validation messages in the [`activemodel/lib/active_model/locale/en.yml`](https://github.com/rails/rails/blob/master/activemodel/lib/active_model/locale/en.yml) file or time and date formats in the [`activesupport/lib/active_support/locale/en.yml`](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml) file. You can use YAML or standard Ruby Hashes to store translations in the default (Simple) backend.
-The I18n library will use **English** as a **default locale**, i.e. if you don't set a different locale, `:en` will be used for looking up translations.
+The I18n library will use **English** as a **default locale**, i.e. if a different locale is not set, `:en` will be used for looking up translations.
NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en)), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Few gems such as [Globalize3](https://github.com/globalize/globalize) may help you implement it.
-The **translations load path** (`I18n.load_path`) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you.
+The **translations load path** (`I18n.load_path`) is an array of paths to files that will be loaded automatically. Configuring this path allows for customization of translations directory structure and file naming scheme.
-NOTE: The backend will lazy-load these translations when a translation is looked up for the first time. This makes it possible to just swap the backend with something else even after translations have already been announced.
+NOTE: The backend lazy-loads these translations when a translation is looked up for the first time. This backend can be swapped with something else even after translations have already been announced.
-The default `application.rb` file has instructions on how to add locales from another directory and how to set a different default locale. Just uncomment and edit the specific lines.
+The default `application.rb` file has instructions on how to add locales from another directory and how to set a different default locale.
```ruby
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
@@ -117,31 +117,25 @@ The default `application.rb` file has instructions on how to add locales from an
# config.i18n.default_locale = :de
```
-### Optional: Custom I18n Configuration Setup
-
-For the sake of completeness, let's mention that if you do not want to use the `application.rb` file for some reason, you can always wire up things manually, too.
-
-To tell the I18n library where it can find your custom translation files you can specify the load path anywhere in your application - just make sure it gets run before any translations are actually looked up. You might also want to change the default locale. The simplest thing possible is to put the following into an initializer:
+The load path must be specified before any translations are looked up. To change the default locale from an initializer instead of `application.rb`:
```ruby
-# in config/initializers/locale.rb
+# config/initializers/locale.rb
-# tell the I18n library where to find your translations
+# Where the I18n library should search for translation files
I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb,yml}')]
-# set default locale to something other than :en
+# Set default locale to something other than :en
I18n.default_locale = :pt
```
-### Setting and Passing the Locale
+### Managing the Locale across Requests
-If you want to translate your Rails application to a **single language other than English** (the default locale), you can set I18n.default_locale to your locale in `application.rb` or an initializer as shown above, and it will persist through the requests.
+The default locale is used for all translations unless `I18n.locale` is explicitly set.
-However, you would probably like to **provide support for more locales** in your application. In such case, you need to set and pass the locale between requests.
+A localized application will likely need to provide support for multiple locales. To accomplish this, the locale should be set at the beginning of each request so that all strings are translated using the desired locale during the lifetime of that request.
-WARNING: You may be tempted to store the chosen locale in a _session_ or a *cookie*. However, **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [*RESTful*](http://en.wikipedia.org/wiki/Representational_State_Transfer). Read more about the RESTful approach in [Stefan Tilkov's articles](http://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below.
-
-The _setting part_ is easy. You can set the locale in a `before_action` in the `ApplicationController` like this:
+The locale can be set in a `before_action` in the `ApplicationController`:
```ruby
before_action :set_locale
@@ -151,11 +145,11 @@ def set_locale
end
```
-This requires you to pass the locale as a URL query parameter as in `http://example.com/books?locale=pt`. (This is, for example, Google's approach.) So `http://localhost:3000?locale=pt` will load the Portuguese localization, whereas `http://localhost:3000?locale=de` would load the German localization, and so on. You may skip the next section and head over to the **Internationalize your application** section, if you want to try things out by manually placing the locale in the URL and reloading the page.
+This example illustrates this using a URL query parameter to set the locale (e.g. `http://example.com/books?locale=pt`). With this approach, `http://localhost:3000?locale=pt` renders the Portuguese localization, while `http://localhost:3000?locale=de` loads a German localization.
-Of course, you probably don't want to manually include the locale in every URL all over your application, or want the URLs look differently, e.g. the usual `http://example.com/pt/books` versus `http://example.com/en/books`. Let's discuss the different options you have.
+The locale can be set using one of many different approaches.
-### Setting the Locale from the Domain Name
+#### Setting the Locale from the Domain Name
One option you have is to set the locale from the domain name where your application runs. For example, we want `www.example.com` to load the English (or default) locale, and `www.example.es` to load the Spanish locale. Thus the _top-level domain name_ is used for locale setting. This has several advantages:
@@ -208,7 +202,7 @@ assuming you would set `APP_CONFIG[:deutsch_website_url]` to some value like `ht
This solution has aforementioned advantages, however, you may not be able or may not want to provide different localizations ("language versions") on different domains. The most obvious solution would be to include locale code in the URL params (or request path).
-### Setting the Locale from the URL Params
+#### Setting the Locale from URL Params
The most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the `I18n.locale = params[:locale]` _before_action_ in the first example. We would like to have URLs like `www.example.com/books?locale=ja` or `www.example.com/ja/books` in this case.
@@ -266,14 +260,23 @@ Do take special care about the **order of your routes**, so this route declarati
NOTE: Have a look at various gems which simplify working with routes: [routing_filter](https://github.com/svenfuchs/routing-filter/tree/master), [rails-translate-routes](https://github.com/francesc/rails-translate-routes), [route_translator](https://github.com/enriclluelles/route_translator).
-### Setting the Locale from the Client Supplied Information
+#### Setting the Locale from User Preferences
+
+An application with authenticated users may allow users to set a locale preference through the application's interface. With this approach, a user's selected locale preference is persisted in the database and used to set the locale for authenticated requests by that user.
+
+```ruby
+def set_locale
+ I18n.locale = current_user.try(:locale) || I18n.default_locale
+end
+```
-In specific cases, it would make sense to set the locale from client-supplied information, i.e. not from the URL. This information may come for example from the users' preferred language (set in their browser), can be based on the users' geographical location inferred from their IP, or users can provide it simply by choosing the locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites - see the box about _sessions_, _cookies_ and RESTful architecture above.
+#### Choosing an Implied Locale
+When an explicit locale has not been set for a request (e.g. via one of the above methods), an application should attempt to infer the desired locale.
-#### Using `Accept-Language`
+##### Inferring Locale from the Language Header
-One source of client supplied information would be an `Accept-Language` HTTP header. People may [set this in their browser](http://www.w3.org/International/questions/qa-lang-priorities) or other clients (such as _curl_).
+The `Accept-Language` HTTP header indicates the preferred language for request's response. Browsers [set this header value based on the user's language preference settings](http://www.w3.org/International/questions/qa-lang-priorities), making it a good first choice when inferring a locale.
A trivial implementation of using an `Accept-Language` header would be:
@@ -290,24 +293,27 @@ private
end
```
-Of course, in a production environment you would need much more robust code, and could use a gem such as Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) or even Rack middleware such as Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb).
-#### Using GeoIP (or Similar) Database
+In practice, more robust code is necessary to do this reliably. Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) library or Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb) Rack middleware provide solutions to this problem.
-Another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as [GeoIP Lite Country](http://www.maxmind.com/app/geolitecountry). The mechanics of the code would be very similar to the code above - you would need to query the database for the user's IP, and look up your preferred locale for the country/region/city returned.
+##### Inferring the Locale from IP Geolocation
-#### User Profile
+The IP address of the client making the request can be used to infer the client's region and thus their locale. Services such as [GeoIP Lite Country](http://www.maxmind.com/app/geolitecountry) or gems like [geocoder](https://github.com/alexreisner/geocoder) can be used to implement this approach.
-You can also provide users of your application with means to set (and possibly over-ride) the locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above - you'd probably let users choose a locale from a dropdown list and save it to their profile in the database. Then you'd set the locale to this value.
+In general, this approach is far less reliable than using the language header and is not recommended for most web applications.
-Internationalizing your Application
+#### Storing the Locale from the Session or Cookies
+
+WARNING: You may be tempted to store the chosen locale in a _session_ or a *cookie*. However, **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [*RESTful*](http://en.wikipedia.org/wiki/Representational_State_Transfer). Read more about the RESTful approach in [Stefan Tilkov's articles](http://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below.
+
+Internationalization and Localization
-----------------------------------
-OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale to use and how to preserve it between requests. With that in place, you're now ready for the really interesting stuff.
+OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale to use and how to preserve it between requests.
-Let's _internationalize_ our application, i.e. abstract every locale-specific parts, and then _localize_ it, i.e. provide necessary translations for these abstracts.
+Next we need to _internationalize_ our application by abstracting every locale-specific element. Finally, we need to _localize_ it by providing necessary translations for these abstracts.
-You most probably have something like this in one of your applications:
+Given the following example:
```ruby
# config/routes.rb
@@ -344,9 +350,9 @@ end
![rails i18n demo untranslated](images/i18n/demo_untranslated.png)
-### Adding Translations
+### Abstracting Localized Code
-Obviously there are **two strings that are localized to English**. In order to internationalize this code, **replace these strings** with calls to Rails' `#t` helper with a key that makes sense for the translation:
+There are two strings in our code that are in English and that users will be rendered in our response ("Hello Flash" and "Hello World"). In order to internationalize this code, these strings need to be replaced by calls to Rails' `#t` helper with an appropriate key for each string:
```ruby
# app/controllers/home_controller.rb
@@ -363,13 +369,15 @@ end
<p><%= flash[:notice] %></p>
```
-When you now render this view, it will show an error message which tells you that the translations for the keys `:hello_world` and `:hello_flash` are missing.
+Now, when this view is rendered, it will show an error message which tells you that the translations for the keys `:hello_world` and `:hello_flash` are missing.
![rails i18n demo translation missing](images/i18n/demo_translation_missing.png)
NOTE: Rails adds a `t` (`translate`) helper method to your views so that you do not need to spell out `I18n.t` all the time. Additionally this helper will catch missing translations and wrap the resulting error message into a `<span class="translation_missing">`.
-So let's add the missing translations into the dictionary files (i.e. do the "localization" part):
+### Providing Translations for Internationalized Strings
+
+Add the missing translations into the translation dictionary files:
```yaml
# config/locales/en.yml
@@ -383,11 +391,11 @@ pirate:
hello_flash: Ahoy Flash
```
-There you go. Because you haven't changed the default_locale, I18n will use English. Your application now shows:
+Because the `default_locale` hasn't changed, translations use the `:en` locale and the response renders the english strings:
![rails i18n demo translated to English](images/i18n/demo_translated_en.png)
-And when you change the URL to pass the pirate locale (`http://localhost:3000?locale=pirate`), you'll get:
+If the locale is set via the URL to the pirate locale (`http://localhost:3000?locale=pirate`), the response renders the pirate strings:
![rails i18n demo translated to pirate](images/i18n/demo_translated_pirate.png)
@@ -395,21 +403,64 @@ NOTE: You need to restart the server when you add new locale files.
You may use YAML (`.yml`) or plain Ruby (`.rb`) files for storing your translations in SimpleStore. YAML is the preferred option among Rails developers. However, it has one big disadvantage. YAML is very sensitive to whitespace and special characters, so the application may not load your dictionary properly. Ruby files will crash your application on first request, so you may easily find what's wrong. (If you encounter any "weird issues" with YAML dictionaries, try putting the relevant portion of your dictionary into a Ruby file.)
-### Passing variables to translations
+### Passing Variables to Translations
+
+One key consideration for successfully internationalizing an application is to
+avoid making incorrect assumptions about grammar rules when abstracting localized
+code. Grammar rules that seem fundamental in one locale may not hold true in
+another one.
-You can use variables in the translation messages and pass their values from the view.
+Improper abstraction is shown in the following example, where assumptions are
+made about the ordering of the different parts of the translation. Note that Rails
+provides a `number_to_currency` helper to handle the following case.
```erb
-# app/views/home/index.html.erb
-<%=t 'greet_username', user: "Bill", message: "Goodbye" %>
+# app/views/products/show.html.erb
+<%= "#{t('currency')}#{@product.price}" %>
+```
+
+```yaml
+# config/locales/en.yml
+en:
+ currency: "$"
+
+# config/locales/es.yml
+es:
+ currency: "€"
+```
+
+If the product's price is 10 then the proper translation for Spanish is "10 €"
+instead of "€10" but the abstraction cannot give it.
+
+To create proper abstraction, the I18n gem ships with a feature called variable
+interpolation that allows you to use variables in translation definitions and
+pass the values for these variables to the translation method.
+
+Proper abstraction is shown in the following example:
+
+```erb
+# app/views/products/show.html.erb
+<%= t('product_price', price: @product.price) %>
```
```yaml
# config/locales/en.yml
en:
- greet_username: "%{message}, %{user}!"
+ product_price: "$%{price}"
+
+# config/locales/es.yml
+es:
+ product_price: "%{price} €"
```
+All grammatical and punctuation decisions are made in the definition itself, so
+the abstraction can give a proper translation.
+
+NOTE: The `default` and `scope` keywords are reserved and can't be used as
+variable names. If used, an `I18n::ReservedInterpolationKey` exception is raised.
+If a translation expects an interpolation variable, but this has not been passed
+to `#translate`, an `I18n::MissingInterpolationArgument` exception is raised.
+
### Adding Date/Time Formats
OK! Now let's add a timestamp to the view, so we can demo the **date/time localization** feature as well. To localize the time format you pass the Time object to `I18n.l` or (preferably) use Rails' `#l` helper. You can pick a format by passing the `:format` option - by default the `:default` format is used.
@@ -610,20 +661,6 @@ class BooksController < ApplicationController
end
```
-### Interpolation
-
-In many cases you want to abstract your translations so that **variables can be interpolated into the translation**. For this reason the I18n API provides an interpolation feature.
-
-All options besides `:default` and `:scope` that are passed to `#translate` will be interpolated to the translation:
-
-```ruby
-I18n.backend.store_translations :en, thanks: 'Thanks %{name}!'
-I18n.translate :thanks, name: 'Jeremy'
-# => 'Thanks Jeremy!'
-```
-
-If a translation uses `:default` or `:scope` as an interpolation variable, an `I18n::ReservedInterpolationKey` exception is raised. If a translation expects an interpolation variable, but this has not been passed to `#translate`, an `I18n::MissingInterpolationArgument` exception is raised.
-
### Pluralization
In English there are only one singular and one plural form for a given string, e.g. "1 message" and "2 messages". Other languages ([Arabic](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ar), [Japanese](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ja), [Russian](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ru) and many more) have different grammars that have additional or fewer [plural forms](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). Thus, the I18n API provides a flexible pluralization feature.
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 199545a3b3..fb499e7b6d 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -106,6 +106,7 @@ A standard Rails application depends on several gems, specifically:
* activemodel
* activerecord
* activesupport
+* activejob
* arel
* builder
* bundler
@@ -532,6 +533,7 @@ require "rails"
action_controller
action_view
action_mailer
+ active_job
rails/test_unit
sprockets
).each do |framework|
diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md
index 1937369776..121cf2b185 100644
--- a/guides/source/nested_model_forms.md
+++ b/guides/source/nested_model_forms.md
@@ -106,7 +106,7 @@ Consider the following typical RESTful controller which will prepare a new Perso
class PeopleController < ApplicationController
def new
@person = Person.new
- @person.built_address
+ @person.build_address
2.times { @person.projects.build }
end
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 4ccc50a4d9..b1e4c8ad86 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -177,6 +177,8 @@ WARNING: A [long-standing bug](https://github.com/rails/rails/issues/1769) preve
```ruby
form_for @geocoder, url: geocoder_path do |f|
+
+# snippet for brevity
```
### Controller Namespaces and Routing
diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md
index 1323742488..50866350f8 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.md
+++ b/guides/source/ruby_on_rails_guides_guidelines.md
@@ -64,7 +64,9 @@ The guides and the API should be coherent and consistent where appropriate. In p
HTML Guides
-----------
-Before generating the guides, make sure that you have the latest version of Bundler installed on your system. As of this writing, you must install Bundler 1.3.5 on your device.
+Before generating the guides, make sure that you have the latest version of
+Bundler installed on your system. As of this writing, you must install Bundler
+1.3.5 or later on your device.
To install the latest version of Bundler, run `gem install bundler`.
@@ -82,6 +84,8 @@ or
bundle exec rake guides:generate:html
```
+Resulting HTML files can be found in the `./output` directory.
+
To process `my_guide.md` and nothing else use the `ONLY` environment variable:
```
diff --git a/guides/source/security.md b/guides/source/security.md
index 91d520e997..46fc8795e2 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -572,7 +572,7 @@ NOTE: _When sanitizing, protecting or verifying something, prefer whitelists ove
A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTML tags. This is opposed to a whitelist which lists the good e-mail addresses, public actions, good HTML tags and so on. Although sometimes it is not possible to create a whitelist (in a SPAM filter, for example), _prefer to use whitelist approaches_:
-* Use before_action only: [...] instead of except: [...]. This way you don't forget to turn it off for newly added actions.
+* Use before_action except: [...] instead of only: [...] for security-related actions. This way you don't forget to enable security checks for newly added actions.
* Allow &lt;strong&gt; instead of removing &lt;script&gt; against Cross-Site Scripting (XSS). See below for details.
* Don't try to correct user input by blacklists:
* This will make the attack work: "&lt;sc&lt;script&gt;ript&gt;".gsub("&lt;script&gt;", "")
@@ -925,7 +925,7 @@ HTTP/1.1 200 OK [Second New response created by attacker begins]
Content-Type: text/html
-&lt;html&gt;&lt;font color=red&gt;hey&lt;/font&gt;&lt;/html&gt; [Arbitary malicious input is
+&lt;html&gt;&lt;font color=red&gt;hey&lt;/font&gt;&lt;/html&gt; [Arbitrary malicious input is
Keep-Alive: timeout=15, max=100 shown as the redirected page]
Connection: Keep-Alive
Transfer-Encoding: chunked
@@ -971,7 +971,7 @@ request:
| `{ "person": [null, null, ...] }` | `{ :person => [] }` |
| `{ "person": ["foo", null] }` | `{ :person => ["foo"] }` |
-It is possible to return to old behaviour and disable `deep_munge` configuring
+It is possible to return to old behavior and disable `deep_munge` configuring
your application if you are aware of the risk and know how to handle it:
```ruby
@@ -1033,4 +1033,5 @@ The security landscape shifts and it is important to keep up to date, because mi
* Subscribe to the Rails security [mailing list](http://groups.google.com/group/rubyonrails-security)
* [Keep up to date on the other application layers](http://secunia.com/) (they have a weekly newsletter, too)
-* A [good security blog](http://ha.ckers.org/blog/) including the [Cross-Site scripting Cheat Sheet](http://ha.ckers.org/xss.html)
+* A [good security blog](https://www.owasp.org) including the [Cross-Site scripting Cheat Sheet](https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet)
+
diff --git a/guides/source/testing.md b/guides/source/testing.md
index f12daf0dbc..e13da01364 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -407,9 +407,49 @@ This test should now pass.
By now you've caught a glimpse of some of the assertions that are available. Assertions are the worker bees of testing. They are the ones that actually perform the checks to ensure that things are going as planned.
-There are a bunch of different types of assertions you can use that come with [`Minitest`](https://github.com/seattlerb/minitest), the default testing library used by Rails.
-
-For a list of all available assertions please check the [Minitest API documentation](http://docs.seattlerb.org/minitest/), specifically [`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html)
+Here's an extract of the assertions you can use with
+[`Minitest`](https://github.com/seattlerb/minitest), the default testing library
+used by Rails. The `[msg]` parameter is an optional string message you can
+specify to make your test failure messages clearer. It's not required.
+
+| Assertion | Purpose |
+| ---------------------------------------------------------------- | ------- |
+| `assert( test, [msg] )` | Ensures that `test` is true.|
+| `assert_not( test, [msg] )` | Ensures that `test` is false.|
+| `assert_equal( expected, actual, [msg] )` | Ensures that `expected == actual` is true.|
+| `assert_not_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.|
+| `assert_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is true.|
+| `assert_not_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is false.|
+| `assert_nil( obj, [msg] )` | Ensures that `obj.nil?` is true.|
+| `assert_not_nil( obj, [msg] )` | Ensures that `obj.nil?` is false.|
+| `assert_empty( obj, [msg] )` | Ensures that `obj` is `empty?`.|
+| `assert_not_empty( obj, [msg] )` | Ensures that `obj` is not `empty?`.|
+| `assert_match( regexp, string, [msg] )` | Ensures that a string matches the regular expression.|
+| `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_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.|
+| `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class`.|
+| `assert_not_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.|
+| `assert_kind_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class` or is descending from it.|
+| `assert_not_kind_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class` and is not descending from it.|
+| `assert_respond_to( obj, symbol, [msg] )` | Ensures that `obj` responds to `symbol`.|
+| `assert_not_respond_to( obj, symbol, [msg] )` | Ensures that `obj` does not respond to `symbol`.|
+| `assert_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is true.|
+| `assert_not_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.|
+| `assert_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is true, e.g. `assert_predicate str, :empty?`|
+| `assert_not_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is false, e.g. `assert_not_predicate str, :empty?`|
+| `assert_send( array, [msg] )` | Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true. This one is weird eh?|
+| `flunk( [msg] )` | Ensures failure. This is useful to explicitly mark a test that isn't finished yet.|
+
+The above are a subset of assertions that minitest supports. For an exhaustive &
+more up-to-date list, please check
+[Minitest API documentation](http://docs.seattlerb.org/minitest/), specifically
+[`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html)
Because of the modular nature of the testing framework, it is possible to create your own assertions. In fact, that's exactly what Rails does. It includes some specialized assertions to make your life easier.
@@ -444,7 +484,7 @@ All the basic assertions such as `assert_equal` defined in `Minitest::Assertions
Each of these classes include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests.
-NOTE: For more information on `Minitest`, refer to [Minitest](http://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest.html)
+NOTE: For more information on `Minitest`, refer to [Minitest](http://docs.seattlerb.org/minitest)
Functional Tests for Your Controllers
-------------------------------------
@@ -858,7 +898,7 @@ For more information on routing assertions available in Rails, see the API docum
Testing Views
-------------
-Testing the response to your request by asserting the presence of key HTML elements and their content is a common way to test the views of your application. The `assert_select` method allows you to query HTML elements of the response by using a simple yet powerful syntax.
+Testing the response to your request by asserting the presence of key HTML elements and their content is a common way to test the views of your application. Like route tests, view tests reside in `test/controllers/` or are part of controller tests. The `assert_select` method allows you to query HTML elements of the response by using a simple yet powerful syntax.
There are two forms of `assert_select`:
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 0aa2152d56..3680055f0c 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -460,7 +460,7 @@ If your application currently depend on MultiJSON directly, you have a few optio
WARNING: Do not simply replace `MultiJson.dump` and `MultiJson.load` with
`JSON.dump` and `JSON.load`. These JSON gem APIs are meant for serializing and
-deserializing arbitrary Ruby objects and are generally [unsafe](http://www.ruby-doc.org/stdlib-2.0.0/libdoc/json/rdoc/JSON.html#method-i-load).
+deserializing arbitrary Ruby objects and are generally [unsafe](http://www.ruby-doc.org/stdlib-2.2.2/libdoc/json/rdoc/JSON.html#method-i-load).
#### JSON gem compatibility
@@ -883,6 +883,20 @@ this gem such as `whitelist_attributes` or `mass_assignment_sanitizer` options.
* To re-enable the old finders, you can use the [activerecord-deprecated_finders gem](https://github.com/rails/activerecord-deprecated_finders).
+* Rails 4.0 has changed to default join table for `has_and_belongs_to_many` relations to strip the common prefix off the second table name. Any existing `has_and_belongs_to_many` relationship between models with a common prefix must be specified with the `join_table` option. For example:
+
+```ruby
+CatalogCategory < ActiveRecord::Base
+ has_and_belongs_to_many :catalog_products, join_table: 'catalog_categories_catalog_products'
+end
+
+CatalogProduct < ActiveRecord::Base
+ has_and_belongs_to_many :catalog_categories, join_table: 'catalog_categories_catalog_products'
+end
+```
+
+* Note that the the prefix takes scopes into account as well, so relations between `Catalog::Category` and `Catalog::Product` or `Catalog::Category` and `CatalogProduct` need to be updated similarly.
+
### Active Resource
Rails 4.0 extracted Active Resource to its own gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your Gemfile.
@@ -912,7 +926,7 @@ Rails 4.0 extracted Active Resource to its own gem. If you still need the featur
Please note that you should wait to set `secret_key_base` until you have 100% of your userbase on Rails 4.x and are reasonably sure you will not need to rollback to Rails 3.x. This is because cookies signed based on the new `secret_key_base` in Rails 4.x are not backwards compatible with Rails 3.x. You are free to leave your existing `secret_token` in place, not set the new `secret_key_base`, and ignore the deprecation warnings until you are reasonably sure that your upgrade is otherwise complete.
-If you are relying on the ability for external applications or Javascript to be able to read your Rails app's signed session cookies (or signed cookies in general) you should not set `secret_key_base` until you have decoupled these concerns.
+If you are relying on the ability for external applications or JavaScript to be able to read your Rails app's signed session cookies (or signed cookies in general) you should not set `secret_key_base` until you have decoupled these concerns.
* Rails 4.0 encrypts the contents of cookie-based sessions if `secret_key_base` has been set. Rails 3.x signed, but did not encrypt, the contents of cookie-based session. Signed cookies are "secure" in that they are verified to have been generated by your app and are tamper-proof. However, the contents can be viewed by end users, and encrypting the contents eliminates this caveat/concern without a significant performance penalty.
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 6193c33593..b61c4448b5 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,4 +1,20 @@
-* Rename `railties/bin` to `railties/exe` to match the new Bundler executables convention.
+* `bin/setup` uses built-in rake tasks (`log:clear`, `tmp:clear`).
+
+ *Mohnish Thallavajhula*
+
+* Fix mailer previews with attachments by using the mail gem's own API to
+ locate the first part of the correct mime type.
+
+ Fixes #14435.
+
+ *Andrew White*
+
+* Remove sqlite support from `rails dbconsole`.
+
+ *Andrew White*
+
+* Rename `railties/bin` to `railties/exe` to match the new Bundler executables
+ convention.
*Islam Wazery*
@@ -22,7 +38,7 @@
*arthurnn*
-* Add `rake initializers`
+* Add `rake initializers`.
This task prints out all defined initializers in the order they are invoked
by Rails. This is helpful for debugging issues related to the initialization
@@ -37,7 +53,7 @@
*Hyonjee Joo*
-* Add `config/initializers/active_record_belongs_to_required_by_default.rb`
+* Add `config/initializers/active_record_belongs_to_required_by_default.rb`.
Newly generated Rails apps have a new initializer called
`active_record_belongs_to_required_by_default.rb` which sets the value of
@@ -56,7 +72,7 @@
'No such middleware' errors when `insert_before` or `insert_after` are added
after the `delete` operation for the middleware being deleted.
- Fixes: #16433.
+ Fixes #16433.
*Guo Xiang Tan*
@@ -68,11 +84,11 @@
*Xavier Noria*
-* Force generated routes to be inserted into routes.rb
+* Force generated routes to be inserted into `config/routes.rb`.
*Andrew White*
-* Don't remove all line endings from routes.rb when revoking scaffold.
+* Don't remove all line endings from `config/routes.rb` when revoking scaffold.
Fixes #15913.
@@ -82,11 +98,12 @@
*Melanie Gilman*
-* Add the `method_source` gem to the default Gemfile for apps
+* Add the `method_source` gem to the default Gemfile for apps.
*Sean Griffin*
-* Drop old test locations from `rake stats`
+* Drop old test locations from `rake stats`:
+
- test/functional
- test/unit
@@ -117,7 +134,7 @@
*Andrew Kozlov*
-* Add `config/initializers/callback_terminator.rb`
+* Add `config/initializers/callback_terminator.rb`.
Newly generated Rails apps have a new initializer called
`callback_terminator.rb` which sets the value of the configuration option
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index b1f7c29b5a..fe789f3c2a 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -52,6 +52,11 @@ module Rails
end
end
+ # Returns a Pathname object of the current rails project,
+ # otherwise it returns nil if there is no project:
+ #
+ # Rails.root
+ # # => #<Pathname:/Users/someuser/some/path/project>
def root
application && application.config.root
end
@@ -94,6 +99,11 @@ module Rails
groups
end
+ # Returns a Pathname object of the public folder of the current
+ # rails project, otherwise it returns nil if there is no project:
+ #
+ # Rails.public_path
+ # # => #<Pathname:/Users/someuser/some/path/project/public>
def public_path
application && Pathname.new(application.paths["public"].first)
end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 3e3ef72742..8075068b3f 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -1,4 +1,5 @@
require 'fileutils'
+require 'yaml'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/object/blank'
require 'active_support/key_generator'
@@ -6,8 +7,7 @@ require 'active_support/message_verifier'
require 'rails/engine'
module Rails
- # In Rails 3.0, a Rails::Application object was introduced which is nothing more than
- # an Engine but with the responsibility of coordinating the whole boot process.
+ # An Engine with the responsibility of coordinating the whole boot process.
#
# == Initialization
#
@@ -228,7 +228,6 @@ module Rails
yaml = Pathname.new("#{paths["config"].existent.first}/#{name}.yml")
if yaml.exist?
- require "yaml"
require "erb"
(YAML.load(ERB.new(yaml.read).result) || {})[Rails.env] || {}
else
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index 5d37a2b699..ea5d20ea24 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -1,14 +1,13 @@
require 'optparse'
require 'irb'
require 'irb/completion'
+require 'rails/commands/console_helper'
module Rails
class Console
- class << self
- def start(*args)
- new(*args).start
- end
+ include ConsoleHelper
+ class << self
def parse_arguments(arguments)
options = {}
@@ -21,23 +20,8 @@ module Rails
opt.parse!(arguments)
end
- if arguments.first && arguments.first[0] != '-'
- env = arguments.first
- if available_environments.include? env
- options[:environment] = env
- else
- options[:environment] = %w(production development test).detect {|e| e =~ /^#{env}/} || env
- end
- end
-
- options
+ set_options_env(arguments, options)
end
-
- private
-
- def available_environments
- Dir['config/environments/*.rb'].map { |fname| File.basename(fname, '.*') }
- end
end
attr_reader :options, :app, :console
@@ -57,12 +41,9 @@ module Rails
end
def environment
- options[:environment] ||= ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
- end
-
- def environment?
- environment
+ options[:environment] ||= super
end
+ alias_method :environment?, :environment
def set_environment!
Rails.env = environment
diff --git a/railties/lib/rails/commands/console_helper.rb b/railties/lib/rails/commands/console_helper.rb
new file mode 100644
index 0000000000..8ee0b60012
--- /dev/null
+++ b/railties/lib/rails/commands/console_helper.rb
@@ -0,0 +1,34 @@
+require 'active_support/concern'
+
+module Rails
+ module ConsoleHelper # :nodoc:
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def start(*args)
+ new(*args).start
+ end
+
+ private
+ def set_options_env(arguments, options)
+ if arguments.first && arguments.first[0] != '-'
+ env = arguments.first
+ if available_environments.include? env
+ options[:environment] = env
+ else
+ options[:environment] = %w(production development test).detect { |e| e =~ /^#{env}/ } || env
+ end
+ end
+ options
+ end
+
+ def available_environments
+ Dir['config/environments/*.rb'].map { |fname| File.basename(fname, '.*') }
+ end
+ end
+
+ def environment
+ ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index 5175e31f14..dca60f948f 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -1,13 +1,49 @@
require 'erb'
require 'yaml'
require 'optparse'
+require 'rails/commands/console_helper'
module Rails
class DBConsole
+ include ConsoleHelper
+
attr_reader :arguments
- def self.start
- new.start
+ class << self
+ def parse_arguments(arguments)
+ options = {}
+
+ OptionParser.new do |opt|
+ opt.banner = "Usage: rails dbconsole [environment] [options]"
+ opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v|
+ options['include_password'] = true
+ end
+
+ opt.on("--mode [MODE]", ['html', 'list', 'line', 'column'],
+ "Automatically put the sqlite3 database in the specified mode (html, list, line, column).") do |mode|
+ options['mode'] = mode
+ end
+
+ opt.on("--header") do |h|
+ options['header'] = h
+ end
+
+ opt.on("-h", "--help", "Show this help message.") do
+ puts opt
+ exit
+ end
+
+ opt.on("-e", "--environment=name", String,
+ "Specifies the environment to run this console under (test/development/production).",
+ "Default: development"
+ ) { |v| options[:environment] = v.strip }
+
+ opt.parse!(arguments)
+ abort opt.to_s unless (0..1).include?(arguments.size)
+ end
+
+ set_options_env(arguments, options)
+ end
end
def initialize(arguments = ARGV)
@@ -15,7 +51,7 @@ module Rails
end
def start
- options = parse_arguments(arguments)
+ options = self.class.parse_arguments(arguments)
ENV['RAILS_ENV'] = options[:environment] || environment
case config["adapter"]
@@ -50,9 +86,6 @@ module Rails
ENV['PGPASSWORD'] = config["password"].to_s if config["password"] && options['include_password']
find_cmd_and_exec('psql', config["database"])
- when "sqlite"
- find_cmd_and_exec('sqlite', config["database"])
-
when "sqlite3"
args = []
@@ -89,7 +122,7 @@ module Rails
find_cmd_and_exec("sqsh", *args)
else
- abort "Unknown command-line client for #{config['database']}. Submit a Rails patch to add support!"
+ abort "Unknown command-line client for #{config['database']}."
end
end
@@ -104,90 +137,37 @@ module Rails
end
def environment
- if Rails.respond_to?(:env)
- Rails.env
- else
- ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
- end
+ Rails.respond_to?(:env) ? Rails.env : super
end
protected
+ def configurations
+ require APP_PATH
+ ActiveRecord::Base.configurations = Rails.application.config.database_configuration
+ ActiveRecord::Base.configurations
+ end
- def configurations
- require APP_PATH
- ActiveRecord::Base.configurations = Rails.application.config.database_configuration
- ActiveRecord::Base.configurations
- end
-
- def parse_arguments(arguments)
- options = {}
-
- OptionParser.new do |opt|
- opt.banner = "Usage: rails dbconsole [environment] [options]"
- opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v|
- options['include_password'] = true
- end
-
- opt.on("--mode [MODE]", ['html', 'list', 'line', 'column'],
- "Automatically put the sqlite3 database in the specified mode (html, list, line, column).") do |mode|
- options['mode'] = mode
- end
+ def find_cmd_and_exec(commands, *args)
+ commands = Array(commands)
- opt.on("--header") do |h|
- options['header'] = h
+ dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
+ unless (ext = RbConfig::CONFIG['EXEEXT']).empty?
+ commands = commands.map{|cmd| "#{cmd}#{ext}"}
end
- opt.on("-h", "--help", "Show this help message.") do
- puts opt
- exit
+ full_path_command = nil
+ found = commands.detect do |cmd|
+ dirs_on_path.detect do |path|
+ full_path_command = File.join(path, cmd)
+ File.file?(full_path_command) && File.executable?(full_path_command)
+ end
end
- opt.on("-e", "--environment=name", String,
- "Specifies the environment to run this console under (test/development/production).",
- "Default: development"
- ) { |v| options[:environment] = v.strip }
-
- opt.parse!(arguments)
- abort opt.to_s unless (0..1).include?(arguments.size)
- end
-
- if arguments.first && arguments.first[0] != '-'
- env = arguments.first
- if available_environments.include? env
- options[:environment] = env
+ if found
+ exec full_path_command, *args
else
- options[:environment] = %w(production development test).detect {|e| e =~ /^#{env}/} || env
+ abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
end
end
-
- options
- end
-
- def available_environments
- Dir['config/environments/*.rb'].map { |fname| File.basename(fname, '.*') }
- end
-
- def find_cmd_and_exec(commands, *args)
- commands = Array(commands)
-
- dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
- unless (ext = RbConfig::CONFIG['EXEEXT']).empty?
- commands = commands.map{|cmd| "#{cmd}#{ext}"}
- end
-
- full_path_command = nil
- found = commands.detect do |cmd|
- dirs_on_path.detect do |path|
- full_path_command = File.join(path, cmd)
- File.file?(full_path_command) && File.executable?(full_path_command)
- end
- end
-
- if found
- exec full_path_command, *args
- else
- abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
- end
- end
end
end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 83cee28fa3..1dede32dd4 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -6,7 +6,7 @@ require 'pathname'
module Rails
# <tt>Rails::Engine</tt> allows you to wrap a specific Rails application or subset of
# functionality and share it with other applications or within a larger packaged application.
- # Since Rails 3.0, every <tt>Rails::Application</tt> is just an engine, which allows for simple
+ # Every <tt>Rails::Application</tt> is just an engine, which allows for simple
# feature and application sharing.
#
# Any <tt>Rails::Engine</tt> is also a <tt>Rails::Railtie</tt>, so the same
@@ -15,10 +15,9 @@ module Rails
#
# == Creating an Engine
#
- # In Rails versions prior to 3.0, your gems automatically behaved as engines, however,
- # this coupled Rails to Rubygems. Since Rails 3.0, if you want a gem to automatically
- # behave as an engine, you have to specify an +Engine+ for it somewhere inside
- # your plugin's +lib+ folder (similar to how we specify a +Railtie+):
+ # If you want a gem to behave as an engine, you have to specify an +Engine+
+ # for it somewhere inside your plugin's +lib+ folder (similar to how we
+ # specify a +Railtie+):
#
# # lib/my_engine.rb
# module MyEngine
@@ -69,10 +68,9 @@ module Rails
#
# == Paths
#
- # Since Rails 3.0, applications and engines have more flexible path configuration (as
- # opposed to the previous hardcoded path configuration). This means that you are not
- # required to place your controllers at <tt>app/controllers</tt>, but in any place
- # which you find convenient.
+ # Applications and engines have flexible path configuration, meaning that you
+ # are not required to place your controllers at <tt>app/controllers</tt>, but
+ # in any place which you find convenient.
#
# For example, let's suppose you want to place your controllers in <tt>lib/controllers</tt>.
# You can set that as an option:
@@ -206,42 +204,51 @@ module Rails
# With such an engine, everything that is inside the +MyEngine+ module will be isolated from
# the application.
#
- # Consider such controller:
+ # Consider this controller:
#
# module MyEngine
# class FooController < ActionController::Base
# end
# end
#
- # If an engine is marked as isolated, +FooController+ has access only to helpers from +Engine+ and
- # <tt>url_helpers</tt> from <tt>MyEngine::Engine.routes</tt>.
+ # If the +MyEngine+ engine is marked as isolated, +FooController+ only has
+ # access to helpers from +MyEngine+, and <tt>url_helpers</tt> from
+ # <tt>MyEngine::Engine.routes</tt>.
#
- # The next thing that changes in isolated engines is the behavior of routes. Normally, when you namespace
- # your controllers, you also need to namespace all your routes. With an isolated engine,
- # the namespace is applied by default, so you can ignore it in routes:
+ # The next thing that changes in isolated engines is the behavior of routes.
+ # Normally, when you namespace your controllers, you also need to namespace
+ # the related routes. With an isolated engine, the engine's namespace is
+ # automatically applied, so you don't need to specify it explicity in your
+ # routes:
#
# MyEngine::Engine.routes.draw do
# resources :articles
# end
#
- # The routes above will automatically point to <tt>MyEngine::ArticlesController</tt>. Furthermore, you don't
- # need to use longer url helpers like <tt>my_engine_articles_path</tt>. Instead, you should simply use
- # <tt>articles_path</tt> as you would do with your application.
+ # If +MyEngine+ is isolated, The routes above will point to
+ # <tt>MyEngine::ArticlesController</tt>. You also don't need to use longer
+ # url helpers like +my_engine_articles_path+. Instead, you should simply use
+ # +articles_path+, like you would do with your main application.
#
- # To make that behavior consistent with other parts of the framework, an isolated engine also has influence on
- # <tt>ActiveModel::Naming</tt>. When you use a namespaced model, like <tt>MyEngine::Article</tt>, it will normally
- # use the prefix "my_engine". In an isolated engine, the prefix will be omitted in url helpers and
- # form fields for convenience.
+ # To make this behavior consistent with other parts of the framework,
+ # isolated engines also have an effect on <tt>ActiveModel::Naming</tt>. In a
+ # normal Rails app, when you use a namespaced model such as
+ # <tt>Namespace::Article</tt>, <tt>ActiveModel::Naming</tt> will generate
+ # names with the prefix "namespace". In an isolated engine, the prefix will
+ # be omitted in url helpers and form fields, for convenience.
#
- # polymorphic_url(MyEngine::Article.new) # => "articles_path"
+ # polymorphic_url(MyEngine::Article.new)
+ # # => "articles_path" # not "my_engine_articles_path"
#
# form_for(MyEngine::Article.new) do
# text_field :title # => <input type="text" name="article[title]" id="article_title" />
# end
#
- # Additionally, an isolated engine will set its name according to namespace, so
- # MyEngine::Engine.engine_name will be "my_engine". It will also set MyEngine.table_name_prefix
- # to "my_engine_", changing the MyEngine::Article model to use the my_engine_articles table.
+ # Additionally, an isolated engine will set its own name according to its
+ # namespace, so <tt>MyEngine::Engine.engine_name</tt> will return
+ # "my_engine". It will also set +MyEngine.table_name_prefix+ to "my_engine_",
+ # meaning for example that <tt>MyEngine::Article</tt> will use the
+ # +my_engine_articles+ database table by default.
#
# == Using Engine's routes outside Engine
#
@@ -405,7 +412,7 @@ module Rails
end
end
- # Finds engine with given path
+ # Finds engine with given path.
def find(path)
expanded_path = File.expand_path path
Rails::Engine.subclasses.each do |klass|
@@ -559,7 +566,7 @@ module Rails
# and the load_once paths.
#
# This needs to be an initializer, since it needs to run once
- # per engine and get the engine as a block parameter
+ # per engine and get the engine as a block parameter.
initializer :set_autoload_paths, before: :bootstrap_hook do
ActiveSupport::Dependencies.autoload_paths.unshift(*_all_autoload_paths)
ActiveSupport::Dependencies.autoload_once_paths.unshift(*_all_autoload_once_paths)
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 10deeb0ba2..119a7cb829 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -336,7 +336,7 @@ module Rails
end
def spring_install?
- !options[:skip_spring] && Process.respond_to?(:fork) && !RUBY_PLATFORM.include?("cygwin")
+ !options[:skip_spring] && !options.dev? && Process.respond_to?(:fork) && !RUBY_PLATFORM.include?("cygwin")
end
def run_bundle
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
index d9713b0238..d99b27cb2d 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -17,6 +17,7 @@
<%%= f.label :password %><br>
<%%= f.password_field :password %>
</div>
+
<div class="field">
<%%= f.label :password_confirmation %><br>
<%%= f.password_field :password_confirmation %>
@@ -25,6 +26,7 @@
<%%= f.<%= attribute.field_type %> :<%= attribute.column_name %> %>
<% end -%>
</div>
+
<% end -%>
<div class="actions">
<%%= f.submit %>
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 899b33e529..1047a2c429 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -319,7 +319,7 @@ module Rails
if app_const =~ /^\d/
raise Error, "Invalid application name #{app_name}. Please give a name which does not start with numbers."
elsif RESERVED_NAMES.include?(app_name)
- raise Error, "Invalid application name #{app_name}. Please give a name which does not match one of the reserved rails words."
+ raise Error, "Invalid application name #{app_name}. Please give a name which does not match one of the reserved rails words: #{RESERVED_NAMES}"
elsif Object.const_defined?(app_const_base)
raise Error, "Invalid application name #{app_name}, constant #{app_const_base} is already in use. Please choose another application name."
end
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup
index eee810be30..3a5a2cc1e3 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup
@@ -22,8 +22,7 @@ chdir APP_ROOT do
system 'ruby bin/rake db:setup'
puts "\n== Removing old logs and tempfiles =="
- rm_f Dir.glob('log/*')
- rm_rf 'tmp/cache'
+ system 'ruby bin/rake log:clear tmp:clear'
puts "\n== Restarting application server =="
touch 'tmp/restart.txt'
diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb
index 6c583e5811..0a4c509a31 100644
--- a/railties/lib/rails/generators/rails/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb
@@ -19,7 +19,8 @@ module Rails
end
end
- hook_for :template_engine, :test_framework, :helper, :assets
+ hook_for :template_engine, :test_framework
+ hook_for :helper, :assets, hide: true
private
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 68c3829515..7f5bf0c8b8 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -8,7 +8,7 @@ module Rails
# generator.
#
# This allows you to override entire operations, like the creation of the
- # Gemfile, README, or JavaScript files, without needing to know exactly
+ # Gemfile, \README, or JavaScript files, without needing to know exactly
# what those operations do so you can create another template action.
class PluginBuilder
def rakefile
@@ -364,7 +364,7 @@ task default: :test
elsif camelized =~ /^\d/
raise Error, "Invalid plugin name #{original_name}. Please give a name which does not start with numbers."
elsif RESERVED_NAMES.include?(name)
- raise Error, "Invalid plugin name #{original_name}. Please give a name which does not match one of the reserved rails words."
+ raise Error, "Invalid plugin name #{original_name}. Please give a name which does not match one of the reserved rails words: #{RESERVED_NAMES}"
elsif Object.const_defined?(camelized)
raise Error, "Invalid plugin name #{original_name}, constant #{camelized} is already in use. Please choose another plugin name."
end
diff --git a/railties/lib/rails/generators/rails/resource/resource_generator.rb b/railties/lib/rails/generators/rails/resource/resource_generator.rb
index 8014feb75f..3acf21df13 100644
--- a/railties/lib/rails/generators/rails/resource/resource_generator.rb
+++ b/railties/lib/rails/generators/rails/resource/resource_generator.rb
@@ -1,6 +1,5 @@
require 'rails/generators/resource_helpers'
require 'rails/generators/rails/model/model_generator'
-require 'active_support/core_ext/object/blank'
module Rails
module Generators
diff --git a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
index 69af1e8307..b7818883d1 100644
--- a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
+++ b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
@@ -1,8 +1,11 @@
-body { background-color: #fff; color: #333; }
+body {
+ background-color: #fff;
+ color: #333;
+}
body, p, ol, ul, td {
font-family: verdana, arial, helvetica, sans-serif;
- font-size: 13px;
+ font-size: 13px;
line-height: 18px;
margin: 33px;
}
@@ -13,9 +16,18 @@ pre {
font-size: 11px;
}
-a { color: #000; }
-a:visited { color: #666; }
-a:hover { color: #fff; background-color:#000; }
+a {
+ color: #000;
+}
+
+a:visited {
+ color: #666;
+}
+
+a:hover {
+ color: #fff;
+ background-color: #000;
+}
th {
padding-bottom: 5px;
@@ -27,7 +39,8 @@ td {
padding-right: 5px;
}
-div.field, div.actions {
+div.field,
+div.actions {
margin-bottom: 10px;
}
@@ -56,7 +69,7 @@ div.field, div.actions {
padding: 5px 5px 5px 15px;
font-size: 12px;
margin: -7px;
- margin-bottom: 0px;
+ margin-bottom: 0;
background-color: #c00;
color: #fff;
}
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
index 8d825ae7b0..76313575e9 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
@@ -9,7 +9,6 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
test "should get index" do
get :index
assert_response :success
- assert_not_nil assigns(:<%= table_name %>)
end
test "should get new" do
@@ -22,7 +21,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
post :create, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
end
- assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
+ assert_redirected_to <%= singular_table_name %>_path(<%= class_name %>.last)
end
test "should show <%= singular_table_name %>" do
@@ -37,7 +36,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
test "should update <%= singular_table_name %>" do
patch :update, params: { id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
- assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
+ assert_redirected_to <%= singular_table_name %>_path(<%= "@#{singular_table_name}" %>)
end
test "should destroy <%= singular_table_name %>" do
diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb
index 6e61cc3cb5..778105c5f7 100644
--- a/railties/lib/rails/info_controller.rb
+++ b/railties/lib/rails/info_controller.rb
@@ -18,7 +18,7 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc:
def routes
if path = params[:path]
- path = URI.escape path
+ path = URI.parser.escape path
normalized_path = with_leading_slash path
render json: {
exact: match_route {|it| it.match normalized_path },
diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb
index 32740d66da..41422a656c 100644
--- a/railties/lib/rails/mailers_controller.rb
+++ b/railties/lib/rails/mailers_controller.rb
@@ -16,10 +16,10 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
@page_title = "Mailer Previews for #{@preview.preview_name}"
render action: 'mailer'
else
- email = File.basename(params[:path])
+ @email_action = File.basename(params[:path])
- if @preview.email_exists?(email)
- @email = @preview.call(email)
+ if @preview.email_exists?(@email_action)
+ @email = @preview.call(@email_action)
if params[:part]
part_type = Mime::Type.lookup(params[:part])
@@ -28,14 +28,14 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
response.content_type = part_type
render text: part.respond_to?(:decoded) ? part.decoded : part
else
- raise AbstractController::ActionNotFound, "Email part '#{part_type}' not found in #{@preview.name}##{email}"
+ raise AbstractController::ActionNotFound, "Email part '#{part_type}' not found in #{@preview.name}##{@email_action}"
end
else
@part = find_preferred_part(request.format, Mime::HTML, Mime::TEXT)
render action: 'email', layout: false, formats: %w[html]
end
else
- raise AbstractController::ActionNotFound, "Email '#{email}' not found in #{@preview.name}"
+ raise AbstractController::ActionNotFound, "Email '#{@email_action}' not found in #{@preview.name}"
end
end
end
@@ -54,18 +54,20 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
end
def find_preferred_part(*formats)
- if @email.multipart?
- formats.each do |format|
- return find_part(format) if @email.parts.any?{ |p| p.mime_type == format }
+ formats.each do |format|
+ if part = @email.find_first_mime_type(format)
+ return part
end
- else
+ end
+
+ if formats.any?{ |f| @email.mime_type == f }
@email
end
end
def find_part(format)
- if @email.multipart?
- @email.parts.find{ |p| p.mime_type == format }
+ if part = @email.find_first_mime_type(format)
+ part
elsif @email.mime_type == format
@email
end
diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb
index 9962e6d943..12676b18bc 100644
--- a/railties/lib/rails/rack/logger.rb
+++ b/railties/lib/rails/rack/logger.rb
@@ -7,6 +7,10 @@ require 'rack/body_proxy'
module Rails
module Rack
# Sets log tags, logs the request, calls the app, and flushes the logs.
+ #
+ # Log tags (+taggers+) can be an Array containing: methods that the +request+
+ # object responds to, objects that respond to +to_s+ or Proc objects that accept
+ # an instance of the +request+ object.
class Logger < ActiveSupport::LogSubscriber
def initialize(app, taggers = nil)
@app = app
diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb
index 0b08a01896..bb6e49966d 100644
--- a/railties/lib/rails/templates/rails/mailers/email.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/email.html.erb
@@ -39,6 +39,10 @@
padding: 1px;
}
+ dd:empty:before {
+ content: "\00a0"; // &nbsp;
+ }
+
iframe {
border: 0;
width: 100%;
@@ -84,7 +88,7 @@
<% unless @email.attachments.nil? || @email.attachments.empty? %>
<dt>Attachments:</dt>
<dd>
- <%= @email.attachments.map { |a| a.respond_to?(:original_filename) ? a.original_filename : a.filename }.inspect %>
+ <%= @email.attachments.map { |a| a.respond_to?(:original_filename) ? a.original_filename : a.filename }.join(', ') %>
</dd>
<% end %>
@@ -99,7 +103,14 @@
</dl>
</header>
-<iframe seamless name="messageBody" src="?part=<%= Rack::Utils.escape(@part.mime_type) %>"></iframe>
+<% if @part && @part.mime_type %>
+ <iframe seamless name="messageBody" src="?part=<%= Rack::Utils.escape(@part.mime_type) %>"></iframe>
+<% else %>
+ <p>
+ You are trying to preview an email that does not have any content.
+ This is probably because the <em>mail</em> method has not been called in <em><%= @preview.preview_name %>#<%= @email_action %></em>.
+ </p>
+<% end %>
</body>
</html>
diff --git a/railties/lib/rails/templates/rails/mailers/index.html.erb b/railties/lib/rails/templates/rails/mailers/index.html.erb
index c4c9757d57..000930c039 100644
--- a/railties/lib/rails/templates/rails/mailers/index.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/index.html.erb
@@ -1,8 +1,8 @@
<% @previews.each do |preview| %>
-<h3><%= link_to preview.preview_name.titleize, "/rails/mailers/#{preview.preview_name}" %></h3>
+<h3><%= link_to preview.preview_name.titleize, url_for(controller: "rails/mailers", action: "preview", path: preview.preview_name) %></h3>
<ul>
<% preview.emails.each do |email| %>
-<li><%= link_to email, "/rails/mailers/#{preview.preview_name}/#{email}" %></li>
+<li><%= link_to email, url_for(controller: "rails/mailers", action: "preview", path: "#{preview.preview_name}/#{email}") %></li>
<% end %>
</ul>
<% end %>
diff --git a/railties/lib/rails/templates/rails/mailers/mailer.html.erb b/railties/lib/rails/templates/rails/mailers/mailer.html.erb
index 607c8d1677..c12ead0f90 100644
--- a/railties/lib/rails/templates/rails/mailers/mailer.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/mailer.html.erb
@@ -1,6 +1,6 @@
<h3><%= @preview.preview_name.titleize %></h3>
<ul>
<% @preview.emails.each do |email| %>
-<li><%= link_to email, "/rails/mailers/#{@preview.preview_name}/#{email}" %></li>
+<li><%= link_to email, url_for(controller: "rails/mailers", action: "preview", path: "#{@preview.preview_name}/#{email}") %></li>
<% end %>
</ul>
diff --git a/railties/lib/rails/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb
index 6726c23fc9..acf04af416 100644
--- a/railties/lib/rails/templates/rails/welcome/index.html.erb
+++ b/railties/lib/rails/templates/rails/welcome/index.html.erb
@@ -18,7 +18,9 @@
color: #000;
}
- a {color: #03c}
+ a {
+ color: #03c;
+ }
a:hover {
background-color: #03c;
@@ -64,7 +66,10 @@
height: 64px;
}
- #header h1, #header h2 {margin: 0}
+ #header h1,
+ #header h2 {
+ margin: 0;
+ }
#header h2 {
color: #888;
@@ -104,7 +109,9 @@
color: #555;
}
- #about-content td.value {color: #000}
+ #about-content td.value {
+ color: #000;
+ }
#about-content ul {
padding: 0;
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index 5cf44e6331..a83e39faee 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -27,13 +27,15 @@ if defined?(ActiveRecord::Base)
end
class ActionController::TestCase
- setup do
+ def before_setup
@routes = Rails.application.routes
+ super
end
end
class ActionDispatch::IntegrationTest
- setup do
+ def before_setup
@routes = Rails.application.routes
+ super
end
end
diff --git a/railties/lib/rails/test_unit/sub_test_task.rb b/railties/lib/rails/test_unit/sub_test_task.rb
deleted file mode 100644
index 6fa96d2ced..0000000000
--- a/railties/lib/rails/test_unit/sub_test_task.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-require 'rake/testtask'
-
-module Rails
- class TestTask < Rake::TestTask # :nodoc: all
- # A utility class which is used primarily in "rails/test_unit/testing.rake"
- # to help define rake tasks corresponding to <tt>rake test</tt>.
- #
- # This class takes a TestInfo class and defines the appropriate rake task
- # based on the information, then invokes it.
- class TestCreator # :nodoc:
- def initialize(info)
- @info = info
- end
-
- def invoke_rake_task
- if @info.files.any?
- create_and_run_single_test
- reset_application_tasks
- else
- Rake::Task[ENV['TEST'] ? 'test:single' : 'test:run'].invoke
- end
- end
-
- private
-
- def create_and_run_single_test
- Rails::TestTask.new('test:single') { |t|
- t.test_files = @info.files
- }
- ENV['TESTOPTS'] ||= @info.opts
- Rake::Task['test:single'].invoke
- end
-
- def reset_application_tasks
- Rake.application.top_level_tasks.replace @info.tasks
- end
- end
-
- # This is a utility class used by the <tt>TestTask::TestCreator</tt> class.
- # This class takes a set of test tasks and checks to see if they correspond
- # to test files (or can be transformed into test files). Calling <tt>files</tt>
- # provides the set of test files and is used when initializing tests after
- # a call to <tt>rake test</tt>.
- class TestInfo # :nodoc:
- def initialize(tasks)
- @tasks = tasks
- @files = nil
- end
-
- def files
- @files ||= @tasks.map { |task|
- [task, translate(task)].find { |file| test_file?(file) }
- }.compact
- end
-
- def translate(file)
- if file =~ /^app\/(.*)$/
- "test/#{$1.sub(/\.rb$/, '')}_test.rb"
- else
- "test/#{file}_test.rb"
- end
- end
-
- def tasks
- @tasks - test_file_tasks - opt_names
- end
-
- def opts
- opts = opt_names
- if opts.any?
- "-n #{opts.join ' '}"
- end
- end
-
- private
-
- def test_file_tasks
- @tasks.find_all { |task|
- [task, translate(task)].any? { |file| test_file?(file) }
- }
- end
-
- def test_file?(file)
- file =~ /^test/ && File.file?(file) && !File.directory?(file)
- end
-
- def opt_names
- (@tasks - test_file_tasks).reject { |t| task_defined? t }
- end
-
- def task_defined?(task)
- Rake::Task.task_defined? task
- end
- end
-
- def self.test_creator(tasks)
- info = TestInfo.new(tasks)
- TestCreator.new(info)
- end
-
- def initialize(name = :test)
- super
- @libs << "test" # lib *and* test seem like a better default
- end
-
- def define
- task @name do
- if ENV['TESTOPTS']
- ARGV.replace Shellwords.split ENV['TESTOPTS']
- end
- libs = @libs - $LOAD_PATH
- $LOAD_PATH.unshift(*libs)
- file_list.each { |fl|
- FileList[fl].to_a.each { |f| require File.expand_path f }
- }
- end
- end
- end
-
- # Silence the default description to cut down on `rake -T` noise.
- class SubTestTask < Rake::TestTask # :nodoc:
- def desc(string)
- # Ignore the description.
- end
- end
-end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index f6b7d4c855..1e2a9ba040 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -191,6 +191,7 @@ module ApplicationTests
test "asset pipeline should use a Sprockets::Index 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"
@@ -464,17 +465,6 @@ module ApplicationTests
assert_match "src='/sub/uri/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/app-*.js"].first)
end
- test "assets:cache:clean should clean cache" do
- ENV["RAILS_ENV"] = "production"
- precompile!
-
- quietly do
- Dir.chdir(app_path){ `bundle exec rake assets:clobber` }
- end
-
- assert !File.exist?("#{app_path}/tmp/cache/assets")
- end
-
private
def app_with_assets_in_view
diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb
new file mode 100644
index 0000000000..1bdced02e9
--- /dev/null
+++ b/railties/test/application/bin_setup_test.rb
@@ -0,0 +1,54 @@
+require 'isolation/abstract_unit'
+
+module ApplicationTests
+ class BinSetupTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ def test_bin_setup
+ Dir.chdir(app_path) do
+ app_file 'db/schema.rb', <<-RUBY
+ ActiveRecord::Schema.define(version: 20140423102712) do
+ create_table(:articles) {}
+ end
+ RUBY
+
+ list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip }
+ File.write("log/my.log", "zomg!")
+
+ assert_equal '[]', list_tables.call
+ assert_equal 5, File.size("log/my.log")
+ assert_not File.exist?("tmp/restart.txt")
+ `bin/setup 2>&1`
+ assert_equal 0, File.size("log/my.log")
+ assert_equal '["articles", "schema_migrations"]', list_tables.call
+ assert File.exist?("tmp/restart.txt")
+ end
+ end
+
+ def test_bin_setup_output
+ Dir.chdir(app_path) do
+ app_file 'db/schema.rb', ""
+
+ output = `bin/setup 2>&1`
+ assert_equal(<<-OUTPUT, output)
+== Installing dependencies ==
+The Gemfile's dependencies are satisfied
+
+== Preparing database ==
+
+== Removing old logs and tempfiles ==
+
+== Restarting application server ==
+ OUTPUT
+ end
+ end
+ end
+end
diff --git a/railties/test/application/configuration/base_test.rb b/railties/test/application/configuration/base_test.rb
deleted file mode 100644
index d6a82b139d..0000000000
--- a/railties/test/application/configuration/base_test.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'isolation/abstract_unit'
-require 'rack/test'
-require 'env_helpers'
-
-module ApplicationTests
- module ConfigurationTests
- class BaseTest < ActiveSupport::TestCase
- def setup
- build_app
- boot_rails
- FileUtils.rm_rf("#{app_path}/config/environments")
- end
-
- def teardown
- teardown_app
- FileUtils.rm_rf(new_app) if File.directory?(new_app)
- end
-
- private
- def new_app
- File.expand_path("#{app_path}/../new_app")
- end
-
- def copy_app
- FileUtils.cp_r(app_path, new_app)
- end
-
- def app
- @app ||= Rails.application
- end
-
- def require_environment
- require "#{app_path}/config/environment"
- end
- end
- end
-end \ No newline at end of file
diff --git a/railties/test/application/configuration/custom_test.rb b/railties/test/application/configuration/custom_test.rb
index f8d22f6d97..28b3b2f2d6 100644
--- a/railties/test/application/configuration/custom_test.rb
+++ b/railties/test/application/configuration/custom_test.rb
@@ -1,22 +1,54 @@
-require 'application/configuration/base_test'
-
-class ApplicationTests::ConfigurationTests::CustomTest < ApplicationTests::ConfigurationTests::BaseTest
- test 'access custom configuration point' do
- add_to_config <<-RUBY
- config.x.payment_processing.schedule = :daily
- config.x.payment_processing.retries = 3
- config.x.super_debugger = true
- config.x.hyper_debugger = false
- config.x.nil_debugger = nil
- RUBY
- require_environment
-
- x = Rails.configuration.x
- assert_equal :daily, x.payment_processing.schedule
- assert_equal 3, x.payment_processing.retries
- assert_equal true, x.super_debugger
- assert_equal false, x.hyper_debugger
- assert_equal nil, x.nil_debugger
- assert_nil x.i_do_not_exist.zomg
+require 'isolation/abstract_unit'
+
+module ApplicationTests
+ module ConfigurationTests
+ class CustomTest < ActiveSupport::TestCase
+ def setup
+ build_app
+ boot_rails
+ FileUtils.rm_rf("#{app_path}/config/environments")
+ end
+
+ def teardown
+ teardown_app
+ FileUtils.rm_rf(new_app) if File.directory?(new_app)
+ end
+
+ test 'access custom configuration point' do
+ add_to_config <<-RUBY
+ config.x.payment_processing.schedule = :daily
+ config.x.payment_processing.retries = 3
+ config.x.super_debugger = true
+ config.x.hyper_debugger = false
+ config.x.nil_debugger = nil
+ RUBY
+ require_environment
+
+ x = Rails.configuration.x
+ assert_equal :daily, x.payment_processing.schedule
+ assert_equal 3, x.payment_processing.retries
+ assert_equal true, x.super_debugger
+ assert_equal false, x.hyper_debugger
+ assert_equal nil, x.nil_debugger
+ assert_nil x.i_do_not_exist.zomg
+ end
+
+ private
+ def new_app
+ File.expand_path("#{app_path}/../new_app")
+ end
+
+ def copy_app
+ FileUtils.cp_r(app_path, new_app)
+ end
+
+ def app
+ @app ||= Rails.application
+ end
+
+ def require_environment
+ require "#{app_path}/config/environment"
+ end
+ end
end
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 8f5b2d0d68..38516a1c1a 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -690,7 +690,7 @@ module ApplicationTests
_ = ActionMailer::Base
- assert_equal [::MyPreviewMailInterceptor], ActionMailer::Base.preview_interceptors
+ assert_equal [ActionMailer::InlinePreviewInterceptor, ::MyPreviewMailInterceptor], ActionMailer::Base.preview_interceptors
end
test "registers multiple preview interceptors with ActionMailer" do
@@ -703,7 +703,20 @@ module ApplicationTests
_ = ActionMailer::Base
- assert_equal [MyPreviewMailInterceptor, MyOtherPreviewMailInterceptor], ActionMailer::Base.preview_interceptors
+ assert_equal [ActionMailer::InlinePreviewInterceptor, MyPreviewMailInterceptor, MyOtherPreviewMailInterceptor], ActionMailer::Base.preview_interceptors
+ end
+
+ test "default preview interceptor can be removed" do
+ app_file 'config/initializers/preview_interceptors.rb', <<-RUBY
+ ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor)
+ RUBY
+
+ require "#{app_path}/config/environment"
+ require "mail"
+
+ _ = ActionMailer::Base
+
+ assert_equal [], ActionMailer::Base.preview_interceptors
end
test "registers observers with ActionMailer" do
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index 85066210f3..1027bca2c1 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -210,7 +210,7 @@ class LoadingTest < ActiveSupport::TestCase
app_file 'config/routes.rb', <<-RUBY
$counter ||= 0
Rails.application.routes.draw do
- get '/c', to: lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
+ get '/c', to: lambda { |env| User.name; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
end
RUBY
@@ -243,7 +243,7 @@ class LoadingTest < ActiveSupport::TestCase
$counter ||= 1
$counter *= 2
Rails.application.routes.draw do
- get '/c', to: lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
+ get '/c', to: lambda { |env| User.name; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
end
RUBY
diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb
index 1752a9f3c6..e462d2c15e 100644
--- a/railties/test/application/mailer_previews_test.rb
+++ b/railties/test/application/mailer_previews_test.rb
@@ -1,5 +1,7 @@
require 'isolation/abstract_unit'
require 'rack/test'
+require 'base64'
+
module ApplicationTests
class MailerPreviewsTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
@@ -327,6 +329,32 @@ module ApplicationTests
assert_match "Email &#39;bar&#39; not found in NotifierPreview", last_response.body
end
+ test "mailer preview NullMail" do
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ # does not call +mail+
+ end
+ end
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo"
+ assert_match "You are trying to preview an email that does not have any content.", last_response.body
+ assert_match "notifier#foo", last_response.body
+ end
+
test "mailer preview email part not found" do
mailer 'notifier', <<-RUBY
class Notifier < ActionMailer::Base
@@ -428,6 +456,222 @@ module ApplicationTests
assert_match '<option selected value="?part=text%2Fplain">View as plain-text email</option>', last_response.body
end
+ test "mailer previews create correct links when loaded on a subdirectory" do
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers", {}, 'SCRIPT_NAME' => '/my_app'
+ assert_match '<h3><a href="/my_app/rails/mailers/notifier">Notifier</a></h3>', last_response.body
+ assert_match '<li><a href="/my_app/rails/mailers/notifier/foo">foo</a></li>', last_response.body
+ end
+
+ test "plain text mailer preview with attachment" do
+ image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo="
+
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ attachments['pixel.png'] = File.read("#{app_path}/public/images/pixel.png", mode: 'rb')
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo"
+ assert_equal 200, last_response.status
+ assert_match %r[<iframe seamless name="messageBody"], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/plain"
+ assert_equal 200, last_response.status
+ assert_match %r[Hello, World!], last_response.body
+ end
+
+ test "multipart mailer preview with attachment" do
+ image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo="
+
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ attachments['pixel.png'] = File.read("#{app_path}/public/images/pixel.png", mode: 'rb')
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ html_template 'notifier/foo', <<-RUBY
+ <p>Hello, World!</p>
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo"
+ assert_equal 200, last_response.status
+ assert_match %r[<iframe seamless name="messageBody"], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/plain"
+ assert_equal 200, last_response.status
+ assert_match %r[Hello, World!], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/html"
+ assert_equal 200, last_response.status
+ assert_match %r[<p>Hello, World!</p>], last_response.body
+ end
+
+ test "multipart mailer preview with inline attachment" do
+ image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo="
+
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ attachments['pixel.png'] = File.read("#{app_path}/public/images/pixel.png", mode: 'rb')
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ html_template 'notifier/foo', <<-RUBY
+ <p>Hello, World!</p>
+ <%= image_tag attachments['pixel.png'].url %>
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo"
+ assert_equal 200, last_response.status
+ assert_match %r[<iframe seamless name="messageBody"], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/plain"
+ assert_equal 200, last_response.status
+ assert_match %r[Hello, World!], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/html"
+ assert_equal 200, last_response.status
+ assert_match %r[<p>Hello, World!</p>], last_response.body
+ assert_match %r[src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo="], last_response.body
+ end
+
+ test "multipart mailer preview with attached email" do
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ message = ::Mail.new do
+ from 'foo@example.com'
+ to 'bar@example.com'
+ subject 'Important Message'
+
+ text_part do
+ body 'Goodbye, World!'
+ end
+
+ html_part do
+ body '<p>Goodbye, World!</p>'
+ end
+ end
+
+ attachments['message.eml'] = message.to_s
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ html_template 'notifier/foo', <<-RUBY
+ <p>Hello, World!</p>
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo"
+ assert_equal 200, last_response.status
+ assert_match %r[<iframe seamless name="messageBody"], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/plain"
+ assert_equal 200, last_response.status
+ assert_match %r[Hello, World!], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/html"
+ assert_equal 200, last_response.status
+ assert_match %r[<p>Hello, World!</p>], last_response.body
+ end
+
private
def build_app
super
@@ -449,5 +693,9 @@ module ApplicationTests
def text_template(name, contents)
app_file("app/views/#{name}.text.erb", contents)
end
+
+ def image_file(name, contents)
+ app_file("public/images/#{name}", Base64.strict_decode64(contents), 'wb')
+ end
end
end
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index c414732f92..5cc9790b28 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -181,6 +181,34 @@ module ApplicationTests
end
end
+ test "db:schema:load with inflections" do
+ Dir.chdir(app_path) do
+ app_file 'config/initializers/inflection.rb', <<-RUBY
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular 'goose', 'geese'
+ end
+ RUBY
+ app_file 'config/initializers/primary_key_table_name.rb', <<-RUBY
+ ActiveRecord::Base.primary_key_prefix_type = :table_name
+ RUBY
+ app_file 'db/schema.rb', <<-RUBY
+ ActiveRecord::Schema.define(version: 20140423102712) do
+ create_table("goose".pluralize) do |t|
+ t.string :name
+ end
+ end
+ RUBY
+
+ `bin/rake db:schema:load`
+
+ tables = `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip
+ assert_match(/"geese"/, tables)
+
+ columns = `bin/rails runner 'p ActiveRecord::Base.connection.columns("geese").map(&:name)'`.strip
+ assert_equal columns, '["gooseid", "name"]'
+ end
+ end
+
def db_test_load_structure
Dir.chdir(app_path) do
`rails generate model book title:string;
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 0648b11813..de14f269df 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -190,7 +190,7 @@ module ApplicationTests
bundle exec rake db:migrate test`
end
- assert_match(/7 runs, 13 assertions, 0 failures, 0 errors/, output)
+ assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output)
assert_no_match(/Errors running/, output)
end
@@ -203,7 +203,7 @@ module ApplicationTests
bundle exec rake db:migrate test`
end
- assert_match(/7 runs, 13 assertions, 0 failures, 0 errors/, output)
+ assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output)
assert_no_match(/Errors running/, output)
end
diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb
index a3cd1eb0ed..7950ed6aa7 100644
--- a/railties/test/commands/dbconsole_test.rb
+++ b/railties/test/commands/dbconsole_test.rb
@@ -99,19 +99,15 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
end
def test_rails_env_is_development_when_argument_is_dev
- dbconsole = Rails::DBConsole.new
-
- dbconsole.stub(:available_environments, ['development', 'test']) do
- options = dbconsole.send(:parse_arguments, ['dev'])
+ Rails::DBConsole.stub(:available_environments, ['development', 'test']) do
+ options = Rails::DBConsole.send(:parse_arguments, ['dev'])
assert_match('development', options[:environment])
end
end
def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present
- dbconsole = Rails::DBConsole.new
-
- dbconsole.stub(:available_environments, ['dev']) do
- options = dbconsole.send(:parse_arguments, ['dev'])
+ Rails::DBConsole.stub(:available_environments, ['dev']) do
+ options = Rails::DBConsole.send(:parse_arguments, ['dev'])
assert_match('dev', options[:environment])
end
end
@@ -158,12 +154,6 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
assert_equal 'q1w2e3', ENV['PGPASSWORD']
end
- def test_sqlite
- start(adapter: 'sqlite', database: 'db')
- assert !aborted
- assert_equal ['sqlite', 'db'], dbconsole.find_cmd_and_exec_args
- end
-
def test_sqlite3
start(adapter: 'sqlite3', database: 'db.sqlite3')
assert !aborted
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 282e8cc4f9..2bfa05a0b8 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -560,6 +560,14 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_spring_with_dev_option
+ run_generator [destination_root, "--dev"]
+
+ assert_file "Gemfile" do |content|
+ assert_no_match(/spring/, content)
+ end
+ end
+
def test_generator_if_skip_turbolinks_is_given
run_generator [destination_root, "--skip-turbolinks"]
diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb
index 18a26fde05..1c32fc1bfd 100644
--- a/railties/test/generators/named_base_test.rb
+++ b/railties/test/generators/named_base_test.rb
@@ -1,6 +1,6 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator'
-require 'mocha/setup' # FIXME: stop using mocha
+require 'minitest/mock'
class NamedBaseTest < Rails::Generators::TestCase
include GeneratorsTestHelper
@@ -80,10 +80,13 @@ class NamedBaseTest < Rails::Generators::TestCase
def test_application_name
g = generator ['Admin::Foo']
- Rails.stubs(:application).returns(Object.new)
- assert_name g, "object", :application_name
- Rails.stubs(:application).returns(nil)
- assert_name g, "application", :application_name
+ Rails.stub(:application, Object.new) do
+ assert_name g, "object", :application_name
+ end
+
+ Rails.stub(:application, nil) do
+ assert_name g, "application", :application_name
+ end
end
def test_index_helper
@@ -103,11 +106,11 @@ class NamedBaseTest < Rails::Generators::TestCase
def test_hide_namespace
g = generator ['Hidden']
- g.class.stubs(:namespace).returns('hidden')
-
- assert !Rails::Generators.hidden_namespaces.include?('hidden')
- g.class.hide!
- assert Rails::Generators.hidden_namespaces.include?('hidden')
+ g.class.stub(:namespace, 'hidden') do
+ assert !Rails::Generators.hidden_namespaces.include?('hidden')
+ g.class.hide!
+ assert Rails::Generators.hidden_namespaces.include?('hidden')
+ end
end
def test_scaffold_plural_names_with_model_name_option
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index a0f244da28..d6e5f4bd89 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -38,7 +38,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_equal "Invalid plugin name 43things. Please give a name which does not start with numbers.\n", content
content = capture(:stderr){ run_generator [File.join(destination_root, "plugin")] }
- assert_equal "Invalid plugin name plugin. Please give a name which does not match one of the reserved rails words.\n", content
+ assert_equal "Invalid plugin name plugin. Please give a name which does not match one of the reserved rails words: [\"application\", \"destroy\", \"plugin\", \"runner\", \"test\"]\n", content
content = capture(:stderr){ run_generator [File.join(destination_root, "Digest")] }
assert_equal "Invalid plugin name Digest, constant Digest is already in use. Please choose another plugin name.\n", content
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 68f07f29d7..3bcf3894ce 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -56,7 +56,7 @@ module SharedGeneratorTests
reserved_words = %w[application destroy plugin runner test]
reserved_words.each do |reserved|
content = capture(:stderr){ run_generator [File.join(destination_root, reserved)] }
- assert_match(/Invalid \w+ name #{reserved}. Please give a name which does not match one of the reserved rails words.\n/, content)
+ assert_match(/Invalid \w+ name #{reserved}. Please give a name which does not match one of the reserved rails words: \["application", "destroy", "plugin", "runner", "test"\]\n/, content)
end
end
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 127e059bf1..ce75aba4eb 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -34,6 +34,12 @@ class GeneratorsTest < Rails::Generators::TestCase
assert_match "Maybe you meant 'migration'", output
end
+ def test_generator_multiple_suggestions
+ name = :tas
+ output = capture(:stdout){ Rails::Generators.invoke name }
+ assert_match "Maybe you meant 'task', 'job' or", output
+ end
+
def test_help_when_a_generator_with_required_arguments_is_invoked_without_arguments
output = capture(:stdout){ Rails::Generators.invoke :model, [] }
assert_match(/Description:/, output)
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 4509797da1..65d8a55421 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -270,9 +270,9 @@ module TestHelpers
File.open(file, "w+") { |f| f.puts contents }
end
- def app_file(path, contents)
+ def app_file(path, contents, mode = 'w')
FileUtils.mkdir_p File.dirname("#{app_path}/#{path}")
- File.open("#{app_path}/#{path}", 'w') do |f|
+ File.open("#{app_path}/#{path}", mode) do |f|
f.puts contents
end
end
diff --git a/railties/test/path_generation_test.rb b/railties/test/path_generation_test.rb
index e3dfcdfb7d..27e64b97b7 100644
--- a/railties/test/path_generation_test.rb
+++ b/railties/test/path_generation_test.rb
@@ -1,21 +1,17 @@
require 'abstract_unit'
require 'active_support/core_ext/object/with_options'
require 'active_support/core_ext/object/json'
-require 'rails'
-require 'rails/application'
-
-ROUTING = ActionDispatch::Routing
class PathGenerationTest < ActiveSupport::TestCase
attr_reader :app
- class TestSet < ROUTING::RouteSet
+ class TestSet < ActionDispatch::Routing::RouteSet
def initialize(block)
@block = block
super()
end
- class Dispatcher < ROUTING::RouteSet::Dispatcher
+ class Dispatcher < ActionDispatch::Routing::RouteSet::Dispatcher
def initialize(defaults, set, block)
super(defaults)
@block = block
diff --git a/railties/test/test_info_test.rb b/railties/test/test_info_test.rb
deleted file mode 100644
index b9c3a9c0c7..0000000000
--- a/railties/test/test_info_test.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require 'abstract_unit'
-require 'rails/test_unit/sub_test_task'
-
-module Rails
- class TestInfoTest < ActiveSupport::TestCase
- def test_test_files
- info = new_test_info ['test']
- assert_predicate info.files, :empty?
- assert_nil info.opts
- assert_equal ['test'], info.tasks
- end
-
- def test_with_file
- info = new_test_info ['test', __FILE__]
- assert_equal [__FILE__], info.files
- assert_nil info.opts
- assert_equal ['test'], info.tasks
- end
-
- def test_with_opts
- info = new_test_info ['test', __FILE__, '/foo/']
- assert_equal [__FILE__], info.files
- assert_equal '-n /foo/', info.opts
- assert_equal ['test'], info.tasks
- end
-
- def test_with_model_shorthand
- info = new_test_info ['test', 'models/foo', '/foo/']
-
- def info.test_file?(file)
- file == "test/models/foo_test.rb" || super
- end
-
- assert_equal ['test/models/foo_test.rb'], info.files
- assert_equal '-n /foo/', info.opts
- assert_equal ['test'], info.tasks
- end
-
- def test_with_model_path
- info = new_test_info ['test', 'app/models/foo.rb', '/foo/']
-
- def info.test_file?(file)
- file == "test/models/foo_test.rb" || super
- end
-
- assert_equal ['test/models/foo_test.rb'], info.files
- assert_equal '-n /foo/', info.opts
- assert_equal ['test'], info.tasks
- end
-
- private
- def new_test_info(tasks)
- Class.new(TestTask::TestInfo) {
- def task_defined?(task)
- task == "test"
- end
- }.new tasks
- end
- end
-end