aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md4
-rw-r--r--RELEASING_RAILS.md22
-rw-r--r--actioncable/app/assets/javascripts/action_cable.coffee.erb6
-rw-r--r--actioncable/lib/action_cable/channel/naming.rb1
-rw-r--r--actioncable/lib/action_cable/channel/streams.rb4
-rw-r--r--actioncable/package.json24
-rw-r--r--actionmailer/CHANGELOG.md4
-rw-r--r--actionmailer/lib/action_mailer/base.rb2
-rw-r--r--actionmailer/lib/action_mailer/delivery_job.rb21
-rw-r--r--actionmailer/lib/action_mailer/message_delivery.rb59
-rw-r--r--actionmailer/lib/action_mailer/rescuable.rb27
-rw-r--r--actionmailer/test/mailers/delayed_mailer.rb20
-rw-r--r--actionmailer/test/message_delivery_test.rb51
-rw-r--r--actionpack/lib/abstract_controller/base.rb6
-rw-r--r--actionpack/lib/action_controller/metal/rescue.rb13
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb24
-rw-r--r--actionpack/lib/action_dispatch/routing.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/assertion_response.rb16
-rw-r--r--actionpack/test/assertions/response_assertions_test.rb2
-rw-r--r--actionpack/test/controller/rescue_test.rb31
-rw-r--r--actionpack/test/controller/routing_test.rb2
-rw-r--r--actionpack/test/controller/test_case_test.rb4
-rw-r--r--actionpack/test/dispatch/request_test.rb31
-rw-r--r--actionview/lib/action_view/digestor.rb9
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb6
-rw-r--r--actionview/lib/action_view/lookup_context.rb12
-rw-r--r--actionview/lib/action_view/railtie.rb2
-rw-r--r--actionview/lib/action_view/renderer/template_renderer.rb6
-rw-r--r--actionview/lib/action_view/template/resolver.rb17
-rw-r--r--actionview/lib/action_view/test_case.rb2
-rw-r--r--actionview/test/actionpack/abstract/layouts_test.rb191
-rw-r--r--actionview/test/template/form_options_helper_test.rb2
-rw-r--r--activejob/lib/active_job/arguments.rb5
-rw-r--r--activejob/lib/active_job/execution.rb2
-rw-r--r--activemodel/lib/active_model/errors.rb13
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb6
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb2
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb2
-rw-r--r--activerecord/CHANGELOG.md4
-rw-r--r--activerecord/lib/active_record/associations/association.rb3
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb16
-rw-r--r--activerecord/lib/active_record/attribute.rb2
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb2
-rw-r--r--activerecord/lib/active_record/coders/json.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb2
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb5
-rw-r--r--activerecord/lib/active_record/migration.rb6
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb8
-rw-r--r--activerecord/lib/active_record/suppressor.rb3
-rw-r--r--activerecord/lib/active_record/type/serialized.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb7
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb2
-rw-r--r--activerecord/test/cases/attributes_test.rb11
-rw-r--r--activerecord/test/cases/base_test.rb4
-rw-r--r--activerecord/test/cases/calculations_test.rb4
-rw-r--r--activerecord/test/cases/coders/json_test.rb15
-rw-r--r--activerecord/test/cases/fixtures_test.rb2
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb2
-rw-r--r--activerecord/test/cases/migration_test.rb4
-rw-r--r--activerecord/test/cases/query_cache_test.rb5
-rw-r--r--activerecord/test/cases/quoting_test.rb6
-rw-r--r--activerecord/test/cases/relations_test.rb5
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb33
-rw-r--r--activerecord/test/cases/suppressor_test.rb12
-rw-r--r--activesupport/CHANGELOG.md4
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb5
-rw-r--r--activesupport/lib/active_support/rescuable.rb142
-rw-r--r--activesupport/lib/active_support/xml_mini.rb33
-rw-r--r--activesupport/test/core_ext/array/conversions_test.rb6
-rw-r--r--activesupport/test/core_ext/array/grouping_test.rb5
-rw-r--r--activesupport/test/core_ext/bigdecimal_test.rb2
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb17
-rw-r--r--activesupport/test/core_ext/duration_test.rb2
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb34
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb7
-rw-r--r--activesupport/test/core_ext/object/deep_dup_test.rb2
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb22
-rw-r--r--activesupport/test/inflector_test_cases.rb3
-rw-r--r--activesupport/test/rescuable_test.rb21
-rw-r--r--guides/source/2_3_release_notes.md2
-rw-r--r--guides/source/5_0_release_notes.md71
-rw-r--r--guides/source/action_cable_overview.md324
-rw-r--r--guides/source/active_support_core_extensions.md7
-rw-r--r--guides/source/api_app.md9
-rw-r--r--guides/source/api_documentation_guidelines.md2
-rw-r--r--guides/source/asset_pipeline.md17
-rw-r--r--guides/source/caching_with_rails.md6
-rw-r--r--guides/source/configuring.md75
-rw-r--r--guides/source/documents.yaml4
-rw-r--r--guides/source/security.md36
-rw-r--r--guides/source/testing.md2
-rw-r--r--guides/source/upgrading_ruby_on_rails.md2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/cable.yml9
-rw-r--r--railties/test/application/assets_test.rb2
-rw-r--r--tasks/release.rb31
113 files changed, 1258 insertions, 549 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 961b48733c..f6ebef7e89 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -21,6 +21,10 @@
* Before submitting, please read the [Contributing to Ruby on Rails](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) guide to know more about coding conventions and benchmarks.
+#### **Did you fix whitespace, format code, or make a purely cosmetic patch?**
+
+Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted (read more about [our rationales behind this decision](https://github.com/rails/rails/pull/13771#issuecomment-32746700)).
+
#### **Do you intend to add a new feature or change an existing one?**
* Suggest your change in the [rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code.
diff --git a/RELEASING_RAILS.md b/RELEASING_RAILS.md
index 7575a1fefa..5ed5a8b029 100644
--- a/RELEASING_RAILS.md
+++ b/RELEASING_RAILS.md
@@ -103,18 +103,24 @@ branch.
Run `rake install` to generate the gems and install them locally. Then try
generating a new app and ensure that nothing explodes.
+Verify that Action Cable's package.json is updated with the RC version.
+
This will stop you from looking silly when you push an RC to rubygems.org and
then realize it is broken.
-### Release the gem.
+### Release to RubyGems and NPM.
+
+IMPORTANT: The Action Cable client is released as an NPM package, so you must
+have Node.js installed, have an NPM account (npmjs.com), and be an actioncable
+package owner (`npm owner ls actioncable`) to do a full release. Do not release
+until you're set up with NPM!
-IMPORTANT: Due to YAML parse problems on the rubygems.org server, it is safest
-to use Ruby 1.8 when releasing.
+Run `rake release`. This will populate the gemspecs and NPM package.json with
+the current RAILS_VERSION, commit the changes, tag it, and push the gems to
+rubygems.org.
-Run `rake release`. This will populate the gemspecs with data from
-RAILS_VERSION, commit the changes, tag it, and push the gems to rubygems.org.
-Here are the commands that `rake release` should use, so you can understand
-what to do in case anything goes wrong:
+Here are the commands that `rake release` uses so you can understand what to do
+in case anything goes wrong:
```
$ rake all:build
@@ -122,7 +128,7 @@ $ git commit -am'updating RAILS_VERSION'
$ git tag -m 'v3.0.10.rc1 release' v3.0.10.rc1
$ git push
$ git push --tags
-$ for i in $(ls pkg); do gem push $i; done
+$ for i in $(ls pkg); do gem push $i; npm publish; done
```
### Send Rails release announcements
diff --git a/actioncable/app/assets/javascripts/action_cable.coffee.erb b/actioncable/app/assets/javascripts/action_cable.coffee.erb
index f0422d9d9c..32f9f517f4 100644
--- a/actioncable/app/assets/javascripts/action_cable.coffee.erb
+++ b/actioncable/app/assets/javascripts/action_cable.coffee.erb
@@ -33,3 +33,9 @@
if @debugging
messages.push(Date.now())
console.log("[ActionCable]", messages...)
+
+# NOTE: We expose ActionCable as a browser global so we can reference it
+# internally without concern for how the module is loaded.
+window?.ActionCable = @ActionCable
+
+module?.exports = @ActionCable
diff --git a/actioncable/lib/action_cable/channel/naming.rb b/actioncable/lib/action_cable/channel/naming.rb
index 4c9d53b15a..8e1b2a4af0 100644
--- a/actioncable/lib/action_cable/channel/naming.rb
+++ b/actioncable/lib/action_cable/channel/naming.rb
@@ -10,6 +10,7 @@ module ActionCable
#
# ChatChannel.channel_name # => 'chat'
# Chats::AppearancesChannel.channel_name # => 'chats:appearances'
+ # FooChats::BarAppearancesChannel.channel_name # => 'foo_chats:bar_appearances'
def channel_name
@channel_name ||= name.sub(/Channel$/, '').gsub('::',':').underscore
end
diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb
index 200c9d053c..0a0a95f453 100644
--- a/actioncable/lib/action_cable/channel/streams.rb
+++ b/actioncable/lib/action_cable/channel/streams.rb
@@ -19,14 +19,14 @@ module ActionCable
# end
#
# Based on the above example, the subscribers of this channel will get whatever data is put into the,
- # let's say, `comments_for_45` broadcasting as soon as it's put there.
+ # let's say, <tt>comments_for_45</tt> broadcasting as soon as it's put there.
#
# An example broadcasting for this channel looks like so:
#
# ActionCable.server.broadcast "comments_for_45", author: 'DHH', content: 'Rails is just swell'
#
# If you have a stream that is related to a model, then the broadcasting used can be generated from the model and channel.
- # The following example would subscribe to a broadcasting like `comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE`
+ # The following example would subscribe to a broadcasting like <tt>comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE</tt>.
#
# class CommentsChannel < ApplicationCable::Channel
# def subscribed
diff --git a/actioncable/package.json b/actioncable/package.json
new file mode 100644
index 0000000000..37f82fa1ea
--- /dev/null
+++ b/actioncable/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "actioncable",
+ "version": "5.0.0-rc1",
+ "description": "WebSocket framework for Ruby on Rails.",
+ "main": "lib/assets/compiled/action_cable.js",
+ "files": [
+ "lib/assets/compiled/*.js"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "rails/rails"
+ },
+ "keywords": [
+ "websockets",
+ "actioncable",
+ "rails"
+ ],
+ "author": "David Heinemeier Hansson <david@loudthinking.com>",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/rails/rails/issues"
+ },
+ "homepage": "http://rubyonrails.org/"
+}
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index d45e74133a..3b9f503a0b 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,2 +1,6 @@
+* Exception handling: use `rescue_from` to handle exceptions raised by
+ mailer actions, by message delivery, and by deferred delivery jobs.
+
+ *Jeremy Daer*
Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actionmailer/CHANGELOG.md) for previous changes.
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 10ee5490c3..ea5af9e4f2 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -5,6 +5,7 @@ require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/anonymous'
require 'action_mailer/log_subscriber'
+require 'action_mailer/rescuable'
module ActionMailer
# Action Mailer allows you to send email from your application using a mailer model and views.
@@ -420,6 +421,7 @@ module ActionMailer
# * <tt>deliver_later_queue_name</tt> - The name of the queue used with <tt>deliver_later</tt>.
class Base < AbstractController::Base
include DeliveryMethods
+ include Rescuable
include Previews
abstract!
diff --git a/actionmailer/lib/action_mailer/delivery_job.rb b/actionmailer/lib/action_mailer/delivery_job.rb
index 52772af2d3..d371c1b61a 100644
--- a/actionmailer/lib/action_mailer/delivery_job.rb
+++ b/actionmailer/lib/action_mailer/delivery_job.rb
@@ -3,11 +3,32 @@ require 'active_job'
module ActionMailer
# The <tt>ActionMailer::DeliveryJob</tt> class is used when you
# want to send emails outside of the request-response cycle.
+ #
+ # Exceptions are rescued and handled by the mailer class.
class DeliveryJob < ActiveJob::Base # :nodoc:
queue_as { ActionMailer::Base.deliver_later_queue_name }
+ rescue_from StandardError, with: :handle_exception_with_mailer_class
+
def perform(mailer, mail_method, delivery_method, *args) #:nodoc:
mailer.constantize.public_send(mail_method, *args).send(delivery_method)
end
+
+ private
+ # "Deserialize" the mailer class name by hand in case another argument
+ # (like a Global ID reference) raised DeserializationError.
+ def mailer_class
+ if mailer = Array(@serialized_arguments).first || Array(arguments).first
+ mailer.constantize
+ end
+ end
+
+ def handle_exception_with_mailer_class(exception)
+ if klass = mailer_class
+ klass.handle_exception exception
+ else
+ raise exception
+ end
+ end
end
end
diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb
index d638057d72..c5ba5f9f1d 100644
--- a/actionmailer/lib/action_mailer/message_delivery.rb
+++ b/actionmailer/lib/action_mailer/message_delivery.rb
@@ -1,7 +1,6 @@
require 'delegate'
module ActionMailer
-
# The <tt>ActionMailer::MessageDelivery</tt> class is used by
# <tt>ActionMailer::Base</tt> when creating a new mailer.
# <tt>MessageDelivery</tt> is a wrapper (+Delegator+ subclass) around a lazy
@@ -14,30 +13,35 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_later # enqueue email delivery as a job through Active Job
# Notifier.welcome(User.first).message # a Mail::Message object
class MessageDelivery < Delegator
- def initialize(mailer, mail_method, *args) #:nodoc:
- @mailer = mailer
- @mail_method = mail_method
- @args = args
- @obj = nil
+ def initialize(mailer_class, action, *args) #:nodoc:
+ @mailer_class, @action, @args = mailer_class, action, args
+
+ # The mail is only processed if we try to call any methods on it.
+ # Typical usage will leave it unloaded and call deliver_later.
+ @processed_mailer = nil
+ @mail_message = nil
end
+ # Method calls are delegated to the Mail::Message that's ready to deliver.
def __getobj__ #:nodoc:
- @obj ||= begin
- mailer = @mailer.new
- mailer.process @mail_method, *@args
- mailer.message
- end
+ @mail_message ||= processed_mailer.message
end
- def __setobj__(obj) #:nodoc:
- @obj = obj
+ # Unused except for delegator internals (dup, marshaling).
+ def __setobj__(mail_message) #:nodoc:
+ @mail_message = mail_message
end
- # Returns the Mail::Message object
+ # Returns the resulting Mail::Message
def message
__getobj__
end
+ # Was the delegate loaded, causing the mailer action to be processed?
+ def processed?
+ @processed_mailer || @mail_message
+ end
+
# Enqueues the email to be delivered through Active Job. When the
# job runs it will send the email using +deliver_now!+. That means
# that the message will be sent bypassing checking +perform_deliveries+
@@ -78,7 +82,9 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_now!
#
def deliver_now!
- message.deliver!
+ processed_mailer.handle_exceptions do
+ message.deliver!
+ end
end
# Delivers an email:
@@ -86,24 +92,33 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_now
#
def deliver_now
- message.deliver
+ processed_mailer.handle_exceptions do
+ message.deliver
+ end
end
private
+ # Returns the processed Mailer instance. We keep this instance
+ # on hand so we can delegate exception handling to it.
+ def processed_mailer
+ @processed_mailer ||= @mailer_class.new.tap do |mailer|
+ mailer.process @action, *@args
+ end
+ end
def enqueue_delivery(delivery_method, options={})
- if @obj
- raise "You've accessed the message before asking to deliver it " \
- "later, so you may have made local changes that would be " \
- "silently lost if we enqueued a job to deliver it. Why? Only " \
+ if processed?
+ ::Kernel.raise "You've accessed the message before asking to " \
+ "deliver it later, so you may have made local changes that would " \
+ "be silently lost if we enqueued a job to deliver it. Why? Only " \
"the mailer method *arguments* are passed with the delivery job! " \
"Do not access the message in any way if you mean to deliver it " \
"later. Workarounds: 1. don't touch the message before calling " \
"#deliver_later, 2. only touch the message *within your mailer " \
"method*, or 3. use a custom Active Job instead of #deliver_later."
else
- args = @mailer.name, @mail_method.to_s, delivery_method.to_s, *@args
- ActionMailer::DeliveryJob.set(options).perform_later(*args)
+ args = @mailer_class.name, @action.to_s, delivery_method.to_s, *@args
+ ::ActionMailer::DeliveryJob.set(options).perform_later(*args)
end
end
end
diff --git a/actionmailer/lib/action_mailer/rescuable.rb b/actionmailer/lib/action_mailer/rescuable.rb
new file mode 100644
index 0000000000..f2eabfa057
--- /dev/null
+++ b/actionmailer/lib/action_mailer/rescuable.rb
@@ -0,0 +1,27 @@
+module ActionMailer #:nodoc:
+ # Provides `rescue_from` for mailers. Wraps mailer action processing,
+ # mail job processing, and mail delivery.
+ module Rescuable
+ extend ActiveSupport::Concern
+ include ActiveSupport::Rescuable
+
+ class_methods do
+ def handle_exception(exception) #:nodoc:
+ rescue_with_handler(exception) || raise(exception)
+ end
+ end
+
+ def handle_exceptions #:nodoc:
+ yield
+ rescue => exception
+ rescue_with_handler(exception) || raise
+ end
+
+ private
+ def process(*)
+ handle_exceptions do
+ super
+ end
+ end
+ end
+end
diff --git a/actionmailer/test/mailers/delayed_mailer.rb b/actionmailer/test/mailers/delayed_mailer.rb
index 62d4baa434..e6211ef028 100644
--- a/actionmailer/test/mailers/delayed_mailer.rb
+++ b/actionmailer/test/mailers/delayed_mailer.rb
@@ -1,6 +1,26 @@
+require 'active_job/arguments'
+
+class DelayedMailerError < StandardError; end
+
class DelayedMailer < ActionMailer::Base
+ cattr_accessor :last_error
+ cattr_accessor :last_rescue_from_instance
+
+ rescue_from DelayedMailerError do |error|
+ @@last_error = error
+ @@last_rescue_from_instance = self
+ end
+
+ rescue_from ActiveJob::DeserializationError do |error|
+ @@last_error = error
+ @@last_rescue_from_instance = self
+ end
def test_message(*)
mail(from: 'test-sender@test.com', to: 'test-receiver@test.com', subject: 'Test Subject', body: 'Test Body')
end
+
+ def test_raise(klass_name)
+ raise klass_name.constantize, 'boom'
+ end
end
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index f06d69369f..aaed94d519 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -12,16 +12,23 @@ class MessageDeliveryTest < ActiveSupport::TestCase
ActionMailer::Base.deliver_later_queue_name = :test_queue
ActionMailer::Base.delivery_method = :test
ActiveJob::Base.logger = Logger.new(nil)
- @mail = DelayedMailer.test_message(1, 2, 3)
ActionMailer::Base.deliveries.clear
ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
+
+ DelayedMailer.last_error = nil
+ DelayedMailer.last_rescue_from_instance = nil
+
+ @mail = DelayedMailer.test_message(1, 2, 3)
end
teardown do
ActiveJob::Base.logger = @previous_logger
ActionMailer::Base.delivery_method = @previous_delivery_method
ActionMailer::Base.deliver_later_queue_name = @previous_deliver_later_queue_name
+
+ DelayedMailer.last_error = nil
+ DelayedMailer.last_rescue_from_instance = nil
end
test 'should have a message' do
@@ -101,4 +108,46 @@ class MessageDeliveryTest < ActiveSupport::TestCase
@mail.deliver_later
end
end
+
+ test 'job delegates error handling to mailer' do
+ # Superclass not rescued by mailer's rescue_from RuntimeError
+ message = DelayedMailer.test_raise('StandardError')
+ assert_raise(StandardError) { message.deliver_later }
+ assert_nil DelayedMailer.last_error
+ assert_nil DelayedMailer.last_rescue_from_instance
+
+ # Rescued by mailer's rescue_from RuntimeError
+ message = DelayedMailer.test_raise('DelayedMailerError')
+ assert_nothing_raised { message.deliver_later }
+ assert_equal 'boom', DelayedMailer.last_error.message
+ assert_kind_of DelayedMailer, DelayedMailer.last_rescue_from_instance
+ end
+
+ class DeserializationErrorFixture
+ include GlobalID::Identification
+
+ def self.find(id)
+ raise 'boom, missing find'
+ end
+
+ attr_reader :id
+ def initialize(id = 1)
+ @id = id
+ end
+
+ def to_global_id(options = {})
+ super app: 'foo'
+ end
+ end
+
+ test 'job delegates deserialization errors to mailer class' do
+ # Inject an argument that can't be deserialized.
+ message = DelayedMailer.test_message(DeserializationErrorFixture.new)
+
+ # DeserializationError is raised, rescued, and delegated to the handler
+ # on the mailer class.
+ assert_nothing_raised { message.deliver_later }
+ assert_equal DelayedMailer, DelayedMailer.last_rescue_from_instance
+ assert_equal 'Error while trying to deserialize arguments: boom, missing find', DelayedMailer.last_error.message
+ end
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index d4317399ed..e90886775e 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -94,7 +94,11 @@ module AbstractController
# ==== Returns
# * <tt>String</tt>
def controller_path
- @controller_path ||= name.sub(/Controller$/, ''.freeze).underscore unless anonymous?
+ @controller_path ||= if anonymous?
+ superclass.controller_path
+ else
+ name.sub(/Controller$/, ''.freeze).underscore
+ end
end
# Refresh the cached action_methods when a new action_method is added.
diff --git a/actionpack/lib/action_controller/metal/rescue.rb b/actionpack/lib/action_controller/metal/rescue.rb
index f1c967b982..17f4030f25 100644
--- a/actionpack/lib/action_controller/metal/rescue.rb
+++ b/actionpack/lib/action_controller/metal/rescue.rb
@@ -6,17 +6,6 @@ module ActionController #:nodoc:
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
- def rescue_with_handler(exception)
- if exception.cause
- handler_index = index_of_handler_for_rescue(exception) || Float::INFINITY
- cause_handler_index = index_of_handler_for_rescue(exception.cause)
- if cause_handler_index && cause_handler_index <= handler_index
- exception = exception.cause
- end
- end
- super(exception)
- end
-
# Override this method if you want to customize when detailed
# exceptions must be shown. This method is only called when
# consider_all_requests_local is false. By default, it returns
@@ -31,7 +20,7 @@ module ActionController #:nodoc:
super
rescue Exception => exception
request.env['action_dispatch.show_detailed_exceptions'] ||= show_detailed_exceptions?
- rescue_with_handler(exception) || raise(exception)
+ rescue_with_handler(exception) || raise
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 37f41ae988..7a1350a46d 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -217,7 +217,7 @@ module ActionDispatch
@protocol ||= ssl? ? 'https://' : 'http://'
end
- # Returns the \host for this request, such as "example.com".
+ # Returns the \host and port for this request, such as "example.com:8080".
#
# class Request < Rack::Request
# include ActionDispatch::Http::URL
@@ -226,6 +226,9 @@ module ActionDispatch
# req = Request.new 'HTTP_HOST' => 'example.com'
# req.raw_host_with_port # => "example.com"
#
+ # req = Request.new 'HTTP_HOST' => 'example.com:80'
+ # req.raw_host_with_port # => "example.com:80"
+ #
# req = Request.new 'HTTP_HOST' => 'example.com:8080'
# req.raw_host_with_port # => "example.com:8080"
def raw_host_with_port
@@ -236,7 +239,7 @@ module ActionDispatch
end
end
- # Returns the host for this request, such as example.com.
+ # Returns the host for this request, such as "example.com".
#
# class Request < Rack::Request
# include ActionDispatch::Http::URL
@@ -249,12 +252,16 @@ module ActionDispatch
end
# Returns a \host:\port string for this request, such as "example.com" or
- # "example.com:8080".
+ # "example.com:8080". Port is only included if it is not a default port
+ # (80 or 443)
#
# class Request < Rack::Request
# include ActionDispatch::Http::URL
# end
#
+ # req = Request.new 'HTTP_HOST' => 'example.com'
+ # req.host_with_port # => "example.com"
+ #
# req = Request.new 'HTTP_HOST' => 'example.com:80'
# req.host_with_port # => "example.com"
#
@@ -347,6 +354,17 @@ module ActionDispatch
standard_port? ? '' : ":#{port}"
end
+ # Returns the requested port, such as 8080, based on SERVER_PORT
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'SERVER_PORT' => '80'
+ # req.server_port # => 80
+ #
+ # req = Request.new 'SERVER_PORT' => '8080'
+ # req.server_port # => 8080
def server_port
get_header('SERVER_PORT').to_i
end
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 67f441dfec..dd6ac9db9c 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -118,11 +118,11 @@ module ActionDispatch
# controller :blog do
# get 'blog/show' => :list
# get 'blog/delete' => :delete
- # get 'blog/edit/:id' => :edit
+ # get 'blog/edit' => :edit
# end
#
# # provides named routes for show, delete, and edit
- # link_to @article.title, show_path(id: @article.id)
+ # link_to @article.title, blog_show_path(id: @article.id)
#
# == Pretty URLs
#
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 4c2a4cfeb0..8ff3b42a40 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -120,7 +120,7 @@ module ActionDispatch
if options_constraints.is_a?(Hash)
@defaults = Hash[options_constraints.find_all { |key, default|
- URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
+ URL_OPTIONS.include?(key) && (String === default || Integer === default)
}].merge @defaults
@blocks = blocks
constraints.merge! options_constraints
@@ -824,7 +824,7 @@ module ActionDispatch
if options[:constraints].is_a?(Hash)
defaults = options[:constraints].select do |k, v|
- URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
+ URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
end
options[:defaults] = defaults.merge(options[:defaults] || {})
diff --git a/actionpack/lib/action_dispatch/testing/assertion_response.rb b/actionpack/lib/action_dispatch/testing/assertion_response.rb
index 3fb81ff083..404b96bbcd 100644
--- a/actionpack/lib/action_dispatch/testing/assertion_response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertion_response.rb
@@ -1,14 +1,7 @@
module ActionDispatch
- # This is a class that abstracts away an asserted response.
- # It purposely does not inherit from Response, because it doesn't need it.
- # That means it does not have headers or a body.
- #
- # As an input to the initializer, we take a Fixnum, a String, or a Symbol.
- # If it's a Fixnum or String, we figure out what its symbolized name.
- # If it's a Symbol, we figure out what its corresponding code is.
- # The resulting code will be a Fixnum, for real HTTP codes, and it will
- # be a String for the pseudo-HTTP codes, such as:
- # :success, :missing, :redirect and :error
+ # This is a class that abstracts away an asserted response. It purposely
+ # does not inherit from Response because it doesn't need it. That means it
+ # does not have headers or a body.
class AssertionResponse
attr_reader :code, :name
@@ -19,6 +12,9 @@ module ActionDispatch
error: "5XX"
}
+ # Accepts a specific response status code as an Integer (404) or String
+ # ('404') or a response status range as a Symbol pseudo-code (:success,
+ # indicating any 200-299 status code).
def initialize(code_or_name)
if code_or_name.is_a?(Symbol)
@name = code_or_name
diff --git a/actionpack/test/assertions/response_assertions_test.rb b/actionpack/test/assertions/response_assertions_test.rb
index 579ce0ed29..57a67a48b5 100644
--- a/actionpack/test/assertions/response_assertions_test.rb
+++ b/actionpack/test/assertions/response_assertions_test.rb
@@ -35,7 +35,7 @@ module ActionDispatch
end
end
- def test_assert_response_fixnum
+ def test_assert_response_integer
@response = FakeResponse.new 400
assert_response 400
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index ed78f859ce..c088e5a043 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -131,22 +131,6 @@ class RescueController < ActionController::Base
def missing_template
end
- def io_error_in_view
- begin
- raise IOError.new('this is io error')
- rescue
- raise ActionView::TemplateError.new(nil)
- end
- end
-
- def zero_division_error_in_view
- begin
- raise ZeroDivisionError.new('this is zero division error')
- rescue
- raise ActionView::TemplateError.new(nil)
- end
- end
-
def exception_with_more_specific_handler_for_wrapper
raise RecordInvalid
rescue
@@ -251,17 +235,6 @@ class ControllerInheritanceRescueControllerTest < ActionController::TestCase
end
class RescueControllerTest < ActionController::TestCase
-
- def test_io_error_in_view
- get :io_error_in_view
- assert_equal 'io error', @response.body
- end
-
- def test_zero_division_error_in_view
- get :zero_division_error_in_view
- assert_equal 'action_view templater error', @response.body
- end
-
def test_rescue_handler
get :not_authorized
assert_response :forbidden
@@ -276,7 +249,6 @@ class RescueControllerTest < ActionController::TestCase
get :record_invalid
end
end
-
def test_rescue_handler_with_argument_as_string
assert_called_with @controller, :show_errors, [Exception] do
get :record_invalid_raise_as_string
@@ -314,7 +286,6 @@ class RescueControllerTest < ActionController::TestCase
get :resource_unavailable
assert_equal "RescueController::ResourceUnavailable", @response.body
end
-
def test_block_rescue_handler_with_argument_as_string
get :resource_unavailable_raise_as_string
assert_equal "RescueController::ResourceUnavailableToRescueAsString", @response.body
@@ -322,7 +293,7 @@ class RescueControllerTest < ActionController::TestCase
test 'rescue when wrapper has more specific handler than cause' do
get :exception_with_more_specific_handler_for_wrapper
- assert_response :unprocessable_entity
+ assert_response :forbidden
end
test 'rescue when cause has more specific handler than wrapper' do
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 168677829a..03bf8f8295 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -626,7 +626,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
assert_equal '/pages/boo', url_for(rs, { :controller => 'pages', :action => 'boo' })
end
- def test_route_with_fixnum_default
+ def test_route_with_integer_default
rs.draw do
get 'page(/:id)' => 'content#show_page', :id => 1
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index ebcdda6074..6160b3395a 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -553,7 +553,7 @@ XML
assert_equal 'created', flash[:notice]
end
- def test_params_passing_with_fixnums
+ def test_params_passing_with_integer
get :test_params, params: {
page: { name: "Page name", month: 4, year: 2004, day: 6 }
}
@@ -565,7 +565,7 @@ XML
)
end
- def test_params_passing_with_fixnums_when_not_html_request
+ def test_params_passing_with_integers_when_not_html_request
get :test_params, params: { format: 'json', count: 999 }
parsed_params = ::JSON.parse(@response.body)
assert_equal(
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index a4cb8ce449..8a5d85ab84 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -358,6 +358,17 @@ class RequestPort < BaseRequestTest
request = stub_request 'HTTP_HOST' => 'www.example.org:8080'
assert_equal ':8080', request.port_string
end
+
+ test "server port" do
+ request = stub_request 'SERVER_PORT' => '8080'
+ assert_equal 8080, request.server_port
+
+ request = stub_request 'SERVER_PORT' => '80'
+ assert_equal 80, request.server_port
+
+ request = stub_request 'SERVER_PORT' => ''
+ assert_equal 0, request.server_port
+ end
end
class RequestPath < BaseRequestTest
@@ -417,6 +428,11 @@ class RequestPath < BaseRequestTest
end
class RequestHost < BaseRequestTest
+ test "host without specifying port" do
+ request = stub_request 'HTTP_HOST' => 'rubyonrails.org'
+ assert_equal "rubyonrails.org", request.host_with_port
+ end
+
test "host with default port" do
request = stub_request 'HTTP_HOST' => 'rubyonrails.org:80'
assert_equal "rubyonrails.org", request.host_with_port
@@ -427,6 +443,21 @@ class RequestHost < BaseRequestTest
assert_equal "rubyonrails.org:81", request.host_with_port
end
+ test "raw without specifying port" do
+ request = stub_request 'HTTP_HOST' => 'rubyonrails.org'
+ assert_equal "rubyonrails.org", request.raw_host_with_port
+ end
+
+ test "raw host with default port" do
+ request = stub_request 'HTTP_HOST' => 'rubyonrails.org:80'
+ assert_equal "rubyonrails.org:80", request.raw_host_with_port
+ end
+
+ test "raw host with non default port" do
+ request = stub_request 'HTTP_HOST' => 'rubyonrails.org:81'
+ assert_equal "rubyonrails.org:81", request.raw_host_with_port
+ end
+
test "proxy request" do
request = stub_request 'HTTP_HOST' => 'glu.ttono.us:80'
assert_equal "glu.ttono.us", request.host_with_port
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index b99d1af998..b91e61da18 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -4,14 +4,7 @@ require 'monitor'
module ActionView
class Digestor
- @@digest_mutex = Mutex.new
-
- class PerRequestDigestCacheExpiry < Struct.new(:app) # :nodoc:
- def call(env)
- ActionView::LookupContext::DetailsKey.clear
- app.call(env)
- end
- end
+ @@digest_mutex = Mutex.new
class << self
# Supported options:
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index 4c7c4b91c6..4eaaa239e2 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -41,11 +41,11 @@ module ActionView
#
# ==== \Template digest
#
- # The template digest that's added to the cache key is computed by taking an md5 of the
+ # The template digest that's added to the cache key is computed by taking an MD5 of the
# contents of the entire template file. This ensures that your caches will automatically
# expire when you change the template file.
#
- # Note that the md5 is taken of the entire template file, not just what's within the
+ # Note that the MD5 is taken of the entire template file, not just what's within the
# cache do/end call. So it's possible that changing something outside of that call will
# still expire the cache.
#
@@ -118,7 +118,7 @@ module ActionView
#
# If you use a helper method, for example, inside a cached block and
# you then update that helper, you'll have to bump the cache as well.
- # It doesn't really matter how you do it, but the md5 of the template file
+ # It doesn't really matter how you do it, but the MD5 of the template file
# must change. One recommendation is to simply be explicit in a comment, like:
#
# <%# Helper Dependency Updated: May 6, 2012 at 6pm %>
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index 626c4b8f5e..9db1460ee7 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -63,7 +63,7 @@ module ActionView
details = details.dup
details[:formats] &= Template::Types.symbols
end
- @details_keys[details] ||= new
+ @details_keys[details] ||= Concurrent::Map.new
end
def self.clear
@@ -71,13 +71,7 @@ module ActionView
end
def self.digest_caches
- @details_keys.values.map(&:digest_cache)
- end
-
- attr_reader :digest_cache
-
- def initialize
- @digest_cache = Concurrent::Map.new
+ @details_keys.values
end
end
@@ -236,7 +230,7 @@ module ActionView
end
def digest_cache
- details_key.digest_cache
+ details_key
end
def initialize_details(target, details)
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index c83614c89a..dfb99f4ea9 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -40,7 +40,7 @@ module ActionView
initializer "action_view.per_request_digest_cache" do |app|
ActiveSupport.on_load(:action_view) do
if app.config.consider_all_requests_local
- app.middleware.use ActionView::Digestor::PerRequestDigestCacheExpiry
+ app.executor.to_run { ActionView::LookupContext::DetailsKey.clear }
end
end
end
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index 9d15bbfca7..1d6afb90fe 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -84,13 +84,13 @@ module ActionView
when String
begin
if layout =~ /^\//
- with_fallbacks { find_template(layout, nil, false, keys, details) }
+ with_fallbacks { find_template(layout, nil, false, [], details) }
else
- find_template(layout, nil, false, keys, details)
+ find_template(layout, nil, false, [], details)
end
rescue ActionView::MissingTemplate
all_details = @details.merge(:formats => @lookup_context.default_formats)
- raise unless template_exists?(layout, nil, false, keys, all_details)
+ raise unless template_exists?(layout, nil, false, [], all_details)
end
when Proc
resolve_layout(layout.call(formats), keys, formats)
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index c5e69b1833..bf68e93c58 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -88,6 +88,23 @@ module ActionView
@query_cache.clear
end
+ # Get the cache size. Do not call this
+ # method. This method is not guaranteed to be here ever.
+ def size # :nodoc:
+ size = 0
+ @data.each_value do |v1|
+ v1.each_value do |v2|
+ v2.each_value do |v3|
+ v3.each_value do |v4|
+ size += v4.size
+ end
+ end
+ end
+ end
+
+ size + @query_cache.size
+ end
+
private
def canonical_no_templates(templates)
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index 120962b5aa..ec16b0609e 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -14,7 +14,7 @@ module ActionView
attr_accessor :request, :response, :params
class << self
- attr_writer :controller_path
+ attr_accessor :controller_path
end
def controller_path=(path)
diff --git a/actionview/test/actionpack/abstract/layouts_test.rb b/actionview/test/actionpack/abstract/layouts_test.rb
index 80bc665b0a..78f6e78c61 100644
--- a/actionview/test/actionpack/abstract/layouts_test.rb
+++ b/actionview/test/actionpack/abstract/layouts_test.rb
@@ -12,7 +12,9 @@ module AbstractControllerTests
abstract!
self.view_paths = [ActionView::FixtureResolver.new(
+ "some/template.erb" => "hello <%= foo %> bar",
"layouts/hello.erb" => "With String <%= yield %>",
+ "layouts/hello_locals.erb" => "With String <%= yield %>",
"layouts/hello_override.erb" => "With Override <%= yield %>",
"layouts/overwrite.erb" => "Overwrite <%= yield %>",
"layouts/with_false_layout.erb" => "False Layout <%= yield %>",
@@ -32,6 +34,14 @@ module AbstractControllerTests
end
end
+ class WithStringLocals < Base
+ layout "hello_locals"
+
+ def index
+ render :template => 'some/template', locals: { foo: "less than 3" }
+ end
+ end
+
class WithString < Base
layout "hello"
@@ -39,6 +49,10 @@ module AbstractControllerTests
render :template => ActionView::Template::Text.new("Hello string!")
end
+ def action_has_layout_false
+ render template: ActionView::Template::Text.new("Hello string!")
+ end
+
def overwrite_default
render :template => ActionView::Template::Text.new("Hello string!"), :layout => :default
end
@@ -82,7 +96,7 @@ module AbstractControllerTests
end
end
- class WithProcReturningNil < Base
+ class WithProcReturningNil < WithString
layout proc { nil }
def index
@@ -90,6 +104,14 @@ module AbstractControllerTests
end
end
+ class WithProcReturningFalse < WithString
+ layout proc { false }
+
+ def index
+ render template: ActionView::Template::Text.new("Hello false!")
+ end
+ end
+
class WithZeroArityProc < Base
layout proc { "overwrite" }
@@ -189,6 +211,14 @@ module AbstractControllerTests
end
end
+ class WithOnlyConditionalFlipped < WithOnlyConditional
+ layout "hello_override", only: :index
+ end
+
+ class WithOnlyConditionalFlippedAndInheriting < WithOnlyConditional
+ layout nil, only: :index
+ end
+
class WithExceptConditional < WithStringImpliedChild
layout "overwrite", :except => :show
@@ -201,6 +231,45 @@ module AbstractControllerTests
end
end
+ class AbstractWithString < Base
+ layout "hello"
+ abstract!
+ end
+
+ class AbstractWithStringChild < AbstractWithString
+ def index
+ render template: ActionView::Template::Text.new("Hello abstract child!")
+ end
+ end
+
+ class AbstractWithStringChildDefaultsToInherited < AbstractWithString
+ layout nil
+
+ def index
+ render template: ActionView::Template::Text.new("Hello abstract child!")
+ end
+ end
+
+ class WithConditionalOverride < WithString
+ layout "overwrite", only: :overwritten
+
+ def non_overwritten
+ render template: ActionView::Template::Text.new("Hello non overwritten!")
+ end
+
+ def overwritten
+ render template: ActionView::Template::Text.new("Hello overwritten!")
+ end
+ end
+
+ class WithConditionalOverrideFlipped < WithConditionalOverride
+ layout "hello_override", only: :non_overwritten
+ end
+
+ class WithConditionalOverrideFlippedAndInheriting < WithConditionalOverride
+ layout nil, only: :non_overwritten
+ end
+
class TestBase < ActiveSupport::TestCase
test "when no layout is specified, and no default is available, render without a layout" do
controller = Blank.new
@@ -208,6 +277,31 @@ module AbstractControllerTests
assert_equal "Hello blank!", controller.response_body
end
+ test "with locals" do
+ controller = WithStringLocals.new
+ controller.process(:index)
+ assert_equal "With String hello less than 3 bar", controller.response_body
+ end
+
+ test "cache should not grow when locals change for a string template" do
+ cache = WithString.view_paths.paths.first.instance_variable_get(:@cache)
+
+ controller = WithString.new
+ controller.process(:index) # heat the cache
+
+ size = cache.size
+
+ 10.times do |x|
+ controller = WithString.new
+ controller.define_singleton_method :index do
+ render :template => ActionView::Template::Text.new("Hello string!"), :locals => { :"x#{x}" => :omg }
+ end
+ controller.process(:index)
+ end
+
+ assert_equal size, cache.size
+ end
+
test "when layout is specified as a string, render with that layout" do
controller = WithString.new
controller.process(:index)
@@ -264,10 +358,16 @@ module AbstractControllerTests
assert_equal "Overwrite Hello proc!", controller.response_body
end
- test "when layout is specified as a proc and the proc returns nil, don't use a layout" do
+ test "when layout is specified as a proc and the proc returns nil, use inherited layout" do
controller = WithProcReturningNil.new
controller.process(:index)
- assert_equal "Hello nil!", controller.response_body
+ assert_equal "With String Hello nil!", controller.response_body
+ end
+
+ test "when layout is specified as a proc and the proc returns false, use no layout instead of inherited layout" do
+ controller = WithProcReturningFalse.new
+ controller.process(:index)
+ assert_equal "Hello false!", controller.response_body
end
test "when layout is specified as a proc without parameters it works just the same" do
@@ -328,12 +428,24 @@ module AbstractControllerTests
end
test "when a grandchild has nil layout specified, the child has an implied layout, and the " \
- "parent has specified a layout, use the child controller layout" do
+ "parent has specified a layout, use the grand child controller layout" do
controller = WithGrandChildOfImplied.new
controller.process(:index)
assert_equal "With Grand Child Hello string!", controller.response_body
end
+ test "a child inherits layout from abstract controller" do
+ controller = AbstractWithStringChild.new
+ controller.process(:index)
+ assert_equal "With String Hello abstract child!", controller.response_body
+ end
+
+ test "a child inherits layout from abstract controller2" do
+ controller = AbstractWithStringChildDefaultsToInherited.new
+ controller.process(:index)
+ assert_equal "With String Hello abstract child!", controller.response_body
+ end
+
test "raises an exception when specifying layout true" do
assert_raises ArgumentError do
Object.class_eval do
@@ -356,6 +468,30 @@ module AbstractControllerTests
assert_equal "With Implied Hello index!", controller.response_body
end
+ test "when specify an :only option which match current action name and is opposite from parent controller" do
+ controller = WithOnlyConditionalFlipped.new
+ controller.process(:show)
+ assert_equal "With Implied Hello show!", controller.response_body
+ end
+
+ test "when specify an :only option which does not match current action name and is opposite from parent controller" do
+ controller = WithOnlyConditionalFlipped.new
+ controller.process(:index)
+ assert_equal "With Override Hello index!", controller.response_body
+ end
+
+ test "when specify to inherit and an :only option which match current action name and is opposite from parent controller" do
+ controller = WithOnlyConditionalFlippedAndInheriting.new
+ controller.process(:show)
+ assert_equal "With Implied Hello show!", controller.response_body
+ end
+
+ test "when specify to inherit and an :only option which does not match current action name and is opposite from parent controller" do
+ controller = WithOnlyConditionalFlippedAndInheriting.new
+ controller.process(:index)
+ assert_equal "Overwrite Hello index!", controller.response_body
+ end
+
test "when specify an :except option which match current action name" do
controller = WithExceptConditional.new
controller.process(:show)
@@ -368,6 +504,42 @@ module AbstractControllerTests
assert_equal "Overwrite Hello index!", controller.response_body
end
+ test "when specify overwrite as an :only option which match current action name" do
+ controller = WithConditionalOverride.new
+ controller.process(:overwritten)
+ assert_equal "Overwrite Hello overwritten!", controller.response_body
+ end
+
+ test "when specify overwrite as an :only option which does not match current action name" do
+ controller = WithConditionalOverride.new
+ controller.process(:non_overwritten)
+ assert_equal "Hello non overwritten!", controller.response_body
+ end
+
+ test "when specify overwrite as an :only option which match current action name and is opposite from parent controller" do
+ controller = WithConditionalOverrideFlipped.new
+ controller.process(:overwritten)
+ assert_equal "Hello overwritten!", controller.response_body
+ end
+
+ test "when specify overwrite as an :only option which does not match current action name and is opposite from parent controller" do
+ controller = WithConditionalOverrideFlipped.new
+ controller.process(:non_overwritten)
+ assert_equal "With Override Hello non overwritten!", controller.response_body
+ end
+
+ test "when specify to inherit and overwrite as an :only option which match current action name and is opposite from parent controller" do
+ controller = WithConditionalOverrideFlippedAndInheriting.new
+ controller.process(:overwritten)
+ assert_equal "Hello overwritten!", controller.response_body
+ end
+
+ test "when specify to inherit and overwrite as an :only option which does not match current action name and is opposite from parent controller" do
+ controller = WithConditionalOverrideFlippedAndInheriting.new
+ controller.process(:non_overwritten)
+ assert_equal "Overwrite Hello non overwritten!", controller.response_body
+ end
+
test "layout for anonymous controller" do
klass = Class.new(WithString) do
def index
@@ -379,6 +551,17 @@ module AbstractControllerTests
controller.process(:index)
assert_equal "With String index", controller.response_body
end
+
+ test "when layout is disabled with #action_has_layout? returning false, render no layout" do
+ controller = WithString.new
+ controller.instance_eval do
+ def action_has_layout?
+ false
+ end
+ end
+ controller.process(:action_has_layout_false)
+ assert_equal "Hello string!", controller.response_body
+ end
end
end
end
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index c5b63d33f1..7a5904f151 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -798,7 +798,7 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
- def test_select_with_fixnum
+ def test_select_with_integer
@post = Post.new
@post.category = ""
assert_dom_equal(
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index 33bd5b4eb3..a5c749e5e7 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -24,7 +24,7 @@ module ActiveJob
end
# Raised when an unsupported argument type is set as a job argument. We
- # currently support NilClass, Fixnum, Float, String, TrueClass, FalseClass,
+ # currently support NilClass, Integer, Fixnum, Float, String, TrueClass, FalseClass,
# Bignum, BigDecimal, and objects that can be represented as GlobalIDs (ex: Active Record).
# Raised if you set the key for a Hash something else than a string or
# a symbol. Also raised when trying to serialize an object which can't be
@@ -34,7 +34,8 @@ module ActiveJob
module Arguments
extend self
# :nodoc:
- TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum, BigDecimal ]
+ # Calls #uniq since Integer, Fixnum, and Bignum are all the same class on Ruby 2.4+
+ TYPE_WHITELIST = [ NilClass, String, Integer, Fixnum, Bignum, Float, BigDecimal, TrueClass, FalseClass ].uniq
# Serializes a set of arguments. Whitelisted types are returned
# as-is. Arrays/Hashes are serialized element by element.
diff --git a/activejob/lib/active_job/execution.rb b/activejob/lib/active_job/execution.rb
index 7c4151fc90..4e4acfc2c2 100644
--- a/activejob/lib/active_job/execution.rb
+++ b/activejob/lib/active_job/execution.rb
@@ -34,7 +34,7 @@ module ActiveJob
perform(*arguments)
end
rescue => exception
- rescue_with_handler(exception) || raise(exception)
+ rescue_with_handler(exception) || raise
end
def perform(*)
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 685e235730..696e6d31da 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -310,9 +310,9 @@ module ActiveModel
# <tt>:strict</tt> option can also be set to any other exception.
#
# person.errors.add(:name, :invalid, strict: true)
- # # => ActiveModel::StrictValidationFailed: name is invalid
+ # # => ActiveModel::StrictValidationFailed: Name is invalid
# person.errors.add(:name, :invalid, strict: NameIsInvalid)
- # # => NameIsInvalid: name is invalid
+ # # => NameIsInvalid: Name is invalid
#
# person.errors.messages # => {}
#
@@ -531,6 +531,15 @@ module ActiveModel
end
# Raised when unknown attributes are supplied via mass assignment.
+ #
+ # class Person
+ # include ActiveModel::AttributeAssignment
+ # include ActiveModel::Validations
+ # end
+ #
+ # person = Person.new
+ # person.assign_attributes(name: 'Gorby')
+ # # => ActiveModel::UnknownAttributeError: unknown attribute 'name' for Person.
class UnknownAttributeError < NoMethodError
attr_reader :record, :attribute
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index 52111e5442..a201f72ed0 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -29,8 +29,7 @@ module ActiveModel
end
module ClassMethods
- # Defines a callback that will get called right before validation
- # happens.
+ # Defines a callback that will get called right before validation.
#
# class Person
# include ActiveModel::Validations
@@ -65,8 +64,7 @@ module ActiveModel
set_callback(:validation, :before, *args, &block)
end
- # Defines a callback that will get called right after validation
- # happens.
+ # Defines a callback that will get called right after validation.
#
# class Person
# include ActiveModel::Validations
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index ad7012df48..9a0a0655de 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -120,7 +120,7 @@ module ActiveModel
# * <tt>:only_integer</tt> - Specifies whether the value has to be an
# integer, e.g. an integral value (default is +false+).
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
- # +false+). Notice that for fixnum and float columns empty strings are
+ # +false+). Notice that for Integer and Float columns empty strings are
# converted to +nil+.
# * <tt>:greater_than</tt> - Specifies the value must be greater than the
# supplied value.
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index ee901b75fb..11dce1df20 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -355,7 +355,7 @@ class LengthValidationTest < ActiveModel::TestCase
assert_equal ["Your essay must be at least 5 words."], t.errors[:content]
end
- def test_validates_length_of_for_fixnum
+ def test_validates_length_of_for_integer
Topic.validates_length_of(:approved, is: 4)
t = Topic.new("title" => "uhohuhoh", "content" => "whatever", approved: 1)
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 0c14c0ea86..c677178253 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,2 +1,6 @@
+* Handle JSON deserialization correctly if the column default from database
+ adapter returns `''` instead of `nil`.
+
+ *Johannes Opper*
Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index f7edfbfb5f..62e867a353 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -217,7 +217,8 @@ module ActiveRecord
unless record.is_a?(reflection.klass)
fresh_class = reflection.class_name.safe_constantize
unless fresh_class && record.is_a?(fresh_class)
- message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\
+ "got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})"
raise ActiveRecord::AssociationTypeMismatch, message
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 2dca6b612e..6d06ce5c6c 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -246,9 +246,12 @@ module ActiveRecord
end
end
- # Count all records using SQL. Construct options and pass them with
- # scope to the target class's +count+.
+ # Returns the number of records. If no arguments are given, it counts all
+ # columns using SQL. If one argument is given, it counts only the passed
+ # column using SQL. If a block is given, it counts the number of records
+ # yielding a true value.
def count(column_name = nil)
+ return super if block_given?
relation = scope
if association_scope.distinct_value
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
@@ -280,7 +283,7 @@ module ActiveRecord
_options = records.extract_options!
dependent = _options[:dependent] || options[:dependent]
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
delete_or_destroy(records, dependent)
end
@@ -291,7 +294,7 @@ module ActiveRecord
# +:dependent+ option.
def destroy(*records)
return if records.empty?
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
delete_or_destroy(records, :destroy)
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index b9aed05135..5d1e7ffb73 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -597,7 +597,7 @@ module ActiveRecord
# Pet.find(1)
# # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=1
#
- # You can pass +Fixnum+ or +String+ values, it finds the records
+ # You can pass +Integer+ or +String+ values, it finds the records
# responding to the +id+ and executes delete on them.
#
# class Person < ActiveRecord::Base
@@ -661,7 +661,7 @@ module ActiveRecord
#
# Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
#
- # You can pass +Fixnum+ or +String+ values, it finds the records
+ # You can pass +Integer+ or +String+ values, it finds the records
# responding to the +id+ and then deletes them from the database.
#
# person.pets.size # => 3
@@ -715,12 +715,13 @@ module ActiveRecord
end
alias uniq distinct
- # Count all records using SQL.
+ # Count all records.
#
# class Person < ActiveRecord::Base
# has_many :pets
# end
#
+ # # This will perform the count using SQL.
# person.pets.count # => 3
# person.pets
# # => [
@@ -728,8 +729,13 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
- def count(column_name = nil)
- @association.count(column_name)
+ #
+ # Passing a block will select all of a person's pets in SQL and then
+ # perform the count using Ruby.
+ #
+ # person.pets.count { |pet| pet.name.include?('-') } # => 2
+ def count(column_name = nil, &block)
+ @association.count(column_name, &block)
end
# Returns the size of the collection. If the collection hasn't been loaded,
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 3c4c8f10ec..24231dc9e1 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -170,7 +170,7 @@ module ActiveRecord
super(name, nil, Type::Value.new)
end
- def value
+ def type_cast(*)
nil
end
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index 4c22be8235..b96d8e9352 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -38,7 +38,7 @@ module ActiveRecord
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
- # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
def assign_multiparameter_attributes(pairs)
execute_callstack_for_multiparameter_attributes(
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index e902eb7531..7e19dceaed 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -360,7 +360,7 @@ module ActiveRecord
# person = Person.new
# person[:age] = '22'
# person[:age] # => 22
- # person[:age] # => Fixnum
+ # person[:age].class # => Integer
def []=(attr_name, value)
write_attribute(attr_name, value)
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index 5599b590ca..70c2d2f25d 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -26,7 +26,7 @@ module ActiveRecord
end
# Updates the attribute identified by <tt>attr_name</tt> with the
- # specified +value+. Empty strings for fixnum and float columns are
+ # specified +value+. Empty strings for Integer and Float columns are
# turned into +nil+.
def write_attribute(attr_name, value)
write_attribute_with_type_cast(attr_name, value, true)
diff --git a/activerecord/lib/active_record/coders/json.rb b/activerecord/lib/active_record/coders/json.rb
index 75d3bfe625..cb185a881e 100644
--- a/activerecord/lib/active_record/coders/json.rb
+++ b/activerecord/lib/active_record/coders/json.rb
@@ -6,7 +6,7 @@ module ActiveRecord
end
def self.load(json)
- ActiveSupport::JSON.decode(json) unless json.nil?
+ ActiveSupport::JSON.decode(json) unless json.blank?
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index f3abd01290..f437dafec2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -896,15 +896,9 @@ module ActiveRecord
end
end
- # Retrieving the connection pool happens a lot so we cache it in @class_to_pool.
+ # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool.
# This makes retrieving the connection pool O(1) once the process is warm.
# When a connection is established or removed, we invalidate the cache.
- #
- # Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil.
- # However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that
- # #fetch is significantly slower than #[]. So in the nil case, no caching will
- # take place, but that's ok since the nil case is not the common one that we wish
- # to optimise for.
def retrieve_connection_pool(spec_name)
owner_to_pool.fetch(spec_name) do
# Check if a connection was previously established in an ancestor process,
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 fdd6bffa13..0f565277e3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -707,7 +707,7 @@ module ActiveRecord
case length
when Hash
column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
- when Fixnum
+ when Integer
column_names.each {|name| option_strings[name] += "(#{length})"}
end
end
@@ -832,7 +832,7 @@ module ActiveRecord
# Increase timeout so the server doesn't disconnect us.
wait_timeout = @config[:wait_timeout]
- wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
defaults = [':default', :default].to_set
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb
index 7427a25ad5..4da240edb2 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb
@@ -14,6 +14,8 @@ module ActiveRecord
def cast(value)
case value
when ::String
+ return if value.blank?
+
if value[0] == '(' && value[-1] == ')'
value = value[1...-1]
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 1e37ffefc6..67b8efac66 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -184,9 +184,12 @@ module ActiveRecord
end
end
+
+ # In de/serialize we change `nil` to 0, so that we can allow passing
+ # `nil` values to `lock_version`, and not result in `ActiveRecord::StaleObjectError`
+ # during update record.
class LockingType < DelegateClass(Type::Value) # :nodoc:
def deserialize(value)
- # `nil` *should* be changed to 0
super.to_i
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index f30861b4d0..81fe053fe1 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -166,13 +166,13 @@ module ActiveRecord
class EnvironmentMismatchError < ActiveRecordError
def initialize(current: nil, stored: nil)
msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
- msg << "You are running in `#{ current }` environment."
+ msg << "You are running in `#{ current }` environment. "
msg << "If you are sure you want to continue, first set the environment using:\n\n"
msg << "\tbin/rails db:environment:set"
if defined?(Rails.env)
- super("#{msg} RAILS_ENV=#{::Rails.env}")
+ super("#{msg} RAILS_ENV=#{::Rails.env}\n\n")
else
- super(msg)
+ super("#{msg}\n\n")
end
end
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 54c9af4898..d6d92b8607 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -37,7 +37,11 @@ module ActiveRecord
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
# between databases. In invalid cases, an error from the database is thrown.
def count(column_name = nil)
- calculate(:count, column_name)
+ if block_given?
+ to_a.count { |*block_args| yield(*block_args) }
+ else
+ calculate(:count, column_name)
+ end
end
# Calculates the average value on a given column. Returns +nil+ if there's
@@ -89,7 +93,7 @@ module ActiveRecord
#
# There are two basic forms of output:
#
- # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
+ # * Single aggregate value: The single value is type cast to Integer for COUNT, Float
# for AVG, and the given column's type for everything else.
#
# * Grouped values: This returns an ordered hash of the values and groups them. It
diff --git a/activerecord/lib/active_record/suppressor.rb b/activerecord/lib/active_record/suppressor.rb
index 8ec4b48d31..d9acb1a1dc 100644
--- a/activerecord/lib/active_record/suppressor.rb
+++ b/activerecord/lib/active_record/suppressor.rb
@@ -30,10 +30,11 @@ module ActiveRecord
module ClassMethods
def suppress(&block)
+ previous_state = SuppressorRegistry.suppressed[name]
SuppressorRegistry.suppressed[name] = true
yield
ensure
- SuppressorRegistry.suppressed[name] = false
+ SuppressorRegistry.suppressed[name] = previous_state
end
end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 4ff0740cfb..a3a5241780 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -32,7 +32,7 @@ module ActiveRecord
def changed_in_place?(raw_old_value, value)
return false if value.nil?
- raw_new_value = serialize(value)
+ raw_new_value = encoded(value)
raw_old_value.nil? != raw_new_value.nil? ||
subtype.changed_in_place?(raw_old_value, raw_new_value)
end
@@ -52,6 +52,12 @@ module ActiveRecord
def default_value?(value)
value == coder.load(nil)
end
+
+ def encoded(value)
+ unless default_value?(value)
+ coder.dump(value)
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
index 9e250c2b7c..66f0a70394 100644
--- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -104,6 +104,13 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
assert_equal ActiveRecord::Point.new(1, 2), p.x
end
+ def test_empty_string_assignment
+ assert_nothing_raised { PostgresqlPoint.new(x: "") }
+
+ p = PostgresqlPoint.new(x: "")
+ assert_equal nil, p.x
+ end
+
def test_array_of_points_round_trip
expected_value = [
ActiveRecord::Point.new(1, 2),
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index eef70f5691..9dadd114a1 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -168,7 +168,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
e = assert_raise(ActiveRecord::AssociationTypeMismatch) {
Admin::RegionalUser.new(region: 'wrong value')
}
- assert_match(/^Region\([^)]+\) expected, got String\([^)]+\)$/, e.message)
+ assert_match(/^Region\([^)]+\) expected, got "wrong value" which is an instance of String\([^)]+\)$/, e.message)
ensure
Admin.send :remove_const, "Region" if Admin.const_defined?("Region")
Admin.send :remove_const, "RegionalUser" if Admin.const_defined?("RegionalUser")
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index e975f4fbdd..7ec0dfce7a 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1315,7 +1315,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, summit.client_of
end
- def test_deleting_by_fixnum_id
+ def test_deleting_by_integer_id
david = Developer.find(1)
assert_difference 'david.projects.count', -1 do
@@ -1352,7 +1352,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, companies(:first_firm).clients_of_firm.reload.size
end
- def test_destroying_by_fixnum_id
+ def test_destroying_by_integer_id
force_signal37_to_load_all_clients_of_firm
assert_difference "Client.count", -1 do
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index aff0dabee7..aa35844a03 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -65,7 +65,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } }
end
- def test_ordered_habtm
+ def test_ordered_has_many_through
person_prime = Class.new(ActiveRecord::Base) do
def self.name; 'Person'; end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index c7bd9d2119..3047914b70 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -598,7 +598,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete(Object.new) }
end
- def test_deleting_by_fixnum_id_from_has_many_through
+ def test_deleting_by_integer_id_from_has_many_through
post = posts(:thinking)
assert_difference 'post.tags.count', -1 do
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index 2bebbfa205..7bcaa53aa2 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -38,7 +38,7 @@ module ActiveRecord
data.reload
assert_equal 2, data.overloaded_float
- assert_kind_of Fixnum, OverloadedType.last.overloaded_float
+ assert_kind_of Integer, OverloadedType.last.overloaded_float
assert_equal 2.0, UnoverloadedType.last.overloaded_float
assert_kind_of Float, UnoverloadedType.last.overloaded_float
end
@@ -63,6 +63,15 @@ module ActiveRecord
end
end
+ test "model with nonexistent attribute with default value can be saved" do
+ klass = Class.new(OverloadedType) do
+ attribute :non_existent_string_with_default, :string, default: 'nonexistent'
+ end
+
+ model = klass.new
+ assert model.save
+ end
+
test "changing defaults" do
data = OverloadedType.new
unoverloaded_data = UnoverloadedType.new
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index eef2d29d02..3191393a41 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -940,7 +940,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_kind_of Integer, m1.world_population
assert_equal 6000000000, m1.world_population
- assert_kind_of Fixnum, m1.my_house_population
+ assert_kind_of Integer, m1.my_house_population
assert_equal 3, m1.my_house_population
assert_kind_of BigDecimal, m1.bank_balance
@@ -968,7 +968,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_kind_of Integer, m1.world_population
assert_equal 6000000000, m1.world_population
- assert_kind_of Fixnum, m1.my_house_population
+ assert_kind_of Integer, m1.my_house_population
assert_equal 3, m1.my_house_population
assert_kind_of BigDecimal, m1.bank_balance
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 8f2682c781..cfae700159 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -482,6 +482,10 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 1, Account.where(firm_name: '37signals').order(:firm_name).reverse_order.count
end
+ def test_count_with_block
+ assert_equal 4, Account.count { |account| account.credit_limit.modulo(10).zero? }
+ end
+
def test_should_sum_expression
# Oracle adapter returns floating point value 636.0 after SUM
if current_adapter?(:OracleAdapter)
diff --git a/activerecord/test/cases/coders/json_test.rb b/activerecord/test/cases/coders/json_test.rb
new file mode 100644
index 0000000000..d22d93d129
--- /dev/null
+++ b/activerecord/test/cases/coders/json_test.rb
@@ -0,0 +1,15 @@
+require "cases/helper"
+
+module ActiveRecord
+ module Coders
+ class JSONTest < ActiveRecord::TestCase
+ def test_returns_nil_if_empty_string_given
+ assert_nil JSON.load("")
+ end
+
+ def test_returns_nil_if_nil_given
+ assert_nil JSON.load(nil)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index da934ab8fe..9455d4886c 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -857,7 +857,7 @@ class FoxyFixturesTest < ActiveRecord::TestCase
assert_equal("X marks the spot!", pirates(:mark).catchphrase)
end
- def test_supports_label_interpolation_for_fixnum_label
+ def test_supports_label_interpolation_for_integer_label
assert_equal("#1 pirate!", pirates(1).catchphrase)
end
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index c7a1b81a75..29546525f3 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -154,7 +154,7 @@ module ActiveRecord
assert_equal String, bob.first_name.class
assert_equal String, bob.last_name.class
assert_equal String, bob.bio.class
- assert_equal Fixnum, bob.age.class
+ assert_kind_of Integer, bob.age
assert_equal Time, bob.birthday.class
if current_adapter?(:OracleAdapter)
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index a4b0de3f4e..6ad028d31b 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -180,7 +180,7 @@ class MigrationTest < ActiveRecord::TestCase
# is_a?(Bignum)
assert_kind_of Integer, b.world_population
assert_equal 6000000000, b.world_population
- assert_kind_of Fixnum, b.my_house_population
+ assert_kind_of Integer, b.my_house_population
assert_equal 3, b.my_house_population
assert_kind_of BigDecimal, b.bank_balance
assert_equal BigDecimal("1586.43"), b.bank_balance
@@ -204,7 +204,7 @@ class MigrationTest < ActiveRecord::TestCase
assert_in_delta BigDecimal("2.71828182845905"), b.value_of_e, 0.00000000000001
else
# - SQL standard is an integer
- assert_kind_of Fixnum, b.value_of_e
+ assert_kind_of Integer, b.value_of_e
assert_equal 2, b.value_of_e
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index e53239cdee..406643d6fb 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -144,13 +144,12 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_cache_does_not_wrap_string_results_in_arrays
Task.cache do
- # Oracle adapter returns count() as Fixnum or Float
+ # Oracle adapter returns count() as Integer or Float
if current_adapter?(:OracleAdapter)
assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
elsif current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter)
# Future versions of the sqlite3 adapter will return numeric
- assert_instance_of Fixnum,
- Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
+ assert_instance_of Fixnum, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
else
assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
end
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index 6d91f96bf6..c01c82f4f5 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -102,9 +102,9 @@ module ActiveRecord
assert_equal float.to_s, @quoter.quote(float, nil)
end
- def test_quote_fixnum
- fixnum = 1
- assert_equal fixnum.to_s, @quoter.quote(fixnum, nil)
+ def test_quote_integer
+ integer = 1
+ assert_equal integer.to_s, @quoter.quote(integer, nil)
end
def test_quote_bignum
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 3e2fadc294..5604124bb3 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1086,6 +1086,11 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 9, posts.where(:comments_count => 0).count
end
+ def test_count_with_block
+ posts = Post.all
+ assert_equal 10, posts.count { |p| p.comments_count.even? }
+ end
+
def test_count_on_association_relation
author = Author.last
another_author = Author.first
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 6056156698..846be857d0 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -295,4 +295,37 @@ class SerializedAttributeTest < ActiveRecord::TestCase
topic.update_attribute :content, nil
assert_equal [topic], Topic.where(content: nil)
end
+
+ def test_mutation_detection_does_not_double_serialize
+ coder = Object.new
+ def coder.dump(value)
+ return if value.nil?
+ value + " encoded"
+ end
+ def coder.load(value)
+ return if value.nil?
+ value.gsub(" encoded", "")
+ end
+ type = Class.new(ActiveModel::Type::Value) do
+ include ActiveModel::Type::Helpers::Mutable
+
+ def serialize(value)
+ return if value.nil?
+ value + " serialized"
+ end
+
+ def deserialize(value)
+ return if value.nil?
+ value.gsub(" serialized", "")
+ end
+ end.new
+ model = Class.new(Topic) do
+ attribute :foo, type
+ serialize :foo, coder
+ end
+
+ topic = model.create!(foo: "bar")
+ topic.foo
+ refute topic.changed?
+ end
end
diff --git a/activerecord/test/cases/suppressor_test.rb b/activerecord/test/cases/suppressor_test.rb
index 7d44e36419..2f00241de2 100644
--- a/activerecord/test/cases/suppressor_test.rb
+++ b/activerecord/test/cases/suppressor_test.rb
@@ -60,4 +60,16 @@ class SuppressorTest < ActiveRecord::TestCase
end
end
end
+
+ def test_suppresses_when_nested_multiple_times
+ assert_no_difference -> { Notification.count } do
+ Notification.suppress do
+ Notification.suppress { }
+ Notification.create
+ Notification.create!
+ Notification.new.save
+ Notification.new.save!
+ end
+ end
+ end
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 773182056b..25b8af7d34 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,2 +1,6 @@
+* Rescuable: If a handler doesn't match the exception, check for handlers
+ matching the exception's cause.
+
+ *Jeremy Daer*
Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
index 22fc7ecf92..074e2eabf8 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -3,10 +3,8 @@ require 'bigdecimal/util'
module ActiveSupport
module BigDecimalWithDefaultFormat #:nodoc:
- DEFAULT_STRING_FORMAT = 'F'
-
- def to_s(format = nil)
- super(format || DEFAULT_STRING_FORMAT)
+ def to_s(format = 'F')
+ super(format)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index b0f9a8be34..1d8c33b43e 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -26,8 +26,6 @@ class Class
# Returns an array with the direct children of +self+.
#
- # Integer.subclasses # => [Fixnum, Bignum]
- #
# class Foo; end
# class Bar < Foo; end
# class Baz < Bar; end
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 4da7fdd159..6206546672 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
@@ -294,6 +294,11 @@ module DateAndTime
end
alias :at_end_of_year :end_of_year
+ # Returns a Range representing the whole day of the current date/time.
+ def all_day
+ beginning_of_day..end_of_day
+ end
+
# 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)
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 9a893157ea..8ebe758078 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -34,7 +34,9 @@ module Enumerable
# => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
def index_by
if block_given?
- Hash[map { |elem| [yield(elem), elem] }]
+ result = {}
+ each { |elem| result[yield(elem)] = elem }
+ result
else
to_enum(:index_by) { size if respond_to?(:size) }
end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index dd5ebe6d8d..2fc514cfce 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -55,8 +55,7 @@ class Hash
#
# XML_TYPE_NAMES = {
# "Symbol" => "symbol",
- # "Fixnum" => "integer",
- # "Bignum" => "integer",
+ # "Integer" => "integer",
# "BigDecimal" => "decimal",
# "Float" => "float",
# "TrueClass" => "boolean",
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index b25925b9d4..6586a351f8 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -134,6 +134,12 @@ module ActiveSupport::NumericWithFormat
deprecate to_formatted_s: :to_s
end
-[Fixnum, Bignum, Float, BigDecimal].each do |klass|
- klass.prepend(ActiveSupport::NumericWithFormat)
+# Ruby 2.4+ unifies Fixnum & Bignum into Integer.
+if Integer == Fixnum
+ Integer.prepend ActiveSupport::NumericWithFormat
+else
+ Fixnum.prepend ActiveSupport::NumericWithFormat
+ Bignum.prepend ActiveSupport::NumericWithFormat
end
+Float.prepend ActiveSupport::NumericWithFormat
+BigDecimal.prepend ActiveSupport::NumericWithFormat
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index befa5aee21..9bc5ee65ba 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -70,7 +70,7 @@ class Numeric
# Numbers are not duplicable:
#
# 3.duplicable? # => false
- # 3.dup # => TypeError: can't dup Fixnum
+ # 3.dup # => TypeError: can't dup Integer
def duplicable?
false
end
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index 8c16d95b62..3b6d9da216 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -99,7 +99,7 @@ class Object
#
# "a".try!(:upcase) # => "A"
# nil.try!(:upcase) # => nil
- # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum
+ # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer
end
class Delegator
diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index ebd0dd3fc7..213a91aa7a 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -1,5 +1,5 @@
class String
- # If you pass a single Fixnum, returns a substring of one character at that
+ # If you pass a single integer, returns a substring of one character at that
# position. The first character of the string is at position 0, the next at
# position 1, and so on. If a range is supplied, a substring containing
# characters at offsets given by the range is returned. In both cases, if an
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index b755726db2..e81b48ab26 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -227,11 +227,6 @@ class Time
end
alias :at_end_of_minute :end_of_minute
- # Returns a Range representing the whole day of the current time.
- def all_day
- beginning_of_day..end_of_day
- end
-
def plus_with_duration(other) #:nodoc:
if ActiveSupport::Duration === other
other.since(self)
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index 73bc52b56f..2c05deee41 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -1,7 +1,6 @@
require 'active_support/concern'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/string/inflections'
-require 'active_support/core_ext/array/extract_options'
module ActiveSupport
# Rescuable module adds support for easier exception handling.
@@ -48,14 +47,12 @@ module ActiveSupport
# end
#
# Exceptions raised inside exception handlers are not propagated up.
- def rescue_from(*klasses, &block)
- options = klasses.extract_options!
-
- unless options.has_key?(:with)
+ def rescue_from(*klasses, with: nil, &block)
+ unless with
if block_given?
- options[:with] = block
+ with = block
else
- raise ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument."
+ raise ArgumentError, 'Need a handler. Pass the with: keyword argument or provide a block.'
end
end
@@ -65,65 +62,104 @@ module ActiveSupport
elsif klass.is_a?(String)
klass
else
- raise ArgumentError, "#{klass} is neither an Exception nor a String"
+ raise ArgumentError, "#{klass.inspect} must be an Exception class or a String referencing an Exception class"
end
# Put the new handler at the end because the list is read in reverse.
- self.rescue_handlers += [[key, options[:with]]]
+ self.rescue_handlers += [[key, with]]
end
end
- end
- # Tries to rescue the exception by looking up and calling a registered handler.
- def rescue_with_handler(exception)
- if handler = handler_for_rescue(exception)
- handler.arity != 0 ? handler.call(exception) : handler.call
- true # don't rely on the return value of the handler
+ # Matches an exception to a handler based on the exception class.
+ #
+ # If no handler matches the exception, check for a handler matching the
+ # (optional) exception.cause. If no handler matches the exception or its
+ # cause, this returns nil so you can deal with unhandled exceptions.
+ # Be sure to re-raise unhandled exceptions if this is what you expect.
+ #
+ # begin
+ # …
+ # rescue => exception
+ # rescue_with_handler(exception) || raise
+ # end
+ #
+ # Returns the exception if it was handled and nil if it was not.
+ def rescue_with_handler(exception, object: self)
+ if handler = handler_for_rescue(exception, object: object)
+ handler.call exception
+ exception
+ end
end
- end
- def handler_for_rescue(exception)
- # We go from right to left because pairs are pushed onto rescue_handlers
- # as rescue_from declarations are found.
- _, rescuer = self.class.rescue_handlers.reverse.detect do |klass_name, handler|
- # The purpose of allowing strings in rescue_from is to support the
- # declaration of handler associations for exception classes whose
- # definition is yet unknown.
- #
- # Since this loop needs the constants it would be inconsistent to
- # assume they should exist at this point. An early raised exception
- # could trigger some other handler and the array could include
- # precisely a string whose corresponding constant has not yet been
- # seen. This is why we are tolerant to unknown constants.
- #
- # Note that this tolerance only matters if the exception was given as
- # a string, otherwise a NameError will be raised by the interpreter
- # itself when rescue_from CONSTANT is executed.
- klass = self.class.const_get(klass_name) rescue nil
- klass ||= (klass_name.constantize rescue nil)
- klass === exception if klass
+ def handler_for_rescue(exception, object: self) #:nodoc:
+ case rescuer = find_rescue_handler(exception)
+ when Symbol
+ method = object.method(rescuer)
+ if method.arity == 0
+ -> e { method.call }
+ else
+ method
+ end
+ when Proc
+ if rescuer.arity == 0
+ -> e { object.instance_exec(&rescuer) }
+ else
+ -> e { object.instance_exec(e, &rescuer) }
+ end
+ end
end
- case rescuer
- when Symbol
- method(rescuer)
- when Proc
- if rescuer.arity == 0
- Proc.new { instance_exec(&rescuer) }
- else
- Proc.new { |_exception| instance_exec(_exception, &rescuer) }
+ private
+ def find_rescue_handler(exception)
+ if exception
+ # Handlers are in order of declaration but the most recently declared
+ # is the highest priority match, so we search for matching handlers
+ # in reverse.
+ _, handler = rescue_handlers.reverse_each.detect do |class_or_name, _|
+ if klass = constantize_rescue_handler_class(class_or_name)
+ klass === exception
+ end
+ end
+
+ handler || find_rescue_handler(exception.cause)
+ end
+ end
+
+ def constantize_rescue_handler_class(class_or_name)
+ case class_or_name
+ when String, Symbol
+ begin
+ # Try a lexical lookup first since we support
+ #
+ # class Super
+ # rescue_from 'Error', with: …
+ # end
+ #
+ # class Sub
+ # class Error < StandardError; end
+ # end
+ #
+ # so an Error raised in Sub will hit the 'Error' handler.
+ const_get class_or_name
+ rescue NameError
+ class_or_name.safe_constantize
+ end
+ else
+ class_or_name
+ end
end
- end
end
- def index_of_handler_for_rescue(exception)
- handlers = self.class.rescue_handlers.reverse_each.with_index
- _, index = handlers.detect do |(klass_name, _), _|
- klass = self.class.const_get(klass_name) rescue nil
- klass ||= (klass_name.constantize rescue nil)
- klass === exception if klass
- end
- index
+ # Delegates to the class method, but uses the instance as the subject for
+ # rescue_from handlers (method calls, instance_exec blocks).
+ def rescue_with_handler(exception)
+ self.class.rescue_with_handler exception, object: self
+ end
+
+ # Internal handler lookup. Delegates to class method. Some libraries call
+ # this directly, so keeping it around for compatibility.
+ def handler_for_rescue(exception) #:nodoc:
+ self.class.handler_for_rescue exception, object: self
end
end
end
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index df7b081993..99fc26549e 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -32,20 +32,25 @@ module ActiveSupport
"binary" => "base64"
} unless defined?(DEFAULT_ENCODINGS)
- TYPE_NAMES = {
- "Symbol" => "symbol",
- "Fixnum" => "integer",
- "Bignum" => "integer",
- "BigDecimal" => "decimal",
- "Float" => "float",
- "TrueClass" => "boolean",
- "FalseClass" => "boolean",
- "Date" => "date",
- "DateTime" => "dateTime",
- "Time" => "dateTime",
- "Array" => "array",
- "Hash" => "hash"
- } unless defined?(TYPE_NAMES)
+ unless defined?(TYPE_NAMES)
+ TYPE_NAMES = {
+ "Symbol" => "symbol",
+ "Integer" => "integer",
+ "BigDecimal" => "decimal",
+ "Float" => "float",
+ "TrueClass" => "boolean",
+ "FalseClass" => "boolean",
+ "Date" => "date",
+ "DateTime" => "dateTime",
+ "Time" => "dateTime",
+ "Array" => "array",
+ "Hash" => "hash"
+ }
+
+ # No need to map these on Ruby 2.4+
+ TYPE_NAMES["Fixnum"] = "integer" unless Fixnum == Integer
+ TYPE_NAMES["Bignum"] = "integer" unless Bignum == Integer
+ end
FORMATTING = {
"symbol" => Proc.new { |symbol| symbol.to_s },
diff --git a/activesupport/test/core_ext/array/conversions_test.rb b/activesupport/test/core_ext/array/conversions_test.rb
index 507e13f968..de36e2026d 100644
--- a/activesupport/test/core_ext/array/conversions_test.rb
+++ b/activesupport/test/core_ext/array/conversions_test.rb
@@ -101,10 +101,10 @@ class ToXmlTest < ActiveSupport::TestCase
end
def test_to_xml_with_non_hash_elements
- xml = [1, 2, 3].to_xml(skip_instruct: true, indent: 0)
+ xml = %w[1 2 3].to_xml(skip_instruct: true, indent: 0)
- assert_equal '<fixnums type="array"><fixnum', xml.first(29)
- assert xml.include?(%(<fixnum type="integer">2</fixnum>)), xml
+ assert_equal '<strings type="array"><string', xml.first(29)
+ assert xml.include?(%(<string>2</string>)), xml
end
def test_to_xml_with_non_hash_different_type_elements
diff --git a/activesupport/test/core_ext/array/grouping_test.rb b/activesupport/test/core_ext/array/grouping_test.rb
index fb7367b0bf..0682241f0b 100644
--- a/activesupport/test/core_ext/array/grouping_test.rb
+++ b/activesupport/test/core_ext/array/grouping_test.rb
@@ -3,11 +3,12 @@ require 'active_support/core_ext/array'
class GroupingTest < ActiveSupport::TestCase
def setup
- Fixnum.send :private, :/ # test we avoid Integer#/ (redefined by mathn)
+ # In Ruby < 2.4, test we avoid Integer#/ (redefined by mathn)
+ Fixnum.send :private, :/ unless Fixnum == Integer
end
def teardown
- Fixnum.send :public, :/
+ Fixnum.send :public, :/ unless Fixnum == Integer
end
def test_in_groups_of_with_perfect_fit
diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb
index 423a3f2e9d..6e82e3892b 100644
--- a/activesupport/test/core_ext/bigdecimal_test.rb
+++ b/activesupport/test/core_ext/bigdecimal_test.rb
@@ -5,5 +5,7 @@ class BigDecimalTest < ActiveSupport::TestCase
def test_to_s
bd = BigDecimal.new '0.01'
assert_equal '0.01', bd.to_s
+ assert_equal '+0.01', bd.to_s('+F')
+ assert_equal '+0.0 1', bd.to_s('+1F')
end
end
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index 932675a50d..8052d38c33 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -284,6 +284,23 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_all_day
+ beginning_of_day = Time.local(2011,6,7,0,0,0)
+ end_of_day = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000))
+ assert_equal beginning_of_day..end_of_day, Date.new(2011,6,7).all_day
+ end
+
+ def test_all_day_when_zone_is_set
+ zone = ActiveSupport::TimeZone["Hawaii"]
+ with_env_tz "UTC" do
+ with_tz_default zone do
+ beginning_of_day = zone.local(2011,6,7,0,0,0)
+ end_of_day = zone.local(2011,6,7,23,59,59,Rational(999999999, 1000))
+ assert_equal beginning_of_day..end_of_day, Date.new(2011,6,7).all_day
+ end
+ 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)
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index ce69364c68..502e2811fa 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -12,7 +12,7 @@ class DurationTest < ActiveSupport::TestCase
assert d.is_a?(ActiveSupport::Duration)
assert_kind_of ActiveSupport::Duration, d
assert_kind_of Numeric, d
- assert_kind_of Fixnum, d
+ assert_kind_of Integer, d
assert !d.is_a?(Hash)
k = Class.new
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index be8583e704..f0a4c4dddc 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -36,12 +36,12 @@ class HashExtTest < ActiveSupport::TestCase
def setup
@strings = { 'a' => 1, 'b' => 2 }
@nested_strings = { 'a' => { 'b' => { 'c' => 3 } } }
- @symbols = { :a => 1, :b => 2 }
+ @symbols = { :a => 1, :b => 2 }
@nested_symbols = { :a => { :b => { :c => 3 } } }
- @mixed = { :a => 1, 'b' => 2 }
- @nested_mixed = { 'a' => { :b => { 'c' => 3 } } }
- @fixnums = { 0 => 1, 1 => 2 }
- @nested_fixnums = { 0 => { 1 => { 2 => 3} } }
+ @mixed = { :a => 1, 'b' => 2 }
+ @nested_mixed = { 'a' => { :b => { 'c' => 3 } } }
+ @integers = { 0 => 1, 1 => 2 }
+ @nested_integers = { 0 => { 1 => { 2 => 3} } }
@illegal_symbols = { [] => 3 }
@nested_illegal_symbols = { [] => { [] => 3} }
@upcase_strings = { 'A' => 1, 'B' => 2 }
@@ -196,14 +196,14 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_dup.deep_symbolize_keys!
end
- def test_symbolize_keys_preserves_fixnum_keys
- assert_equal @fixnums, @fixnums.symbolize_keys
- assert_equal @fixnums, @fixnums.dup.symbolize_keys!
+ def test_symbolize_keys_preserves_integer_keys
+ assert_equal @integers, @integers.symbolize_keys
+ assert_equal @integers, @integers.dup.symbolize_keys!
end
- def test_deep_symbolize_keys_preserves_fixnum_keys
- assert_equal @nested_fixnums, @nested_fixnums.deep_symbolize_keys
- assert_equal @nested_fixnums, @nested_fixnums.deep_dup.deep_symbolize_keys!
+ def test_deep_symbolize_keys_preserves_integer_keys
+ assert_equal @nested_integers, @nested_integers.deep_symbolize_keys
+ assert_equal @nested_integers, @nested_integers.deep_dup.deep_symbolize_keys!
end
def test_stringify_keys
@@ -299,14 +299,14 @@ class HashExtTest < ActiveSupport::TestCase
assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
end
- def test_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
- assert_equal @fixnums, @fixnums.with_indifferent_access.symbolize_keys
- assert_raise(NoMethodError) { @fixnums.with_indifferent_access.dup.symbolize_keys! }
+ def test_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
+ assert_equal @integers, @integers.with_indifferent_access.symbolize_keys
+ assert_raise(NoMethodError) { @integers.with_indifferent_access.dup.symbolize_keys! }
end
- def test_deep_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
- assert_equal @nested_fixnums, @nested_fixnums.with_indifferent_access.deep_symbolize_keys
- assert_raise(NoMethodError) { @nested_fixnums.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ def test_deep_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
+ assert_equal @nested_integers, @nested_integers.with_indifferent_access.deep_symbolize_keys
+ assert_raise(NoMethodError) { @nested_integers.with_indifferent_access.deep_dup.deep_symbolize_keys! }
end
def test_stringify_keys_for_hash_with_indifferent_access
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 5654aeb4f8..69c30a8a9e 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -387,16 +387,9 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
end
def test_to_s__injected_on_proper_types
- assert_equal Fixnum, 1230.class
assert_equal '1.23 Thousand', 1230.to_s(:human)
-
- assert_equal Float, Float(1230).class
assert_equal '1.23 Thousand', Float(1230).to_s(:human)
-
- assert_equal Bignum, (100**10).class
assert_equal '100000 Quadrillion', (100**10).to_s(:human)
-
- assert_equal BigDecimal, BigDecimal("1000010").class
assert_equal '1 Million', BigDecimal("1000010").to_s(:human)
end
diff --git a/activesupport/test/core_ext/object/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb
index 791b5e7172..aa839201ea 100644
--- a/activesupport/test/core_ext/object/deep_dup_test.rb
+++ b/activesupport/test/core_ext/object/deep_dup_test.rb
@@ -51,7 +51,7 @@ class DeepDupTest < ActiveSupport::TestCase
end
def test_deep_dup_with_hash_class_key
- hash = { Fixnum => 1 }
+ hash = { Integer => 1 }
dup = hash.deep_dup
assert_equal 1, dup.keys.length
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index ec56c500d3..d68a77680b 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -345,7 +345,7 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
class StringAccessTest < ActiveSupport::TestCase
- test "#at with Fixnum, returns a substring of one character at that position" do
+ test "#at with Integer, returns a substring of one character at that position" do
assert_equal "h", "hello".at(0)
end
@@ -358,19 +358,19 @@ class StringAccessTest < ActiveSupport::TestCase
assert_equal nil, "hello".at(/nonexisting/)
end
- test "#from with positive Fixnum, returns substring from the given position to the end" do
+ test "#from with positive Integer, returns substring from the given position to the end" do
assert_equal "llo", "hello".from(2)
end
- test "#from with negative Fixnum, position is counted from the end" do
+ test "#from with negative Integer, position is counted from the end" do
assert_equal "lo", "hello".from(-2)
end
- test "#to with positive Fixnum, substring from the beginning to the given position" do
+ test "#to with positive Integer, substring from the beginning to the given position" do
assert_equal "hel", "hello".to(2)
end
- test "#to with negative Fixnum, position is counted from the end" do
+ test "#to with negative Integer, position is counted from the end" do
assert_equal "hell", "hello".to(-2)
end
@@ -384,14 +384,14 @@ class StringAccessTest < ActiveSupport::TestCase
assert_equal 'x', 'x'.first
end
- test "#first with Fixnum, returns a substring from the beginning to position" do
+ test "#first with Integer, returns a substring from the beginning to position" do
assert_equal "he", "hello".first(2)
assert_equal "", "hello".first(0)
assert_equal "hello", "hello".first(10)
assert_equal 'x', 'x'.first(4)
end
- test "#first with Fixnum >= string length still returns a new string" do
+ test "#first with Integer >= string length still returns a new string" do
string = "hello"
different_string = string.first(5)
assert_not_same different_string, string
@@ -402,14 +402,14 @@ class StringAccessTest < ActiveSupport::TestCase
assert_equal 'x', 'x'.last
end
- test "#last with Fixnum, returns a substring from the end to position" do
+ test "#last with Integer, returns a substring from the end to position" do
assert_equal "llo", "hello".last(3)
assert_equal "hello", "hello".last(10)
assert_equal "", "hello".last(0)
assert_equal 'x', 'x'.last(4)
end
- test "#last with Fixnum >= string length still returns a new string" do
+ test "#last with Integer >= string length still returns a new string" do
string = "hello"
different_string = string.last(5)
assert_not_same different_string, string
@@ -682,7 +682,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert_equal @string, @string.html_safe
end
- test "A fixnum is safe by default" do
+ test "An integer is safe by default" do
assert 5.html_safe?
end
@@ -813,7 +813,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert_equal ["<p>", "<b>", "<h1>"], @other_string
end
- test "Concatting a fixnum to safe always yields safe" do
+ test "Concatting an integer to safe always yields safe" do
string = @string.html_safe
string = string.concat(13)
assert_equal "hello".concat(13), string
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index 14fe97a986..c7dc1dadb6 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -270,7 +270,8 @@ module InflectorTestCases
"maybe you'll be there" => "Maybe You'll Be There",
"¿por qué?" => '¿Por Qué?',
"Fred’s" => "Fred’s",
- "Fred`s" => "Fred`s"
+ "Fred`s" => "Fred`s",
+ ActiveSupport::SafeBuffer.new("confirmation num") => "Confirmation Num"
}
OrdinalNumbers = {
diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb
index bd43ad0797..e42e6d2973 100644
--- a/activesupport/test/rescuable_test.rb
+++ b/activesupport/test/rescuable_test.rb
@@ -3,9 +3,6 @@ require 'abstract_unit'
class WraithAttack < StandardError
end
-class NuclearExplosion < StandardError
-end
-
class MadRonon < StandardError
end
@@ -19,6 +16,10 @@ module WeirdError
end
class Stargate
+ # Nest this so the 'NuclearExplosion' handler needs a lexical const_get
+ # to find it.
+ class NuclearExplosion < StandardError; end
+
attr_accessor :result
include ActiveSupport::Rescuable
@@ -57,6 +58,14 @@ class Stargate
raise MadRonon.new("dex")
end
+ def fall_back_to_cause
+ # This exception is the cause and has a handler.
+ ronanize
+ rescue
+ # This is the exception we'll handle that doesn't have a cause.
+ raise 'unhandled RuntimeError with a handleable cause'
+ end
+
def weird
StandardError.new.tap do |exc|
def exc.weird?
@@ -74,7 +83,6 @@ class Stargate
def sos_first
@result = 'sos_first'
end
-
end
class CoolStargate < Stargate
@@ -127,4 +135,9 @@ class RescuableTest < ActiveSupport::TestCase
result = @cool_stargate.send(:rescue_handlers).collect(&:first)
assert_equal expected, result
end
+
+ def test_rescue_falls_back_to_exception_cause
+ @stargate.dispatch :fall_back_to_cause
+ assert_equal 'unhandled RuntimeError with a handleable cause', @stargate.result
+ end
end
diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md
index 2776bc4e6d..06761b67bb 100644
--- a/guides/source/2_3_release_notes.md
+++ b/guides/source/2_3_release_notes.md
@@ -614,7 +614,7 @@ A few pieces of older code are deprecated in this release:
* Some integration test helpers have been removed. `response.headers["Status"]` and `headers["Status"]` will no longer return anything. Rack does not allow "Status" in its return headers. However you can still use the `status` and `status_message` helpers. `response.headers["cookie"]` and `headers["cookie"]` will no longer return any CGI cookies. You can inspect `headers["Set-Cookie"]` to see the raw cookie header or use the `cookies` helper to get a hash of the cookies sent to the client.
* `formatted_polymorphic_url` is deprecated. Use `polymorphic_url` with `:format` instead.
* The `:http_only` option in `ActionController::Response#set_cookie` has been renamed to `:httponly`.
-* The `:connector` and `:skip_last_comma` options of `to_sentence` have been replaced by `:words_connnector`, `:two_words_connector`, and `:last_word_connector` options.
+* The `:connector` and `:skip_last_comma` options of `to_sentence` have been replaced by `:words_connector`, `:two_words_connector`, and `:last_word_connector` options.
* Posting a multipart form with an empty `file_field` control used to submit an empty string to the controller. Now it submits a nil, due to differences between Rack's multipart parser and the old Rails one.
Credits
diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md
index 2c679ba632..2583b32925 100644
--- a/guides/source/5_0_release_notes.md
+++ b/guides/source/5_0_release_notes.md
@@ -48,7 +48,74 @@ ToDo...
### Active Record attributes API
-ToDo...
+Defines an attribute with a type on a model. It will override the type of existing attributes if needed.
+This allows control over how values are converted to and from SQL when assigned to a model.
+It also changes the behavior of values passed to ActiveRecord::Base.where, which lets use our domain objects across much of Active Record,
+without having to rely on implementation details or monkey patching.
+
+Some things that you can achieve with this:
+* The type detected by Active Record can be overridden.
+* A default can also be provided.
+* Attributes do not need to be backed by a database column.
+
+```ruby
+
+# db/schema.rb
+create_table :store_listings, force: true do |t|
+ t.decimal :price_in_cents
+ t.string :my_string, default: "original default"
+end
+
+# app/models/store_listing.rb
+class StoreListing < ActiveRecord::Base
+end
+
+store_listing = StoreListing.new(price_in_cents: '10.1')
+
+# before
+store_listing.price_in_cents # => BigDecimal.new(10.1)
+StoreListing.new.my_string # => "original default"
+
+class StoreListing < ActiveRecord::Base
+ attribute :price_in_cents, :integer # custom type
+ attribute :my_string, :string, default: "new default" # default value
+ attribute :my_default_proc, :datetime, default: -> { Time.now } # default value
+ attribute :field_without_db_column, :integer, array: true
+end
+
+# after
+store_listing.price_in_cents # => 10
+StoreListing.new.my_string # => "new default"
+StoreListing.new.my_default_proc # => 2015-05-30 11:04:48 -0600
+model = StoreListing.new(field_without_db_column: ["1", "2", "3"])
+model.attributes #=> {field_without_db_column: [1, 2, 3]}
+```
+
+**Creating Custom Types:**
+
+You can define your own custom types, as long as they respond
+to the methods defined on the value type. The method +deserialize+ or
++cast+ will be called on your type object, with raw input from the
+database or from your controllers. This is useful, for example, when doing custom conversion,
+like Money data.
+
+**Querying:**
+
+When `ActiveRecord::Base.where` is called, it will
+use the type defined by the model class to convert the value to SQL,
+calling +serialize+ on your type object.
+
+This gives the objects ability to specify, how to convert values when performing SQL queries.
+
+**Dirty Tracking:**
+
+The type of an attribute is given the opportunity to change how dirty
+tracking is performed.
+
+See its
+[documentation](http://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html)
+for a detailed write up.
+
### Test Runner
[Pull Request](https://github.com/rails/rails/pull/19216)
@@ -814,7 +881,7 @@ Please refer to the [Changelog][active-support] for detailed changes.
([commit](https://github.com/rails/rails/commit/a5e507fa0b8180c3d97458a9b86c195e9857d8f6))
* Added `Integer#positive?` and `Integer#negative?` query methods
- in the vein of `Fixnum#zero?`.
+ in the vein of `Integer#zero?`.
([commit](https://github.com/rails/rails/commit/e54277a45da3c86fecdfa930663d7692fd083daa))
* Added a bang version to `ActiveSupport::OrderedOptions` get methods which will raise
diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md
index 5cc280072e..93bcb6157a 100644
--- a/guides/source/action_cable_overview.md
+++ b/guides/source/action_cable_overview.md
@@ -12,41 +12,39 @@ After reading this guide, you will know:
Introduction
------------
-Action Cable seamlessly integrates WebSockets with the rest of your Rails application.
-It allows for real-time features to be written in Ruby in the same style and form as
-the rest of your Rails application, while still being performant and scalable. It's
-a full-stack offering that provides both a client-side JavaScript framework and a
-server-side Ruby framework. You have access to your full domain model written with
-Active Record or your ORM of choice.
+Action Cable seamlessly integrates
+[WebSockets](https://en.wikipedia.org/wiki/WebSocket) with the rest of your
+Rails application. It allows for real-time features to be written in Ruby in the
+same style and form as the rest of your Rails application, while still being
+performant and scalable. It's a full-stack offering that provides both a
+client-side JavaScript framework and a server-side Ruby framework. You have
+access to your full domain model written with Active Record or your ORM of
+choice.
What is Pub/Sub
---------------
-Pub/Sub, or Publish-Subscribe, refers to a message queue paradigm whereby senders
-of information (publishers), send data to an abstract class of recipients (subscribers),
-without specifying individual recipients. Action Cable uses this approach to communicate
-between the server and many clients.
-
-What is Action Cable
---------------------
-
-Action Cable is a server which can handle multiple connection instances, with one
-client-server connection instance established per WebSocket connection.
+[Pub/Sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern), or
+Publish-Subscribe, refers to a message queue paradigm whereby senders of
+information (publishers), send data to an abstract class of recipients
+(subscribers), without specifying individual recipients. Action Cable uses this
+approach to communicate between the server and many clients.
## Server-Side Components
### Connections
-Connections form the foundation of the client-server relationship. For every WebSocket
-the cable server is accepting, a Connection object will be instantiated on the server side.
-This instance becomes the parent of all the channel subscriptions that are created from there on.
-The Connection itself does not deal with any specific application logic beyond authentication
-and authorization. The client of a WebSocket connection is called a consumer. An individual
-user will create one consumer-connection pair per browser tab, window, or device they have open.
+*Connections* form the foundation of the client-server relationship. For every
+WebSocket accepted by the server, a connection object is instantiated. This
+object becomes the parent of all the *channel subscriptions* that are created
+from there on. The connection itself does not deal with any specific application
+logic beyond authentication and authorization. The client of a WebSocket
+connection is called the connection *consumer*. An individual user will create
+one consumer-connection pair per browser tab, window, or device they have open.
-Connections are instantiated via the `ApplicationCable::Connection` class in Ruby.
-In this class, you authorize the incoming connection, and proceed to establish it
-if the user can be identified.
+Connections are instances of `ApplicationCable::Connection`. In this class, you
+authorize the incoming connection, and proceed to establish it if the user can
+be identified.
#### Connection Setup
@@ -78,17 +76,17 @@ create a delegate by the same name on any channel instances created off the conn
This example relies on the fact that you will already have handled authentication of the user
somewhere else in your application, and that a successful authentication sets a signed
-cookie with the `user_id`.
+cookie with the user ID.
The cookie is then automatically sent to the connection instance when a new connection
is attempted, and you use that to set the `current_user`. By identifying the connection
-by this same current_user, you're also ensuring that you can later retrieve all open
+by this same current user, you're also ensuring that you can later retrieve all open
connections by a given user (and potentially disconnect them all if the user is deleted
or deauthorized).
### Channels
-A channel encapsulates a logical unit of work, similar to what a controller does in a
+A *channel* encapsulates a logical unit of work, similar to what a controller does in a
regular MVC setup. By default, Rails creates a parent `ApplicationCable::Channel` class
for encapsulating shared logic between your channels.
@@ -103,7 +101,7 @@ end
```
Then you would create your own channel classes. For example, you could have a
-**ChatChannel** and an **AppearanceChannel**:
+`ChatChannel` and an `AppearanceChannel`:
```ruby
# app/channels/chat_channel.rb
@@ -119,15 +117,15 @@ A consumer could then be subscribed to either or both of these channels.
#### Subscriptions
-When a consumer is subscribed to a channel, they act as a subscriber;
-This connection is called a subscription.
-Incoming messages are then routed to these channel subscriptions based on
-an identifier sent by the cable consumer.
+Consumers subscribe to channels, acting as *subscribers*. Their connection is
+called a *subscription*. Produced messages are then routed to these channel
+subscriptions based on an identifier sent by the cable consumer.
```ruby
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
- # Called when the consumer has successfully become a subscriber of this channel
+ # Called when the consumer has successfully
+ # become a subscriber of this channel.
def subscribed
end
end
@@ -138,7 +136,7 @@ end
### Connections
Consumers require an instance of the connection on their side. This can be
-established using the following Javascript, which is generated by default in Rails:
+established using the following JavaScript, which is generated by default by Rails:
#### Connect Consumer
@@ -155,18 +153,13 @@ established using the following Javascript, which is generated by default in Rai
}).call(this);
```
-This will ready a consumer that'll connect against /cable on your server by default.
+This will ready a consumer that'll connect against `/cable` on your server by default.
The connection won't be established until you've also specified at least one subscription
you're interested in having.
#### Subscriber
-When a consumer is subscribed to a channel, they act as a subscriber. A
-consumer can act as a subscriber to a given channel any number of times.
-For example, a consumer could subscribe to multiple chat rooms at the same time.
-(remember that a physical user may have multiple consumers, one per tab/device open to your connection).
-
-A consumer becomes a subscriber, by creating a subscription to a given channel:
+A consumer becomes a subscriber by creating a subscription to a given channel:
```coffeescript
# app/assets/javascripts/cable/subscriptions/chat.coffee
@@ -179,12 +172,20 @@ App.cable.subscriptions.create { channel: "AppearanceChannel" }
While this creates the subscription, the functionality needed to respond to
received data will be described later on.
+A consumer can act as a subscriber to a given channel any number of times. For
+example, a consumer could subscribe to multiple chat rooms at the same time:
+
+```coffeescript
+App.cable.subscriptions.create { channel: "ChatChannel", room: "1st Room" }
+App.cable.subscriptions.create { channel: "ChatChannel", room: "2nd Room" }
+```
+
## Client-Server Interactions
### Streams
-Streams provide the mechanism by which channels route published content
-(broadcasts) to its subscribers.
+*Streams* provide the mechanism by which channels route published content
+(broadcasts) to their subscribers.
```ruby
# app/channels/chat_channel.rb
@@ -208,21 +209,30 @@ class CommentsChannel < ApplicationCable::Channel
end
```
-You can then broadcast to this channel using: `CommentsChannel.broadcast_to(@post, @comment)`
+You can then broadcast to this channel like this:
+
+```ruby
+CommentsChannel.broadcast_to(@post, @comment)
+```
-### Broadcastings
+### Broadcasting
-A broadcasting is a pub/sub link where anything transmitted by a publisher
+A *broadcasting* is a pub/sub link where anything transmitted by a publisher
is routed directly to the channel subscribers who are streaming that named
broadcasting. Each channel can be streaming zero or more broadcastings.
-Broadcastings are purely an online queue and time dependent;
-If a consumer is not streaming (subscribed to a given channel), they'll not
-get the broadcast should they connect later.
+
+Broadcastings are purely an online queue and time dependent. If a consumer is
+not streaming (subscribed to a given channel), they'll not get the broadcast
+should they connect later.
Broadcasts are called elsewhere in your Rails application:
```ruby
- WebNotificationsChannel.broadcast_to current_user, title: 'New things!', body: 'All the news fit to print'
+WebNotificationsChannel.broadcast_to(
+ current_user,
+ title: 'New things!',
+ body: 'All the news fit to print'
+)
```
The `WebNotificationsChannel.broadcast_to` call places a message in the current
@@ -231,14 +241,14 @@ broadcasting name for each user. For a user with an ID of 1, the broadcasting
name would be `web_notifications_1`.
The channel has been instructed to stream everything that arrives at
-`web_notifications_1` directly to the client by invoking the `#received(data)`
+`web_notifications_1` directly to the client by invoking the `received`
callback.
### Subscriptions
-When a consumer is subscribed to a channel, they act as a subscriber;
-This connection is called a subscription. Incoming messages are then routed
-to these channel subscriptions based on an identifier sent by the cable consumer.
+When a consumer is subscribed to a channel, they act as a subscriber. This
+connection is called a subscription. Incoming messages are then routed to
+these channel subscriptions based on an identifier sent by the cable consumer.
```coffeescript
# app/assets/javascripts/cable/subscriptions/chat.coffee
@@ -260,10 +270,10 @@ App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
"""
```
-### Passing Parameters to Channel
+### Passing Parameters to Channels
-You can pass parameters from the client-side to the server-side when
-creating a subscription. For example:
+You can pass parameters from the client side to the server side when creating a
+subscription. For example:
```ruby
# app/channels/chat_channel.rb
@@ -274,8 +284,8 @@ class ChatChannel < ApplicationCable::Channel
end
```
-Pass an object as the first argument to `subscriptions.create`, and that object
-will become your params hash in your cable channel. The keyword `channel` is required.
+An object passed as the first argument to `subscriptions.create` becomes the
+params hash in the cable channel. The keyword `channel` is required:
```coffeescript
# app/assets/javascripts/cable/subscriptions/chat.coffee
@@ -297,14 +307,18 @@ App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
```
```ruby
-# Somewhere in your app this is called, perhaps from a NewCommentJob
-ChatChannel.broadcast_to "chat_#{room}", sent_by: 'Paul', body: 'This is a cool chat app.'
+# Somewhere in your app this is called, perhaps
+# from a NewCommentJob.
+ChatChannel.broadcast_to(
+ "chat_#{room}",
+ sent_by: 'Paul',
+ body: 'This is a cool chat app.'
+)
```
+### Rebroadcasting a Message
-### Rebroadcasting message
-
-A common use case is to rebroadcast a message sent by one client to any
+A common use case is to *rebroadcast* a message sent by one client to any
other connected clients.
```ruby
@@ -315,7 +329,7 @@ class ChatChannel < ApplicationCable::Channel
end
def receive(data)
- ChatChannel.broadcast_to "chat_#{params[:room]}", data
+ ChatChannel.broadcast_to("chat_#{params[:room]}", data)
end
end
```
@@ -333,20 +347,21 @@ The rebroadcast will be received by all connected clients, _including_ the
client that sent the message. Note that params are the same as they were when
you subscribed to the channel.
-## Full-stack examples
+## Full-Stack Examples
The following setup steps are common to both examples:
- 1. [Setup your connection](#connection-setup)
- 2. [Setup your parent channel](#parent-channel-setup)
- 3. [Connect your consumer](#connect-consumer)
+ 1. [Setup your connection](#connection-setup).
+ 2. [Setup your parent channel](#parent-channel-setup).
+ 3. [Connect your consumer](#connect-consumer).
+
+### Example 1: User Appearances
-### Example 1: User appearances
Here's a simple example of a channel that tracks whether a user is online or not
and what page they're on. (This is useful for creating presence features like showing
a green dot next to a user name if they're online).
-#### Create the server-side Appearance Channel:
+Create the server-side appearance channel:
```ruby
# app/channels/appearance_channel.rb
@@ -360,7 +375,7 @@ class AppearanceChannel < ApplicationCable::Channel
end
def appear(data)
- current_user.appear on: data['appearing_on']
+ current_user.appear(on: data['appearing_on'])
end
def away
@@ -369,35 +384,34 @@ class AppearanceChannel < ApplicationCable::Channel
end
```
-When `#subscribed` callback is invoked by the consumer, a client-side subscription
-is initiated. In this case, we take that opportunity to say "the current user has
-indeed appeared". That appear/disappear API could be backed by Redis, a database,
-or whatever else.
+When a subscription is initiated the `subscribed` callback gets fired and we
+take that opportunity to say "the current user has indeed appeared". That
+appear/disappear API could be backed by Redis, a database, or whatever else.
-#### Create the client-side Appearance Channel subscription:
+Create the client-side appearance channel subscription:
```coffeescript
# app/assets/javascripts/cable/subscriptions/appearance.coffee
App.cable.subscriptions.create "AppearanceChannel",
- # Called when the subscription is ready for use on the server
+ # Called when the subscription is ready for use on the server.
connected: ->
@install()
@appear()
- # Called when the WebSocket connection is closed
+ # Called when the WebSocket connection is closed.
disconnected: ->
@uninstall()
- # Called when the subscription is rejected by the server
+ # Called when the subscription is rejected by the server.
rejected: ->
@uninstall()
appear: ->
- # Calls `AppearanceChannel#appear(data)` on the server
+ # Calls `AppearanceChannel#appear(data)` on the server.
@perform("appear", appearing_on: $("main").data("appearing-on"))
away: ->
- # Calls `AppearanceChannel#away` on the server
+ # Calls `AppearanceChannel#away` on the server.
@perform("away")
@@ -419,13 +433,33 @@ App.cable.subscriptions.create "AppearanceChannel",
```
##### Client-Server Interaction
-1. **Client** establishes a connection with the **Server** via `App.cable = ActionCable.createConsumer("ws://cable.example.com")`. [*` cable.js`*] The **Server** identified this connection instance by `current_user`.
-2. **Client** initiates a subscription to the `Appearance Channel` for their connection via `App.cable.subscriptions.create "AppearanceChannel"`. [*`appearance.coffee`*]
-3. **Server** recognizes a new subscription has been initiated for `AppearanceChannel` channel performs the `subscribed` callback, which calls the `appear` method on the `current_user`. [*`appearance_channel.rb`*]
-4. **Client** recognizes that a subscription has been established and calls `connected` [*`appearance.coffee`*] which in turn calls `@install` and `@appear`. `@appear` calls`AppearanceChannel#appear(data)` on the server, and supplies a data hash of `appearing_on: $("main").data("appearing-on")`. This is possible because the server-side channel instance will automatically expose the public methods declared on the class (minus the callbacks), so that these can be reached as remote procedure calls via a subscription's `perform` method.
-5. **Server** receives the request for the `appear` action on the `AppearanceChannel` channel for the connection identified by `current_user`. [*`appearance_channel.rb`*] The server retrieves the data with the `appearing_on` key from the data hash and sets it as the value for the `on:` key being passed to `current_user.appear`.
-### Example 2: Receiving new web notifications
+1. **Client** connects to the **Server** via `App.cable =
+ActionCable.createConsumer("ws://cable.example.com")`. (`cable.js`). The
+**Server** identifies this connection by `current_user`.
+
+2. **Client** subscribes to the appearance channel via
+`App.cable.subscriptions.create(channel: "AppearanceChannel")`. (`appearance.coffee`)
+
+3. **Server** recognizes a new subscription has been initiated for the
+appearance channel and runs its `subscribed` callback, calling the `appear`
+method on `current_user`. (`appearance_channel.rb`)
+
+4. **Client** recognizes that a subscription has been established and calls
+`connected` (`appearance.coffee`) which in turn calls `@install` and `@appear`.
+`@appear` calls `AppearanceChannel#appear(data)` on the server, and supplies a
+data hash of `{ appearing_on: $("main").data("appearing-on") }`. This is
+possible because the server-side channel instance automatically exposes all
+public methods declared on the class (minus the callbacks), so that these can be
+reached as remote procedure calls via a subscription's `perform` method.
+
+5. **Server** receives the request for the `appear` action on the appearance
+channel for the connection identified by `current_user`
+(`appearance_channel.rb`). **Server** retrieves the data with the
+`:appearing_on` key from the data hash and sets it as the value for the `:on`
+key being passed to `current_user.appear`.
+
+### Example 2: Receiving New Web Notifications
The appearance example was all about exposing server functionality to
client-side invocation over the WebSocket connection. But the great thing
@@ -435,7 +469,7 @@ where the server invokes an action on the client.
This is a web notification channel that allows you to trigger client-side
web notifications when you broadcast to the right streams:
-#### Create the server-side Web Notifications Channel:
+Create the server-side web notifications channel:
```ruby
# app/channels/web_notifications_channel.rb
@@ -446,34 +480,41 @@ class WebNotificationsChannel < ApplicationCable::Channel
end
```
-#### Create the client-side Web Notifications Channel subscription:
+Create the client-side web notifications channel subscription:
+
```coffeescript
# app/assets/javascripts/cable/subscriptions/web_notifications.coffee
-# Client-side which assumes you've already requested the right to send web notifications
+# Client-side which assumes you've already requested
+# the right to send web notifications.
App.cable.subscriptions.create "WebNotificationsChannel",
received: (data) ->
new Notification data["title"], body: data["body"]
```
-#### Broadcast content to a Web Notification Channel instance from elsewhere in your application
+Broadcast content to a web notification channel instance from elsewhere in your
+application:
```ruby
# Somewhere in your app this is called, perhaps from a NewCommentJob
- WebNotificationsChannel.broadcast_to current_user, title: 'New things!', body: 'All the news fit to print'
+WebNotificationsChannel.broadcast_to(
+ current_user,
+ title: 'New things!',
+ body: 'All the news fit to print'
+)
```
The `WebNotificationsChannel.broadcast_to` call places a message in the current
-subscription adapter (Redis by default)'s pubsub queue under a separate
-broadcasting name for each user. For a user with an ID of 1, the broadcasting
-name would be `web_notifications_1`.
+subscription adapter's pubsub queue under a separate broadcasting name for each
+user. For a user with an ID of 1, the broadcasting name would be
+"web_notifications_1".
The channel has been instructed to stream everything that arrives at
-`web_notifications_1` directly to the client by invoking the `#received(data)`
-callback. The data is the hash sent as the second parameter to the server-side
-broadcast call, JSON encoded for the trip across the wire, and unpacked for
-the data argument arriving to `#received`.
+"web_notifications_1" directly to the client by invoking the `received`
+callback. The data passed as argument is the hash sent as the second parameter
+to the server-side broadcast call, JSON encoded for the trip across the wire,
+and unpacked for the data argument arriving to `received`.
-### More complete examples
+### More Complete Examples
See the [rails/actioncable-examples](https://github.com/rails/actioncable-examples)
repository for a full example of how to setup Action Cable in a Rails app and adding channels.
@@ -484,26 +525,20 @@ Action Cable has two required configurations: a subscription adapter and allowed
### Subscription Adapter
-By default, `ActionCable::Server::Base` will look for a configuration file
-in `Rails.root.join('config/cable.yml')`. The file must specify an adapter
-and a URL for each Rails environment. See the "Dependencies" section for
-additional information on adapters.
+By default, Action Cable looks for a configuration file in `config/cable.yml`.
+The file must specify an adapter and a URL for each Rails environment. See the
+[Dependencies](#dependencies) section for additional information on adapters.
```yaml
-production: &production
- adapter: redis
- url: redis://10.10.3.153:6381
-development: &development
+development:
adapter: async
-test: *development
-```
-This format allows you to specify one configuration per Rails environment.
-You can also change the location of the Action Cable config file in
-a Rails initializer with something like:
+test:
+ adapter: async
-```ruby
-Rails.application.paths.add "config/redis/cable", with: "somewhere/else/cable.yml"
+production:
+ adapter: redis
+ url: redis://10.10.3.153:6381
```
### Allowed Request Origins
@@ -513,44 +548,46 @@ passed to the server config as an array. The origins can be instances of
strings or regular expressions, against which a check for match will be performed.
```ruby
-Rails.application.config.action_cable.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/]
+config.action_cable.allowed_request_origins = ['http://rubyonrails.com', %r{http://ruby.*}]
```
To disable and allow requests from any origin:
```ruby
-Rails.application.config.action_cable.disable_request_forgery_protection = true
+config.action_cable.disable_request_forgery_protection = true
```
By default, Action Cable allows all requests from localhost:3000 when running
in the development environment.
-
### Consumer Configuration
-To configure the URL, add a call to `action_cable_meta_tag` in your HTML layout HEAD.
-This uses a url or path typically set via `config.action_cable.url` in the environment configuration files.
+To configure the URL, add a call to `action_cable_meta_tag` in your HTML layout
+HEAD. This uses a URL or path typically set via `config.action_cable.url` in the
+environment configuration files.
### Other Configurations
-The other common option to configure is the log tags applied to the per-connection logger. Here's close to what we're using in Basecamp:
+The other common option to configure is the log tags applied to the
+per-connection logger. Here's close to what we're using in Basecamp:
```ruby
-Rails.application.config.action_cable.log_tags = [
+config.action_cable.log_tags = [
-> request { request.env['bc.account_id'] || "no-account" },
:action_cable,
-> request { request.uuid }
]
```
-For a full list of all configuration options, see the `ActionCable::Server::Configuration` class.
+For a full list of all configuration options, see the
+`ActionCable::Server::Configuration` class.
-Also note that your server must provide at least the same number of
-database connections as you have workers. The default worker pool is
-set to 100, so that means you have to make at least that available.
-You can change that in `config/database.yml` through the `pool` attribute.
+Also note that your server must provide at least the same number of database
+connections as you have workers. The default worker pool size is set to 100, so
+that means you have to make at least that available. You can change that in
+`config/database.yml` through the `pool` attribute.
-## Running standalone cable servers
+## Running Standalone Cable Servers
### In App
@@ -565,30 +602,30 @@ class Application < Rails::Application
end
```
-You can use `App.cable = ActionCable.createConsumer()` to connect to the
-cable server if `action_cable_meta_tag` is included in the layout. A custom
-path is specified as first argument to `createConsumer`
-(e.g. `App.cable = ActionCable.createConsumer("/websocket")`).
+You can use `App.cable = ActionCable.createConsumer()` to connect to the cable
+server if `action_cable_meta_tag` is invoked in the layout. A custom path is
+specified as first argument to `createConsumer` (e.g. `App.cable =
+ActionCable.createConsumer("/websocket")`).
-For every instance of your server you create and for every worker
-your server spawns, you will also have a new instance of ActionCable,
-but the use of Redis keeps messages synced across connections.
+For every instance of your server you create and for every worker your server
+spawns, you will also have a new instance of Action Cable, but the use of Redis
+keeps messages synced across connections.
### Standalone
-The cable servers can be separated from your normal application server.
-It's still a Rack application, but it is its own Rack application.
-The recommended basic setup is as follows:
+The cable servers can be separated from your normal application server. It's
+still a Rack application, but it is its own Rack application. The recommended
+basic setup is as follows:
```ruby
# cable/config.ru
-require ::File.expand_path('../../config/environment', __FILE__)
+require_relative 'config/environment'
Rails.application.eager_load!
run ActionCable.server
```
-Then you start the server using a binstub in bin/cable ala:
+Then you start the server using a binstub in `bin/cable` ala:
```
#!/bin/bash
@@ -624,4 +661,5 @@ The Action Cable server implements the Rack socket hijacking API,
thereby allowing the use of a multithreaded pattern for managing connections
internally, irrespective of whether the application server is multi-threaded or not.
-Accordingly, Action Cable works with all the popular application servers -- Unicorn, Puma and Passenger.
+Accordingly, Action Cable works with popular servers like Unicorn, Puma, and
+Passenger.
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 5462e6b2b8..a45690c03f 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -252,7 +252,7 @@ Note that `try` will swallow no-method errors, returning nil instead. If you wan
```ruby
@number.try(:nest) # => nil
-@number.try!(:nest) # NoMethodError: undefined method `nest' for 1:Fixnum
+@number.try!(:nest) # NoMethodError: undefined method `nest' for 1:Integer
```
NOTE: Defined in `active_support/core_ext/object/try.rb`.
@@ -1742,7 +1742,7 @@ NOTE: Defined in `active_support/core_ext/string/inflections.rb`.
The method `constantize` resolves the constant reference expression in its receiver:
```ruby
-"Fixnum".constantize # => Fixnum
+"Integer".constantize # => Integer
module M
X = 1
@@ -2611,8 +2611,7 @@ To do so, the method loops over the pairs and builds nodes that depend on the _v
```ruby
XML_TYPE_NAMES = {
"Symbol" => "symbol",
- "Fixnum" => "integer",
- "Bignum" => "integer",
+ "Integer" => "integer",
"BigDecimal" => "decimal",
"Float" => "float",
"TrueClass" => "boolean",
diff --git a/guides/source/api_app.md b/guides/source/api_app.md
index cad8d53f31..485294dc02 100644
--- a/guides/source/api_app.md
+++ b/guides/source/api_app.md
@@ -289,7 +289,7 @@ You can learn more about how to use `Rack::Sendfile` with popular
front-ends in [the Rack::Sendfile
documentation](http://rubydoc.info/github/rack/rack/master/Rack/Sendfile).
-Here are some values for popular servers, once they are configured, to support
+Here are some values for this header for some popular servers, once these servers are configured to support
accelerated file sending:
```ruby
@@ -395,6 +395,13 @@ included into `ActionController::API` in the rails console:
```bash
$ bin/rails c
>> ActionController::API.ancestors - ActionController::Metal.ancestors
+=> [ActionController::API,
+ ActiveRecord::Railties::ControllerRuntime,
+ ActionDispatch::Routing::RouteSet::MountedHelpers,
+ ActionController::ParamsWrapper,
+ ... ,
+ AbstractController::Rendering,
+ ActionView::ViewPaths]
```
### Adding Other Modules
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index cd208c2e13..5b34330936 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -120,7 +120,7 @@ On the other hand, big chunks of structured documentation may have a separate "E
The results of expressions follow them and are introduced by "# => ", vertically aligned:
```ruby
-# For checking if a fixnum is even or odd.
+# For checking if an integer is even or odd.
#
# 1.even? # => false
# 1.odd? # => true
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 93acebf000..e6631a513c 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -435,11 +435,11 @@ Sprockets uses manifest files to determine which assets to include and serve.
These manifest files contain _directives_ - instructions that tell Sprockets
which files to require in order to build a single CSS or JavaScript file. With
these directives, Sprockets loads the files specified, processes them if
-necessary, concatenates them into one single file and then compresses them (if
-`Rails.application.config.assets.compress` is true). By serving one file rather
-than many, the load time of pages can be greatly reduced because the browser
-makes fewer requests. Compression also reduces file size, enabling the
-browser to download them faster.
+necessary, concatenates them into one single file and then compresses them
+(based on value of `Rails.application.config.assets.js_compressor`). By serving
+one file rather than many, the load time of pages can be greatly reduced because
+the browser makes fewer requests. Compression also reduces file size, enabling
+the browser to download them faster.
For example, a new Rails application includes a default
@@ -1105,11 +1105,6 @@ NOTE: You will need an [ExecJS](https://github.com/rails/execjs#readme)
supported runtime in order to use `uglifier`. If you are using Mac OS X or
Windows you have a JavaScript runtime installed in your operating system.
-NOTE: The `config.assets.compress` initialization option is no longer used in
-Rails to enable either CSS or JavaScript compression. Setting it will have no
-effect on the application. Instead, setting `config.assets.css_compressor` and
-`config.assets.js_compressor` will control compression of CSS and JavaScript
-assets.
### Serving GZipped version of assets
@@ -1291,7 +1286,7 @@ config.assets.js_compressor = :uglifier
# Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = false
-# Generate digests for assets URLs. This is planned for deprecation.
+# Generate digests for assets URLs.
config.assets.digest = true
# Precompile additional assets (application.js, application.css, and all
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 745f09f523..6c734c1d78 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -100,8 +100,8 @@ called key-based expiration.
Cache fragments will also be expired when the view fragment changes (e.g., the
HTML in the view changes). The string of characters at the end of the key is a
-template tree digest. It is an md5 hash computed based on the contents of the
-view fragment you are caching. If you change the view fragment, the md5 hash
+template tree digest. It is an MD5 hash computed based on the contents of the
+view fragment you are caching. If you change the view fragment, the MD5 hash
will change, expiring the existing file.
TIP: Cache stores like Memcached will automatically delete old cache files.
@@ -258,7 +258,7 @@ comment format anywhere in the template, like:
If you use a helper method, for example, inside a cached block and you then update
that helper, you'll have to bump the cache as well. It doesn't really matter how
-you do it, but the md5 of the template file must change. One recommendation is to
+you do it, but the MD5 of the template file must change. One recommendation is to
simply be explicit in a comment, like:
```html+erb
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 2afb9356ec..66aae112d8 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -60,11 +60,11 @@ These configuration methods are to be called on a `Rails::Railtie` object, such
* `config.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets, or when you want to work around the concurrency constraints built-in in browsers using different domain aliases. Shorter version of `config.action_controller.asset_host`.
-* `config.autoload_once_paths` accepts an array of paths from which Rails will autoload constants that won't be wiped per request. Relevant if `config.cache_classes` is false, which is the case in development mode by default. Otherwise, all autoloading happens only once. All elements of this array must also be in `autoload_paths`. Default is an empty array.
+* `config.autoload_once_paths` accepts an array of paths from which Rails will autoload constants that won't be wiped per request. Relevant if `config.cache_classes` is `false`, which is the case in development mode by default. Otherwise, all autoloading happens only once. All elements of this array must also be in `autoload_paths`. Default is an empty array.
* `config.autoload_paths` accepts an array of paths from which Rails will autoload constants. Default is all directories under `app`.
-* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to false in development mode, and true in test and production modes.
+* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to `false` in development mode, and `true` in test and production modes.
* `config.action_view.cache_template_loading` controls whether or not templates should be reloaded on each request. Defaults to whatever is set for `config.cache_classes`.
@@ -73,9 +73,9 @@ 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.
+* `config.colorize_logging` specifies whether or not to use ANSI color codes when logging information. Defaults to `true`.
-* `config.consider_all_requests_local` is a flag. If true then any error will cause detailed debugging information to be dumped in the HTTP response, and the `Rails::Info` controller will show the application runtime context in `/rails/info/properties`. True by default in development and test environments, and false in production mode. For finer-grained control, set this to false and implement `local_request?` in controllers to specify which requests should provide debugging information on errors.
+* `config.consider_all_requests_local` is a flag. If `true` then any error will cause detailed debugging information to be dumped in the HTTP response, and the `Rails::Info` controller will show the application runtime context in `/rails/info/properties`. `true` by default in development and test environments, and `false` in production mode. For finer-grained control, set this to `false` and implement `local_request?` in controllers to specify which requests should provide debugging information on errors.
* `config.console` allows you to set class that will be used as console you run `rails console`. It's best to run it in `console` block:
@@ -88,9 +88,9 @@ application. Accepts a valid week day symbol (e.g. `:monday`).
end
```
-* `config.eager_load` when true, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks and any other registered namespace.
+* `config.eager_load` when `true`, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks and any other registered namespace.
-* `config.eager_load_namespaces` registers namespaces that are eager loaded when `config.eager_load` is true. All namespaces in the list must respond to the `eager_load!` method.
+* `config.eager_load_namespaces` registers namespaces that are eager loaded when `config.eager_load` is `true`. All namespaces in the list must respond to the `eager_load!` method.
* `config.eager_load_paths` accepts an array of paths from which Rails will eager load on boot if cache classes is enabled. Defaults to every folder in the `app` directory of the application.
@@ -100,7 +100,7 @@ application. Accepts a valid week day symbol (e.g. `:monday`).
* `config.debug_exception_response_format` sets the format used in responses when errors occur in development mode. Defaults to `:api` for API only apps and `:default` for normal apps.
-* `config.file_watcher` is the class used to detect file updates in the file system when `config.reload_classes_only_on_change` is true. Rails ships with `ActiveSupport::FileUpdateChecker`, the default, and `ActiveSupport::EventedFileUpdateChecker` (this one depends on the [listen](https://github.com/guard/listen) gem). Custom classes must conform to the `ActiveSupport::FileUpdateChecker` API.
+* `config.file_watcher` is the class used to detect file updates in the file system when `config.reload_classes_only_on_change` is `true`. Rails ships with `ActiveSupport::FileUpdateChecker`, the default, and `ActiveSupport::EventedFileUpdateChecker` (this one depends on the [listen](https://github.com/guard/listen) gem). Custom classes must conform to the `ActiveSupport::FileUpdateChecker` API.
* `config.filter_parameters` used for filtering out the parameters that
you don't want shown in the logs, such as passwords or credit card
@@ -134,11 +134,11 @@ defaults to `:debug` for all environments. The available log levels are: `:debug
* `config.middleware` allows you to configure the application's middleware. This is covered in depth in the [Configuring Middleware](#configuring-middleware) section below.
-* `config.reload_classes_only_on_change` enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to true. If `config.cache_classes` is true, this option is ignored.
+* `config.reload_classes_only_on_change` enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to `true`. If `config.cache_classes` is `true`, this option is ignored.
* `secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`.
-* `config.public_file_server.enabled` configures Rails to serve static files from the public directory. This option defaults to true, but in the production environment it is set to false because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to true. Otherwise, you won't be able to use page caching and request for files that exist under the public directory.
+* `config.public_file_server.enabled` configures Rails to serve static files from the public directory. This option defaults to `true`, but in the production environment it is set to `false` because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to `true.` Otherwise, you won't be able to use page caching and request for files that exist under the public directory.
* `config.session_store` is usually set up in `config/initializers/session_store.rb` and specifies what class to use to store the session. Possible values are `:cookie_store` which is the default, `:mem_cache_store`, and `:disabled`. The last one tells Rails not to deal with sessions. Custom session stores can also be specified:
@@ -153,12 +153,10 @@ defaults to `:debug` for all environments. The available log levels are: `:debug
### Configuring Assets
* `config.assets.enabled` a flag that controls whether the asset
-pipeline is enabled. It is set to true by default.
+pipeline is enabled. It is set to `true` by default.
* `config.assets.raise_runtime_errors` Set this flag to `true` to enable additional runtime error checking. Recommended in `config/environments/development.rb` to minimize unexpected behavior when deploying to `production`.
-* `config.assets.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/environments/production.rb`.
-
* `config.assets.css_compressor` defines the CSS compressor to use. It is set by default by `sass-rails`. The unique alternative value at the moment is `:yui`, which uses the `yui-compressor` gem.
* `config.assets.js_compressor` defines the JavaScript compressor to use. Possible values are `:closure`, `:uglifier` and `:yui` which require the use of the `closure-compiler`, `uglifier` or `yui-compressor` gems respectively.
@@ -179,7 +177,7 @@ pipeline is enabled. It is set to true by default.
* `config.assets.compile` is a boolean that can be used to turn on live Sprockets compilation in production.
-* `config.assets.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to the same configured at `config.logger`. Setting `config.assets.logger` to false will turn off served assets logging.
+* `config.assets.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to the same configured at `config.logger`. Setting `config.assets.logger` to `false` will turn off served assets logging.
### Configuring Generators
@@ -292,17 +290,17 @@ All these configuration options are delegated to the `I18n` library.
* `config.active_record.schema_migrations_table_name` lets you set a string to be used as the name of the schema migrations table.
-* `config.active_record.pluralize_table_names` specifies whether Rails will look for singular or plural table names in the database. If set to true (the default), then the Customer class will use the `customers` table. If set to false, then the Customer class will use the `customer` table.
+* `config.active_record.pluralize_table_names` specifies whether Rails will look for singular or plural table names in the database. If set to `true` (the default), then the Customer class will use the `customers` table. If set to false, then the Customer class will use the `customer` table.
* `config.active_record.default_timezone` determines whether to use `Time.local` (if set to `:local`) or `Time.utc` (if set to `:utc`) when pulling dates and times from the database. The default is `:utc`.
* `config.active_record.schema_format` controls the format for dumping the database schema to a file. The options are `:ruby` (the default) for a database-independent version that depends on migrations, or `:sql` for a set of (potentially database-dependent) SQL statements.
-* `config.active_record.error_on_ignored_order_or_limit` specifies if an error should be raised if the order or limit of a query is ignored during a batch query. The options are true (raise error) or false (warn). Default is false.
+* `config.active_record.error_on_ignored_order_or_limit` specifies if an error should be raised if the order or limit of a query is ignored during a batch query. The options are `true` (raise error) or `false` (warn). Default is `false`.
-* `config.active_record.timestamped_migrations` controls whether migrations are numbered with serial integers or with timestamps. The default is true, to use timestamps, which are preferred if there are multiple developers working on the same application.
+* `config.active_record.timestamped_migrations` controls whether migrations are numbered with serial integers or with timestamps. The default is `true`, to use timestamps, which are preferred if there are multiple developers working on the same application.
-* `config.active_record.lock_optimistically` controls whether Active Record will use optimistic locking and is true by default.
+* `config.active_record.lock_optimistically` controls whether Active Record will use optimistic locking and is `true` by default.
* `config.active_record.cache_timestamp_format` controls the format of the timestamp value in the cache key. Default is `:nsec`.
@@ -310,13 +308,13 @@ All these configuration options are delegated to the `I18n` library.
* `config.active_record.partial_writes` is a boolean value and controls whether or not partial writes are used (i.e. whether updates only set attributes that are dirty). Note that when using partial writes, you should also use optimistic locking `config.active_record.lock_optimistically` since concurrent updates may write attributes based on a possibly stale read state. The default value is `true`.
-* `config.active_record.maintain_test_schema` is a boolean value which controls whether Active Record should try to keep your test database schema up-to-date with `db/schema.rb` (or `db/structure.sql`) when you run your tests. The default is true.
+* `config.active_record.maintain_test_schema` is a boolean value which controls whether Active Record should try to keep your test database schema up-to-date with `db/schema.rb` (or `db/structure.sql`) when you run your tests. The default is `true`.
* `config.active_record.dump_schema_after_migration` is a flag which
controls whether or not schema dump should happen (`db/schema.rb` or
- `db/structure.sql`) when you run migrations. This is set to false in
+ `db/structure.sql`) when you run migrations. This is set to `false` in
`config/environments/production.rb` which is generated by Rails. The
- default value is true if this configuration is not set.
+ default value is `true` if this configuration is not set.
* `config.active_record.dump_schemas` controls which database schemas will be dumped when calling db:structure:dump.
The options are `:schema_search_path` (the default) which dumps any schemas listed in schema_search_path,
@@ -334,11 +332,11 @@ All these configuration options are delegated to the `I18n` library.
* `config.active_record.index_nested_attribute_errors` allows errors for nested
has_many relationships to be displayed with an index as well as the error.
- Defaults to false.
+ Defaults to `false`.
The MySQL adapter adds one additional configuration option:
-* `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns as booleans. True by default.
+* `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns as booleans. Defaults to `true`.
The schema dumper adds one additional configuration option:
@@ -350,7 +348,7 @@ The schema dumper adds one additional configuration option:
* `config.action_controller.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets rather than the application server itself.
-* `config.action_controller.perform_caching` configures whether the application should perform the caching features provided by the Action Controller component or not. Set to false in development mode, true in production.
+* `config.action_controller.perform_caching` configures whether the application should perform the caching features provided by the Action Controller component or not. Set to `false` in development mode, `true` in production.
* `config.action_controller.default_static_extension` configures the extension used for cached pages. Defaults to `.html`.
@@ -392,6 +390,10 @@ 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.ignore_accept_header` is used to determine whether to ignore accept headers from a request. Defaults to `false`.
+
+* `config.action_dispatch.x_sendfile_header` specifies server specific X-Sendfile header. This is useful for accelerated file sending from server. For example it can be set to 'X-Sendfile' for Apache.
+
* `config.action_dispatch.http_auth_salt` sets the HTTP Auth salt value. Defaults
to `'http authentication'`.
@@ -406,7 +408,7 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
* `config.action_dispatch.perform_deep_munge` configures whether `deep_munge`
method should be performed on the parameters. See [Security Guide](security.html#unsafe-query-generation)
- for more information. It defaults to true.
+ for more information. It defaults to `true`.
* `config.action_dispatch.rescue_responses` configures what exceptions are assigned to an HTTP status. It accepts a hash and you can specify pairs of exception/status. By default, this is defined as:
@@ -461,7 +463,7 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
* `config.action_view.embed_authenticity_token_in_remote_forms` allows you to
set the default behavior for `authenticity_token` in forms with `remote:
- true`. By default it's set to false, which means that remote forms will not
+ true`. By default it's set to `false`, which means that remote forms will not
include `authenticity_token`, which is helpful when you're fragment-caching
the form. Remote forms get the authenticity from the `meta` tag, so embedding
is unnecessary unless you support browsers without JavaScript. In such case
@@ -480,9 +482,9 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
error should be raised for missing translations.
* `config.action_view.automatically_disable_submit_tag` determines whether
- submit_tag should automatically disable on click, this defaults to true.
+ submit_tag should automatically disable on click, this defaults to `true`.
-* `config.action_view.debug_missing_translation` determines whether to wrap the missing translations key in a `<span>` tag or not. This defaults to true.
+* `config.action_view.debug_missing_translation` determines whether to wrap the missing translations key in a `<span>` tag or not. This defaults to `true`.
### Configuring Action Mailer
@@ -497,16 +499,19 @@ There are a number of settings available on `config.action_mailer`:
* `:user_name` - If your mail server requires authentication, set the username in this setting.
* `:password` - If your mail server requires authentication, set the password in this setting.
* `:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`.
+ * `:enable_starttls_auto` - Detects if STARTTLS is enabled in your SMTP server and starts to use it. It defaults to `true`.
+ * `:openssl_verify_mode` - When using TLS, you can set how OpenSSL checks the certificate. This is useful if you need to validate a self-signed and/or a wildcard certificate. This can be one of the OpenSSL verify constants, `:none`, `:peer`, `:client_once`, `:fail_if_no_peer_cert`, or the constant directly `OpenSSL::SSL::VERIFY_NONE`.
+ * `:ssl/:tls` - Enables the SMTP connection to use SMTP/TLS (SMTPS: SMTP over direct TLS connection).
* `config.action_mailer.sendmail_settings` allows detailed configuration for the `sendmail` delivery method. It accepts a hash of options, which can include any of these options:
* `:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.
* `:arguments` - The command line arguments. Defaults to `-i -t`.
-* `config.action_mailer.raise_delivery_errors` specifies whether to raise an error if email delivery cannot be completed. It defaults to true.
+* `config.action_mailer.raise_delivery_errors` specifies whether to raise an error if email delivery cannot be completed. It defaults to `true`.
* `config.action_mailer.delivery_method` defines the delivery method and defaults to `:smtp`. See the [configuration section in the Action Mailer guide](action_mailer_basics.html#action-mailer-configuration) for more info.
-* `config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default. It can be convenient to set it to false for testing.
+* `config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default. It can be convenient to set it to `false` for testing.
* `config.action_mailer.default_options` configures Action Mailer defaults. Use to set options like `from` or `reply_to` for every mailer. These default to:
@@ -552,7 +557,7 @@ There are a number of settings available on `config.action_mailer`:
* `config.action_mailer.deliver_later_queue_name` specifies the queue name for
mailers. By default this is `mailers`.
-* `config.action_mailer.perform_caching` specifies whether the mailer templates should perform fragment caching or not. By default this is false in all environments.
+* `config.action_mailer.perform_caching` specifies whether the mailer templates should perform fragment caching or not. By default this is `false` in all environments.
### Configuring Active Support
@@ -569,7 +574,7 @@ There are a few configuration options available in Active Support:
* `config.active_support.time_precision` sets the precision of JSON encoded time values. Defaults to `3`.
-* `ActiveSupport.halt_callback_chains_on_return_false` specifies whether Active Record and Active Model callback chains can be halted by returning `false` in a 'before' callback. When set to `false`, callback chains are halted only when explicitly done so with `throw(:abort)`. When set to `true`, callback chains are halted when a callback returns false (the previous behavior before Rails 5) and a deprecation warning is given. Defaults to `true` during the deprecation period. New Rails 5 apps generate an initializer file called `callback_terminator.rb` which sets the value to `false`. This file is *not* added when running `rails app:update`, so returning `false` will still work on older apps ported to Rails 5 and display a deprecation warning to prompt users to update their code.
+* `ActiveSupport.halt_callback_chains_on_return_false` specifies whether Active Record and Active Model callback chains can be halted by returning `false` in a 'before' callback. When set to `false`, callback chains are halted only when explicitly done so with `throw(:abort)`. When set to `true`, callback chains are halted when a callback returns `false` (the previous behavior before Rails 5) and a deprecation warning is given. Defaults to `true` during the deprecation period. New Rails 5 apps generate an initializer file called `callback_terminator.rb` which sets the value to `false`. This file is *not* added when running `rails app:update`, so returning `false` will still work on older apps ported to Rails 5 and display a deprecation warning to prompt users to update their code.
* `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`.
@@ -1063,7 +1068,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde
* `action_controller.compile_config_methods`: Initializes methods for the config settings specified so that they are quicker to access.
-* `active_record.initialize_timezone`: Sets `ActiveRecord::Base.time_zone_aware_attributes` to true, as well as setting `ActiveRecord::Base.default_timezone` to UTC. When attributes are read from the database, they will be converted into the time zone specified by `Time.zone`.
+* `active_record.initialize_timezone`: Sets `ActiveRecord::Base.time_zone_aware_attributes` to `true`, as well as setting `ActiveRecord::Base.default_timezone` to UTC. When attributes are read from the database, they will be converted into the time zone specified by `Time.zone`.
* `active_record.logger`: Sets `ActiveRecord::Base.logger` - if it's not already set - to `Rails.logger`.
@@ -1122,13 +1127,13 @@ Below is a comprehensive list of all the initializers found in Rails in the orde
* `build_middleware_stack`: Builds the middleware stack for the application, returning an object which has a `call` method which takes a Rack environment object for the request.
-* `eager_load!`: If `config.eager_load` is true, runs the `config.before_eager_load` hooks and then calls `eager_load!` which will load all `config.eager_load_namespaces`.
+* `eager_load!`: If `config.eager_load` is `true`, runs the `config.before_eager_load` hooks and then calls `eager_load!` which will load all `config.eager_load_namespaces`.
* `finisher_hook`: Provides a hook for after the initialization of process of the application is complete, as well as running all the `config.after_initialize` blocks for the application, railties and engines.
* `set_routes_reloader`: Configures Action Dispatch to reload the routes file using `ActionDispatch::Callbacks.to_prepare`.
-* `disable_dependency_loading`: Disables the automatic dependency loading if the `config.eager_load` is set to true.
+* `disable_dependency_loading`: Disables the automatic dependency loading if the `config.eager_load` is set to `true`.
Database pooling
----------------
@@ -1233,7 +1238,7 @@ Evented File System Monitor
If the [listen gem](https://github.com/guard/listen) is loaded Rails uses an
evented file system monitor to detect changes when `config.cache_classes` is
-false:
+`false`:
```ruby
group :development do
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 03943d0f25..a5b8a75509 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -139,6 +139,10 @@
name: Using Rails for API-only Applications
url: api_app.html
description: This guide explains how to effectively use Rails to develop a JSON API application.
+ -
+ name: Action Cable Overview
+ url: action_cable_overview.html
+ description: This guide explains how Action Cable works, and how to use WebSockets to create real-time features.
-
name: Extending Rails
diff --git a/guides/source/security.md b/guides/source/security.md
index 16c5291037..c6bc1f3878 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -41,24 +41,24 @@ NOTE: _HTTP is a stateless protocol. Sessions make it stateful._
Most applications need to keep track of certain state of a particular user. This could be the contents of a shopping basket or the user id of the currently logged in user. Without the idea of sessions, the user would have to identify, and probably authenticate, on every request.
Rails will create a new session automatically if a new user accesses the application. It will load an existing session if the user has already used the application.
-A session usually consists of a hash of values and a session id, usually a 32-character string, to identify the hash. Every cookie sent to the client's browser includes the session id. And the other way round: the browser will send it to the server on every request from the client. In Rails you can save and retrieve values using the session method:
+A session usually consists of a hash of values and a session ID, usually a 32-character string, to identify the hash. Every cookie sent to the client's browser includes the session ID. And the other way round: the browser will send it to the server on every request from the client. In Rails you can save and retrieve values using the session method:
```ruby
session[:user_id] = @current_user.id
User.find(session[:user_id])
```
-### Session id
+### Session ID
-NOTE: _The session id is a 32 byte long MD5 hash value._
+NOTE: _The session ID is a 32-character random hex string._
-A session id consists of the hash value of a random string. The random string is the current time, a random number between 0 and 1, the process id number of the Ruby interpreter (also basically a random number) and a constant string. Currently it is not feasible to brute-force Rails' session ids. To date MD5 is uncompromised, but there have been collisions, so it is theoretically possible to create another input text with the same hash value. But this has had no security impact to date.
+The session ID is generated using `SecureRandom.hex` which generates a random hex string using platform specific methods (such as OpenSSL, /dev/urandom or Win32) for generating cryptographically secure random numbers. Currently it is not feasible to brute-force Rails' session IDs.
### Session Hijacking
-WARNING: _Stealing a user's session id lets an attacker use the web application in the victim's name._
+WARNING: _Stealing a user's session ID lets an attacker use the web application in the victim's name._
-Many web applications have an authentication system: a user provides a user name and password, the web application checks them and stores the corresponding user id in the session hash. From now on, the session is valid. On every request the application will load the user, identified by the user id in the session, without the need for new authentication. The session id in the cookie identifies the session.
+Many web applications have an authentication system: a user provides a user name and password, the web application checks them and stores the corresponding user id in the session hash. From now on, the session is valid. On every request the application will load the user, identified by the user id in the session, without the need for new authentication. The session ID in the cookie identifies the session.
Hence, the cookie serves as temporary authentication for the web application. Anyone who seizes a cookie from someone else, may use the web application as this user - with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures:
@@ -89,7 +89,7 @@ This will also be a good idea, if you modify the structure of an object and old
NOTE: _Rails provides several storage mechanisms for the session hashes. The most important is `ActionDispatch::Session::CookieStore`._
-Rails 2 introduced a new default session storage, CookieStore. CookieStore saves the session hash directly in a cookie on the client-side. The server retrieves the session hash from the cookie and eliminates the need for a session id. That will greatly increase the speed of the application, but it is a controversial storage option and you have to think about the security implications of it:
+Rails 2 introduced a new default session storage, CookieStore. CookieStore saves the session hash directly in a cookie on the client-side. The server retrieves the session hash from the cookie and eliminates the need for a session ID. That will greatly increase the speed of the application, but it is a controversial storage option and you have to think about the security implications of it:
* Cookies imply a strict size limit of 4kB. This is fine as you should not store large amounts of data in a session anyway, as described before. _Storing the current user's database id in a session is usually ok_.
@@ -137,16 +137,16 @@ 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 them. 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:
+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: 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).
+* 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 maintain the session by accessing the web application periodically in order to keep an expiring session alive.
-* The attacker forces 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.
+* The attacker forces 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.
* From now on, the victim and the attacker will co-use the web application with the same session: The session became valid and the victim didn't notice the attack.
@@ -168,7 +168,7 @@ Another countermeasure is to _save user-specific properties in the session_, ver
NOTE: _Sessions that never expire extend the time-frame for attacks such as cross-site request forgery (CSRF), session hijacking and session fixation._
-One possibility is to set the expiry time-stamp of the cookie with the session id. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to _expire sessions in a database table_. Call `Session.sweep("20 minutes")` to expire sessions that were used longer than 20 minutes ago.
+One possibility is to set the expiry time-stamp of the cookie with the session ID. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to _expire sessions in a database table_. Call `Session.sweep("20 minutes")` to expire sessions that were used longer than 20 minutes ago.
```ruby
class Session < ApplicationRecord
@@ -196,11 +196,11 @@ This attack method works by including malicious code or a link in a page that ac
![](images/csrf.png)
-In the [session chapter](#sessions) you have learned that most Rails applications use cookie-based sessions. Either they store the session id in the cookie and have a server-side session hash, or the entire session hash is on the client-side. In either case the browser will automatically send along the cookie on every request to a domain, if it can find a cookie for that domain. The controversial point is that if the request comes from a site of a different domain, it will also send the cookie. Let's start with an example:
+In the [session chapter](#sessions) you have learned that most Rails applications use cookie-based sessions. Either they store the session ID in the cookie and have a server-side session hash, or the entire session hash is on the client-side. In either case the browser will automatically send along the cookie on every request to a domain, if it can find a cookie for that domain. The controversial point is that if the request comes from a site of a different domain, it will also send the cookie. Let's start with an example:
* Bob browses a message board and views a post from a hacker where there is a crafted HTML image element. The element references a command in Bob's project management application, rather than an image file: `<img src="http://www.webapp.com/project/1/destroy">`
* Bob's session at `www.webapp.com` is still alive, because he didn't log out a few minutes ago.
-* By viewing the post, the browser finds an image tag. It tries to load the suspected image from `www.webapp.com`. As explained before, it will also send along the cookie with the valid session id.
+* By viewing the post, the browser finds an image tag. It tries to load the suspected image from `www.webapp.com`. As explained before, it will also send along the cookie with the valid session ID.
* The web application at `www.webapp.com` verifies the user information in the corresponding session hash and destroys the project with the ID 1. It then returns a result page which is an unexpected result for the browser, so it will not display the image.
* Bob doesn't notice the attack - but a few days later he finds out that project number one is gone.
@@ -677,14 +677,12 @@ INFO: _The most widespread, and one of the most devastating security vulnerabili
An entry point is a vulnerable URL and its parameters where an attacker can start an attack.
-The most common entry points are message posts, user comments, and guest books, but project titles, document names and search result pages have also been vulnerable - just about everywhere where the user can input data. But the input does not necessarily have to come from input boxes on web sites, it can be in any URL parameter - obvious, hidden or internal. Remember that the user may intercept any traffic. Applications, such as the [Live HTTP Headers Firefox plugin](http://livehttpheaders.mozdev.org/), or client-site proxies make it easy to change requests.
+The most common entry points are message posts, user comments, and guest books, but project titles, document names and search result pages have also been vulnerable - just about everywhere where the user can input data. But the input does not necessarily have to come from input boxes on web sites, it can be in any URL parameter - obvious, hidden or internal. Remember that the user may intercept any traffic. Applications or client-site proxies make it easy to change requests. There are also other attack vectors like banner advertisements.
XSS attacks work like this: An attacker injects some code, the web application saves it and displays it on a page, later presented to a victim. Most XSS examples simply display an alert box, but it is more powerful than that. XSS can steal the cookie, hijack the session, redirect the victim to a fake website, display advertisements for the benefit of the attacker, change elements on the web site to get confidential information or install malicious software through security holes in the web browser.
During the second half of 2007, there were 88 vulnerabilities reported in Mozilla browsers, 22 in Safari, 18 in IE, and 12 in Opera. 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) also documented 239 browser plug-in vulnerabilities in the last six months of 2007. [Mpack](http://pandalabs.pandasecurity.com/mpack-uncovered/) is a very active and up-to-date attack framework which exploits these vulnerabilities. For criminal hackers, it is very attractive to exploit an SQL-Injection vulnerability in a web application framework and insert malicious code in every textual table column. In April 2008 more than 510,000 sites were hacked like this, among them the British government, United Nations, and many more high targets.
-A relatively new, and unusual, form of entry points are banner advertisements. In earlier 2008, malicious code appeared in banner ads on popular sites, such as MySpace and Excite, according to [Trend Micro](http://blog.trendmicro.com/myspace-excite-and-blick-serve-up-malicious-banner-ads/).
-
#### HTML/JavaScript Injection
The most common XSS language is of course the most popular client-side scripting language JavaScript, often in combination with HTML. _Escaping user input is essential_.
@@ -722,7 +720,7 @@ The log files on www.attacker.com will read like this:
GET http://www.attacker.com/_app_session=836c1c25278e5b321d6bea4f19cb57e2
```
-You can mitigate these attacks (in the obvious way) by adding the **httpOnly** flag to cookies, so that document.cookie may not be read by JavaScript. Http only cookies can be used from IE v6.SP1, Firefox v2.0.0.5 and Opera 9.5. Safari is still considering, it ignores the option. But other, older browsers (such as WebTV and IE 5.5 on Mac) can actually cause the page to fail to load. Be warned that cookies [will still be visible using Ajax](https://www.owasp.org/index.php/HTTPOnly#Browsers_Supporting_HttpOnly), though.
+You can mitigate these attacks (in the obvious way) by adding the **httpOnly** flag to cookies, so that document.cookie may not be read by JavaScript. HTTP only cookies can be used from IE v6.SP1, Firefox v2.0.0.5, Opera 9.5, Safari 4 and Chrome 1.0.154 onwards. But other, older browsers (such as WebTV and IE 5.5 on Mac) can actually cause the page to fail to load. Be warned that cookies [will still be visible using Ajax](https://www.owasp.org/index.php/HTTPOnly#Browsers_Supporting_HttpOnly), though.
##### Defacement
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 34c831c802..050bdda9e3 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -1289,5 +1289,5 @@ end
assert_equal Date.new(2004, 10, 24), user.activation_date # The change was visible only inside the `travel_to` block.
```
-Please see [`ActiveSupport::TimeHelpers` API Documentation](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html)
+Please see [`ActiveSupport::Testing::TimeHelpers` API Documentation](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html)
for in-depth information about the available time helpers.
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 59eddb6302..82080c4def 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -881,7 +881,7 @@ Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must rep
* Rails 4.0 has removed the identity map from Active Record, due to [some inconsistencies with associations](https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6). If you have manually enabled it in your application, you will have to remove the following config that has no effect anymore: `config.active_record.identity_map`.
-* The `delete` method in collection associations can now receive `Fixnum` or `String` arguments as record ids, besides records, pretty much like the `destroy` method does. Previously it raised `ActiveRecord::AssociationTypeMismatch` for such arguments. From Rails 4.0 on `delete` automatically tries to find the records matching the given ids before deleting them.
+* The `delete` method in collection associations can now receive `Integer` or `String` arguments as record ids, besides records, pretty much like the `destroy` method does. Previously it raised `ActiveRecord::AssociationTypeMismatch` for such arguments. From Rails 4.0 on `delete` automatically tries to find the records matching the given ids before deleting them.
* In Rails 4.0 when a column or a table is renamed the related indexes are also renamed. If you have migrations which rename the indexes, they are no longer needed.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/cable.yml b/railties/lib/rails/generators/rails/app/templates/config/cable.yml
index aa4e832748..0bbde6f74f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/cable.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/cable.yml
@@ -1,10 +1,9 @@
-# Action Cable uses Redis by default to administer connections, channels, and sending/receiving messages over the WebSocket.
-production:
- adapter: redis
- url: redis://localhost:6379/1
-
development:
adapter: async
test:
adapter: async
+
+production:
+ adapter: redis
+ url: redis://localhost:6379/1
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index e32eea42b7..9e8531b482 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -307,7 +307,7 @@ module ApplicationTests
assert_not_equal asset_path, assets["assets"]["application.css"]
end
- test "precompile appends the md5 hash to files referenced with asset_path and run in production with digest true" do
+ test "precompile appends the MD5 hash to files referenced with asset_path and run in production with digest true" do
app_file "app/assets/images/rails.png", "notactuallyapng"
app_file "app/assets/stylesheets/application.css.erb", "p { background-image: url(<%= asset_path('rails.png') %>) }"
diff --git a/tasks/release.rb b/tasks/release.rb
index 61b44a4c56..e54b03eafa 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -44,6 +44,36 @@ directory "pkg"
raise "Could not insert PRE in #{file}" unless $1
File.open(file, 'w') { |f| f.write ruby }
+
+ if File.exist?("#{framework}/package.json")
+ Dir.chdir("#{framework}") do
+ # This "npm-ifies" the current version
+ # With npm, versions such as "5.0.0.rc1" or "5.0.0.beta1.1" are not compliant with its
+ # versioning system, so they must be transformed to "5.0.0-rc1" and "5.0.0-beta1-1" respectively.
+
+ # In essence, the code below runs through all "."s that appear in the version,
+ # and checks to see if their index in the version string is greater than or equal to 2,
+ # and if so, it will change the "." to a "-".
+
+ # Sample version transformations:
+ # irb(main):001:0> version = "5.0.1.1"
+ # => "5.0.1.1"
+ # irb(main):002:0> version.gsub(/\./).with_index { |s, i| i >= 2 ? '-' : s }
+ # => "5.0.1-1"
+ # irb(main):003:0> version = "5.0.0.rc1"
+ # => "5.0.0.rc1"
+ # irb(main):004:0> version.gsub(/\./).with_index { |s, i| i >= 2 ? '-' : s }
+ # => "5.0.0-rc1"
+ version = version.gsub(/\./).with_index { |s, i| i >= 2 ? '-' : s }
+
+ # Check if npm is installed, and raise an error if not
+ if sh 'which npm'
+ sh "npm version #{version} --no-git-tag-version"
+ else
+ raise 'You must have npm installed to release Rails.'
+ end
+ end
+ end
end
task gem => %w(update_versions pkg) do
@@ -61,6 +91,7 @@ directory "pkg"
task :push => :build do
sh "gem push #{gem}"
+ sh "npm publish" if File.exist?("#{framework}/package.json")
end
end
end