aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml7
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--actioncable/CHANGELOG.md35
-rw-r--r--actioncable/app/assets/javascripts/action_cable/connection.coffee41
-rw-r--r--actioncable/app/assets/javascripts/action_cable/consumer.coffee19
-rw-r--r--actioncable/app/assets/javascripts/action_cable/subscription.coffee6
-rw-r--r--actioncable/lib/action_cable.rb3
-rw-r--r--actioncable/lib/action_cable/channel/base.rb23
-rw-r--r--actioncable/lib/action_cable/channel/streams.rb32
-rw-r--r--actioncable/lib/action_cable/connection/base.rb41
-rw-r--r--actioncable/lib/action_cable/connection/client_socket.rb8
-rw-r--r--actioncable/lib/action_cable/connection/faye_client_socket.rb9
-rw-r--r--actioncable/lib/action_cable/connection/internal_channel.rb4
-rw-r--r--actioncable/lib/action_cable/connection/message_buffer.rb4
-rw-r--r--actioncable/lib/action_cable/connection/subscriptions.rb25
-rw-r--r--actioncable/lib/action_cable/connection/web_socket.rb8
-rw-r--r--actioncable/lib/action_cable/server/base.rb12
-rw-r--r--actioncable/lib/action_cable/server/broadcasting.rb19
-rw-r--r--actioncable/lib/action_cable/server/configuration.rb2
-rw-r--r--actioncable/lib/rails/generators/channel/channel_generator.rb1
-rw-r--r--actioncable/lib/rails/generators/channel/templates/assets/cable.js13
-rw-r--r--actioncable/test/channel/base_test.rb79
-rw-r--r--actioncable/test/channel/rejection_test.rb2
-rw-r--r--actioncable/test/channel/stream_test.rb160
-rw-r--r--actioncable/test/client_test.rb36
-rw-r--r--actioncable/test/connection/identifier_test.rb6
-rw-r--r--actioncable/test/connection/subscriptions_test.rb2
-rw-r--r--actioncable/test/stubs/test_connection.rb23
-rw-r--r--actioncable/test/stubs/test_server.rb12
-rw-r--r--actioncable/test/subscription_adapter/common.rb3
-rw-r--r--actioncable/test/subscription_adapter/evented_redis_test.rb11
-rw-r--r--actioncable/test/subscription_adapter/postgresql_test.rb1
-rw-r--r--actioncable/test/test_helper.rb8
-rw-r--r--actionmailer/CHANGELOG.md33
-rw-r--r--actionmailer/lib/action_mailer/base.rb2
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb2
-rw-r--r--actionmailer/lib/action_mailer/message_delivery.rb16
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb2
-rw-r--r--actionmailer/test/delivery_methods_test.rb2
-rw-r--r--actionmailer/test/i18n_with_controller_test.rb4
-rw-r--r--actionmailer/test/message_delivery_test.rb8
-rw-r--r--actionmailer/test/url_test.rb14
-rw-r--r--actionpack/CHANGELOG.md40
-rw-r--r--actionpack/lib/action_controller/api.rb39
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb59
-rw-r--r--actionpack/lib/action_controller/metal/cookies.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb7
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb7
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb64
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb1
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb2
-rw-r--r--actionpack/test/controller/api/with_cookies_test.rb21
-rw-r--r--actionpack/test/controller/live_stream_test.rb2
-rw-r--r--actionpack/test/controller/render_test.rb58
-rw-r--r--actionpack/test/dispatch/request_test.rb35
-rw-r--r--actionpack/test/dispatch/response_test.rb35
-rw-r--r--actionview/CHANGELOG.md6
-rw-r--r--actionview/lib/action_view/flows.rb9
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb9
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb2
-rw-r--r--actionview/test/template/form_helper_test.rb32
-rw-r--r--actionview/test/template/form_tag_helper_test.rb4
-rw-r--r--activejob/lib/active_job/test_helper.rb23
-rw-r--r--activejob/lib/rails/generators/job/job_generator.rb15
-rw-r--r--activejob/lib/rails/generators/job/templates/application_job.rb4
-rw-r--r--activemodel/CHANGELOG.md5
-rw-r--r--activemodel/lib/active_model/dirty.rb10
-rw-r--r--activemodel/lib/active_model/errors.rb7
-rw-r--r--activemodel/lib/active_model/validations/length.rb2
-rw-r--r--activemodel/test/cases/validations_test.rb16
-rw-r--r--activerecord/CHANGELOG.md32
-rw-r--r--activerecord/lib/active_record/associations.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb1
-rw-r--r--activerecord/lib/active_record/base.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb71
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/quoting.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb44
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb42
-rw-r--r--activerecord/lib/active_record/core.rb9
-rw-r--r--activerecord/lib/active_record/fixture_set/file.rb8
-rw-r--r--activerecord/lib/active_record/migration.rb6
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb2
-rw-r--r--activerecord/lib/active_record/query_cache.rb32
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake6
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb16
-rw-r--r--activerecord/lib/active_record/relation/batches.rb2
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb4
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb6
-rw-r--r--activerecord/test/cases/adapter_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/boolean_test.rb14
-rw-r--r--activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb45
-rw-r--r--activerecord/test/cases/adapters/mysql2/quoting_test.rb21
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb14
-rw-r--r--activerecord/test/cases/date_time_precision_test.rb2
-rw-r--r--activerecord/test/cases/dirty_test.rb4
-rw-r--r--activerecord/test/cases/finder_test.rb7
-rw-r--r--activerecord/test/cases/fixture_set/file_test.rb6
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb20
-rw-r--r--activerecord/test/cases/statement_cache_test.rb12
-rw-r--r--activerecord/test/cases/time_precision_test.rb2
-rw-r--r--activerecord/test/models/owner.rb3
-rw-r--r--activerecord/test/models/pet.rb3
-rw-r--r--activerecord/test/models/pet_treasure.rb6
-rw-r--r--activerecord/test/schema/schema.rb14
-rw-r--r--activesupport/CHANGELOG.md35
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb2
-rw-r--r--activesupport/lib/active_support/callbacks.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/date_time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/zones.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/marshal.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb1
-rw-r--r--activesupport/lib/active_support/execution_wrapper.rb45
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb19
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb9
-rw-r--r--activesupport/lib/active_support/number_helper.rb9
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_phone_converter.rb13
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb8
-rw-r--r--activesupport/lib/active_support/reloader.rb8
-rw-r--r--activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/throws.rb2
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb18
-rw-r--r--activesupport/test/core_ext/marshal_test.rb21
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb13
-rw-r--r--activesupport/test/executor_test.rb107
-rw-r--r--activesupport/test/file_update_checker_shared_tests.rb23
-rw-r--r--activesupport/test/json/encoding_test.rb5
-rw-r--r--activesupport/test/number_helper_test.rb2
-rw-r--r--guides/CHANGELOG.md23
-rw-r--r--guides/source/2_2_release_notes.md2
-rw-r--r--guides/source/3_1_release_notes.md2
-rw-r--r--guides/source/5_0_release_notes.md16
-rw-r--r--guides/source/action_cable_overview.md8
-rw-r--r--guides/source/action_controller_overview.md2
-rw-r--r--guides/source/action_view_overview.md4
-rw-r--r--guides/source/active_model_basics.md2
-rw-r--r--guides/source/active_record_validations.md14
-rw-r--r--guides/source/association_basics.md4
-rw-r--r--guides/source/caching_with_rails.md32
-rw-r--r--guides/source/configuring.md4
-rw-r--r--guides/source/development_dependencies_install.md1
-rw-r--r--guides/source/form_helpers.md2
-rw-r--r--guides/source/getting_started.md3
-rw-r--r--guides/source/security.md2
-rw-r--r--guides/source/testing.md3
-rw-r--r--railties/CHANGELOG.md41
-rw-r--r--railties/lib/rails/application.rb5
-rw-r--r--railties/lib/rails/application/bootstrap.rb7
-rw-r--r--railties/lib/rails/application/configuration.rb10
-rw-r--r--railties/lib/rails/application/finisher.rb39
-rw-r--r--railties/lib/rails/commands/runner.rb7
-rw-r--r--railties/lib/rails/commands/server.rb7
-rw-r--r--railties/lib/rails/dev_caching.rb3
-rw-r--r--railties/lib/rails/generators/actions.rb2
-rw-r--r--railties/lib/rails/generators/actions/create_migration.rb1
-rw-r--r--railties/lib/rails/generators/app_base.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt4
-rw-r--r--railties/lib/rails/railtie.rb72
-rw-r--r--railties/lib/rails/tasks/framework.rake4
-rw-r--r--railties/lib/rails/tasks/restart.rake9
-rw-r--r--railties/lib/rails/tasks/tmp.rake10
-rw-r--r--railties/test/application/assets_test.rb3
-rw-r--r--railties/test/application/rake/dev_test.rb9
-rw-r--r--railties/test/application/rake/restart_test.rb9
-rw-r--r--railties/test/commands/server_test.rb14
-rw-r--r--railties/test/generators/app_generator_test.rb4
-rw-r--r--railties/test/generators/channel_generator_test.rb8
-rw-r--r--railties/test/generators/job_generator_test.rb7
-rw-r--r--railties/test/generators/plugin_generator_test.rb13
189 files changed, 1903 insertions, 718 deletions
diff --git a/.travis.yml b/.travis.yml
index bd1a320de5..ef85107515 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -29,6 +29,13 @@ rvm:
- 2.3.0
- ruby-head
matrix:
+ include:
+ # Latest compiled version in http://rubies.travis-ci.org
+ - rvm: jruby-9.0.5.0
+ jdk: oraclejdk8
+ env:
+ - "JRUBY_OPTS='--dev -J-Xmx1024M'"
+ - "GEM='ap'"
allow_failures:
- rvm: ruby-head
fast_finish: true
diff --git a/Gemfile b/Gemfile
index dd09ddae68..5205896846 100644
--- a/Gemfile
+++ b/Gemfile
@@ -3,7 +3,7 @@ source 'https://rubygems.org'
gemspec
# We need a newish Rake since Active Job sets its test tasks' descriptions.
-gem 'rake', '>= 10.3'
+gem 'rake', '>= 11.1'
# This needs to be with require false to ensure correct loading order, as it has to
# be loaded after loading the test library.
diff --git a/Gemfile.lock b/Gemfile.lock
index c76c4d7c2f..ce854e6183 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -197,7 +197,7 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
- rake (10.5.0)
+ rake (11.1.1)
rb-fsevent (0.9.7)
rb-inotify (0.9.5)
ffi (>= 0.5.0)
@@ -306,7 +306,7 @@ DEPENDENCIES
racc (>= 1.4.6)
rack-cache (~> 1.2)
rails!
- rake (>= 10.3)
+ rake (>= 11.1)
redcarpet (~> 3.2.3)
redis
resque (< 1.26)
diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md
index fb9e498e49..5162a31cf8 100644
--- a/actioncable/CHANGELOG.md
+++ b/actioncable/CHANGELOG.md
@@ -1,7 +1,36 @@
-* Allow channel identifiers with no backslahes/escaping to be accepted
- by the subscription storer.
+* WebSocket protocol negotiation.
- *Jon Moss*
+ Introduces an Action Cable protocol version that moves independently
+ of and, hopefully, more slowly than Action Cable itself. Client sockets
+ negotiate a protocol with the Cable server using WebSockets' native
+ subprotocol support:
+ * https://tools.ietf.org/html/rfc6455#section-1.9
+ * https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#Subprotocols
+
+ If they can't negotiate a compatible protocol (usually due to upgrading
+ the Cable server with a browser still running old JavaScript) then the
+ client knows to disconnect, cease retrying, and tell the app that it hit
+ a protocol mismatch.
+
+ This allows us to evolve the Action Cable message format, handshaking,
+ pings, acknowledgements, and more without breaking older clients'
+ expectations of server behavior.
+
+ *Daniel Rhodes*
+
+* Pubsub: automatic stream decoding.
+
+ stream_for @room, coder: ActiveSupport::JSON do |message|
+ # `message` is a Ruby hash here instead of a JSON string
+
+ The `coder` must respond to `#decode`. Defaults to `coder: nil`
+ which skips decoding entirely.
+
+ *Jeremy Daer*
+
+* Add ActiveSupport::Notifications to ActionCable::Channel.
+
+ *Matthew Wear*
* Safely support autoloading and class unloading, by preventing concurrent
loads, and disconnecting all cables during reload.
diff --git a/actioncable/app/assets/javascripts/action_cable/connection.coffee b/actioncable/app/assets/javascripts/action_cable/connection.coffee
index 3a139acf3a..d6a6397804 100644
--- a/actioncable/app/assets/javascripts/action_cable/connection.coffee
+++ b/actioncable/app/assets/javascripts/action_cable/connection.coffee
@@ -2,7 +2,8 @@
# Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation.
-{message_types} = ActionCable.INTERNAL
+{message_types, protocols} = ActionCable.INTERNAL
+[supportedProtocols..., unsupportedProtocol] = protocols
class ActionCable.Connection
@reopenDelay: 500
@@ -10,6 +11,7 @@ class ActionCable.Connection
constructor: (@consumer) ->
{@subscriptions} = @consumer
@monitor = new ActionCable.ConnectionMonitor this
+ @disconnected = true
send: (data) ->
if @isOpen()
@@ -23,15 +25,16 @@ class ActionCable.Connection
ActionCable.log("Attempted to open WebSocket, but existing socket is #{@getState()}")
throw new Error("Existing connection must be closed before opening")
else
- ActionCable.log("Opening WebSocket, current state is #{@getState()}")
+ ActionCable.log("Opening WebSocket, current state is #{@getState()}, subprotocols: #{protocols}")
@uninstallEventHandlers() if @webSocket?
- @webSocket = new WebSocket(@consumer.url)
+ @webSocket = new WebSocket(@consumer.url, protocols)
@installEventHandlers()
@monitor.start()
true
- close: ->
- @webSocket?.close()
+ close: ({allowReconnect} = {allowReconnect: true}) ->
+ @monitor.stop() unless allowReconnect
+ @webSocket?.close() if @isActive()
reopen: ->
ActionCable.log("Reopening WebSocket, current state is #{@getState()}")
@@ -46,6 +49,9 @@ class ActionCable.Connection
else
@open()
+ getProtocol: ->
+ @webSocket?.protocol
+
isOpen: ->
@isState("open")
@@ -54,6 +60,9 @@ class ActionCable.Connection
# Private
+ isProtocolSupported: ->
+ @getProtocol() in supportedProtocols
+
isState: (states...) ->
@getState() in states
@@ -74,10 +83,12 @@ class ActionCable.Connection
events:
message: (event) ->
+ return unless @isProtocolSupported()
{identifier, message, type} = JSON.parse(event.data)
switch type
when message_types.welcome
@monitor.recordConnect()
+ @subscriptions.reload()
when message_types.ping
@monitor.recordPing()
when message_types.confirmation
@@ -88,20 +99,18 @@ class ActionCable.Connection
@subscriptions.notify(identifier, "received", message)
open: ->
- ActionCable.log("WebSocket onopen event")
+ ActionCable.log("WebSocket onopen event, using '#{@getProtocol()}' subprotocol")
@disconnected = false
- @subscriptions.reload()
+ if not @isProtocolSupported()
+ ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.")
+ @close(allowReconnect: false)
- close: ->
+ close: (event) ->
ActionCable.log("WebSocket onclose event")
- @disconnect()
+ return if @disconnected
+ @disconnected = true
+ @monitor.recordDisconnect()
+ @subscriptions.notifyAll("disconnected", {willAttemptReconnect: @monitor.isRunning()})
error: ->
ActionCable.log("WebSocket onerror event")
- @disconnect()
-
- disconnect: ->
- return if @disconnected
- @disconnected = true
- @subscriptions.notifyAll("disconnected")
- @monitor.recordDisconnect()
diff --git a/actioncable/app/assets/javascripts/action_cable/consumer.coffee b/actioncable/app/assets/javascripts/action_cable/consumer.coffee
index 7aae1ed8ed..3298be717f 100644
--- a/actioncable/app/assets/javascripts/action_cable/consumer.coffee
+++ b/actioncable/app/assets/javascripts/action_cable/consumer.coffee
@@ -14,6 +14,19 @@
# App.appearance = App.cable.subscriptions.create "AppearanceChannel"
#
# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.
+#
+# When a consumer is created, it automatically connects with the server.
+#
+# To disconnect from the server, call
+#
+# App.cable.disconnect()
+#
+# and to restart the connection:
+#
+# App.cable.connect()
+#
+# Any channel subscriptions which existed prior to disconnecting will
+# automatically resubscribe.
class ActionCable.Consumer
constructor: (@url) ->
@subscriptions = new ActionCable.Subscriptions this
@@ -22,6 +35,12 @@ class ActionCable.Consumer
send: (data) ->
@connection.send(data)
+ connect: ->
+ @connection.open()
+
+ disconnect: ->
+ @connection.close(allowReconnect: false)
+
ensureActiveConnection: ->
unless @connection.isActive()
@connection.open()
diff --git a/actioncable/app/assets/javascripts/action_cable/subscription.coffee b/actioncable/app/assets/javascripts/action_cable/subscription.coffee
index 61a3fb1309..8e0805a174 100644
--- a/actioncable/app/assets/javascripts/action_cable/subscription.coffee
+++ b/actioncable/app/assets/javascripts/action_cable/subscription.coffee
@@ -8,6 +8,12 @@
# connected: ->
# # Called once the subscription has been successfully completed
#
+# disconnected: ({ willAttemptReconnect: boolean }) ->
+# # Called when the client has disconnected with the server.
+# # The object will have an `willAttemptReconnect` property which
+# # says whether the client has the intention of attempting
+# # to reconnect.
+#
# appear: ->
# @perform 'appear', appearing_on: @appearingOn()
#
diff --git a/actioncable/lib/action_cable.rb b/actioncable/lib/action_cable.rb
index 68a5fff3e7..b6d2842867 100644
--- a/actioncable/lib/action_cable.rb
+++ b/actioncable/lib/action_cable.rb
@@ -35,7 +35,8 @@ module ActionCable
confirmation: 'confirm_subscription'.freeze,
rejection: 'reject_subscription'.freeze
},
- default_mount_path: '/cable'.freeze
+ default_mount_path: '/cable'.freeze,
+ protocols: ["actioncable-v1-json".freeze, "actioncable-unsupported".freeze].freeze
}
# Singleton instance of the server
diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb
index 714d9887d4..845b747fc5 100644
--- a/actioncable/lib/action_cable/channel/base.rb
+++ b/actioncable/lib/action_cable/channel/base.rb
@@ -160,7 +160,10 @@ module ActionCable
action = extract_action(data)
if processable_action?(action)
- dispatch_action(action, data)
+ payload = { channel_class: self.class.name, action: action, data: data }
+ ActiveSupport::Notifications.instrument("perform_action.action_cable", payload) do
+ dispatch_action(action, data)
+ end
else
logger.error "Unable to process #{action_signature(action, data)}"
end
@@ -192,7 +195,11 @@ module ActionCable
# the proper channel identifier marked as the recipient.
def transmit(data, via: nil)
logger.info "#{self.class.name} transmitting #{data.inspect.truncate(300)}".tap { |m| m << " (via #{via})" if via }
- connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, message: data)
+
+ payload = { channel_class: self.class.name, data: data, via: via }
+ ActiveSupport::Notifications.instrument("transmit.action_cable", payload) do
+ connection.transmit identifier: @identifier, message: data
+ end
end
def defer_subscription_confirmation!
@@ -265,8 +272,11 @@ module ActionCable
def transmit_subscription_confirmation
unless subscription_confirmation_sent?
logger.info "#{self.class.name} is transmitting the subscription confirmation"
- connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:confirmation])
- @subscription_confirmation_sent = true
+
+ ActiveSupport::Notifications.instrument("transmit_subscription_confirmation.action_cable", channel_class: self.class.name) do
+ connection.transmit identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:confirmation]
+ @subscription_confirmation_sent = true
+ end
end
end
@@ -277,7 +287,10 @@ module ActionCable
def transmit_subscription_rejection
logger.info "#{self.class.name} is transmitting the subscription rejection"
- connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:rejection])
+
+ ActiveSupport::Notifications.instrument("transmit_subscription_rejection.action_cable", channel_class: self.class.name) do
+ connection.transmit identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:rejection]
+ end
end
end
end
diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb
index 23d7320a28..f654ce0bfa 100644
--- a/actioncable/lib/action_cable/channel/streams.rb
+++ b/actioncable/lib/action_cable/channel/streams.rb
@@ -46,9 +46,7 @@ module ActionCable
# def subscribed
# @room = Chat::Room[params[:room_number]]
#
- # stream_for @room, -> (encoded_message) do
- # message = ActiveSupport::JSON.decode(encoded_message)
- #
+ # stream_for @room, coder: ActiveSupport::JSON do |message|
# if message['originated_at'].present?
# elapsed_time = (Time.now.to_f - message['originated_at']).round(2)
#
@@ -71,16 +69,23 @@ module ActionCable
# Start streaming from the named <tt>broadcasting</tt> pubsub queue. Optionally, you can pass a <tt>callback</tt> that'll be used
# instead of the default of just transmitting the updates straight to the subscriber.
- def stream_from(broadcasting, callback = nil)
+ # Pass `coder: ActiveSupport::JSON` to decode messages as JSON before passing to the callback.
+ # Defaults to `coder: nil` which does no decoding, passes raw messages.
+ def stream_from(broadcasting, callback = nil, coder: nil, &block)
broadcasting = String(broadcasting)
# Don't send the confirmation until pubsub#subscribe is successful
defer_subscription_confirmation!
- callback ||= default_stream_callback(broadcasting)
- streams << [ broadcasting, callback ]
+ if handler = callback || block
+ handler = -> message { handler.(coder.decode(message)) } if coder
+ else
+ handler = default_stream_handler(broadcasting, coder: coder)
+ end
+
+ streams << [ broadcasting, handler ]
connection.server.event_loop.post do
- pubsub.subscribe(broadcasting, callback, lambda do
+ pubsub.subscribe(broadcasting, handler, lambda do
transmit_subscription_confirmation
logger.info "#{self.class.name} is streaming from #{broadcasting}"
end)
@@ -90,8 +95,11 @@ module ActionCable
# Start streaming the pubsub queue for the <tt>model</tt> in this channel. Optionally, you can pass a
# <tt>callback</tt> that'll be used instead of the default of just transmitting the updates straight
# to the subscriber.
- def stream_for(model, callback = nil)
- stream_from(broadcasting_for([ channel_name, model ]), callback)
+ #
+ # Pass `coder: ActiveSupport::JSON` to decode messages as JSON before passing to the callback.
+ # Defaults to `coder: nil` which does no decoding, passes raw messages.
+ def stream_for(model, callback = nil, coder: nil, &block)
+ stream_from(broadcasting_for([ channel_name, model ]), callback || block, coder: coder)
end
# Unsubscribes all streams associated with this channel from the pubsub queue.
@@ -109,9 +117,11 @@ module ActionCable
@_streams ||= []
end
- def default_stream_callback(broadcasting)
+ def default_stream_handler(broadcasting, coder:)
+ coder ||= ActiveSupport::JSON
+
-> (message) do
- transmit ActiveSupport::JSON.decode(message), via: "streamed from #{broadcasting}"
+ transmit coder.decode(message), via: "streamed from #{broadcasting}"
end
end
end
diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb
index b4488265cb..9a7dfbe761 100644
--- a/actioncable/lib/action_cable/connection/base.rb
+++ b/actioncable/lib/action_cable/connection/base.rb
@@ -48,11 +48,11 @@ module ActionCable
include InternalChannel
include Authorization
- attr_reader :server, :env, :subscriptions, :logger, :worker_pool
+ attr_reader :server, :env, :subscriptions, :logger, :worker_pool, :protocol
delegate :event_loop, :pubsub, to: :server
- def initialize(server, env)
- @server, @env = server, env
+ def initialize(server, env, coder: ActiveSupport::JSON)
+ @server, @env, @coder = server, env, coder
@worker_pool = server.worker_pool
@logger = new_tagged_logger
@@ -67,7 +67,7 @@ module ActionCable
# Called by the server when a new WebSocket connection is established. This configures the callbacks intended for overwriting by the user.
# This method should not be called directly -- instead rely upon on the #connect (and #disconnect) callbacks.
- def process # :nodoc:
+ def process #:nodoc:
logger.info started_request_message
if websocket.possible? && allow_request_origin?
@@ -77,20 +77,22 @@ module ActionCable
end
end
- # Data received over the WebSocket connection is handled by this method. It's expected that everything inbound is JSON encoded.
- # The data is routed to the proper channel that the connection has subscribed to.
- def receive(data_in_json)
+ # Decodes WebSocket messages and dispatches them to subscribed channels.
+ # WebSocket message transfer encoding is always JSON.
+ def receive(websocket_message) #:nodoc:
+ send_async :dispatch_websocket_message, websocket_message
+ end
+
+ def dispatch_websocket_message(websocket_message) #:nodoc:
if websocket.alive?
- subscriptions.execute_command ActiveSupport::JSON.decode(data_in_json)
+ subscriptions.execute_command decode(websocket_message)
else
- logger.error "Received data without a live WebSocket (#{data_in_json.inspect})"
+ logger.error "Ignoring message processed after the WebSocket was closed: #{websocket_message.inspect})"
end
end
- # Send raw data straight back down the WebSocket. This is not intended to be called directly. Use the #transmit available on the
- # Channel instead, as that'll automatically address the correct subscriber and wrap the message in JSON.
- def transmit(data) # :nodoc:
- websocket.transmit data
+ def transmit(cable_message) # :nodoc:
+ websocket.transmit encode(cable_message)
end
# Close the WebSocket connection.
@@ -115,7 +117,7 @@ module ActionCable
end
def beat
- transmit ActiveSupport::JSON.encode(type: ActionCable::INTERNAL[:message_types][:ping], message: Time.now.to_i)
+ transmit type: ActionCable::INTERNAL[:message_types][:ping], message: Time.now.to_i
end
def on_open # :nodoc:
@@ -152,7 +154,16 @@ module ActionCable
attr_reader :message_buffer
private
+ def encode(cable_message)
+ @coder.encode cable_message
+ end
+
+ def decode(websocket_message)
+ @coder.decode websocket_message
+ end
+
def handle_open
+ @protocol = websocket.protocol
connect if respond_to?(:connect)
subscribe_to_internal_channel
send_welcome_message
@@ -178,7 +189,7 @@ module ActionCable
# Send welcome message to the internal connection monitor channel.
# This ensures the connection monitor state is reset after a successful
# websocket connection.
- transmit ActiveSupport::JSON.encode(type: ActionCable::INTERNAL[:message_types][:welcome])
+ transmit type: ActionCable::INTERNAL[:message_types][:welcome]
end
def allow_request_origin?
diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb
index 7d6de78582..6f29f32ea9 100644
--- a/actioncable/lib/action_cable/connection/client_socket.rb
+++ b/actioncable/lib/action_cable/connection/client_socket.rb
@@ -29,7 +29,7 @@ module ActionCable
attr_reader :env, :url
- def initialize(env, event_target, event_loop)
+ def initialize(env, event_target, event_loop, protocols)
@env = env
@event_target = event_target
@event_loop = event_loop
@@ -42,7 +42,7 @@ module ActionCable
@ready_state = CONNECTING
# The driver calls +env+, +url+, and +write+
- @driver = ::WebSocket::Driver.rack(self)
+ @driver = ::WebSocket::Driver.rack(self, protocols: protocols)
@driver.on(:open) { |e| open }
@driver.on(:message) { |e| receive_message(e.data) }
@@ -111,6 +111,10 @@ module ActionCable
@ready_state == OPEN
end
+ def protocol
+ @driver.protocol
+ end
+
private
def open
return unless @ready_state == CONNECTING
diff --git a/actioncable/lib/action_cable/connection/faye_client_socket.rb b/actioncable/lib/action_cable/connection/faye_client_socket.rb
index 47d09a9e14..a4bfe7db17 100644
--- a/actioncable/lib/action_cable/connection/faye_client_socket.rb
+++ b/actioncable/lib/action_cable/connection/faye_client_socket.rb
@@ -3,9 +3,10 @@ require 'faye/websocket'
module ActionCable
module Connection
class FayeClientSocket
- def initialize(env, event_target, stream_event_loop)
+ def initialize(env, event_target, stream_event_loop, protocols)
@env = env
@event_target = event_target
+ @protocols = protocols
@faye = nil
end
@@ -23,6 +24,10 @@ module ActionCable
@faye && @faye.close
end
+ def protocol
+ @faye && @faye.protocol
+ end
+
def rack_response
connect
@faye.rack_response
@@ -31,7 +36,7 @@ module ActionCable
private
def connect
return if @faye
- @faye = Faye::WebSocket.new(@env)
+ @faye = Faye::WebSocket.new(@env, @protocols)
@faye.on(:open) { |event| @event_target.on_open }
@faye.on(:message) { |event| @event_target.on_message(event.data) }
diff --git a/actioncable/lib/action_cable/connection/internal_channel.rb b/actioncable/lib/action_cable/connection/internal_channel.rb
index 3c5d39f59a..f70d52f99b 100644
--- a/actioncable/lib/action_cable/connection/internal_channel.rb
+++ b/actioncable/lib/action_cable/connection/internal_channel.rb
@@ -11,7 +11,7 @@ module ActionCable
def subscribe_to_internal_channel
if connection_identifier.present?
- callback = -> (message) { process_internal_message(message) }
+ callback = -> (message) { process_internal_message decode(message) }
@_internal_subscriptions ||= []
@_internal_subscriptions << [ internal_channel, callback ]
@@ -27,8 +27,6 @@ module ActionCable
end
def process_internal_message(message)
- message = ActiveSupport::JSON.decode(message)
-
case message['type']
when 'disconnect'
logger.info "Removing connection (#{connection_identifier})"
diff --git a/actioncable/lib/action_cable/connection/message_buffer.rb b/actioncable/lib/action_cable/connection/message_buffer.rb
index 19f2e6e918..6a80770cae 100644
--- a/actioncable/lib/action_cable/connection/message_buffer.rb
+++ b/actioncable/lib/action_cable/connection/message_buffer.rb
@@ -30,7 +30,7 @@ module ActionCable
protected
attr_reader :connection
- attr_accessor :buffered_messages
+ attr_reader :buffered_messages
private
def valid?(message)
@@ -38,7 +38,7 @@ module ActionCable
end
def receive(message)
- connection.send_async :receive, message
+ connection.receive message
end
def buffer(message)
diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb
index 5aa907c2d3..3742f248d1 100644
--- a/actioncable/lib/action_cable/connection/subscriptions.rb
+++ b/actioncable/lib/action_cable/connection/subscriptions.rb
@@ -23,13 +23,13 @@ module ActionCable
end
def add(data)
- id_options = decode_hash(data['identifier'])
- identifier = normalize_identifier(id_options)
+ id_key = data['identifier']
+ id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access
subscription_klass = connection.server.channel_classes[id_options[:channel]]
if subscription_klass
- subscriptions[identifier] ||= subscription_klass.new(connection, identifier, id_options)
+ subscriptions[id_key] ||= subscription_klass.new(connection, id_key, id_options)
else
logger.error "Subscription class not found (#{data.inspect})"
end
@@ -37,7 +37,7 @@ module ActionCable
def remove(data)
logger.info "Unsubscribing from channel: #{data['identifier']}"
- remove_subscription subscriptions[normalize_identifier(data['identifier'])]
+ remove_subscription subscriptions[data['identifier']]
end
def remove_subscription(subscription)
@@ -46,7 +46,7 @@ module ActionCable
end
def perform_action(data)
- find(data).perform_action(decode_hash(data['data']))
+ find(data).perform_action ActiveSupport::JSON.decode(data['data'])
end
def identifiers
@@ -63,21 +63,8 @@ module ActionCable
private
delegate :logger, to: :connection
- def normalize_identifier(identifier)
- identifier = ActiveSupport::JSON.encode(identifier) if identifier.is_a?(Hash)
- identifier
- end
-
- # If `data` is a Hash, this means that the original JSON
- # sent by the client had no backslashes in it, and does
- # not need to be decoded again.
- def decode_hash(data)
- data = ActiveSupport::JSON.decode(data) unless data.is_a?(Hash)
- data.with_indifferent_access
- end
-
def find(data)
- if subscription = subscriptions[normalize_identifier(data['identifier'])]
+ if subscription = subscriptions[data['identifier']]
subscription
else
raise "Unable to find subscription with identifier: #{data['identifier']}"
diff --git a/actioncable/lib/action_cable/connection/web_socket.rb b/actioncable/lib/action_cable/connection/web_socket.rb
index 0bec9b6a96..11f28c37e8 100644
--- a/actioncable/lib/action_cable/connection/web_socket.rb
+++ b/actioncable/lib/action_cable/connection/web_socket.rb
@@ -4,8 +4,8 @@ module ActionCable
module Connection
# Wrap the real socket to minimize the externally-presented API
class WebSocket
- def initialize(env, event_target, event_loop, client_socket_class)
- @websocket = ::WebSocket::Driver.websocket?(env) ? client_socket_class.new(env, event_target, event_loop) : nil
+ def initialize(env, event_target, event_loop, client_socket_class, protocols: ActionCable::INTERNAL[:protocols])
+ @websocket = ::WebSocket::Driver.websocket?(env) ? client_socket_class.new(env, event_target, event_loop, protocols) : nil
end
def possible?
@@ -24,6 +24,10 @@ module ActionCable
websocket.close
end
+ def protocol
+ websocket.protocol
+ end
+
def rack_response
websocket.rack_response
end
diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb
index 778f5ffeed..b1a0e11631 100644
--- a/actioncable/lib/action_cable/server/base.rb
+++ b/actioncable/lib/action_cable/server/base.rb
@@ -52,7 +52,17 @@ module ActionCable
@event_loop || @mutex.synchronize { @event_loop ||= config.event_loop_class.new }
end
- # The thread worker pool for handling all the connection work on this server. Default size is set by config.worker_pool_size.
+ # The worker pool is where we run connection callbacks and channel actions. We do as little as possible on the server's main thread.
+ # The worker pool is an executor service that's backed by a pool of threads working from a task queue. The thread pool size maxes out
+ # at 4 worker threads by default. Tune the size yourself with config.action_cable.worker_pool_size.
+ #
+ # Using Active Record, Redis, etc within your channel actions means you'll get a separate connection from each thread in the worker pool.
+ # Plan your deployment accordingly: 5 servers each running 5 Puma workers each running an 8-thread worker pool means at least 200 database
+ # connections.
+ #
+ # Also, ensure that your database connection pool size is as least as large as your worker pool size. Otherwise, workers may oversubscribe
+ # the db connection pool and block while they wait for other workers to release their connections. Use a smaller worker pool or a larger
+ # db connection pool instead.
def worker_pool
@worker_pool || @mutex.synchronize { @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size) }
end
diff --git a/actioncable/lib/action_cable/server/broadcasting.rb b/actioncable/lib/action_cable/server/broadcasting.rb
index 98025f27f2..8f93564113 100644
--- a/actioncable/lib/action_cable/server/broadcasting.rb
+++ b/actioncable/lib/action_cable/server/broadcasting.rb
@@ -19,27 +19,28 @@ module ActionCable
# new Notification data['title'], body: data['body']
module Broadcasting
# Broadcast a hash directly to a named <tt>broadcasting</tt>. This will later be JSON encoded.
- def broadcast(broadcasting, message)
- broadcaster_for(broadcasting).broadcast(message)
+ def broadcast(broadcasting, message, coder: ActiveSupport::JSON)
+ broadcaster_for(broadcasting, coder: coder).broadcast(message)
end
# Returns a broadcaster for a named <tt>broadcasting</tt> that can be reused. Useful when you have an object that
# may need multiple spots to transmit to a specific broadcasting over and over.
- def broadcaster_for(broadcasting)
- Broadcaster.new(self, String(broadcasting))
+ def broadcaster_for(broadcasting, coder: ActiveSupport::JSON)
+ Broadcaster.new(self, String(broadcasting), coder: coder)
end
private
class Broadcaster
- attr_reader :server, :broadcasting
+ attr_reader :server, :broadcasting, :coder
- def initialize(server, broadcasting)
- @server, @broadcasting = server, broadcasting
+ def initialize(server, broadcasting, coder:)
+ @server, @broadcasting, @coder = server, broadcasting, coder
end
def broadcast(message)
- server.logger.info "[ActionCable] Broadcasting to #{broadcasting}: #{message}"
- server.pubsub.broadcast broadcasting, ActiveSupport::JSON.encode(message)
+ server.logger.info "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect}"
+ encoded = coder ? coder.encode(message) : message
+ server.pubsub.broadcast broadcasting, encoded
end
end
end
diff --git a/actioncable/lib/action_cable/server/configuration.rb b/actioncable/lib/action_cable/server/configuration.rb
index 5fe71caed2..0bb378cf03 100644
--- a/actioncable/lib/action_cable/server/configuration.rb
+++ b/actioncable/lib/action_cable/server/configuration.rb
@@ -14,7 +14,7 @@ module ActionCable
@log_tags = []
@connection_class = ActionCable::Connection::Base
- @worker_pool_size = 100
+ @worker_pool_size = 4
@disable_request_forgery_protection = false
end
diff --git a/actioncable/lib/rails/generators/channel/channel_generator.rb b/actioncable/lib/rails/generators/channel/channel_generator.rb
index d89ab45816..3bcf5f1898 100644
--- a/actioncable/lib/rails/generators/channel/channel_generator.rb
+++ b/actioncable/lib/rails/generators/channel/channel_generator.rb
@@ -13,6 +13,7 @@ module Rails
template "channel.rb", File.join('app/channels', class_path, "#{file_name}_channel.rb")
if options[:assets]
+ template "assets/cable.js", "app/assets/javascripts/cable.js"
template "assets/channel.coffee", File.join('app/assets/javascripts/channels', class_path, "#{file_name}.coffee")
end
diff --git a/actioncable/lib/rails/generators/channel/templates/assets/cable.js b/actioncable/lib/rails/generators/channel/templates/assets/cable.js
new file mode 100644
index 0000000000..71ee1e66de
--- /dev/null
+++ b/actioncable/lib/rails/generators/channel/templates/assets/cable.js
@@ -0,0 +1,13 @@
+// Action Cable provides the framework to deal with WebSockets in Rails.
+// You can generate new channels where WebSocket features live using the rails generate channel command.
+//
+//= require action_cable
+//= require_self
+//= require_tree ./channels
+
+(function() {
+ this.App || (this.App = {});
+
+ App.cable = ActionCable.createConsumer();
+
+}).call(this);
diff --git a/actioncable/test/channel/base_test.rb b/actioncable/test/channel/base_test.rb
index d41bf3064b..daa782eeb3 100644
--- a/actioncable/test/channel/base_test.rb
+++ b/actioncable/test/channel/base_test.rb
@@ -146,12 +146,12 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase
test "transmitting data" do
@channel.perform_action 'action' => :get_latest
- expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "message" => { "data" => "latest" }
+ expected = { "identifier" => "{id: 1}", "message" => { "data" => "latest" }}
assert_equal expected, @connection.last_transmission
end
test "subscription confirmation" do
- expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription"
+ expected = { "identifier" => "{id: 1}", "type" => "confirm_subscription" }
assert_equal expected, @connection.last_transmission
end
@@ -166,6 +166,81 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase
end
end
+ test "notification for perform_action" do
+ begin
+ events = []
+ ActiveSupport::Notifications.subscribe "perform_action.action_cable" do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+
+ data = {'action' => :speak, 'content' => 'hello'}
+ @channel.perform_action data
+
+ assert_equal 1, events.length
+ assert_equal 'perform_action.action_cable', events[0].name
+ assert_equal 'ActionCable::Channel::BaseTest::ChatChannel', events[0].payload[:channel_class]
+ assert_equal :speak, events[0].payload[:action]
+ assert_equal data, events[0].payload[:data]
+ ensure
+ ActiveSupport::Notifications.unsubscribe "perform_action.action_cable"
+ end
+ end
+
+ test "notification for transmit" do
+ begin
+ events = []
+ ActiveSupport::Notifications.subscribe 'transmit.action_cable' do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+
+ @channel.perform_action 'action' => :get_latest
+ expected_data = {data: 'latest'}
+
+ assert_equal 1, events.length
+ assert_equal 'transmit.action_cable', events[0].name
+ assert_equal 'ActionCable::Channel::BaseTest::ChatChannel', events[0].payload[:channel_class]
+ assert_equal expected_data, events[0].payload[:data]
+ assert_nil events[0].payload[:via]
+ ensure
+ ActiveSupport::Notifications.unsubscribe 'transmit.action_cable'
+ end
+ end
+
+ test "notification for transmit_subscription_confirmation" do
+ begin
+ events = []
+ ActiveSupport::Notifications.subscribe 'transmit_subscription_confirmation.action_cable' do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+
+ @channel.stubs(:subscription_confirmation_sent?).returns(false)
+ @channel.send(:transmit_subscription_confirmation)
+
+ assert_equal 1, events.length
+ assert_equal 'transmit_subscription_confirmation.action_cable', events[0].name
+ assert_equal 'ActionCable::Channel::BaseTest::ChatChannel', events[0].payload[:channel_class]
+ ensure
+ ActiveSupport::Notifications.unsubscribe 'transmit_subscription_confirmation.action_cable'
+ end
+ end
+
+ test "notification for transmit_subscription_rejection" do
+ begin
+ events = []
+ ActiveSupport::Notifications.subscribe 'transmit_subscription_rejection.action_cable' do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+
+ @channel.send(:transmit_subscription_rejection)
+
+ assert_equal 1, events.length
+ assert_equal 'transmit_subscription_rejection.action_cable', events[0].name
+ assert_equal 'ActionCable::Channel::BaseTest::ChatChannel', events[0].payload[:channel_class]
+ ensure
+ ActiveSupport::Notifications.unsubscribe 'transmit_subscription_rejection.action_cable'
+ end
+ end
+
private
def assert_logged(message)
old_logger = @connection.logger
diff --git a/actioncable/test/channel/rejection_test.rb b/actioncable/test/channel/rejection_test.rb
index aa93396d44..15db57d6ba 100644
--- a/actioncable/test/channel/rejection_test.rb
+++ b/actioncable/test/channel/rejection_test.rb
@@ -18,7 +18,7 @@ class ActionCable::Channel::RejectionTest < ActiveSupport::TestCase
@connection.expects(:subscriptions).returns mock().tap { |m| m.expects(:remove_subscription).with instance_of(SecretChannel) }
@channel = SecretChannel.new @connection, "{id: 1}", { id: 1 }
- expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "reject_subscription"
+ expected = { "identifier" => "{id: 1}", "type" => "reject_subscription" }
assert_equal expected, @connection.last_transmission
end
diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb
index 526ea92e4f..f51f19eb7d 100644
--- a/actioncable/test/channel/stream_test.rb
+++ b/actioncable/test/channel/stream_test.rb
@@ -2,18 +2,43 @@ require 'test_helper'
require 'stubs/test_connection'
require 'stubs/room'
-class ActionCable::Channel::StreamTest < ActionCable::TestCase
+module ActionCable::StreamTests
+ class Connection < ActionCable::Connection::Base
+ attr_reader :websocket
+
+ def send_async(method, *args)
+ send method, *args
+ end
+ end
+
class ChatChannel < ActionCable::Channel::Base
def subscribed
if params[:id]
@room = Room.new params[:id]
- stream_from "test_room_#{@room.id}"
+ stream_from "test_room_#{@room.id}", coder: pick_coder(params[:coder])
end
end
def send_confirmation
transmit_subscription_confirmation
end
+
+ private def pick_coder(coder)
+ case coder
+ when nil, 'json'
+ ActiveSupport::JSON
+ when 'custom'
+ DummyEncoder
+ when 'none'
+ nil
+ end
+ end
+ end
+
+ module DummyEncoder
+ extend self
+ def encode(*) '{ "foo": "encoded" }' end
+ def decode(*) { foo: 'decoded' } end
end
class SymbolChannel < ActionCable::Channel::Base
@@ -22,69 +47,114 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase
end
end
- test "streaming start and stop" do
- run_in_eventmachine do
- connection = TestConnection.new
- connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("test_room_1", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
- channel = ChatChannel.new connection, "{id: 1}", { id: 1 }
+ class StreamTest < ActionCable::TestCase
+ test "streaming start and stop" do
+ run_in_eventmachine do
+ connection = TestConnection.new
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("test_room_1", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
+ channel = ChatChannel.new connection, "{id: 1}", { id: 1 }
- connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) }
- channel.unsubscribe_from_channel
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) }
+ channel.unsubscribe_from_channel
+ end
end
- end
- test "stream from non-string channel" do
- run_in_eventmachine do
- connection = TestConnection.new
- connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("channel", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
- channel = SymbolChannel.new connection, ""
+ test "stream from non-string channel" do
+ run_in_eventmachine do
+ connection = TestConnection.new
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("channel", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
+ channel = SymbolChannel.new connection, ""
- connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) }
- channel.unsubscribe_from_channel
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) }
+ channel.unsubscribe_from_channel
+ end
end
- end
- test "stream_for" do
- run_in_eventmachine do
- connection = TestConnection.new
- connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
+ test "stream_for" do
+ run_in_eventmachine do
+ connection = TestConnection.new
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:stream_tests:chat:Room#1-Campfire", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
- channel = ChatChannel.new connection, ""
- channel.stream_for Room.new(1)
+ channel = ChatChannel.new connection, ""
+ channel.stream_for Room.new(1)
+ end
end
- end
- test "stream_from subscription confirmation" do
- run_in_eventmachine do
- connection = TestConnection.new
+ test "stream_from subscription confirmation" do
+ run_in_eventmachine do
+ connection = TestConnection.new
- ChatChannel.new connection, "{id: 1}", { id: 1 }
- assert_nil connection.last_transmission
+ ChatChannel.new connection, "{id: 1}", { id: 1 }
+ assert_nil connection.last_transmission
- wait_for_async
+ wait_for_async
- expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription"
- connection.transmit(expected)
+ confirmation = { "identifier" => "{id: 1}", "type" => "confirm_subscription" }
+ connection.transmit(confirmation)
- assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation within 0.1s"
+ assert_equal confirmation, connection.last_transmission, "Did not receive subscription confirmation within 0.1s"
+ end
end
- end
- test "subscription confirmation should only be sent out once" do
- run_in_eventmachine do
- connection = TestConnection.new
+ test "subscription confirmation should only be sent out once" do
+ run_in_eventmachine do
+ connection = TestConnection.new
- channel = ChatChannel.new connection, "test_channel"
- channel.send_confirmation
- channel.send_confirmation
+ channel = ChatChannel.new connection, "test_channel"
+ channel.send_confirmation
+ channel.send_confirmation
- wait_for_async
+ wait_for_async
- expected = ActiveSupport::JSON.encode "identifier" => "test_channel", "type" => "confirm_subscription"
- assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation"
+ expected = { "identifier" => "test_channel", "type" => "confirm_subscription" }
+ assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation"
- assert_equal 1, connection.transmissions.size
+ assert_equal 1, connection.transmissions.size
+ end
end
end
+ require 'action_cable/subscription_adapter/inline'
+
+ class StreamEncodingTest < ActionCable::TestCase
+ setup do
+ @server = TestServer.new(subscription_adapter: ActionCable::SubscriptionAdapter::Inline)
+ @server.config.allowed_request_origins = %w( http://rubyonrails.com )
+ @server.stubs(:channel_classes).returns(ChatChannel.name => ChatChannel)
+ end
+
+ test 'custom encoder' do
+ run_in_eventmachine do
+ connection = open_connection
+ subscribe_to connection, identifiers: { id: 1 }
+
+ connection.websocket.expects(:transmit)
+ @server.broadcast 'test_room_1', { foo: 'bar' }, coder: DummyEncoder
+ wait_for_async
+ end
+ end
+
+ private
+ def subscribe_to(connection, identifiers:)
+ receive connection, command: 'subscribe', identifiers: identifiers
+ end
+
+ def open_connection
+ env = Rack::MockRequest.env_for '/test', 'HTTP_HOST' => 'localhost', 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket', 'HTTP_ORIGIN' => 'http://rubyonrails.com'
+
+ Connection.new(@server, env).tap do |connection|
+ connection.process
+ assert connection.websocket.possible?
+
+ wait_for_async
+ assert connection.websocket.alive?
+ end
+ end
+
+ def receive(connection, command:, identifiers:)
+ identifier = JSON.generate(channel: 'ActionCable::StreamTests::ChatChannel', **identifiers)
+ connection.dispatch_websocket_message JSON.generate(command: command, identifier: identifier)
+ wait_for_async
+ end
+ end
end
diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb
index 5f5c09d1a1..5ac453db35 100644
--- a/actioncable/test/client_test.rb
+++ b/actioncable/test/client_test.rb
@@ -75,11 +75,11 @@ class ClientTest < ActionCable::TestCase
end
@ws.on(:message) do |event|
- hash = JSON.parse(event.data)
- if hash['type'] == 'ping'
+ message = JSON.parse(event.data)
+ if message['type'] == 'ping'
@pings += 1
else
- @messages << hash
+ @messages << message
@has_messages.release
end
end
@@ -116,8 +116,8 @@ class ClientTest < ActionCable::TestCase
list
end
- def send_message(hash)
- @ws.send(JSON.dump(hash))
+ def send_message(message)
+ @ws.send(JSON.generate(message))
end
def close
@@ -148,9 +148,9 @@ class ClientTest < ActionCable::TestCase
with_puma_server do |port|
c = faye_client(port)
assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack
- c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel')
+ c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'EchoChannel')
assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
- c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello')
+ c.send_message command: 'message', identifier: JSON.generate(channel: 'EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello')
assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "message"=>{"dong"=>"hello"}}, c.read_message)
c.close
end
@@ -165,12 +165,12 @@ class ClientTest < ActionCable::TestCase
clients.map {|c| Concurrent::Future.execute {
assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack
- c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel')
+ c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'EchoChannel')
assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message)
- c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello')
+ c.send_message command: 'message', identifier: JSON.generate(channel: 'EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello')
assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message)
barrier_1.wait WAIT_WHEN_EXPECTING_EVENT
- c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'bulk', message: 'hello')
+ c.send_message command: 'message', identifier: JSON.generate(channel: 'EchoChannel'), data: JSON.generate(action: 'bulk', message: 'hello')
barrier_2.wait WAIT_WHEN_EXPECTING_EVENT
assert_equal clients.size, c.read_messages(clients.size).size
} }.each(&:wait!)
@@ -185,9 +185,9 @@ class ClientTest < ActionCable::TestCase
clients.map {|c| Concurrent::Future.execute {
assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack
- c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel')
+ c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'EchoChannel')
assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message)
- c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello')
+ c.send_message command: 'message', identifier: JSON.generate(channel: 'EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello')
assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message)
} }.each(&:wait!)
@@ -199,16 +199,16 @@ class ClientTest < ActionCable::TestCase
with_puma_server do |port|
c = faye_client(port)
assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack
- c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel')
+ c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'EchoChannel')
assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
- c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'delay', message: 'hello')
+ c.send_message command: 'message', identifier: JSON.generate(channel: 'EchoChannel'), data: JSON.generate(action: 'delay', message: 'hello')
c.close # disappear before write
c = faye_client(port)
assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack
- c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel')
+ c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'EchoChannel')
assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
- c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello')
+ c.send_message command: 'message', identifier: JSON.generate(channel: 'EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello')
assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message)
c.close # disappear before read
end
@@ -217,7 +217,7 @@ class ClientTest < ActionCable::TestCase
def test_unsubscribe_client
with_puma_server do |port|
app = ActionCable.server
- identifier = JSON.dump(channel: 'EchoChannel')
+ identifier = JSON.generate(channel: 'EchoChannel')
c = faye_client(port)
assert_equal({"type" => "welcome"}, c.read_message)
@@ -240,7 +240,7 @@ class ClientTest < ActionCable::TestCase
with_puma_server do |port|
c = faye_client(port)
assert_equal({"type" => "welcome"}, c.read_message)
- c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel')
+ c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'EchoChannel')
assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
ActionCable.server.restart
diff --git a/actioncable/test/connection/identifier_test.rb b/actioncable/test/connection/identifier_test.rb
index c3d5f1f90b..b48d9af809 100644
--- a/actioncable/test/connection/identifier_test.rb
+++ b/actioncable/test/connection/identifier_test.rb
@@ -40,8 +40,7 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase
open_connection_with_stubbed_pubsub
@connection.websocket.expects(:close)
- message = ActiveSupport::JSON.encode('type' => 'disconnect')
- @connection.process_internal_message message
+ @connection.process_internal_message 'type' => 'disconnect'
end
end
@@ -50,8 +49,7 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase
open_connection_with_stubbed_pubsub
@connection.websocket.expects(:close).never
- message = ActiveSupport::JSON.encode('type' => 'unknown')
- @connection.process_internal_message message
+ @connection.process_internal_message 'type' => 'unknown'
end
end
diff --git a/actioncable/test/connection/subscriptions_test.rb b/actioncable/test/connection/subscriptions_test.rb
index f91597f567..53e8547245 100644
--- a/actioncable/test/connection/subscriptions_test.rb
+++ b/actioncable/test/connection/subscriptions_test.rb
@@ -88,7 +88,7 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase
channel1 = subscribe_to_chat_channel
- channel2_id = ActiveSupport::JSON.encode({ id: 2, channel: 'ActionCable::Connection::SubscriptionsTest::ChatChannel' })
+ channel2_id = ActiveSupport::JSON.encode(id: 2, channel: 'ActionCable::Connection::SubscriptionsTest::ChatChannel')
channel2 = subscribe_to_chat_channel(channel2_id)
channel1.expects(:unsubscribe_from_channel)
diff --git a/actioncable/test/stubs/test_connection.rb b/actioncable/test/stubs/test_connection.rb
index 8ba284fdc6..885450dda6 100644
--- a/actioncable/test/stubs/test_connection.rb
+++ b/actioncable/test/stubs/test_connection.rb
@@ -3,24 +3,31 @@ require 'stubs/user'
class TestConnection
attr_reader :identifiers, :logger, :current_user, :server, :transmissions
- def initialize(user = User.new("lifo"))
+ delegate :pubsub, to: :server
+
+ def initialize(user = User.new("lifo"), coder: ActiveSupport::JSON, subscription_adapter: SuccessAdapter)
+ @coder = coder
@identifiers = [ :current_user ]
@current_user = user
@logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new)
- @server = TestServer.new
+ @server = TestServer.new(subscription_adapter: subscription_adapter)
@transmissions = []
end
- def pubsub
- SuccessAdapter.new(server)
+ def transmit(cable_message)
+ @transmissions << encode(cable_message)
end
- def transmit(data)
- @transmissions << data
+ def last_transmission
+ decode @transmissions.last if @transmissions.any?
end
- def last_transmission
- @transmissions.last
+ def decode(websocket_message)
+ @coder.decode websocket_message
+ end
+
+ def encode(cable_message)
+ @coder.encode cable_message
end
end
diff --git a/actioncable/test/stubs/test_server.rb b/actioncable/test/stubs/test_server.rb
index 9e860825f3..b86f422a13 100644
--- a/actioncable/test/stubs/test_server.rb
+++ b/actioncable/test/stubs/test_server.rb
@@ -2,22 +2,26 @@ require 'ostruct'
class TestServer
include ActionCable::Server::Connections
+ include ActionCable::Server::Broadcasting
- attr_reader :logger, :config
+ attr_reader :logger, :config, :mutex
- def initialize
+ def initialize(subscription_adapter: SuccessAdapter)
@logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new)
- @config = OpenStruct.new(log_tags: [], subscription_adapter: SuccessAdapter)
+
+ @config = OpenStruct.new(log_tags: [], subscription_adapter: subscription_adapter)
@config.use_faye = ENV['FAYE'].present?
@config.client_socket_class = if @config.use_faye
ActionCable::Connection::FayeClientSocket
else
ActionCable::Connection::ClientSocket
end
+
+ @mutex = Monitor.new
end
def pubsub
- @config.subscription_adapter.new(self)
+ @pubsub ||= @config.subscription_adapter.new(self)
end
def event_loop
diff --git a/actioncable/test/subscription_adapter/common.rb b/actioncable/test/subscription_adapter/common.rb
index 82f0abbbf3..285c690df0 100644
--- a/actioncable/test/subscription_adapter/common.rb
+++ b/actioncable/test/subscription_adapter/common.rb
@@ -20,8 +20,7 @@ module CommonSubscriptionAdapterTest
end
def teardown
- @tx_adapter.shutdown if @tx_adapter && @tx_adapter != @rx_adapter
- @rx_adapter.shutdown if @rx_adapter
+ [@rx_adapter, @tx_adapter].uniq.each(&:shutdown)
end
diff --git a/actioncable/test/subscription_adapter/evented_redis_test.rb b/actioncable/test/subscription_adapter/evented_redis_test.rb
index 70333e51bd..6d20e6ed78 100644
--- a/actioncable/test/subscription_adapter/evented_redis_test.rb
+++ b/actioncable/test/subscription_adapter/evented_redis_test.rb
@@ -4,6 +4,17 @@ require_relative './common'
class EventedRedisAdapterTest < ActionCable::TestCase
include CommonSubscriptionAdapterTest
+ def setup
+ super
+
+ # em-hiredis is warning-rich
+ @previous_verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ def teardown
+ $VERBOSE = @previous_verbose
+ end
+
def cable_config
{ adapter: 'evented_redis', url: 'redis://127.0.0.1:6379/12' }
end
diff --git a/actioncable/test/subscription_adapter/postgresql_test.rb b/actioncable/test/subscription_adapter/postgresql_test.rb
index 3b4fadbb2a..214352a0b2 100644
--- a/actioncable/test/subscription_adapter/postgresql_test.rb
+++ b/actioncable/test/subscription_adapter/postgresql_test.rb
@@ -21,6 +21,7 @@ class PostgresqlAdapterTest < ActionCable::TestCase
begin
ActiveRecord::Base.connection
rescue
+ @rx_adapter = @tx_adapter = nil
skip "Couldn't connect to PostgreSQL: #{database_config.inspect}"
end
diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb
index 030362d512..de1ee96770 100644
--- a/actioncable/test/test_helper.rb
+++ b/actioncable/test/test_helper.rb
@@ -2,11 +2,13 @@ require 'action_cable'
require 'active_support/testing/autorun'
require 'puma'
-
require 'mocha/setup'
-
require 'rack/mock'
-require 'active_support/core_ext/hash/indifferent_access'
+
+begin
+ require 'byebug'
+rescue LoadError
+end
# Require all the stubs and models
Dir[File.dirname(__FILE__) + '/stubs/*.rb'].each {|file| require file }
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 604e332dad..76d99f31c7 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,8 +1,30 @@
-## Rails 5.0.0.beta3 (February 24, 2016) ##
+* Disallow calling `#deliver_later` after making local modifications to
+ the message which would be lost when the delivery job is enqueued.
+
+ Prevents a common, hard-to-find bug like:
+
+ message = Notifier.welcome(user, foo)
+ message.message_id = my_generated_message_id
+ message.deliver_later
+
+ The message_id is silently lost! *Only the mailer arguments are passed
+ to the delivery job.*
+
+ This raises an exception now. Make modifications to the message within
+ the mailer method instead, or use a custom Active Job to manage delivery
+ instead of using #deliver_later.
-* Add support to fragment cache in Action Mailer.
+ *Jeremy Daer*
+
+* Removes `-t` from default Sendmail arguments to match the underlying
+ `Mail::Sendmail` setting.
+
+ *Clayton Liggitt*
+
+
+## Rails 5.0.0.beta3 (February 24, 2016) ##
- Now you can use fragment caching in your mailers views.
+* Add support for fragment caching in Action Mailer views.
*Stan Lo*
@@ -19,8 +41,7 @@
## Rails 5.0.0.beta1 (December 18, 2015) ##
-* `config.force_ssl = true` will set
- `config.action_mailer.default_url_options = { protocol: 'https' }`.
+* `config.action_mailer.default_url_options[:protocol]` is now set to `https` if `config.force_ssl` is set to `true`.
*Andrew Kampjes*
@@ -29,7 +50,7 @@
*Chris McGrath*
-* `assert_emails` in block form use the given number as expected value.
+* `assert_emails` in block form, uses the given number as expected value.
This makes the error message much easier to understand.
*Yuji Yaginuma*
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index a223cf82a1..3df524580c 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -86,7 +86,7 @@ module ActionMailer
# Like Action Controller, each mailer class has a corresponding view directory in which each
# method of the class looks for a template with its name.
#
- # To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same
+ # To define a template to be used with a mailer, create an <tt>.erb</tt> file with the same
# name as the method in your mailer model. For example, in the mailer defined above, the template at
# <tt>app/views/notifier_mailer/welcome.text.erb</tt> would be used to generate the email.
#
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index 4758b55a2a..571c8e7d2a 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -36,7 +36,7 @@ module ActionMailer
add_delivery_method :sendmail, Mail::Sendmail,
location: '/usr/sbin/sendmail',
- arguments: '-i -t'
+ arguments: '-i'
add_delivery_method :test, Mail::TestMailer
end
diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb
index 5fcb5a0c88..d638057d72 100644
--- a/actionmailer/lib/action_mailer/message_delivery.rb
+++ b/actionmailer/lib/action_mailer/message_delivery.rb
@@ -18,6 +18,7 @@ module ActionMailer
@mailer = mailer
@mail_method = mail_method
@args = args
+ @obj = nil
end
def __getobj__ #:nodoc:
@@ -91,8 +92,19 @@ module ActionMailer
private
def enqueue_delivery(delivery_method, options={})
- args = @mailer.name, @mail_method.to_s, delivery_method.to_s, *@args
- ActionMailer::DeliveryJob.set(options).perform_later(*args)
+ 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 " \
+ "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)
+ end
end
end
end
diff --git a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb
index f23e575fe5..00fb9bd48f 100644
--- a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb
+++ b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb
@@ -1,4 +1,4 @@
-<% module_namespacing do %>
+<% module_namespacing do -%>
class ApplicationMailer < ActionMailer::Base
default from: 'from@example.com'
layout 'mailer'
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index 2786fe0d07..bcbd036f26 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -39,7 +39,7 @@ class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase
test "default sendmail settings" do
settings = {
location: '/usr/sbin/sendmail',
- arguments: '-i -t'
+ arguments: '-i'
}
assert_equal settings, ActionMailer::Base.sendmail_settings
end
diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb
index 6124ffeb52..50c4b74eb8 100644
--- a/actionmailer/test/i18n_with_controller_test.rb
+++ b/actionmailer/test/i18n_with_controller_test.rb
@@ -25,7 +25,9 @@ end
class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new
Routes.draw do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
class RoutedRackApp
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index b834cdd08c..f06d69369f 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -93,4 +93,12 @@ class MessageDeliveryTest < ActiveSupport::TestCase
@mail.deliver_later(queue: :another_queue)
end
end
+
+ test 'deliver_later after accessing the message is disallowed' do
+ @mail.message # Load the message, which calls the mailer method.
+
+ assert_raise RuntimeError do
+ @mail.deliver_later
+ end
+ end
end
diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb
index 7928fe9542..70bd05055f 100644
--- a/actionmailer/test/url_test.rb
+++ b/actionmailer/test/url_test.rb
@@ -79,9 +79,11 @@ class ActionMailerUrlTest < ActionMailer::TestCase
UrlTestMailer.delivery_method = :test
AppRoutes.draw do
- get ':controller(/:action(/:id))'
- get '/welcome' => 'foo#bar', as: 'welcome'
- get '/dummy_model' => 'foo#baz', as: 'dummy_model'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ get '/welcome' => 'foo#bar', as: 'welcome'
+ get '/dummy_model' => 'foo#baz', as: 'dummy_model'
+ end
end
# string
@@ -108,8 +110,10 @@ class ActionMailerUrlTest < ActionMailer::TestCase
UrlTestMailer.delivery_method = :test
AppRoutes.draw do
- get ':controller(/:action(/:id))'
- get '/welcome' => "foo#bar", as: "welcome"
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ get '/welcome' => "foo#bar", as: "welcome"
+ end
end
expected = new_mail
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 1b153300b0..0ca3d2eb01 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,7 +1,43 @@
* Add extension synonyms `yml` and `yaml` for MIME type `application/x-yaml`.
-
+
*bogdanvlviv*
+* Adds support for including ActionController::Cookies in API controllers.
+ Previously, including the module would raise when trying to define
+ a `cookies` helper method. Skip calling #helper_method if it is not
+ defined -- if we don't have helpers, we needn't define one.
+
+ Fixes #24304
+
+ *Ryan T. Hosford*
+
+* ETags: Introduce `Response#strong_etag=` and `#weak_etag=` and analogous
+ options for `fresh_when` and `stale?`. `Response#etag=` sets a weak ETag.
+
+ Strong ETags are desirable when you're serving byte-for-byte identical
+ responses that support Range requests, like PDFs or videos (typically
+ done by reproxying the response from a backend storage service).
+ Also desirable when fronted by some CDNs that support strong ETags
+ only, like Akamai.
+
+ *Jeremy Daer*
+
+* ETags: No longer strips quotes (") from ETag values before comparing them.
+ Quotes are significant, part of the ETag. A quoted ETag and an unquoted
+ one are not the same entity.
+
+ *Jeremy Daer*
+
+* ETags: Support `If-None-Match: *`. Rarely useful for GET requests; meant
+ to provide some optimistic concurrency control for PUT requests.
+
+ *Jeremy Daer*
+
+* `ActionDispatch::ParamsParser` is deprecated and was removed from the middleware
+ stack. To configure the parameter parsers use `ActionDispatch::Request.parameter_parsers=`.
+
+ *tenderlove*
+
* When a `respond_to` collector with a block doesn't have a response, then
a `:no_content` response should be rendered. This brings the default
rendering behavior introduced by https://github.com/rails/rails/issues/19036
@@ -179,7 +215,7 @@
*Derek Prior*
-* `ActionController::TestCase` will be moved to it's own gem in Rails 5.1
+* `ActionController::TestCase` will be moved to its own gem in Rails 5.1
With the speed improvements made to `ActionDispatch::IntegrationTest` we no
longer need to keep two separate code bases for testing controllers. In
diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb
index ff12705abe..6bbebb7b4c 100644
--- a/actionpack/lib/action_controller/api.rb
+++ b/actionpack/lib/action_controller/api.rb
@@ -14,22 +14,22 @@ module ActionController
# flash, assets, and so on. This makes the entire controller stack thinner,
# suitable for API applications. It doesn't mean you won't have such
# features if you need them: they're all available for you to include in
- # your application, they're just not part of the default API Controller stack.
+ # your application, they're just not part of the default API controller stack.
#
- # By default, only the ApplicationController in a \Rails application inherits
- # from <tt>ActionController::API</tt>. All other controllers in turn inherit
- # from ApplicationController.
+ # Normally, +ApplicationController+ is the only controller that inherits from
+ # <tt>ActionController::API</tt>. All other controllers in turn inherit from
+ # +ApplicationController+.
#
# A sample controller could look like this:
#
# class PostsController < ApplicationController
# def index
- # @posts = Post.all
- # render json: @posts
+ # posts = Post.all
+ # render json: posts
# end
# end
#
- # Request, response and parameters objects all work the exact same way as
+ # Request, response, and parameters objects all work the exact same way as
# <tt>ActionController::Base</tt>.
#
# == Renders
@@ -37,18 +37,18 @@ module ActionController
# The default API Controller stack includes all renderers, which means you
# can use <tt>render :json</tt> and brothers freely in your controllers. Keep
# in mind that templates are not going to be rendered, so you need to ensure
- # your controller is calling either <tt>render</tt> or <tt>redirect</tt> in
- # all actions, otherwise it will return 204 No Content response.
+ # your controller is calling either <tt>render</tt> or <tt>redirect_to</tt> in
+ # all actions, otherwise it will return 204 No Content.
#
# def show
- # @post = Post.find(params[:id])
- # render json: @post
+ # post = Post.find(params[:id])
+ # render json: post
# end
#
# == Redirects
#
# Redirects are used to move from one action to another. You can use the
- # <tt>redirect</tt> method in your controllers in the same way as
+ # <tt>redirect_to</tt> method in your controllers in the same way as in
# <tt>ActionController::Base</tt>. For example:
#
# def create
@@ -56,7 +56,7 @@ module ActionController
# # do stuff here
# end
#
- # == Adding new behavior
+ # == Adding New Behavior
#
# In some scenarios you may want to add back some functionality provided by
# <tt>ActionController::Base</tt> that is not present by default in
@@ -72,18 +72,19 @@ module ActionController
#
# class PostsController < ApplicationController
# def index
- # @posts = Post.all
+ # posts = Post.all
#
# respond_to do |format|
- # format.json { render json: @posts }
- # format.xml { render xml: @posts }
+ # format.json { render json: posts }
+ # format.xml { render xml: posts }
# end
# end
# end
#
- # Quite straightforward. Make sure to check <tt>ActionController::Base</tt>
- # available modules if you want to include any other functionality that is
- # not provided by <tt>ActionController::API</tt> out of the box.
+ # Quite straightforward. Make sure to check the modules included in
+ # <tt>ActionController::Base</tt> if you want to use any other
+ # functionality that is not provided by <tt>ActionController::API</tt>
+ # out of the box.
class API < Metal
abstract!
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index 35befc05e1..480e265e44 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -36,8 +36,23 @@ module ActionController
#
# === Parameters:
#
- # * <tt>:etag</tt>.
- # * <tt>:last_modified</tt>.
+ # * <tt>:etag</tt> Sets a "weak" ETag validator on the response. See the
+ # +:weak_etag+ option.
+ # * <tt>:weak_etag</tt> Sets a "weak" ETag validator on the response.
+ # Requests that set If-None-Match header may return a 304 Not Modified
+ # response if it matches the ETag exactly. A weak ETag indicates semantic
+ # equivalence, not byte-for-byte equality, so they're good for caching
+ # HTML pages in browser caches. They can't be used for responses that
+ # must be byte-identical, like serving Range requests within a PDF file.
+ # * <tt>:strong_etag</tt> Sets a "strong" ETag validator on the response.
+ # Requests that set If-None-Match header may return a 304 Not Modified
+ # response if it matches the ETag exactly. A strong ETag implies exact
+ # equality: the response must match byte for byte. This is necessary for
+ # doing Range requests within a large video or PDF file, for example, or
+ # for compatibility with some CDNs that don't support weak ETags.
+ # * <tt>:last_modified</tt> Sets a "weak" last-update validator on the
+ # response. Subsequent requests that set If-Modified-Since may return a
+ # 304 Not Modified response if last_modified <= If-Modified-Since.
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
# +true+ if you want your application to be cacheable by other devices (proxy caches).
# * <tt>:template</tt> By default, the template digest for the current
@@ -86,12 +101,16 @@ module ActionController
#
# before_action { fresh_when @article, template: 'widgets/show' }
#
- def fresh_when(object = nil, etag: object, last_modified: nil, public: false, template: nil)
+ def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, template: nil)
+ weak_etag ||= etag || object unless strong_etag
last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at)
- if etag || template
- response.etag = combine_etags(etag: etag, last_modified: last_modified,
- public: public, template: template)
+ if strong_etag
+ response.strong_etag = combine_etags strong_etag,
+ last_modified: last_modified, public: public, template: template
+ elsif weak_etag || template
+ response.weak_etag = combine_etags weak_etag,
+ last_modified: last_modified, public: public, template: template
end
response.last_modified = last_modified if last_modified
@@ -107,8 +126,23 @@ module ActionController
#
# === Parameters:
#
- # * <tt>:etag</tt>.
- # * <tt>:last_modified</tt>.
+ # * <tt>:etag</tt> Sets a "weak" ETag validator on the response. See the
+ # +:weak_etag+ option.
+ # * <tt>:weak_etag</tt> Sets a "weak" ETag validator on the response.
+ # requests that set If-None-Match header may return a 304 Not Modified
+ # response if it matches the ETag exactly. A weak ETag indicates semantic
+ # equivalence, not byte-for-byte equality, so they're good for caching
+ # HTML pages in browser caches. They can't be used for responses that
+ # must be byte-identical, like serving Range requests within a PDF file.
+ # * <tt>:strong_etag</tt> Sets a "strong" ETag validator on the response.
+ # Requests that set If-None-Match header may return a 304 Not Modified
+ # response if it matches the ETag exactly. A strong ETag implies exact
+ # equality: the response must match byte for byte. This is necessary for
+ # doing Range requests within a large video or PDF file, for example, or
+ # for compatibility with some CDNs that don't support weak ETags.
+ # * <tt>:last_modified</tt> Sets a "weak" last-update validator on the
+ # response. Subsequent requests that set If-Modified-Since may return a
+ # 304 Not Modified response if last_modified <= If-Modified-Since.
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
# +true+ if you want your application to be cacheable by other devices (proxy caches).
# * <tt>:template</tt> By default, the template digest for the current
@@ -180,8 +214,8 @@ module ActionController
# super if stale? @article, template: 'widgets/show'
# end
#
- def stale?(object = nil, etag: object, last_modified: nil, public: nil, template: nil)
- fresh_when(object, etag: etag, last_modified: last_modified, public: public, template: template)
+ def stale?(object = nil, **freshness_kwargs)
+ fresh_when(object, **freshness_kwargs)
!request.fresh?(response)
end
@@ -231,9 +265,8 @@ module ActionController
end
private
- def combine_etags(options)
- etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact
- etags.unshift options[:etag]
+ def combine_etags(validator, options)
+ [validator, *etaggers.map { |etagger| instance_exec(options, &etagger) }].compact
end
end
end
diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb
index f8efb2b076..44925641a1 100644
--- a/actionpack/lib/action_controller/metal/cookies.rb
+++ b/actionpack/lib/action_controller/metal/cookies.rb
@@ -3,7 +3,7 @@ module ActionController #:nodoc:
extend ActiveSupport::Concern
included do
- helper_method :cookies
+ helper_method :cookies if defined?(helper_method)
end
private
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 35be6d9300..53527c08b6 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -347,7 +347,12 @@ module ActionController
# private
# def authenticate
# authenticate_or_request_with_http_token do |token, options|
- # token == TOKEN
+ # # Compare the tokens in a time-constant manner, to mitigate
+ # # timing attacks.
+ # ActiveSupport::SecurityUtils.secure_compare(
+ # ::Digest::SHA256.hexdigest(token),
+ # ::Digest::SHA256.hexdigest(TOKEN)
+ # )
# end
# end
# end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index b2f0b382b9..5793e28175 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -213,7 +213,7 @@ module ActionController #:nodoc:
if !verified_request?
if logger && log_warning_on_csrf_failure
- logger.warn "Can't verify CSRF token authenticity"
+ logger.warn "Can't verify CSRF token authenticity."
end
handle_unverified_request
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 76e3b4d25a..64672de57e 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -184,6 +184,13 @@ module ActionController
# Returns an unsafe, unfiltered
# <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of this
# parameter.
+ #
+ # params = ActionController::Parameters.new({
+ # name: 'Senjougahara Hitagi',
+ # oddity: 'Heavy stone crab'
+ # })
+ # params.to_unsafe_h
+ # # => {"name"=>"Senjougahara Hitagi", "oddity" => "Heavy stone crab"}
def to_unsafe_h
convert_parameters_to_hashes(@parameters, :to_unsafe_h)
end
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 4bd727c14e..9fa2e38ae3 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -17,9 +17,7 @@ module ActionDispatch
end
def if_none_match_etags
- (if_none_match ? if_none_match.split(/\s*,\s*/) : []).collect do |etag|
- etag.gsub(/^\"|\"$/, "")
- end
+ if_none_match ? if_none_match.split(/\s*,\s*/) : []
end
def not_modified?(modified_at)
@@ -28,8 +26,8 @@ module ActionDispatch
def etag_matches?(etag)
if etag
- etag = etag.gsub(/^\"|\"$/, "")
- if_none_match_etags.include?(etag)
+ validators = if_none_match_etags
+ validators.include?(etag) || validators.include?('*')
end
end
@@ -80,27 +78,63 @@ module ActionDispatch
set_header DATE, utc_time.httpdate
end
- # This method allows you to set the ETag for cached content, which
- # will be returned to the end user.
+ # This method sets a weak ETag validator on the response so browsers
+ # and proxies may cache the response, keyed on the ETag. On subsequent
+ # requests, the If-None-Match header is set to the cached ETag. If it
+ # matches the current ETag, we can return a 304 Not Modified response
+ # with no body, letting the browser or proxy know that their cache is
+ # current. Big savings in request time and network bandwidth.
+ #
+ # Weak ETags are considered to be semantically equivalent but not
+ # byte-for-byte identical. This is perfect for browser caching of HTML
+ # pages where we don't care about exact equality, just what the user
+ # is viewing.
#
- # By default, Action Dispatch sets all ETags to be weak.
- # This ensures that if the content changes only semantically,
- # the whole page doesn't have to be regenerated from scratch
- # by the web server. With strong ETags, pages are compared
- # byte by byte, and are regenerated only if they are not exactly equal.
- def etag=(etag)
- key = ActiveSupport::Cache.expand_cache_key(etag)
- super %(W/"#{Digest::MD5.hexdigest(key)}")
+ # Strong ETags are considered byte-for-byte identical. They allow a
+ # browser or proxy cache to support Range requests, useful for paging
+ # through a PDF file or scrubbing through a video. Some CDNs only
+ # support strong ETags and will ignore weak ETags entirely.
+ #
+ # Weak ETags are what we almost always need, so they're the default.
+ # Check out `#strong_etag=` to provide a strong ETag validator.
+ def etag=(weak_validators)
+ self.weak_etag = weak_validators
+ end
+
+ def weak_etag=(weak_validators)
+ set_header 'ETag', generate_weak_etag(weak_validators)
+ end
+
+ def strong_etag=(strong_validators)
+ set_header 'ETag', generate_strong_etag(strong_validators)
end
def etag?; etag; end
+ # True if an ETag is set and it's a weak validator (preceded with W/)
+ def weak_etag?
+ etag? && etag.starts_with?('W/"')
+ end
+
+ # True if an ETag is set and it isn't a weak validator (not preceded with W/)
+ def strong_etag?
+ etag? && !weak_etag?
+ end
+
private
DATE = 'Date'.freeze
LAST_MODIFIED = "Last-Modified".freeze
SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate])
+ def generate_weak_etag(validators)
+ "W/#{generate_strong_etag(validators)}"
+ end
+
+ def generate_strong_etag(validators)
+ %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(validators))}")
+ end
+
def cache_control_segments
if cache_control = _cache_control
cache_control.delete(' ').split(',')
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index 5c3b7245d6..69a934b7cd 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -5,7 +5,7 @@ module ActionDispatch
# env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
# headers = ActionDispatch::Http::Headers.new(env)
# headers["Content-Type"] # => "text/plain"
- # headers["User-Agent"] # => "curl/7/43/0"
+ # headers["User-Agent"] # => "curl/7.43.0"
#
# Also note that when headers are mapped to CGI-like variables by the Rack
# server, both dashes and underscores are converted to underscores. This
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index 5841c978af..faf3262b8f 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -37,6 +37,7 @@ module ActionDispatch
# The +parsers+ argument can take Hash of parsers where key is identifying
# content mime type, and value is a lambda that is going to process data.
def self.new(app, parsers = {})
+ ActiveSupport::Deprecation.warn('ActionDispatch::ParamsParser is deprecated and will be removed in Rails 5.1. Configure the parameter parsing in ActionDispatch::Request.parameter_parsers.')
parsers = parsers.transform_keys { |key| key.respond_to?(:symbol) ? key.symbol : key }
ActionDispatch::Request.parameter_parsers = ActionDispatch::Request::DEFAULT_PARSERS.merge(parsers)
app
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 16b430c36e..5a747b5f17 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1598,7 +1598,7 @@ module ActionDispatch
route_options = options.dup
if _path && option_path
ActiveSupport::Deprecation.warn <<-eowarn
-Specifying strings for both :path and the route path is deprecated. Change things like this:
+Specifying strings for both :path and the route path is deprecated. Change things like this:
match #{_path.inspect}, :path => #{option_path.inspect}
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 85f202b823..16237bd564 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -517,14 +517,14 @@ module ActionDispatch
if route.segment_keys.include?(:controller)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
Using a dynamic :controller segment in a route is deprecated and
- will be removed in Rails 5.1
+ will be removed in Rails 5.1.
MSG
end
if route.segment_keys.include?(:action)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
Using a dynamic :action segment in a route is deprecated and
- will be removed in Rails 5.1
+ will be removed in Rails 5.1.
MSG
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 60c562d7cd..69ae5a8468 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -95,7 +95,7 @@ module ActionDispatch
ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
xhr and xml_http_request methods are deprecated in favor of
- `get "/posts", xhr: true` and `post "/posts/1", xhr: true`
+ `get "/posts", xhr: true` and `post "/posts/1", xhr: true`.
MSG
process(request_method, path, params: params, headers: headers, xhr: true)
diff --git a/actionpack/test/controller/api/with_cookies_test.rb b/actionpack/test/controller/api/with_cookies_test.rb
new file mode 100644
index 0000000000..4491dc9002
--- /dev/null
+++ b/actionpack/test/controller/api/with_cookies_test.rb
@@ -0,0 +1,21 @@
+require 'abstract_unit'
+
+class WithCookiesController < ActionController::API
+ include ActionController::Cookies
+
+ def with_cookies
+ render plain: cookies[:foobar]
+ end
+end
+
+class WithCookiesTest < ActionController::TestCase
+ tests WithCookiesController
+
+ def test_with_cookies
+ request.cookies[:foobar] = 'bazbang'
+
+ get :with_cookies
+
+ assert_equal 'bazbang', response.body
+ end
+end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 0c3884cd38..a7759c080b 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -205,7 +205,7 @@ module ActionController
def overfill_buffer_and_die
logger = ActionController::Base.logger || Logger.new($stdout)
response.stream.on_error do
- logger.warn 'Error while streaming'
+ logger.warn 'Error while streaming.'
error_latch.count_down
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 82fc8b0f8a..f42efd35af 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -16,6 +16,10 @@ class TestControllerWithExtraEtags < ActionController::Base
render plain: "stale" if stale?(etag: %w(1 2 3), template: false)
end
+ def strong
+ render plain: "stale" if stale?(strong_etag: 'strong')
+ end
+
def with_template
if stale? template: 'test/hello_world'
render plain: 'stale'
@@ -385,7 +389,7 @@ class LastModifiedRenderTest < ActionController::TestCase
def test_request_not_modified_but_etag_differs
@request.if_modified_since = @last_modified
- @request.if_none_match = "234"
+ @request.if_none_match = '"234"'
get :conditional_hello
assert_response :success
end
@@ -414,7 +418,7 @@ class LastModifiedRenderTest < ActionController::TestCase
def test_request_not_modified_but_etag_differs_with_record
@request.if_modified_since = @last_modified
- @request.if_none_match = "234"
+ @request.if_none_match = '"234"'
get :conditional_hello_with_record
assert_response :success
end
@@ -442,7 +446,7 @@ class LastModifiedRenderTest < ActionController::TestCase
def test_request_not_modified_but_etag_differs_with_collection_of_records
@request.if_modified_since = @last_modified
- @request.if_none_match = "234"
+ @request.if_none_match = '"234"'
get :conditional_hello_with_collection_of_records
assert_response :success
end
@@ -477,8 +481,26 @@ end
class EtagRenderTest < ActionController::TestCase
tests TestControllerWithExtraEtags
+ def test_strong_etag
+ @request.if_none_match = strong_etag(['strong', 'ab', :cde, [:f]])
+ get :strong
+ assert_response :not_modified
+
+ @request.if_none_match = '*'
+ get :strong
+ assert_response :not_modified
+
+ @request.if_none_match = '"strong"'
+ get :strong
+ assert_response :ok
+
+ @request.if_none_match = weak_etag(['strong', 'ab', :cde, [:f]])
+ get :strong
+ assert_response :ok
+ end
+
def test_multiple_etags
- @request.if_none_match = etag(["123", 'ab', :cde, [:f]])
+ @request.if_none_match = weak_etag(["123", 'ab', :cde, [:f]])
get :fresh
assert_response :not_modified
@@ -488,7 +510,7 @@ class EtagRenderTest < ActionController::TestCase
end
def test_array
- @request.if_none_match = etag([%w(1 2 3), 'ab', :cde, [:f]])
+ @request.if_none_match = weak_etag([%w(1 2 3), 'ab', :cde, [:f]])
get :array
assert_response :not_modified
@@ -523,9 +545,14 @@ class EtagRenderTest < ActionController::TestCase
end
end
- def etag(record)
- %(W/"#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record))}")
- end
+ private
+ def weak_etag(record)
+ "W/#{strong_etag record}"
+ end
+
+ def strong_etag(record)
+ %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record))}")
+ end
end
class MetalRenderTest < ActionController::TestCase
@@ -713,20 +740,24 @@ class HttpCacheForeverTest < ActionController::TestCase
def test_cache_with_public
get :cache_me_forever, params: {public: true}
+ assert_response :ok
assert_equal "max-age=#{100.years}, public", @response.headers["Cache-Control"]
assert_not_nil @response.etag
+ assert @response.weak_etag?
end
def test_cache_with_private
get :cache_me_forever
+ assert_response :ok
assert_equal "max-age=#{100.years}, private", @response.headers["Cache-Control"]
assert_not_nil @response.etag
- assert_response :success
+ assert @response.weak_etag?
end
def test_cache_response_code_with_if_modified_since
get :cache_me_forever
- assert_response :success
+ assert_response :ok
+
@request.if_modified_since = @response.headers['Last-Modified']
get :cache_me_forever
assert_response :not_modified
@@ -734,13 +765,10 @@ class HttpCacheForeverTest < ActionController::TestCase
def test_cache_response_code_with_etag
get :cache_me_forever
- assert_response :success
- @request.if_modified_since = @response.headers['Last-Modified']
- @request.if_none_match = @response.etag
+ assert_response :ok
+ @request.if_none_match = @response.etag
get :cache_me_forever
assert_response :not_modified
- @request.if_modified_since = @response.headers['Last-Modified']
- @request.if_none_match = @response.etag
end
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 0edad72fd9..a4cb8ce449 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -1152,36 +1152,41 @@ class RequestParameterFilter < BaseRequestTest
end
class RequestEtag < BaseRequestTest
- test "if_none_match_etags none" do
+ test "always matches *" do
+ request = stub_request('HTTP_IF_NONE_MATCH' => '*')
+
+ assert_equal '*', request.if_none_match
+ assert_equal ['*'], request.if_none_match_etags
+
+ assert request.etag_matches?('"strong"')
+ assert request.etag_matches?('W/"weak"')
+ assert_not request.etag_matches?(nil)
+ end
+
+ test "doesn't match absent If-None-Match" do
request = stub_request
assert_equal nil, request.if_none_match
assert_equal [], request.if_none_match_etags
- assert !request.etag_matches?("foo")
- assert !request.etag_matches?(nil)
- end
- test "if_none_match_etags single" do
- header = 'the-etag'
- request = stub_request('HTTP_IF_NONE_MATCH' => header)
-
- assert_equal header, request.if_none_match
- assert_equal [header], request.if_none_match_etags
- assert request.etag_matches?("the-etag")
+ assert_not request.etag_matches?("foo")
+ assert_not request.etag_matches?(nil)
end
- test "if_none_match_etags quoted single" do
+ test "matches opaque ETag validators without unquoting" do
header = '"the-etag"'
request = stub_request('HTTP_IF_NONE_MATCH' => header)
assert_equal header, request.if_none_match
- assert_equal ['the-etag'], request.if_none_match_etags
- assert request.etag_matches?("the-etag")
+ assert_equal ['"the-etag"'], request.if_none_match_etags
+
+ assert request.etag_matches?('"the-etag"')
+ assert_not request.etag_matches?("the-etag")
end
test "if_none_match_etags multiple" do
header = 'etag1, etag2, "third etag", "etag4"'
- expected = ['etag1', 'etag2', 'third etag', 'etag4']
+ expected = ['etag1', 'etag2', '"third etag"', '"etag4"']
request = stub_request('HTTP_IF_NONE_MATCH' => header)
assert_equal header, request.if_none_match
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index cd385982d9..658e0d004b 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -189,7 +189,7 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal({"user_name" => "david", "login" => nil}, @response.cookies)
end
- test "read cache control" do
+ test "read ETag and Cache-Control" do
resp = ActionDispatch::Response.new.tap { |response|
response.cache_control[:public] = true
response.etag = '123'
@@ -197,6 +197,9 @@ class ResponseTest < ActiveSupport::TestCase
}
resp.to_a
+ assert resp.etag?
+ assert resp.weak_etag?
+ assert_not resp.strong_etag?
assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.etag)
assert_equal({:public => true}, resp.cache_control)
@@ -204,6 +207,20 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.headers['ETag'])
end
+ test "read strong ETag" do
+ resp = ActionDispatch::Response.new.tap { |response|
+ response.cache_control[:public] = true
+ response.strong_etag = '123'
+ response.body = 'Hello'
+ }
+ resp.to_a
+
+ assert resp.etag?
+ assert_not resp.weak_etag?
+ assert resp.strong_etag?
+ assert_equal('"202cb962ac59075b964b07152d234b70"', resp.etag)
+ end
+
test "read charset and content type" do
resp = ActionDispatch::Response.new.tap { |response|
response.charset = 'utf-16'
@@ -446,11 +463,19 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type'])
end
- test "we can set strong ETag by directly adding it as header" do
- @response = ActionDispatch::Response.create
- @response.add_header "ETag", '"202cb962ac59075b964b07152d234b70"'
+ test "strong ETag validator" do
+ @app = lambda { |env|
+ ActionDispatch::Response.new.tap { |resp|
+ resp.strong_etag = '123'
+ resp.body = 'Hello'
+ resp.request = ActionDispatch::Request.empty
+ }.to_a
+ }
+
+ get '/'
+ assert_response :ok
- assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag)
assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
+ assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag)
end
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index ecc1e764a5..a1901e8a17 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,9 @@
+* Deprecate `datetime_field` and `datetime_field_tag` helpers.
+ Datetime input type was removed from HTML specification.
+ One can use `datetime_local_field` and `datetime_local_field_tag` instead.
+
+ *Wojciech Wnętrzak*
+
* Added log "Rendering ...", when starting to render a template to log that
we have started rendering something. This helps to easily identify the origin
of queries in the log whether they came from controller or views.
diff --git a/actionview/lib/action_view/flows.rb b/actionview/lib/action_view/flows.rb
index bc61920848..4b912f0b2b 100644
--- a/actionview/lib/action_view/flows.rb
+++ b/actionview/lib/action_view/flows.rb
@@ -37,9 +37,8 @@ module ActionView
end
# Try to get stored content. If the content
- # is not available and we are inside the layout
- # fiber, we set that we are waiting for the given
- # key and yield.
+ # is not available and we're inside the layout fiber,
+ # then it will begin waiting for the given key and yield.
def get(key)
return super if @content.key?(key)
@@ -60,8 +59,8 @@ module ActionView
end
# Appends the contents for the given key. This is called
- # by provides and resumes back to the fiber if it is
- # the key it is waiting for.
+ # by providing and resuming back to the fiber,
+ # if that's the key it's waiting for.
def append!(key, value)
super
@fiber.resume if @waiting_for == key
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index e91b3443c5..7ced37572e 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -1118,6 +1118,10 @@ module ActionView
# # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" />
#
def datetime_field(object_name, method, options = {})
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ datetime_field is deprecated and will be removed in Rails 5.1.
+ Use datetime_local_field instead.
+ MESSAGE
Tags::DatetimeField.new(object_name, method, self, options).render
end
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 82e9ace428..cfff0bef5d 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -691,6 +691,10 @@ module ActionView
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def datetime_field_tag(name, value = nil, options = {})
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ datetime_field_tag is deprecated and will be removed in Rails 5.1.
+ Use datetime_local_field_tag instead.
+ MESSAGE
text_field_tag(name, value, options.merge(type: :datetime))
end
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index 161aa031c6..f0222582c7 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -23,7 +23,7 @@ module ActionView
end
end
- # Formats a +number+ into a US phone number (e.g., (555)
+ # Formats a +number+ into a phone number (US by default e.g., (555)
# 123-9876). You can customize the format in the +options+ hash.
#
# ==== Options
@@ -35,6 +35,8 @@ module ActionView
# end of the generated number.
# * <tt>:country_code</tt> - Sets the country code for the phone
# number.
+ # * <tt>:pattern</tt> - Specifies how the number is divided into three
+ # groups with the custom regexp to override the default format.
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
@@ -52,6 +54,11 @@ module ActionView
#
# number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: ".")
# # => +1.123.555.1234 x 1343
+ #
+ # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)
+ # # => "(755) 6123-4567"
+ # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/))
+ # # => "133-1234-5678"
def number_to_phone(number, options = {})
return unless number
options = options.symbolize_keys
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index 191a881de0..f9784c3483 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -120,7 +120,7 @@ module ActionView
attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
# Vendors the full, link and white list sanitizers.
- # Provided strictly for compatibility and can be removed in Rails 5.
+ # Provided strictly for compatibility and can be removed in Rails 5.1.
def sanitizer_vendor
Rails::Html::Sanitizer
end
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index e77183e39f..310d0ce514 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -1139,13 +1139,17 @@ class FormHelperTest < ActionView::TestCase
def test_datetime_field
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T00:00:00.000+0000" />}
- assert_dom_equal(expected, datetime_field("post", "written_on"))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on"))
+ end
end
def test_datetime_field_with_datetime_value
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T01:02:03.000+0000" />}
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
- assert_dom_equal(expected, datetime_field("post", "written_on"))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on"))
+ end
end
def test_datetime_field_with_extra_attrs
@@ -1154,20 +1158,26 @@ class FormHelperTest < ActionView::TestCase
min_value = DateTime.new(2000, 6, 15, 20, 45, 30)
max_value = DateTime.new(2010, 8, 15, 10, 25, 00)
step = 60
- assert_dom_equal(expected, datetime_field("post", "written_on", min: min_value, max: max_value, step: step))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on", min: min_value, max: max_value, step: step))
+ end
end
def test_datetime_field_with_value_attr
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2013-06-29T13:37:00+00:00" />}
value = DateTime.new(2013,6,29,13,37)
- assert_dom_equal(expected, datetime_field("post", "written_on", value: value))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on", value: value))
+ end
end
def test_datetime_field_with_timewithzone_value
previous_time_zone, Time.zone = Time.zone, 'UTC'
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T15:30:45.000+0000" />}
@post.written_on = Time.zone.parse('2004-06-15 15:30:45')
- assert_dom_equal(expected, datetime_field("post", "written_on"))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on"))
+ end
ensure
Time.zone = previous_time_zone
end
@@ -1175,7 +1185,9 @@ class FormHelperTest < ActionView::TestCase
def test_datetime_field_with_nil_value
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" />}
@post.written_on = nil
- assert_dom_equal(expected, datetime_field("post", "written_on"))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on"))
+ end
end
def test_datetime_field_with_string_values_for_min_and_max
@@ -1183,7 +1195,9 @@ class FormHelperTest < ActionView::TestCase
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
min_value = "2000-06-15T20:45:30.000+0000"
max_value = "2010-08-15T10:25:00.000+0000"
- assert_dom_equal(expected, datetime_field("post", "written_on", min: min_value, max: max_value))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on", min: min_value, max: max_value))
+ end
end
def test_datetime_field_with_invalid_string_values_for_min_and_max
@@ -1191,7 +1205,9 @@ class FormHelperTest < ActionView::TestCase
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
min_value = "foo"
max_value = "bar"
- assert_dom_equal(expected, datetime_field("post", "written_on", min: min_value, max: max_value))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on", min: min_value, max: max_value))
+ end
end
def test_datetime_local_field
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index 07b3fba754..7b93c8dc29 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -622,7 +622,9 @@ class FormTagHelperTest < ActionView::TestCase
def test_datetime_field_tag
expected = %{<input id="appointment" name="appointment" type="datetime" />}
- assert_dom_equal(expected, datetime_field_tag("appointment"))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field_tag("appointment"))
+ end
end
def test_datetime_local_field_tag
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index e06b98736d..3feb82d432 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -193,26 +193,13 @@ module ActiveJob
# end
#
# The block form supports filtering. If the :only option is specified,
- # then only the listed job(s) will be performed.
+ # then only the listed job(s) will not be performed.
#
- # def test_hello_job
- # assert_performed_jobs 1, only: HelloJob do
- # HelloJob.perform_later('jeremy')
- # LoggingJob.perform_later
- # end
- # end
- #
- # An array may also be specified, to support testing multiple jobs.
- #
- # def test_hello_and_logging_jobs
- # assert_nothing_raised do
- # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do
- # HelloJob.perform_later('jeremy')
- # LoggingJob.perform_later('stewie')
- # RescueJob.perform_later('david')
- # end
- # end
+ # def test_no_logging
+ # assert_no_performed_jobs only: LoggingJob do
+ # HelloJob.perform_later('jeremy')
# end
+ # end
#
# Note: This assertion is simply a shortcut for:
#
diff --git a/activejob/lib/rails/generators/job/job_generator.rb b/activejob/lib/rails/generators/job/job_generator.rb
index 2115fb9f71..6e43e4a269 100644
--- a/activejob/lib/rails/generators/job/job_generator.rb
+++ b/activejob/lib/rails/generators/job/job_generator.rb
@@ -17,7 +17,22 @@ module Rails # :nodoc:
def create_job_file
template 'job.rb', File.join('app/jobs', class_path, "#{file_name}_job.rb")
+
+ in_root do
+ if self.behavior == :invoke && !File.exist?(application_job_file_name)
+ template 'application_job.rb', application_job_file_name
+ end
+ end
end
+
+ private
+ def application_job_file_name
+ @application_job_file_name ||= if mountable_engine?
+ "app/jobs/#{namespaced_path}/application_job.rb"
+ else
+ 'app/jobs/application_job.rb'
+ end
+ end
end
end
end
diff --git a/activejob/lib/rails/generators/job/templates/application_job.rb b/activejob/lib/rails/generators/job/templates/application_job.rb
new file mode 100644
index 0000000000..0b113b950e
--- /dev/null
+++ b/activejob/lib/rails/generators/job/templates/application_job.rb
@@ -0,0 +1,4 @@
+<% module_namespacing do -%>
+class ApplicationJob < ActiveJob::Base
+end
+<% end -%>
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index fb7ab5cb40..7be8b2e522 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Allow passing record being validated to the message proc to generate
+ customized error messages for that object using I18n helper.
+
+ *Prathamesh Sonpatki*
+
## Rails 5.0.0.beta3 (February 24, 2016) ##
* No changes.
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 6e2e5afd1b..9aca5f2c90 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -174,11 +174,13 @@ module ActiveModel
end
# Handles <tt>*_changed?</tt> for +method_missing+.
- def attribute_changed?(attr, options = {}) #:nodoc:
+ def attribute_changed?(attr, options = nil) #:nodoc:
result = changes_include?(attr)
- result &&= options[:to] == __send__(attr) if options.key?(:to)
- result &&= options[:from] == changed_attributes[attr] if options.key?(:from)
- result
+ if options
+ result &&= options[:to] == __send__(attr) if options.key?(:to)
+ result &&= options[:from] == changed_attributes[attr] if options.key?(:from)
+ end
+ !!result
end
# Handles <tt>*_was</tt> for +method_missing+.
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 836201535f..d106f65fa2 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -346,7 +346,7 @@ module ActiveModel
# # => {:name=>["can't be empty"]}
def add_on_empty(attributes, options = {})
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- ActiveModel::Errors#add_on_empty is deprecated and will be removed in Rails 5.1
+ ActiveModel::Errors#add_on_empty is deprecated and will be removed in Rails 5.1.
To achieve the same use:
@@ -368,7 +368,7 @@ module ActiveModel
# # => {:name=>["can't be blank"]}
def add_on_blank(attributes, options = {})
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- ActiveModel::Errors#add_on_blank is deprecated and will be removed in Rails 5.1
+ ActiveModel::Errors#add_on_blank is deprecated and will be removed in Rails 5.1.
To achieve the same use:
@@ -486,7 +486,8 @@ module ActiveModel
default: defaults,
model: @base.model_name.human,
attribute: @base.class.human_attribute_name(attribute),
- value: value
+ value: value,
+ object: @base
}.merge!(options)
I18n.translate(key, options)
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index 910cca2f49..79297ac119 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -136,7 +136,7 @@ module ActiveModel
# * <tt>:too_long</tt> - The error message if the attribute goes over the
# maximum (default is: "is too long (maximum is %{count} characters)").
# * <tt>:too_short</tt> - The error message if the attribute goes under the
- # minimum (default is: "is too short (min is %{count} characters)").
+ # minimum (default is: "is too short (minimum is %{count} characters)").
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt>
# method and the attribute is the wrong size (default is: "is the wrong
# length (should be %{count} characters)").
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index f0317ad219..2a4e9f842f 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -444,4 +444,20 @@ class ValidationsTest < ActiveModel::TestCase
assert topic.invalid?
assert duped.valid?
end
+
+ def test_validation_with_message_as_proc_that_takes_a_record_as_a_parameter
+ Topic.validates_presence_of(:title, message: proc { |record| "You have failed me for the last time, #{record.author_name}." })
+
+ t = Topic.new(author_name: 'Admiral')
+ assert t.invalid?
+ assert_equal ["You have failed me for the last time, Admiral."], t.errors[:title]
+ end
+
+ def test_validation_with_message_as_proc_that_takes_record_and_data_as_a_parameters
+ Topic.validates_presence_of(:title, message: proc { |record, data| "#{data[:attribute]} is missing. You have failed me for the last time, #{record.author_name}." })
+
+ t = Topic.new(author_name: 'Admiral')
+ assert t.invalid?
+ assert_equal ["Title is missing. You have failed me for the last time, Admiral."], t.errors[:title]
+ end
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 53b669dc72..291e5a2513 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,20 @@
+* MariaDB 5.3+ supports microsecond datetime precision.
+
+ *Jeremy Daer*
+
+* Delegate `empty?`, `none?` and `one?`. Now they can be invoked as model class methods.
+
+ Example:
+
+ # When no record is found on the table
+ Topic.empty? # => true
+ Topic.none? # => true
+
+ # When only one record is found on the table
+ Topic.one? # => true
+
+ *Kenta Shirai*
+
* The form builder now properly displays values when passing a proc form
default to the attributes API.
@@ -5,6 +22,12 @@
*Sean Griffin*
+* The schema cache is now cleared after the `db:migrate` task is run.
+
+ Closes #24273.
+
+ *Chris Arcand*
+
* MySQL: strict mode respects other SQL modes rather than overwriting them.
Setting `strict: true` adds `STRICT_ALL_TABLES` to `sql_mode`. Setting
`strict: false` removes `STRICT_TRANS_TABLES`, `STRICT_ALL_TABLES`, and
@@ -538,13 +561,13 @@
* Add option to index errors in nested attributes
For models which have nested attributes, errors within those models will
- now be indexed if :index_errors is specified when defining a
+ now be indexed if `:index_errors` is specified when defining a
has_many relationship, or if its set in the global config.
Example:
class Guitar < ActiveRecord::Base
- has_many :tuning_pegs
+ has_many :tuning_pegs, index_errors: true
accepts_nested_attributes_for :tuning_pegs
end
@@ -1422,11 +1445,6 @@
*Hyonjee Joo*
-* Deprecate passing of `start` value to `find_in_batches` and `find_each`
- in favour of `begin_at` value.
-
- *Vipul A M*
-
* Add `foreign_key_exists?` method.
*Tõnis Simo*
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index e13fe33b85..77d17fc975 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -504,7 +504,7 @@ module ActiveRecord
#
# == Customizing the query
#
- # \Associations are built from <tt>Relation</tt>s, and you can use the Relation syntax
+ # \Associations are built from <tt>Relation</tt> objects, and you can use the Relation syntax
# to customize them. For example, to add a condition:
#
# class Blog < ActiveRecord::Base
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index ecf6fb8643..e64af84e1a 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -184,6 +184,7 @@ module ActiveRecord
def self.new(klass, owners, reflection, preload_scope); self; end
def self.run(preloader); end
def self.preloaded_records; []; end
+ def self.owners; []; end
end
# Returns a class containing the logic needed to load preload the data
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 7ed2fe48be..6a1a27ce41 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -169,7 +169,8 @@ module ActiveRecord #:nodoc:
# ActiveRecord::RecordNotFound error if they do not return any records,
# like <tt>Person.find_by_last_name!</tt>.
#
- # It's also possible to use multiple attributes in the same find by separating them with "_and_".
+ # It's also possible to use multiple attributes in the same <tt>find_by_</tt> by separating them with
+ # "_and_".
#
# Person.find_by(user_name: user_name, password: password)
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 7e3760d34b..2eeefb13d7 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -82,7 +82,7 @@ module ActiveRecord
# Quotes the column name. Defaults to no quoting.
def quote_column_name(column_name)
- column_name
+ column_name.to_s
end
# Quotes the table name. Defaults to column name quoting.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index a401310ee0..020d9bbdca 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1051,9 +1051,9 @@ module ActiveRecord
end
# Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
- # Additional options (like <tt>null: false</tt>) are forwarded to #add_column.
+ # Additional options (like +:null+) are forwarded to #add_column.
#
- # add_timestamps(:suppliers, null: false)
+ # add_timestamps(:suppliers, null: true)
#
def add_timestamps(table_name, options = {})
options[:null] = false if options[:null].nil?
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 6704843c07..069346253a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -106,6 +106,7 @@ module ActiveRecord
@schema_cache = SchemaCache.new self
@visitor = nil
@prepared_statements = false
+ @quoted_column_names, @quoted_table_names = {}, {}
end
class Version
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 0e22062913..1797af4e97 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,6 +1,7 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/connection_adapters/mysql/column'
require 'active_record/connection_adapters/mysql/explain_pretty_printer'
+require 'active_record/connection_adapters/mysql/quoting'
require 'active_record/connection_adapters/mysql/schema_creation'
require 'active_record/connection_adapters/mysql/schema_definitions'
require 'active_record/connection_adapters/mysql/schema_dumper'
@@ -11,6 +12,7 @@ require 'active_support/core_ext/string/strip'
module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter < AbstractAdapter
+ include MySQL::Quoting
include MySQL::ColumnDumper
include Savepoints
@@ -32,8 +34,6 @@ module ActiveRecord
class_attribute :emulate_booleans
self.emulate_booleans = true
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
-
NATIVE_DATABASE_TYPES = {
primary_key: "int auto_increment PRIMARY KEY",
string: { name: "varchar", limit: 255 },
@@ -54,7 +54,6 @@ module ActiveRecord
def initialize(connection, logger, connection_options, config)
super(connection, logger, config)
- @quoted_column_names, @quoted_table_names = {}, {}
@visitor = Arel::Visitors::MySQL.new self
@@ -122,7 +121,11 @@ module ActiveRecord
end
def supports_datetime_with_precision?
- version >= '5.6.4'
+ if mariadb?
+ version >= '5.3.0'
+ else
+ version >= '5.6.4'
+ end
end
def supports_advisory_locks?
@@ -163,48 +166,6 @@ module ActiveRecord
raise NotImplementedError
end
- # QUOTING ==================================================
-
- def _quote(value) # :nodoc:
- if value.is_a?(Type::Binary::Data)
- "x'#{value.hex}'"
- else
- super
- end
- end
-
- def quote_column_name(name) #:nodoc:
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
- end
-
- def quote_table_name(name) #:nodoc:
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
- end
-
- def quoted_true
- QUOTED_TRUE
- end
-
- def unquoted_true
- 1
- end
-
- def quoted_false
- QUOTED_FALSE
- end
-
- def unquoted_false
- 0
- end
-
- def quoted_date(value)
- if supports_datetime_with_precision?
- super
- else
- super.sub(/\.\d{6}\z/, '')
- end
- end
-
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity #:nodoc:
@@ -301,9 +262,9 @@ module ActiveRecord
# create_database 'matt_development', charset: :big5
def create_database(name, options = {})
if options[:collation]
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}"
else
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
end
end
@@ -312,7 +273,7 @@ module ActiveRecord
# Example:
# drop_database('sebastian_development')
def drop_database(name) #:nodoc:
- execute "DROP DATABASE IF EXISTS `#{name}`"
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
end
def current_database
@@ -584,8 +545,7 @@ module ActiveRecord
# SHOW VARIABLES LIKE 'name'
def show_variable(name)
- variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
- variables.first['Value'] unless variables.empty?
+ select_value("SELECT @@#{name}", 'SCHEMA')
rescue ActiveRecord::StatementInvalid
nil
end
@@ -856,6 +816,7 @@ module ActiveRecord
sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
end
+ sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
end
sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
@@ -951,8 +912,8 @@ module ActiveRecord
class MysqlString < Type::String # :nodoc:
def serialize(value)
case value
- when true then "1"
- when false then "0"
+ when true then MySQL::Quoting::QUOTED_TRUE
+ when false then MySQL::Quoting::QUOTED_FALSE
else super
end
end
@@ -961,8 +922,8 @@ module ActiveRecord
def cast_value(value)
case value
- when true then "1"
- when false then "0"
+ when true then MySQL::Quoting::QUOTED_TRUE
+ when false then MySQL::Quoting::QUOTED_FALSE
else super
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
new file mode 100644
index 0000000000..fbab654112
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
@@ -0,0 +1,51 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ module Quoting # :nodoc:
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
+
+ def quote_column_name(name)
+ @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
+ end
+
+ def quote_table_name(name)
+ @quoted_table_names[name] ||= super.gsub('.', '`.`')
+ end
+
+ def quoted_true
+ QUOTED_TRUE
+ end
+
+ def unquoted_true
+ 1
+ end
+
+ def quoted_false
+ QUOTED_FALSE
+ end
+
+ def unquoted_false
+ 0
+ end
+
+ def quoted_date(value)
+ if supports_datetime_with_precision?
+ super
+ else
+ super.sub(/\.\d{6}\z/, '')
+ end
+ end
+
+ private
+
+ def _quote(value)
+ if value.is_a?(Type::Binary::Data)
+ "x'#{value.hex}'"
+ else
+ super
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index c1c77a967e..6414459cd1 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -27,8 +27,8 @@ module ActiveRecord
# - schema_name."table.name"
# - "schema.name".table_name
# - "schema.name"."table.name"
- def quote_table_name(name)
- Utils.extract_schema_qualified_name(name.to_s).quoted
+ def quote_table_name(name) # :nodoc:
+ @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted
end
# Quotes schema names for use in SQL queries.
@@ -41,8 +41,8 @@ module ActiveRecord
end
# Quotes column names for use in SQL queries.
- def quote_column_name(name) #:nodoc:
- PGconn.quote_ident(name.to_s)
+ def quote_column_name(name) # :nodoc:
+ @quoted_column_names[name] ||= PGconn.quote_ident(super)
end
# Quote date/time values for use in SQL input.
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index ca2a41b136..39e8492688 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -325,7 +325,7 @@ module ActiveRecord
select_value("SELECT setval('#{quoted_sequence}', #{value})", 'SCHEMA')
else
- @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
+ @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
end
end
end
@@ -340,7 +340,7 @@ module ActiveRecord
end
if @logger && pk && !sequence
- @logger.warn "#{table} has primary key #{pk} with no default sequence"
+ @logger.warn "#{table} has primary key #{pk} with no default sequence."
end
if pk && sequence
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
new file mode 100644
index 0000000000..d3a91f73c8
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -0,0 +1,44 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module SQLite3
+ module Quoting # :nodoc:
+ def quote_string(s)
+ @connection.class.quote(s)
+ end
+
+ def quote_table_name_for_assignment(table, attr)
+ quote_column_name(attr)
+ end
+
+ def quote_column_name(name)
+ @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
+ end
+
+ private
+
+ def _quote(value)
+ if value.is_a?(Type::Binary::Data)
+ "x'#{value.hex}'"
+ else
+ super
+ end
+ end
+
+ def _type_cast(value)
+ case value
+ when BigDecimal
+ value.to_f
+ when String
+ if value.encoding == Encoding::ASCII_8BIT
+ super(value.encode(Encoding::UTF_8))
+ else
+ super
+ end
+ else
+ super
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 7ac81bdf23..786b0ab2ed 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -1,6 +1,7 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/connection_adapters/statement_pool'
require 'active_record/connection_adapters/sqlite3/explain_pretty_printer'
+require 'active_record/connection_adapters/sqlite3/quoting'
require 'active_record/connection_adapters/sqlite3/schema_creation'
gem 'sqlite3', '~> 1.3.6'
@@ -49,6 +50,8 @@ module ActiveRecord
# * <tt>:database</tt> - Path to the database file.
class SQLite3Adapter < AbstractAdapter
ADAPTER_NAME = 'SQLite'.freeze
+
+ include SQLite3::Quoting
include Savepoints
NATIVE_DATABASE_TYPES = {
@@ -84,7 +87,6 @@ module ActiveRecord
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
@visitor = Arel::Visitors::SQLite.new self
- @quoted_column_names = {}
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
@@ -174,44 +176,6 @@ module ActiveRecord
true
end
- # QUOTING ==================================================
-
- def _quote(value) # :nodoc:
- case value
- when Type::Binary::Data
- "x'#{value.hex}'"
- else
- super
- end
- end
-
- def _type_cast(value) # :nodoc:
- case value
- when BigDecimal
- value.to_f
- when String
- if value.encoding == Encoding::ASCII_8BIT
- super(value.encode(Encoding::UTF_8))
- else
- super
- end
- else
- super
- end
- end
-
- def quote_string(s) #:nodoc:
- @connection.class.quote(s)
- end
-
- def quote_table_name_for_assignment(table, attr)
- quote_column_name(attr)
- end
-
- def quote_column_name(name) #:nodoc:
- @quoted_column_names[name] ||= %Q("#{name.to_s.gsub('"', '""')}")
- end
-
#--
# DATABASE STATEMENTS ======================================
#++
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 86ec8000fb..5d74631e32 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -136,7 +136,7 @@ module ActiveRecord
end
def initialize_find_by_cache # :nodoc:
- @find_by_statement_cache = {}.extend(Mutex_m)
+ @find_by_statement_cache = { true => {}.extend(Mutex_m), false => {}.extend(Mutex_m) }
end
def inherited(child_class) # :nodoc:
@@ -159,7 +159,7 @@ module ActiveRecord
id = id.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `find`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
@@ -280,8 +280,9 @@ module ActiveRecord
private
def cached_find_by_statement(key, &block) # :nodoc:
- @find_by_statement_cache[key] || @find_by_statement_cache.synchronize {
- @find_by_statement_cache[key] ||= StatementCache.create(connection, &block)
+ cache = @find_by_statement_cache[connection.prepared_statements]
+ cache[key] || cache.synchronize {
+ cache[key] ||= StatementCache.create(connection, &block)
}
end
diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb
index f969556c50..e4a44244e2 100644
--- a/activerecord/lib/active_record/fixture_set/file.rb
+++ b/activerecord/lib/active_record/fixture_set/file.rb
@@ -52,9 +52,15 @@ module ActiveRecord
end
end
+ def prepare_erb(content)
+ erb = ERB.new(content)
+ erb.filename = @file
+ erb
+ end
+
def render(content)
context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new
- ERB.new(content).result(context.get_binding)
+ prepare_erb(content).result(context.get_binding)
end
# Validate our unmarshalled data.
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 245c05f3e0..99a79024ad 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -156,8 +156,8 @@ module ActiveRecord
class ProtectedEnvironmentError < ActiveRecordError #:nodoc:
def initialize(env = "production")
- msg = "You are attempting to run a destructive action against your '#{env}' database\n"
- msg << "If you are sure you want to continue, run the same command with the environment variable\n"
+ msg = "You are attempting to run a destructive action against your '#{env}' database.\n"
+ msg << "If you are sure you want to continue, run the same command with the environment variable:\n"
msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
super(msg)
end
@@ -540,7 +540,7 @@ module ActiveRecord
MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ # :nodoc:
# This class is used to verify that all migrations have been run before
- # loading a web page if config.active_record.migration_error is set to :page_load
+ # loading a web page if <tt>config.active_record.migration_error</tt> is set to :page_load
class CheckPending
def initialize(app)
@app = app
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index 09d55adcd7..a20d7e0820 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -57,7 +57,7 @@ module ActiveRecord
def index_exists?(table_name, column_name, options = {})
column_names = Array(column_name).map(&:to_s)
options[:name] =
- if options.key?(:name).present?
+ if options[:name].present?
options[:name].to_s
else
index_name(table_name, column: column_names)
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index f451ed1764..bbbc824b25 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -23,23 +23,27 @@ module ActiveRecord
end
end
- def self.install_executor_hooks(executor = ActiveSupport::Executor)
- executor.to_run do
- connection = ActiveRecord::Base.connection
- enabled = connection.query_cache_enabled
- connection_id = ActiveRecord::Base.connection_id
- connection.enable_query_cache!
+ def self.run
+ connection = ActiveRecord::Base.connection
+ enabled = connection.query_cache_enabled
+ connection_id = ActiveRecord::Base.connection_id
+ connection.enable_query_cache!
- @restore_query_cache_settings = lambda do
- ActiveRecord::Base.connection_id = connection_id
- ActiveRecord::Base.connection.clear_query_cache
- ActiveRecord::Base.connection.disable_query_cache! unless enabled
- end
- end
+ [enabled, connection_id]
+ end
- executor.to_complete do
- @restore_query_cache_settings.call if defined?(@restore_query_cache_settings)
+ def self.complete(state)
+ enabled, connection_id = state
+ ActiveRecord::Base.connection_id = connection_id
+ ActiveRecord::Base.connection.clear_query_cache
+ ActiveRecord::Base.connection.disable_query_cache! unless enabled
+ end
+
+ def self.install_executor_hooks(executor = ActiveSupport::Executor)
+ executor.register_hook(self)
+
+ executor.to_complete do
# FIXME: This should be skipped when env['rack.test']
ActiveRecord::Base.clear_active_connections!
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index de5b42e987..4e32d73001 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module Querying
- delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, to: :all
+ delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :empty?, :none?, :one?, to: :all
delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all
delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all
delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 00cf8536e1..fc1b62ee96 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -256,7 +256,7 @@ db_namespace = namespace :db do
end
desc 'Loads a schema.rb file into the database'
- task :load => [:environment, :load_config] do
+ task :load => [:environment, :load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA'])
end
@@ -278,7 +278,7 @@ db_namespace = namespace :db do
desc 'Clears a db/schema_cache.dump file.'
task :clear => [:environment, :load_config] do
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
- FileUtils.rm(filename) if File.exist?(filename)
+ rm_f filename, verbose: false
end
end
@@ -302,7 +302,7 @@ db_namespace = namespace :db do
end
desc "Recreates the databases from the structure.sql file"
- task :load => [:environment, :load_config] do
+ task :load => [:environment, :load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['SCHEMA'])
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index f8dffce2f1..bf398b0d40 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -874,7 +874,7 @@ module ActiveRecord
example_options = options.dup
example_options[:source] = source_reflection_names.first
ActiveSupport::Deprecation.warn \
- "Ambiguous source reflection for through association. Please " \
+ "Ambiguous source reflection for through association. Please " \
"specify a :source directive on your declaration like:\n" \
"\n" \
" class #{active_record.name} < ActiveRecord::Base\n" \
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 777b593812..d042fe5f8b 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -94,12 +94,12 @@ module ActiveRecord
end
def substitute_values(values) # :nodoc:
- binds = values.map do |arel_attr, value|
- QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name))
- end
+ binds = []
+ substitutes = []
- substitutes = values.map do |(arel_attr, _)|
- [arel_attr, Arel::Nodes::BindParam.new]
+ values.each do |arel_attr, value|
+ binds.push QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name))
+ substitutes.push [arel_attr, Arel::Nodes::BindParam.new]
end
[substitutes, binds]
@@ -428,7 +428,7 @@ module ActiveRecord
id = id.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `update`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
object = find(id)
@@ -457,7 +457,7 @@ module ActiveRecord
if conditions
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
Passing conditions to destroy_all is deprecated and will be removed in Rails 5.1.
- To achieve the same use where(conditions).destroy_all
+ To achieve the same use where(conditions).destroy_all.
MESSAGE
where(conditions).destroy_all
else
@@ -527,7 +527,7 @@ module ActiveRecord
if conditions
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
Passing conditions to delete_all is deprecated and will be removed in Rails 5.1.
- To achieve the same use where(conditions).delete_all
+ To achieve the same use where(conditions).delete_all.
MESSAGE
where(conditions).delete_all
else
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index b99807adf3..3639625722 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -2,7 +2,7 @@ require "active_record/relation/batches/batch_enumerator"
module ActiveRecord
module Batches
- ORDER_OR_LIMIT_IGNORED_MESSAGE = "Scoped order and limit are ignored, it's forced to be batch order and batch size"
+ ORDER_OR_LIMIT_IGNORED_MESSAGE = "Scoped order and limit are ignored, it's forced to be batch order and batch size."
# Looping through a collection of records from the database
# (using the Scoping::Named::ClassMethods.all method, for example)
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 27dd0b4143..e7e331f88d 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -312,7 +312,7 @@ module ActiveRecord
conditions = conditions.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `exists?`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
@@ -467,7 +467,7 @@ module ActiveRecord
id = id.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `find`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 953495a8b6..ecce949370 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -136,9 +136,11 @@ module ActiveRecord
end
def convert_dot_notation_to_hash(attributes)
- dot_notation = attributes.keys.select { |s| s.include?(".".freeze) }
+ dot_notation = attributes.select do |k, v|
+ k.include?(".".freeze) && !v.is_a?(Hash)
+ end
- dot_notation.each do |key|
+ dot_notation.each_key do |key|
table_name, column_name = key.split(".".freeze)
value = attributes.delete(key)
attributes[table_name] ||= {}
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index a9f1993842..4f389e9249 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -23,6 +23,12 @@ module ActiveRecord
end
end
+ def test_create_record_with_pk_as_zero
+ Book.create(id: 0)
+ assert_equal 0, Book.find(0).id
+ assert_nothing_raised { Book.destroy(0) }
+ end
+
def test_tables
tables = nil
ActiveSupport::Deprecation.silence { tables = @connection.tables }
diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
index 8575df9e43..739bb275ce 100644
--- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
@@ -43,11 +43,16 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
boolean = BooleanType.create!(archived: true, published: true)
attributes = boolean.reload.attributes_before_type_cast
-
assert_equal 1, attributes["archived"]
assert_equal "1", attributes["published"]
+ boolean = BooleanType.create!(archived: false, published: false)
+ attributes = boolean.reload.attributes_before_type_cast
+ assert_equal 0, attributes["archived"]
+ assert_equal "0", attributes["published"]
+
assert_equal 1, @connection.type_cast(true)
+ assert_equal 0, @connection.type_cast(false)
end
test "test type casting without emulated booleans" do
@@ -55,11 +60,16 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
boolean = BooleanType.create!(archived: true, published: true)
attributes = boolean.reload.attributes_before_type_cast
-
assert_equal 1, attributes["archived"]
assert_equal "1", attributes["published"]
+ boolean = BooleanType.create!(archived: false, published: false)
+ attributes = boolean.reload.attributes_before_type_cast
+ assert_equal 0, attributes["archived"]
+ assert_equal "0", attributes["published"]
+
assert_equal 1, @connection.type_cast(true)
+ assert_equal 0, @connection.type_cast(false)
end
test "with booleans stored as 1 and 0" do
diff --git a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
new file mode 100644
index 0000000000..e349c67c93
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
@@ -0,0 +1,45 @@
+require "cases/helper"
+
+class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ end
+
+ test 'microsecond precision for MySQL gte 5.6.4' do
+ stub_version '5.6.4'
+ assert_microsecond_precision
+ end
+
+ test 'no microsecond precision for MySQL lt 5.6.4' do
+ stub_version '5.6.3'
+ assert_no_microsecond_precision
+ end
+
+ test 'microsecond precision for MariaDB gte 5.3.0' do
+ stub_version '5.5.5-10.1.8-MariaDB-log'
+ assert_microsecond_precision
+ end
+
+ test 'no microsecond precision for MariaDB lt 5.3.0' do
+ stub_version '5.2.9-MariaDB'
+ assert_no_microsecond_precision
+ end
+
+ private
+ def assert_microsecond_precision
+ assert_match_quoted_microsecond_datetime(/\.000001\z/)
+ end
+
+ def assert_no_microsecond_precision
+ assert_match_quoted_microsecond_datetime(/\d\z/)
+ end
+
+ def assert_match_quoted_microsecond_datetime(match)
+ assert_match match, @connection.quoted_date(Time.now.change(usec: 1))
+ end
+
+ def stub_version(full_version_string)
+ @connection.stubs(:full_version).returns(full_version_string)
+ @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/quoting_test.rb b/activerecord/test/cases/adapters/mysql2/quoting_test.rb
deleted file mode 100644
index 2de7e1b526..0000000000
--- a/activerecord/test/cases/adapters/mysql2/quoting_test.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require "cases/helper"
-
-class Mysql2QuotingTest < ActiveRecord::Mysql2TestCase
- setup do
- @connection = ActiveRecord::Base.connection
- end
-
- test 'quoted date precision for gte 5.6.4' do
- @connection.stubs(:full_version).returns('5.6.4')
- @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
- t = Time.now.change(usec: 1)
- assert_match(/\.000001\z/, @connection.quoted_date(t))
- end
-
- test 'quoted date precision for lt 5.6.4' do
- @connection.stubs(:full_version).returns('5.6.3')
- @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
- t = Time.now.change(usec: 1)
- assert_no_match(/\.000001\z/, @connection.quoted_date(t))
- end
-end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index bb8c9fa19c..aff0dabee7 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -11,7 +11,9 @@ require 'models/tagging'
require 'models/author'
require 'models/owner'
require 'models/pet'
+require 'models/pet_treasure'
require 'models/toy'
+require 'models/treasure'
require 'models/contract'
require 'models/company'
require 'models/developer'
@@ -1082,6 +1084,18 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [], person.posts
end
+ def test_preloading_empty_through_with_polymorphic_source_association
+ owner = Owner.create!(name: "Rainbow Unicat")
+ pet = Pet.create!(owner: owner)
+ person = Person.create!(first_name: "Gaga")
+ treasure = Treasure.create!(looter: person)
+ non_looted_treasure = Treasure.create!()
+ PetTreasure.create!(pet: pet, treasure: treasure, rainbow_color: "Ultra violet indigo")
+ PetTreasure.create!(pet: pet, treasure: non_looted_treasure, rainbow_color: "Ultra violet indigo")
+
+ assert_equal [person], Owner.where(name: "Rainbow Unicat").includes(pets: :persons).first.persons.to_a
+ end
+
def test_explicitly_joining_join_table
assert_equal owners(:blackbeard).toys, owners(:blackbeard).toys.with_pet
end
diff --git a/activerecord/test/cases/date_time_precision_test.rb b/activerecord/test/cases/date_time_precision_test.rb
index e996d142a2..f8664d83bd 100644
--- a/activerecord/test/cases/date_time_precision_test.rb
+++ b/activerecord/test/cases/date_time_precision_test.rb
@@ -1,7 +1,7 @@
require 'cases/helper'
require 'support/schema_dumping_helper'
-if ActiveRecord::Base.connection.supports_datetime_with_precision?
+if subsecond_precision_supported?
class DateTimePrecisionTest < ActiveRecord::TestCase
include SchemaDumpingHelper
self.use_transactional_tests = false
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index cd1967c373..a3f8d26100 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -37,8 +37,8 @@ class DirtyTest < ActiveRecord::TestCase
def test_attribute_changes
# New record - no changes.
pirate = Pirate.new
- assert !pirate.catchphrase_changed?
- assert_nil pirate.catchphrase_change
+ assert_equal false, pirate.catchphrase_changed?
+ assert_equal false, pirate.non_validated_parrot_id_changed?
# Change catchphrase.
pirate.catchphrase = 'arrr'
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 692c6bf2d0..f03df2d99e 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -652,11 +652,16 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(approved: true).find(1) }
end
- def test_find_on_hash_conditions_with_explicit_table_name
+ def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_string
assert Topic.where('topics.approved' => false).find(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => true).find(1) }
end
+ def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_symbol
+ assert Topic.where('topics.approved': false).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved': true).find(1) }
+ end
+
def test_find_on_hash_conditions_with_hashed_table_name
assert Topic.where(topics: { approved: false }).find(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(topics: { approved: true }).find(1) }
diff --git a/activerecord/test/cases/fixture_set/file_test.rb b/activerecord/test/cases/fixture_set/file_test.rb
index 242e7a9bec..e64b90507e 100644
--- a/activerecord/test/cases/fixture_set/file_test.rb
+++ b/activerecord/test/cases/fixture_set/file_test.rb
@@ -135,6 +135,12 @@ END
end
end
+ def test_erb_filename
+ filename = 'filename.yaml'
+ erb = File.new(filename).send(:prepare_erb, "<% Rails.env %>\n")
+ assert_equal erb.filename, filename
+ end
+
private
def tmp_yaml(name, contents)
t = Tempfile.new name
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index acba97bbb8..96c94eefa0 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -544,4 +544,24 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal 1, SpecialComment.where(body: 'go crazy').created.count
end
+ def test_model_class_should_respond_to_empty
+ assert !Topic.empty?
+ Topic.delete_all
+ assert Topic.empty?
+ end
+
+ def test_model_class_should_respond_to_none
+ assert !Topic.none?
+ Topic.delete_all
+ assert Topic.none?
+ end
+
+ def test_model_class_should_respond_to_one
+ assert !Topic.one?
+ Topic.delete_all
+ assert !Topic.one?
+ Topic.create!
+ assert Topic.one?
+ end
+
end
diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb
index a704b861cb..104226010a 100644
--- a/activerecord/test/cases/statement_cache_test.rb
+++ b/activerecord/test/cases/statement_cache_test.rb
@@ -94,5 +94,17 @@ module ActiveRecord
additional_books = cache.execute([], Book, Book.connection)
assert first_books != additional_books
end
+
+ def test_unprepared_statements_dont_share_a_cache_with_prepared_statements
+ Book.create(name: "my book")
+ Book.create(name: "my other book")
+
+ book = Book.find_by(name: "my book")
+ other_book = Book.connection.unprepared_statement do
+ Book.find_by(name: "my other book")
+ end
+
+ refute_equal book, other_book
+ end
end
end
diff --git a/activerecord/test/cases/time_precision_test.rb b/activerecord/test/cases/time_precision_test.rb
index 3b6e4dcc2b..628a8eb771 100644
--- a/activerecord/test/cases/time_precision_test.rb
+++ b/activerecord/test/cases/time_precision_test.rb
@@ -1,7 +1,7 @@
require 'cases/helper'
require 'support/schema_dumping_helper'
-if ActiveRecord::Base.connection.supports_datetime_with_precision?
+if subsecond_precision_supported?
class TimePrecisionTest < ActiveRecord::TestCase
include SchemaDumpingHelper
self.use_transactional_tests = false
diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb
index cedb774b10..ce8242cf2f 100644
--- a/activerecord/test/models/owner.rb
+++ b/activerecord/test/models/owner.rb
@@ -1,7 +1,8 @@
class Owner < ActiveRecord::Base
self.primary_key = :owner_id
has_many :pets, -> { order 'pets.name desc' }
- has_many :toys, :through => :pets
+ has_many :toys, through: :pets
+ has_many :persons, through: :pets
belongs_to :last_pet, class_name: 'Pet'
scope :including_last_pet, -> {
diff --git a/activerecord/test/models/pet.rb b/activerecord/test/models/pet.rb
index f7970d7aab..53489fa1b3 100644
--- a/activerecord/test/models/pet.rb
+++ b/activerecord/test/models/pet.rb
@@ -4,6 +4,9 @@ class Pet < ActiveRecord::Base
self.primary_key = :pet_id
belongs_to :owner, :touch => true
has_many :toys
+ has_many :pet_treasures
+ has_many :treasures, through: :pet_treasures
+ has_many :persons, through: :treasures, source: :looter, source_type: 'Person'
class << self
attr_accessor :after_destroy_output
diff --git a/activerecord/test/models/pet_treasure.rb b/activerecord/test/models/pet_treasure.rb
new file mode 100644
index 0000000000..1fe7807ffe
--- /dev/null
+++ b/activerecord/test/models/pet_treasure.rb
@@ -0,0 +1,6 @@
+class PetTreasure < ActiveRecord::Base
+ self.table_name = "pets_treasures"
+
+ belongs_to :pet
+ belongs_to :treasure
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 01dbf2cee6..1027bcb365 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -1,10 +1,4 @@
ActiveRecord::Schema.define do
- def except(adapter_names_to_exclude)
- unless [adapter_names_to_exclude].flatten.include?(adapter_name)
- yield
- end
- end
-
# ------------------------------------------------------------------- #
# #
# Please keep these create table statements in alphabetical order #
@@ -620,6 +614,12 @@ ActiveRecord::Schema.define do
end
end
+ create_table :pets_treasures, force: true do |t|
+ t.column :treasure_id, :integer
+ t.column :pet_id, :integer
+ t.column :rainbow_color, :string
+ end
+
create_table :pirates, force: true do |t|
t.column :catchphrase, :string
t.column :parrot_id, :integer
@@ -991,7 +991,7 @@ ActiveRecord::Schema.define do
create_table :records, force: true do |t|
end
- except 'SQLite' do
+ if supports_foreign_keys?
# fk_test_has_fk should be before fk_test_has_pk
create_table :fk_test_has_fk, force: true do |t|
t.integer :fk_id, null: false
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index b9dcbe53c1..5729a98b99 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,36 @@
+* Fix behavior of JSON encoding for `Exception`.
+
+ *namusyaka*
+
+* Make `number_to_phone` format number with regexp pattern.
+
+ number_to_phone(18812345678, pattern: /(\d{3})(\d{4})(\d{4})/)
+ # => 188-1234-5678
+
+ *Pan Gaoyong*
+
+* Match `String#to_time`'s behaviour to that of ruby's implementation for edge cases.
+
+ `nil` is now returned instead of the current date if the string provided does
+ contain time information, but none that is used to build the `Time` object.
+
+ Fixes #22958.
+
+ *Siim Liiser*
+
+* Rely on the native DateTime#<=> implementation to handle non-datetime like
+ objects instead of returning `nil` ourselves. This restores the ability
+ of `DateTime` instances to be compared with a `Numeric` that represents an
+ astronomical julian day number.
+
+ Fixes #24228.
+
+ *Andrew White*
+
+* Add `String#upcase_first` method.
+
+ *Glauco Custódio*, *bogdanvlviv*
+
* Prevent `Marshal.load` from looping infinitely when trying to autoload a constant
which resolves to a different name.
@@ -44,6 +77,7 @@
*Jon Moss*
+
## Rails 5.0.0.beta2 (February 01, 2016) ##
* Change `number_to_currency` behavior for checking negativity.
@@ -81,6 +115,7 @@
*Akshay Vishnoi*
+
## Rails 5.0.0.beta1 (December 18, 2015) ##
* Add thread_m/cattr_accessor/reader/writer suite of methods for declaring class and module variables that live per-thread.
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index df38dbcf11..1c678dc2af 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -126,7 +126,7 @@ module ActiveSupport
def set_cache_value(value, name, amount, options) # :nodoc:
ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
`set_cache_value` is deprecated and will be removed from Rails 5.1.
- Please use `write_cache_value`
+ Please use `write_cache_value` instead.
MESSAGE
write_cache_value name, value, options
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index e6baddf5db..d878d44d02 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -571,15 +571,15 @@ module ActiveSupport
# Install a callback for the given event.
#
- # set_callback :save, :before, :before_meth
- # set_callback :save, :after, :after_meth, if: :condition
+ # set_callback :save, :before, :before_method
+ # set_callback :save, :after, :after_method, if: :condition
# set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
#
# The second argument indicates whether the callback is to be run +:before+,
# +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
# means the first example above can also be written as:
#
- # set_callback :save, :before_meth
+ # set_callback :save, :before_method
#
# The callback can be specified as a symbol naming an instance method; as a
# proc, lambda, or block; as a string to be instance evaluated(deprecated); or as an
diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb
index bcb228b09a..5450533935 100644
--- a/activesupport/lib/active_support/core_ext/date_time.rb
+++ b/activesupport/lib/active_support/core_ext/date_time.rb
@@ -2,4 +2,3 @@ require 'active_support/core_ext/date_time/acts_like'
require 'active_support/core_ext/date_time/blank'
require 'active_support/core_ext/date_time/calculations'
require 'active_support/core_ext/date_time/conversions'
-require 'active_support/core_ext/date_time/zones'
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 95617fb8c2..ac46f5ffe8 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -165,13 +165,10 @@ class DateTime
# Layers additional behavior on DateTime#<=> so that Time and
# ActiveSupport::TimeWithZone instances can be compared with a DateTime.
def <=>(other)
- if other.kind_of?(Infinity)
- super
- elsif other.respond_to? :to_datetime
+ if other.respond_to? :to_datetime
super other.to_datetime rescue nil
else
- nil
+ super
end
end
-
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/zones.rb b/activesupport/lib/active_support/core_ext/date_time/zones.rb
deleted file mode 100644
index c39f358395..0000000000
--- a/activesupport/lib/active_support/core_ext/date_time/zones.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-require 'date'
-require 'active_support/core_ext/date_and_time/zones'
-
-class DateTime
- include DateAndTime::Zones
-end
diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb
index ca278cb2fa..edfc8296fe 100644
--- a/activesupport/lib/active_support/core_ext/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/marshal.rb
@@ -3,7 +3,7 @@ module ActiveSupport
def load(source)
super(source)
rescue ArgumentError, NameError => exc
- if exc.message.match(%r|undefined class/module (.+)|)
+ if exc.message.match(%r|undefined class/module (.+?)(?:::)?\z|)
# try loading the class/module
loaded = $1.constantize
diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb
index 0db787010c..d49b2fbe54 100644
--- a/activesupport/lib/active_support/core_ext/object/json.rb
+++ b/activesupport/lib/active_support/core_ext/object/json.rb
@@ -197,3 +197,9 @@ class Process::Status #:nodoc:
{ :exitstatus => exitstatus, :pid => pid }
end
end
+
+class Exception
+ def as_json(options = nil)
+ to_s
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index fd79a40e31..71612e09fa 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -18,7 +18,8 @@ class String
# "12/13/2012".to_time # => ArgumentError: argument out of range
def to_time(form = :local)
parts = Date._parse(self, false)
- return if parts.empty?
+ used_keys = %i(year mon mday hour min sec sec_fraction offset)
+ return if (parts.keys & used_keys).empty?
now = Time.now
time = Time.new(
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index cc71b8155d..7277f51076 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -222,6 +222,15 @@ class String
ActiveSupport::Inflector.humanize(self, options)
end
+ # Converts just the first character to uppercase.
+ #
+ # 'what a Lovely Day'.upcase_first # => "What a Lovely Day"
+ # 'w'.upcase_first # => "W"
+ # ''.upcase_first # => ""
+ def upcase_first
+ ActiveSupport::Inflector.upcase_first(self)
+ end
+
# Creates a foreign key name from a class name.
# +separate_class_name_and_id_with_underscore+ sets whether
# the method should put '_' between the name and 'id'.
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index 43b9fd4bf7..005ad93b08 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -250,7 +250,7 @@ end
class String
# Marks a string as trusted safe. It will be inserted into HTML with no
- # additional escaping performed. It is your responsibilty to ensure that the
+ # additional escaping performed. It is your responsibility to ensure that the
# string contains no malicious content. This method is equivalent to the
# `raw` helper in views. It is recommended that you use `sanitize` instead of
# this method. It should never be called on user input.
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index 35f084dd7a..de5b233679 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -65,7 +65,6 @@ module ActiveSupport
def deprecation_message(callstack, message = nil)
message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
- message += '.' unless message =~ /\.$/
"DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}"
end
diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb
index 2bd1c01d35..00c5745a25 100644
--- a/activesupport/lib/active_support/execution_wrapper.rb
+++ b/activesupport/lib/active_support/execution_wrapper.rb
@@ -19,6 +19,32 @@ module ActiveSupport
set_callback(:complete, *args, &block)
end
+ # Register an object to be invoked during both the +run+ and
+ # +complete+ steps.
+ #
+ # +hook.complete+ will be passed the value returned from +hook.run+,
+ # and will only be invoked if +run+ has previously been called.
+ # (Mostly, this means it won't be invoked if an exception occurs in
+ # a preceding +to_run+ block; all ordinary +to_complete+ blocks are
+ # invoked in that situation.)
+ def self.register_hook(hook, outer: false)
+ if outer
+ run_args = [prepend: true]
+ complete_args = [:after]
+ else
+ run_args = complete_args = []
+ end
+
+ to_run(*run_args) do
+ hook_state[hook] = hook.run
+ end
+ to_complete(*complete_args) do
+ if hook_state.key?(hook)
+ hook.complete hook_state[hook]
+ end
+ end
+ end
+
# Run this execution.
#
# Returns an instance, whose +complete!+ method *must* be invoked
@@ -29,7 +55,15 @@ module ActiveSupport
if active?
Null
else
- new.tap(&:run!)
+ new.tap do |instance|
+ success = nil
+ begin
+ instance.run!
+ success = true
+ ensure
+ instance.complete! unless success
+ end
+ end
end
end
@@ -37,11 +71,11 @@ module ActiveSupport
def self.wrap
return yield if active?
- state = run!
+ instance = run!
begin
yield
ensure
- state.complete!
+ instance.complete!
end
end
@@ -74,5 +108,10 @@ module ActiveSupport
ensure
self.class.active.delete Thread.current
end
+
+ private
+ def hook_state
+ @_hook_state ||= {}
+ end
end
end
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 8708a502e6..43e8b7c528 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -112,7 +112,24 @@ module ActiveSupport
# reloading is not triggered.
def max_mtime(paths)
time_now = Time.now
- paths.map {|path| File.mtime(path)}.reject {|mtime| time_now < mtime}.max
+ max_mtime = nil
+
+ # Time comparisons are performed with #compare_without_coercion because
+ # AS redefines these operators in a way that is much slower and does not
+ # bring any benefit in this particular code.
+ #
+ # Read t1.compare_without_coercion(t2) < 0 as t1 < t2.
+ paths.each do |path|
+ mtime = File.mtime(path)
+
+ next if time_now.compare_without_coercion(mtime) < 0
+
+ if max_mtime.nil? || max_mtime.compare_without_coercion(mtime) < 0
+ max_mtime = mtime
+ end
+ end
+
+ max_mtime
end
def compile_glob(hash)
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index f741c0bfb8..f94e12e14f 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -140,6 +140,15 @@ module ActiveSupport
result
end
+ # Converts just the first character to uppercase.
+ #
+ # upcase_first('what a Lovely Day') # => "What a Lovely Day"
+ # upcase_first('w') # => "W"
+ # upcase_first('') # => ""
+ def upcase_first(string)
+ string.length > 0 ? string[0].upcase.concat(string[1..-1]) : ''
+ end
+
# Capitalizes all the words and replaces some characters in the string to
# create a nicer looking title. +titleize+ is meant for creating pretty
# output. It is not used in the Rails internals.
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 55628f0313..7a49bbb960 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -15,7 +15,7 @@ module ActiveSupport
extend self
- # Formats a +number+ into a US phone number (e.g., (555)
+ # Formats a +number+ into a phone number (US by default e.g., (555)
# 123-9876). You can customize the format in the +options+ hash.
#
# ==== Options
@@ -27,6 +27,8 @@ module ActiveSupport
# end of the generated number.
# * <tt>:country_code</tt> - Sets the country code for the phone
# number.
+ # * <tt>:pattern</tt> - Specifies how the number is divided into three
+ # groups with the custom regexp to override the default format.
# ==== Examples
#
# number_to_phone(5551234) # => "555-1234"
@@ -40,6 +42,11 @@ module ActiveSupport
#
# number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.')
# # => "+1.123.555.1234 x 1343"
+ #
+ # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)
+ # # => "(755) 6123-4567"
+ # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/))
+ # # => "133-1234-5678"
def number_to_phone(number, options = {})
NumberToPhoneConverter.convert(number, options)
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
index 45ae8f1a93..43c5540b6f 100644
--- a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
@@ -12,7 +12,7 @@ module ActiveSupport
private
def parts
- left, right = number.to_s.split('.')
+ left, right = number.to_s.split('.'.freeze)
left.gsub!(delimiter_pattern) do |digit_to_delimit|
"#{digit_to_delimit}#{options[:delimiter]}"
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
index af2ee56d91..dee74fa7a6 100644
--- a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
@@ -18,12 +18,16 @@ module ActiveSupport
end
def convert_with_area_code(number)
- number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
+ default_pattern = /(\d{1,3})(\d{3})(\d{4}$)/
+ number.gsub!(regexp_pattern(default_pattern),
+ "(\\1) \\2#{delimiter}\\3")
number
end
def convert_without_area_code(number)
- number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
+ default_pattern = /(\d{0,3})(\d{3})(\d{4})$/
+ number.gsub!(regexp_pattern(default_pattern),
+ "\\1#{delimiter}\\2#{delimiter}\\3")
number.slice!(0, 1) if start_with_delimiter?(number)
number
end
@@ -43,6 +47,11 @@ module ActiveSupport
def phone_ext(ext)
ext.blank? ? "" : " x #{ext}"
end
+
+ def regexp_pattern(default_pattern)
+ opts.fetch :pattern, default_pattern
+ end
+
end
end
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
index 981c562551..9fb7dfb779 100644
--- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -29,9 +29,11 @@ module ActiveSupport
formatted_string =
if BigDecimal === rounded_number && rounded_number.finite?
- s = rounded_number.to_s('F') + '0'*precision
- a, b = s.split('.', 2)
- a + '.' + b[0, precision]
+ s = rounded_number.to_s('F')
+ s << '0'.freeze * precision
+ a, b = s.split('.'.freeze, 2)
+ a << '.'.freeze
+ a << b[0, precision]
else
"%00.#{precision}f" % rounded_number
end
diff --git a/activesupport/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb
index 5d1f0e1e66..5623bdd349 100644
--- a/activesupport/lib/active_support/reloader.rb
+++ b/activesupport/lib/active_support/reloader.rb
@@ -43,7 +43,13 @@ module ActiveSupport
# Initiate a manual reload
def self.reload!
executor.wrap do
- new.tap(&:run!).complete!
+ new.tap do |instance|
+ begin
+ instance.run!
+ ensure
+ instance.complete!
+ end
+ end
end
prepare!
end
diff --git a/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb
index 404ae289c6..3ca4213c71 100644
--- a/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb
+++ b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb
@@ -1,4 +1,4 @@
RaisesArbitraryException = 1
-A::B # Autoloading recursion, also expected to be watched and discarded.
+_ = A::B # Autoloading recursion, also expected to be watched and discarded.
raise Exception, 'arbitray exception message'
diff --git a/activesupport/test/autoloading_fixtures/throws.rb b/activesupport/test/autoloading_fixtures/throws.rb
index 5e47b9699b..e1d96cc512 100644
--- a/activesupport/test/autoloading_fixtures/throws.rb
+++ b/activesupport/test/autoloading_fixtures/throws.rb
@@ -1,4 +1,4 @@
Throws = 1
-A::B # Autoloading recursion, expected to be discarded.
+_ = A::B # Autoloading recursion, expected to be discarded.
throw :t
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index b183a20e0d..16efeeadd5 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -354,6 +354,24 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal nil, DateTime.civil(2000) <=> "Invalid as Time"
end
+ def test_compare_with_integer
+ assert_equal 1, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440587
+ assert_equal 0, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440588
+ assert_equal(-1, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440589)
+ end
+
+ def test_compare_with_float
+ assert_equal 1, DateTime.civil(1970) <=> 2440586.5
+ assert_equal 0, DateTime.civil(1970) <=> 2440587.5
+ assert_equal(-1, DateTime.civil(1970) <=> 2440588.5)
+ end
+
+ def test_compare_with_rational
+ assert_equal 1, DateTime.civil(1970) <=> Rational(4881173, 2)
+ assert_equal 0, DateTime.civil(1970) <=> Rational(4881175, 2)
+ assert_equal(-1, DateTime.civil(1970) <=> Rational(4881177, 2))
+ end
+
def test_to_f
assert_equal 946684800.0, DateTime.civil(2000).to_f
assert_equal 946684800.0, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_f
diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb
index 07c0c0d8cb..380f64c6fd 100644
--- a/activesupport/test/core_ext/marshal_test.rb
+++ b/activesupport/test/core_ext/marshal_test.rb
@@ -29,7 +29,12 @@ class MarshalTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
- assert_kind_of EM, Marshal.load(dumped)
+ object = nil
+ assert_nothing_raised do
+ object = Marshal.load(dumped)
+ end
+
+ assert_kind_of EM, object
end
end
@@ -43,7 +48,12 @@ class MarshalTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
- assert_kind_of ClassFolder::ClassFolderSubclass, Marshal.load(dumped)
+ object = nil
+ assert_nothing_raised do
+ object = Marshal.load(dumped)
+ end
+
+ assert_kind_of ClassFolder::ClassFolderSubclass, object
end
end
@@ -128,7 +138,12 @@ class MarshalTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
- assert_kind_of EM, Marshal.load(f)
+ object = nil
+ assert_nothing_raised do
+ object = Marshal.load(f)
+ end
+
+ assert_kind_of EM, object
end
end
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 2e69816364..f38b225b38 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -76,6 +76,18 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_upcase_first
+ assert_equal "What a Lovely Day", "what a Lovely Day".upcase_first
+ end
+
+ def test_upcase_first_with_one_char
+ assert_equal "W", "w".upcase_first
+ end
+
+ def test_upcase_first_with_empty_string
+ assert_equal "", "".upcase_first
+ end
+
def test_camelize
CamelToUnderscore.each do |camel, underscore|
assert_equal(camel, underscore.camelize)
@@ -444,6 +456,7 @@ class StringConversionsTest < ActiveSupport::TestCase
assert_equal Time.local(2011, 2, 27, 17, 50), "2011-02-27 13:50 -0100".to_time
assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time(:utc)
assert_equal Time.local(2005, 2, 27, 22, 50), "2005-02-27 14:50 -0500".to_time
+ assert_nil "010".to_time
assert_nil "".to_time
end
end
diff --git a/activesupport/test/executor_test.rb b/activesupport/test/executor_test.rb
index 6db6db4fa8..d9b389461a 100644
--- a/activesupport/test/executor_test.rb
+++ b/activesupport/test/executor_test.rb
@@ -1,6 +1,9 @@
require 'abstract_unit'
class ExecutorTest < ActiveSupport::TestCase
+ class DummyError < RuntimeError
+ end
+
def test_wrap_invokes_callbacks
called = []
executor.to_run { called << :run }
@@ -35,6 +38,20 @@ class ExecutorTest < ActiveSupport::TestCase
assert_equal [:run, :body, :complete], called
end
+ def test_exceptions_unwind
+ called = []
+ executor.to_run { called << :run_1 }
+ executor.to_run { raise DummyError }
+ executor.to_run { called << :run_2 }
+ executor.to_complete { called << :complete }
+
+ assert_raises(DummyError) do
+ executor.wrap { called << :body }
+ end
+
+ assert_equal [:run_1, :complete], called
+ end
+
def test_avoids_double_wrapping
called = []
executor.to_run { called << :run }
@@ -51,6 +68,96 @@ class ExecutorTest < ActiveSupport::TestCase
assert_equal [:run, :early, :body, :late, :complete], called
end
+ def test_hooks_carry_state
+ supplied_state = :none
+
+ hook = Class.new do
+ define_method(:run) do
+ :some_state
+ end
+
+ define_method(:complete) do |state|
+ supplied_state = state
+ end
+ end.new
+
+ executor.register_hook(hook)
+
+ executor.wrap { }
+
+ assert_equal :some_state, supplied_state
+ end
+
+ def test_nil_state_is_sufficient
+ supplied_state = :none
+
+ hook = Class.new do
+ define_method(:run) do
+ nil
+ end
+
+ define_method(:complete) do |state|
+ supplied_state = state
+ end
+ end.new
+
+ executor.register_hook(hook)
+
+ executor.wrap { }
+
+ assert_equal nil, supplied_state
+ end
+
+ def test_exception_skips_uninvoked_hook
+ supplied_state = :none
+
+ hook = Class.new do
+ define_method(:run) do
+ :some_state
+ end
+
+ define_method(:complete) do |state|
+ supplied_state = state
+ end
+ end.new
+
+ executor.to_run do
+ raise DummyError
+ end
+ executor.register_hook(hook)
+
+ assert_raises(DummyError) do
+ executor.wrap { }
+ end
+
+ assert_equal :none, supplied_state
+ end
+
+ def test_exception_unwinds_invoked_hook
+ supplied_state = :none
+
+ hook = Class.new do
+ define_method(:run) do
+ :some_state
+ end
+
+ define_method(:complete) do |state|
+ supplied_state = state
+ end
+ end.new
+
+ executor.register_hook(hook)
+ executor.to_run do
+ raise DummyError
+ end
+
+ assert_raises(DummyError) do
+ executor.wrap { }
+ end
+
+ assert_equal :some_state, supplied_state
+ end
+
def test_separate_classes_can_wrap
other_executor = Class.new(ActiveSupport::Executor)
diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb
index 9c07e38fe5..a78c3a67b6 100644
--- a/activesupport/test/file_update_checker_shared_tests.rb
+++ b/activesupport/test/file_update_checker_shared_tests.rb
@@ -5,7 +5,7 @@ module FileUpdateCheckerSharedTests
include FileUtils
def tmpdir
- @tmpdir ||= Dir.mktmpdir(nil, __dir__)
+ @tmpdir
end
def tmpfile(name)
@@ -16,8 +16,8 @@ module FileUpdateCheckerSharedTests
@tmpfiles ||= %w(foo.rb bar.rb baz.rb).map { |f| tmpfile(f) }
end
- def teardown
- FileUtils.rm_rf(@tmpdir) if defined? @tmpdir
+ def run(*args)
+ Dir.mktmpdir(nil, __dir__) { |dir| @tmpdir = dir; super }
end
test 'should not execute the block if no paths are given' do
@@ -134,6 +134,23 @@ module FileUpdateCheckerSharedTests
assert_equal 1, i
end
+ test 'should return max_time for files with mtime = Time.at(0)' do
+ i = 0
+
+ FileUtils.touch(tmpfiles)
+
+ now = Time.now
+ time = Time.at(0) # wrong mtime from the future
+ File.utime(time, time, tmpfiles[0])
+
+ checker = new_checker(tmpfiles) { i += 1 }
+
+ touch(tmpfiles[1..-1])
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
test 'should cache updated result until execute' do
i = 0
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 9f4b62fd8b..5fc2e16336 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -422,6 +422,11 @@ EXPECTED
assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time)
end
+ def test_exception_to_json
+ exception = Exception.new("foo")
+ assert_equal '"foo"', ActiveSupport::JSON.encode(exception)
+ end
+
protected
def object_keys(json_object)
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 6696111476..074c872efc 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -57,6 +57,8 @@ module ActiveSupport
assert_equal("+18005551212", number_helper.number_to_phone(8005551212, :country_code => 1, :delimiter => ''))
assert_equal("22-555-1212", number_helper.number_to_phone(225551212))
assert_equal("+45-22-555-1212", number_helper.number_to_phone(225551212, :country_code => 45))
+ assert_equal("(755) 6123-4567", number_helper.number_to_phone(75561234567, pattern: /(\d{3,4})(\d{4})(\d{4})/, area_code: true))
+ assert_equal("133-1234-5678", number_helper.number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})/))
end
end
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index d35d0f1976..4f4f27d6e4 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,3 +1,16 @@
+* Update example of passing a proc to `:message` option for validating records.
+
+ This behavior was recently changed in [Pull Request #24199](https://github.com/rails/rails/pull/24119) to
+ pass the object being validated as first argument to the `:message` proc,
+ instead of the key of the field being validated.
+
+ *Prathamesh Sonpatki*
+
+* Added new guide: Action Cable Overview.
+
+ *David Kuhta*
+
+
## Rails 5.0.0.beta3 (February 24, 2016) ##
* No changes.
@@ -10,23 +23,23 @@
## Rails 5.0.0.beta1 (December 18, 2015) ##
-* Add code of conduct to contributing guide
+* Add code of conduct to contributing guide.
*Jon Moss*
-* New section in Configuring: Configuring Active Job
+* New section in Configuring: Configuring Active Job.
*Eliot Sykes*
-* New section in Active Record Association Basics: Single Table Inheritance
+* New section in Active Record Association Basics: Single Table Inheritance.
*Andrey Nering*
-* New section in Active Record Querying: Understanding The Method Chaining
+* New section in Active Record Querying: Understanding The Method Chaining.
*Andrey Nering*
-* New section in Configuring: Search Engines Indexing
+* New section in Configuring: Search Engines Indexing.
*Andrey Nering*
diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md
index be00087f63..5e82af5a15 100644
--- a/guides/source/2_2_release_notes.md
+++ b/guides/source/2_2_release_notes.md
@@ -21,7 +21,7 @@ Rails 2.2 supplies an easy system for internationalization (or i18n, for those o
* Lead Contributors: Rails i18 Team
* More information :
* [Official Rails i18 website](http://rails-i18n.org)
- * [Finally. Ruby on Rails gets internationalized](http://www.artweb-design.de/2008/7/18/finally-ruby-on-rails-gets-internationalized)
+ * [Finally. Ruby on Rails gets internationalized](https://web.archive.org/web/20140407075019/http://www.artweb-design.de/2008/7/18/finally-ruby-on-rails-gets-internationalized)
* [Localizing Rails : Demo application](http://github.com/clemens/i18n_demo_app)
### Compatibility with Ruby 1.9 and JRuby
diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md
index 327495704a..feee0f9920 100644
--- a/guides/source/3_1_release_notes.md
+++ b/guides/source/3_1_release_notes.md
@@ -558,4 +558,4 @@ Credits
See the [full list of contributors to Rails](http://contributors.rubyonrails.org/) for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them.
-Rails 3.1 Release Notes were compiled by [Vijay Dev](https://github.com/vijaydev.)
+Rails 3.1 Release Notes were compiled by [Vijay Dev](https://github.com/vijaydev)
diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md
index a868c5f856..6351ff57c1 100644
--- a/guides/source/5_0_release_notes.md
+++ b/guides/source/5_0_release_notes.md
@@ -270,7 +270,7 @@ Please refer to the [Changelog][action-pack] for detailed changes.
* Changed the `protect_from_forgery` prepend default to `false`.
([commit](https://github.com/rails/rails/commit/39794037817703575c35a75f1961b01b83791191))
-* `ActionController::TestCase` will be moved to it's own gem in Rails 5.1. Use
+* `ActionController::TestCase` will be moved to its own gem in Rails 5.1. Use
`ActionDispatch::IntegrationTest` instead.
([commit](https://github.com/rails/rails/commit/4414c5d1795e815b102571425974a8b1d46d932d))
@@ -322,10 +322,6 @@ Please refer to the [Changelog][action-view] for detailed changes.
([Pull Request](https://github.com/rails/rails/pull/18948),
[commit](https://github.com/rails/rails/commit/e93f0f0f133717f9b06b1eaefd3442bd0ff43985))
-* Allow defining explicit collection caching using a `# Template Collection: ...`
- directive inside templates.
- ([Pull Request](https://github.com/rails/rails/pull/20781))
-
* Added wildcard matching to explicit dependencies.
([Pull Request](https://github.com/rails/rails/pull/20904))
@@ -355,9 +351,6 @@ Please refer to the [Changelog][action-mailer] for detailed changes.
* Template lookup now respects default locale and I18n fallbacks.
([commit](https://github.com/rails/rails/commit/ecb1981b))
-* Template can use fragment cache like Action View template.
- ([Pull Request](https://github.com/rails/rails/pull/22825))
-
* Added `_mailer` suffix to mailers created via generator, following the same
naming convention used in controllers and jobs.
([Pull Request](https://github.com/rails/rails/pull/18074))
@@ -446,10 +439,6 @@ Please refer to the [Changelog][active-record] for detailed changes.
* Deprecated `ActiveRecord::Base.errors_in_transactional_callbacks=`.
([commit](https://github.com/rails/rails/commit/07d3d402341e81ada0214f2cb2be1da69eadfe72))
-* Deprecated passing of `start` value to `find_in_batches` and `find_each`
- in favour of `begin_at` value.
- ([Pull Request](https://github.com/rails/rails/pull/18961))
-
* Deprecated `Relation#uniq` use `Relation#distinct` instead.
([commit](https://github.com/rails/rails/commit/adfab2dcf4003ca564d78d4425566dd2d9cd8b4f))
@@ -509,9 +498,6 @@ Please refer to the [Changelog][active-record] for detailed changes.
* Added `#cache_key` to `ActiveRecord::Relation`.
([Pull Request](https://github.com/rails/rails/pull/20884))
-* Added `ActiveRecord::Relation#outer_joins`.
- ([Pull Request](https://github.com/rails/rails/pull/12071))
-
* Require `belongs_to` by default.
([Pull Request](https://github.com/rails/rails/pull/18937)) - Deprecate
`required` option in favor of `optional` for `belongs_to`
diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md
index 28578b3369..d1f17fdce5 100644
--- a/guides/source/action_cable_overview.md
+++ b/guides/source/action_cable_overview.md
@@ -106,11 +106,11 @@ Then you would create your own channel classes. For example, you could have a
**ChatChannel** and an **AppearanceChannel**:
```ruby
-# app/channels/application_cable/chat_channel.rb
+# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
end
-# app/channels/application_cable/appearance_channel.rb
+# app/channels/appearance_channel.rb
class AppearanceChannel < ApplicationCable::Channel
end
```
@@ -125,7 +125,7 @@ Incoming messages are then routed to these channel subscriptions based on
an identifier sent by the cable consumer.
```ruby
-# app/channels/application_cable/chat_channel.rb
+# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
# Called when the consumer has successfully become a subscriber of this channel
def subscribed
@@ -182,7 +182,7 @@ Streams provide the mechanism by which channels route published content
(broadcasts) to its subscribers.
```ruby
-# app/channels/application_cable/chat_channel.rb
+# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 8997363fce..848c9caa59 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -1088,6 +1088,8 @@ You can filter out sensitive request parameters from your log files by appending
config.filter_parameters << :password
```
+NOTE: Provided parameters will be filtered out by partial matching regular expression. Rails adds default `:password` in the appropriate initializer (`initializers/filter_parameter_logging.rb`) and cares about typical application parameters `password` and `password_confirmation`.
+
### Redirects Filtering
Sometimes it's desirable to filter out from log files some sensitive locations your application is redirecting to.
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 46116b1e47..0e6bb76101 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -177,7 +177,7 @@ would produce:
}
```
-See the [Jbuilder documention](https://github.com/rails/jbuilder#jbuilder) for
+See the [Jbuilder documentation](https://github.com/rails/jbuilder#jbuilder) for
more examples and information.
#### Template Caching
@@ -1419,7 +1419,7 @@ number_to_percentage(100, precision: 0) # => 100%
#### number_to_phone
-Formats a number into a US phone number.
+Formats a number into a phone number (US by default).
```ruby
number_to_phone(1235551234) # => 123-555-1234
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index a8199e5d02..e834aeadb1 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -13,7 +13,7 @@ After reading this guide, you will know:
* How an Active Record model behaves.
* How Callbacks and validations work.
* How serializers work.
-* The Rails internationalization (i18n) framework.
+* How Active Model integrates with the Rails internationalization (i18n) framework.
--------------------------------------------------------------------------------
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index bcfdb935b2..1cf4abce10 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -505,6 +505,8 @@ constraints to acceptable values:
* `:less_than_or_equal_to` - Specifies the value must be less than or equal to
the supplied value. The default error message for this option is _"must be
less than or equal to %{count}"_.
+* `:other_than` - Specifies the value must be other than the supplied value.
+ The default error message for this option is _"must be other than %{count}"_.
* `:odd` - Specifies the value must be an odd number if set to true. The
default error message for this option is _"must be odd"_.
* `:even` - Specifies the value must be an even number if set to true. The
@@ -783,7 +785,7 @@ A `String` `:message` value can optionally contain any/all of `%{value}`,
`%{attribute}`, and `%{model}` which will be dynamically replaced when
validation fails.
-A `Proc` `:message` value is given two arguments: a message key for i18n, and
+A `Proc` `:message` value is given two arguments: the object being validated, and
a hash with `:model`, `:attribute`, and `:value` key-value pairs.
```ruby
@@ -799,10 +801,10 @@ class Person < ApplicationRecord
# Proc
validates :username,
uniqueness: {
- # key = "activerecord.errors.models.person.attributes.username.taken"
+ # object = person object being validated
# data = { model: "Person", attribute: "Username", value: <username> }
- message: ->(key, data) do
- "#{data[:value]} taken! Try again #{Time.zone.tomorrow}"
+ message: ->(object, data) do
+ "Hey #{object.name}!, #{data[:value]} is taken already! Try again #{Time.zone.tomorrow}"
end
}
end
@@ -1215,9 +1217,9 @@ person.errors[:name]
person.errors.clear
person.errors.empty? # => true
-p.save # => false
+person.save # => false
-p.errors[:name]
+person.errors[:name]
# => ["can't be blank", "is too short (minimum is 3 characters)"]
```
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 67fd758fe1..4977d4f30e 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -545,12 +545,12 @@ author.books.size # uses the cached copy of books
author.books.empty? # uses the cached copy of books
```
-But what if you want to reload the cache, because data might have been changed by some other part of the application? Just pass `true` to the association call:
+But what if you want to reload the cache, because data might have been changed by some other part of the application? Just call `reload` on the association:
```ruby
author.books # retrieves books from the database
author.books.size # uses the cached copy of books
-author.books(true).empty? # discards the cached copy of books
+author.books.reload.empty? # discards the cached copy of books
# and goes back to the database
```
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index ebd67a4adb..745f09f523 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -512,12 +512,38 @@ class ProductsController < ApplicationController
end
```
-### A note on weak ETags
+### Strong v/s Weak ETags
-Etags generated by Rails are weak by default. Weak etags allow symantically equivalent responses to have the same etags, even if their bodies do not match exactly. This is useful when we don't want the page to be regenerated for minor changes in response body. If you absolutely need to generate a strong etag, it can be assigned to the header directly.
+Rails generates weak ETags by default. Weak ETags allow semantically equivalent
+responses to have the same ETags, even if their bodies do not match exactly.
+This is useful when we don't want the page to be regenerated for minor changes in
+response body.
+
+Weak ETags have a leading `W/` to differentiate them from strong ETags.
+
+```
+ W/"618bbc92e2d35ea1945008b42799b0e7" → Weak ETag
+ "618bbc92e2d35ea1945008b42799b0e7" → Strong ETag
+```
+
+Unlike weak ETag, strong ETag implies that response should be exactly the same
+and byte by byte identical. Useful when doing Range requests within a
+large video or PDF file. Some CDNs support only strong ETags, like Akamai.
+If you absolutely need to generate a strong ETag, it can be done as follows.
+
+```ruby
+ class ProductsController < ApplicationController
+ def show
+ @product = Product.find(params[:id])
+ fresh_when last_modified: @product.published_at.utc, strong_etag: @product
+ end
+ end
+```
+
+You can also set the strong ETag directly on the response.
```ruby
- response.add_header "ETag", Digest::MD5.hexdigest(response.body)
+ response.strong_etag = response.body # => "618bbc92e2d35ea1945008b42799b0e7"
```
References
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index e57d873719..6b9b95f1ab 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -104,7 +104,7 @@ application. Accepts a valid week day symbol (e.g. `:monday`).
* `config.filter_parameters` used for filtering out the parameters that
you don't want shown in the logs, such as passwords or credit card
-numbers. New applications filter out passwords by adding the following `config.filter_parameters+=[:password]` in `config/initializers/filter_parameter_logging.rb`.
+numbers. New applications filter out passwords by adding the following `config.filter_parameters+=[:password]` in `config/initializers/filter_parameter_logging.rb`. Parameters filter works by partial matching regular expression.
* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware, and sets `config.action_mailer.default_url_options` to be `{ protocol: 'https' }`. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](http://edgeapi.rubyonrails.org/classes/ActionDispatch/SSL.html) for details.
@@ -352,7 +352,7 @@ The schema dumper adds one additional configuration option:
* `config.action_controller.default_static_extension` configures the extension used for cached pages. Defaults to `.html`.
-* `config.action_controller.include_all_helpers` configures whether all view helpers are available everywhere or are scoped to the corresponding controller. If set to `false`, `UsersHelper` methods are only available for views rendered as part of `UsersController`. If `true`, `UsersHelper` methods are available everywhere. The default is `true`.
+* `config.action_controller.include_all_helpers` configures whether all view helpers are available everywhere or are scoped to the corresponding controller. If set to `false`, `UsersHelper` methods are only available for views rendered as part of `UsersController`. If `true`, `UsersHelper` methods are available everywhere. The default configuration behavior (when this option is not explicitly set to `true` or `false`) is that all view helpers are available to each controller.
* `config.action_controller.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Controller. Set to `nil` to disable logging.
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 7beb8f72a9..cc24e6f666 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -30,7 +30,6 @@ Ruby on Rails uses Git for source code control. The [Git homepage](http://git-sc
* [Try Git course](http://try.github.io/) is an interactive course that will teach you the basics.
* The [official Documentation](http://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git.
* [Everyday Git](http://schacon.github.io/git/everyday.html) will teach you just enough about Git to get by.
-* The [PeepCode screencast](https://peepcode.com/products/git) on Git is easier to follow.
* [GitHub](http://help.github.com) offers links to a variety of Git resources.
* [Pro Git](http://git-scm.com/book) is an entire book about Git with a Creative Commons license.
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 422bc647ef..048fe190e8 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -174,7 +174,6 @@ URL fields, email fields, number fields and range fields:
<%= search_field(:user, :name) %>
<%= telephone_field(:user, :phone) %>
<%= date_field(:user, :born_on) %>
-<%= datetime_field(:user, :meeting_time) %>
<%= datetime_local_field(:user, :graduation_day) %>
<%= month_field(:user, :birthday_month) %>
<%= week_field(:user, :birthday_week) %>
@@ -195,7 +194,6 @@ Output:
<input id="user_name" name="user[name]" type="search" />
<input id="user_phone" name="user[phone]" type="tel" />
<input id="user_born_on" name="user[born_on]" type="date" />
-<input id="user_meeting_time" name="user[meeting_time]" type="datetime" />
<input id="user_graduation_day" name="user[graduation_day]" type="datetime-local" />
<input id="user_birthday_month" name="user[birthday_month]" type="month" />
<input id="user_birthday_week" name="user[birthday_week]" type="week" />
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index a615751eb5..ae631ae58d 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -223,8 +223,7 @@ the server.
The "Welcome aboard" page is the _smoke test_ for a new Rails application: it
makes sure that you have your software configured correctly enough to serve a
-page. You can also click on the _About your application's environment_ link to
-see a summary of your application's environment.
+page.
### Say "Hello", Rails
diff --git a/guides/source/security.md b/guides/source/security.md
index 4883c1abe7..16c5291037 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -494,6 +494,8 @@ By default, Rails logs all requests being made to the web application. But log f
config.filter_parameters << :password
```
+NOTE: Provided parameters will be filtered out by partial matching regular expression. Rails adds default `:password` in the appropriate initializer (`initializers/filter_parameter_logging.rb`) and cares about typical application parameters `password` and `password_confirmation`.
+
### Good Passwords
INFO: _Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence._
diff --git a/guides/source/testing.md b/guides/source/testing.md
index e302611b2a..34c831c802 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -495,7 +495,8 @@ users(:david)
users(:david).id
# one can also access methods available on the User class
-email(david.partner.email, david.location_tonight)
+david = users(:david)
+david.call(david.partner)
```
To get multiple fixtures at once, you can pass in a list of fixture names. For example:
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 7193238e83..32bfdf272b 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Make `rails restart` command work with Puma by passing the restart command
+ which Puma can use to restart rails server.
+
+ *Prathamesh Sonpatki*
+
* The application generator writes a new file `config/spring.rb`, which tells
Spring to watch additional common files.
@@ -33,7 +38,7 @@
system monitor and the async plugin for spring.
* The Gemfiles of new applications include spring-watcher-listen on Linux and
- Mac OS X (unless --skip-spring).
+ Mac OS X (unless `--skip-spring`).
*Xavier Noria*
@@ -108,7 +113,7 @@
*Chuck Callebs*
-* Allow use of minitest-rails gem with Rails test runner.
+* Allow use of `minitest-rails` gem with Rails test runner.
Fixes #22455.
@@ -121,13 +126,13 @@
*Yuji Yaginuma*
* Make `static_index` part of the `config.public_file_server` config and
- call it `public_file_server.index_name`.
+ call it `config.public_file_server.index_name`.
*Yuki Nishijima*
-* Deprecate `serve_static_files` in favor of `public_file_server.enabled`.
+* Deprecate `config.serve_static_files` in favor of `config.public_file_server.enabled`.
- Unifies the static asset options under `public_file_server`.
+ Unifies the static asset options under `config.public_file_server`.
To upgrade, replace occurrences of:
@@ -165,8 +170,8 @@
*Yuki Nishijima*
-* Route generator should be idempotent
- running generators several times no longer require you to cleanup routes.rb
+* Route generators are now idempotent.
+ Running generators several times no longer require you to cleanup routes.rb.
*Thiago Pinto*
@@ -174,7 +179,7 @@
*Simon Eskildsen*
-* Allow rake:stats to account for rake tasks in lib/tasks
+* Allow `rake stats` to account for rake tasks in lib/tasks.
*Kevin Deisz*
@@ -184,7 +189,7 @@
*James Kerr*
-* Add fail fast to `bin/rails test`
+* Add fail fast to `bin/rails test`.
Adding `--fail-fast` or `-f` when running tests will interrupt the run on
the first failure:
@@ -216,7 +221,7 @@
*Kasper Timm Hansen*
-* Add inline output to `bin/rails test`
+* Add inline output to `bin/rails test`.
Any failures or errors (and skips if running in verbose mode) are output
during a test run:
@@ -247,7 +252,7 @@
*Kasper Timm Hansen*
* Fix displaying mailer previews on non local requests when config
- `action_mailer.show_previews` is set
+ `config.action_mailer.show_previews` is set.
*Wojciech Wnętrzak*
@@ -278,14 +283,14 @@
*Ersin Akinci*
* Make enabling or disabling caching in development mode possible with
- rake dev:cache.
+ `rake dev:cache`.
- Running rake dev:cache will create or remove tmp/caching-dev.txt. When this
- file exists config.action_controller.perform_caching will be set to true in
+ Running `rake dev:cache` will create or remove tmp/caching-dev.txt. When this
+ file exists `config.action_controller.perform_caching` will be set to true in
config/environments/development.rb.
- Additionally, a server can be started with either --dev-caching or
- --no-dev-caching included to toggle caching on startup.
+ Additionally, a server can be started with either `--dev-caching` or
+ `--no-dev-caching` included to toggle caching on startup.
*Jussi Mertanen*, *Chuck Callebs*
@@ -298,11 +303,11 @@
*Yuji Yaginuma*
-* Adding support for passing a block to the `add_source` action of a custom generator
+* Adding support for passing a block to the `add_source` action of a custom generator.
*Mike Dalton*, *Hirofumi Wakasugi*
-* `assert_file` understands paths with special characters
+* `assert_file` now understands paths with special characters
(eg. `v0.1.4~alpha+nightly`).
*Diego Carrion*
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 4729ddcf62..ed106c9918 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -1,4 +1,3 @@
-require 'fileutils'
require 'yaml'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/object/blank'
@@ -246,7 +245,7 @@ module Rails
@app_env_config ||= begin
validate_secret_key_config!
- super.merge({
+ super.merge(
"action_dispatch.parameter_filter" => config.filter_parameters,
"action_dispatch.redirect_filter" => config.filter_redirect,
"action_dispatch.secret_token" => secrets.secret_token,
@@ -262,7 +261,7 @@ module Rails
"action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt,
"action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer,
"action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest
- })
+ )
end
end
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 9baf8aa742..f615f22b26 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -1,6 +1,7 @@
-require "active_support/notifications"
-require "active_support/dependencies"
-require "active_support/descendants_tracker"
+require 'fileutils'
+require 'active_support/notifications'
+require 'active_support/dependencies'
+require 'active_support/descendants_tracker'
module Rails
class Application
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 65cff1561a..f415a20833 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -58,7 +58,7 @@ module Rails
def static_cache_control=(value)
ActiveSupport::Deprecation.warn <<-eow.strip_heredoc
- `static_cache_control` is deprecated and will be removed in Rails 5.1.
+ `config.static_cache_control` is deprecated and will be removed in Rails 5.1.
Please use
`config.public_file_server.headers = { 'Cache-Control' => '#{value}' }`
instead.
@@ -69,8 +69,8 @@ module Rails
def serve_static_files
ActiveSupport::Deprecation.warn <<-eow.strip_heredoc
- `serve_static_files` is deprecated and will be removed in Rails 5.1.
- Please use `public_file_server.enabled` instead.
+ `config.serve_static_files` is deprecated and will be removed in Rails 5.1.
+ Please use `config.public_file_server.enabled` instead.
eow
@public_file_server.enabled
@@ -78,8 +78,8 @@ module Rails
def serve_static_files=(value)
ActiveSupport::Deprecation.warn <<-eow.strip_heredoc
- `serve_static_files` is deprecated and will be removed in Rails 5.1.
- Please use `public_file_server.enabled = #{value}` instead.
+ `config.serve_static_files` is deprecated and will be removed in Rails 5.1.
+ Please use `config.public_file_server.enabled = #{value}` instead.
eow
@public_file_server.enabled = value
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 34f2265108..0aed6c1351 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -62,18 +62,36 @@ module Rails
ActiveSupport.run_load_hooks(:after_initialize, self)
end
+ class MutexHook
+ def initialize(mutex = Mutex.new)
+ @mutex = mutex
+ end
+
+ def run
+ @mutex.lock
+ end
+
+ def complete(_state)
+ @mutex.unlock
+ end
+ end
+
+ module InterlockHook
+ def self.run
+ ActiveSupport::Dependencies.interlock.start_running
+ end
+
+ def self.complete(_state)
+ ActiveSupport::Dependencies.interlock.done_running
+ end
+ end
+
initializer :configure_executor_for_concurrency do |app|
if config.allow_concurrency == false
# User has explicitly opted out of concurrent request
# handling: presumably their code is not threadsafe
- mutex = Mutex.new
- app.executor.to_run(prepend: true) do
- mutex.lock
- end
- app.executor.to_complete(:after) do
- mutex.unlock
- end
+ app.executor.register_hook(MutexHook.new, outer: true)
elsif config.allow_concurrency == :unsafe
# Do nothing, even if we know this is dangerous. This is the
@@ -86,12 +104,7 @@ module Rails
# Without cache_classes + eager_load, the load interlock
# is required for proper operation
- app.executor.to_run(prepend: true) do
- ActiveSupport::Dependencies.interlock.start_running
- end
- app.executor.to_complete(:after) do
- ActiveSupport::Dependencies.interlock.done_running
- end
+ app.executor.register_hook(InterlockHook, outer: true)
end
end
end
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index 5844e9037c..f9c183ac86 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -2,6 +2,7 @@ require 'optparse'
options = { environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup }
code_or_file = nil
+command = 'bin/rails runner'
if ARGV.first.nil?
ARGV.push "-h"
@@ -34,7 +35,7 @@ ARGV.clone.options do |opts|
opts.separator ""
opts.separator "You can also use runner as a shebang line for your executables:"
opts.separator " -------------------------------------------------------------"
- opts.separator " #!/usr/bin/env #{File.expand_path($0)} runner"
+ opts.separator " #!/usr/bin/env #{File.expand_path(command)}"
opts.separator ""
opts.separator " Product.all.each { |p| p.price *= 2 ; p.save! }"
opts.separator " -------------------------------------------------------------"
@@ -52,7 +53,7 @@ Rails.application.require_environment!
Rails.application.load_runner
if code_or_file.nil?
- $stderr.puts "Run '#{$0} -h' for help."
+ $stderr.puts "Run '#{command} -h' for help."
exit 1
elsif File.exist?(code_or_file)
$0 = code_or_file
@@ -62,7 +63,7 @@ else
eval(code_or_file, binding, __FILE__, __LINE__)
rescue SyntaxError, NameError
$stderr.puts "Please specify a valid ruby command or the path of a script to run."
- $stderr.puts "Run '#{$0} -h' for help."
+ $stderr.puts "Run '#{command} -h' for help."
exit 1
end
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index c2434d62e2..7418dff18b 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -94,7 +94,8 @@ module Rails
environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup,
daemonize: false,
caching: nil,
- pid: Options::DEFAULT_PID_PATH
+ pid: Options::DEFAULT_PID_PATH,
+ restart_cmd: restart_command
})
end
@@ -130,5 +131,9 @@ module Rails
Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
end
end
+
+ def restart_command
+ "bin/rails server #{ARGV.join(' ')}"
+ end
end
end
diff --git a/railties/lib/rails/dev_caching.rb b/railties/lib/rails/dev_caching.rb
index 4760010851..f2a53d6417 100644
--- a/railties/lib/rails/dev_caching.rb
+++ b/railties/lib/rails/dev_caching.rb
@@ -1,3 +1,5 @@
+require 'fileutils'
+
module Rails
module DevCaching # :nodoc:
class << self
@@ -15,6 +17,7 @@ module Rails
end
FileUtils.touch 'tmp/restart.txt'
+ FileUtils.rm_f('tmp/pids/server.pid')
end
def enable_by_argument(caching)
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 57309112b5..c947c062fa 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -1,5 +1,3 @@
-require 'open-uri'
-
module Rails
module Generators
module Actions
diff --git a/railties/lib/rails/generators/actions/create_migration.rb b/railties/lib/rails/generators/actions/create_migration.rb
index d664b07652..6c5b55466d 100644
--- a/railties/lib/rails/generators/actions/create_migration.rb
+++ b/railties/lib/rails/generators/actions/create_migration.rb
@@ -1,3 +1,4 @@
+require 'fileutils'
require 'thor/actions'
module Rails
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 89341e6fa2..151bf9a879 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -1,3 +1,4 @@
+require 'fileutils'
require 'digest/md5'
require 'active_support/core_ext/string/strip'
require 'rails/version' unless defined?(Rails::VERSION)
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index d6be69e16c..f58e6ba653 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -95,13 +95,10 @@ module Rails
action_cable_config_exist = File.exist?('config/cable.yml')
ssl_options_exist = File.exist?('config/initializers/ssl_options.rb')
rack_cors_config_exist = File.exist?('config/initializers/cors.rb')
- development_config_exist = File.exist?('config/environments/development.rb')
config
- if development_config_exist
- gsub_file 'config/environments/development.rb', /^(\s+)config\.file_watcher/, '\1# config.file_watcher'
- end
+ gsub_file 'config/environments/development.rb', /^(\s+)config\.file_watcher/, '\1# config.file_watcher'
unless callback_terminator_config_exist
remove_file 'config/initializers/callback_terminator.rb'
diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
index 72258cc96b..d51f79bd49 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
@@ -8,8 +8,8 @@
<%%= stylesheet_link_tag 'application', media: 'all' %>
<%- else -%>
<%- if gemfile_entries.any? { |m| m.name == 'turbolinks' } -%>
- <%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => 'reload' %>
- <%%= javascript_include_tag 'application', 'data-turbolinks-track' => 'reload' %>
+ <%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
+ <%%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<%- else -%>
<%%= stylesheet_link_tag 'application', media: 'all' %>
<%%= javascript_include_tag 'application' %>
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 99dd571a00..d03f8324bc 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -5,34 +5,34 @@ require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/module/delegation'
module Rails
- # Railtie is the core of the Rails framework and provides several hooks to extend
- # Rails and/or modify the initialization process.
+ # <tt>Rails::Railtie</tt> is the core of the Rails framework and provides
+ # several hooks to extend Rails and/or modify the initialization process.
#
- # Every major component of Rails (Action Mailer, Action Controller,
- # Action View and Active Record) is a Railtie. Each of
- # them is responsible for their own initialization. This makes Rails itself
- # absent of any component hooks, allowing other components to be used in
- # place of any of the Rails defaults.
+ # Every major component of Rails (Action Mailer, Action Controller, Active
+ # Record, etc.) implements a railtie. Each of them is responsible for their
+ # own initialization. This makes Rails itself absent of any component hooks,
+ # allowing other components to be used in place of any of the Rails defaults.
#
- # Developing a Rails extension does _not_ require any implementation of
- # Railtie, but if you need to interact with the Rails framework during
- # or after boot, then Railtie is needed.
+ # Developing a Rails extension does _not_ require implementing a railtie, but
+ # if you need to interact with the Rails framework during or after boot, then
+ # a railtie is needed.
#
- # For example, an extension doing any of the following would require Railtie:
+ # For example, an extension doing any of the following would need a railtie:
#
# * creating initializers
# * configuring a Rails framework for the application, like setting a generator
# * adding <tt>config.*</tt> keys to the environment
- # * setting up a subscriber with ActiveSupport::Notifications
- # * adding rake tasks
+ # * setting up a subscriber with <tt>ActiveSupport::Notifications</tt>
+ # * adding Rake tasks
#
- # == Creating your Railtie
+ # == Creating a Railtie
#
- # To extend Rails using Railtie, create a Railtie class which inherits
- # from Rails::Railtie within your extension's namespace. This class must be
- # loaded during the Rails boot process.
+ # To extend Rails using a railtie, create a subclass of <tt>Rails::Railtie</tt>.
+ # This class must be loaded during the Rails boot process, and is conventionally
+ # called <tt>MyNamespace::Railtie</tt>.
#
- # The following example demonstrates an extension which can be used with or without Rails.
+ # The following example demonstrates an extension which can be used with or
+ # without Rails.
#
# # lib/my_gem/railtie.rb
# module MyGem
@@ -45,8 +45,8 @@ module Rails
#
# == Initializers
#
- # To add an initialization step from your Railtie to Rails boot process, you just need
- # to create an initializer block:
+ # To add an initialization step to the Rails boot process from your railtie, just
+ # define the initialization code with the +initializer+ macro:
#
# class MyRailtie < Rails::Railtie
# initializer "my_railtie.configure_rails_initialization" do
@@ -55,7 +55,7 @@ module Rails
# end
#
# If specified, the block can also receive the application object, in case you
- # need to access some application specific configuration, like middleware:
+ # need to access some application-specific configuration, like middleware:
#
# class MyRailtie < Rails::Railtie
# initializer "my_railtie.configure_rails_initialization" do |app|
@@ -63,54 +63,54 @@ module Rails
# end
# end
#
- # Finally, you can also pass <tt>:before</tt> and <tt>:after</tt> as option to initializer,
- # in case you want to couple it with a specific step in the initialization process.
+ # Finally, you can also pass <tt>:before</tt> and <tt>:after</tt> as options to
+ # +initializer+, in case you want to couple it with a specific step in the
+ # initialization process.
#
# == Configuration
#
- # Inside the Railtie class, you can access a config object which contains configuration
- # shared by all railties and the application:
+ # Railties can access a config object which contains configuration shared by all
+ # railties and the application:
#
# class MyRailtie < Rails::Railtie
# # Customize the ORM
# config.app_generators.orm :my_railtie_orm
#
# # Add a to_prepare block which is executed once in production
- # # and before each request in development
+ # # and before each request in development.
# config.to_prepare do
# MyRailtie.setup!
# end
# end
#
- # == Loading rake tasks and generators
+ # == Loading Rake Tasks and Generators
#
- # If your railtie has rake tasks, you can tell Rails to load them through the method
- # rake_tasks:
+ # If your railtie has Rake tasks, you can tell Rails to load them through the method
+ # +rake_tasks+:
#
# class MyRailtie < Rails::Railtie
# rake_tasks do
- # load "path/to/my_railtie.tasks"
+ # load 'path/to/my_railtie.tasks'
# end
# end
#
# By default, Rails loads generators from your load path. However, if you want to place
- # your generators at a different location, you can specify in your Railtie a block which
+ # your generators at a different location, you can specify in your railtie a block which
# will load them during normal generators lookup:
#
# class MyRailtie < Rails::Railtie
# generators do
- # require "path/to/my_railtie_generator"
+ # require 'path/to/my_railtie_generator'
# end
# end
#
# == Application and Engine
#
- # A Rails::Engine is nothing more than a Railtie with some initializers already set.
- # And since Rails::Application is an engine, the same configuration described here
- # can be used in both.
+ # An engine is nothing more than a railtie with some initializers already set. And since
+ # <tt>Rails::Application</tt> is an engine, the same configuration described here can be
+ # used in both.
#
# Be sure to look at the documentation of those specific classes for more information.
- #
class Railtie
autoload :Configuration, "rails/railtie/configuration"
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index 61fb8311a5..3e771167ee 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -26,12 +26,12 @@ namespace :app do
default_templates.each do |type, names|
local_template_type_dir = File.join(project_templates, type)
- FileUtils.mkdir_p local_template_type_dir
+ mkdir_p local_template_type_dir, verbose: false
names.each do |name|
dst_name = File.join(local_template_type_dir, name)
src_name = File.join(generators_lib, type, name, "templates")
- FileUtils.cp_r src_name, dst_name
+ cp_r src_name, dst_name, verbose: false
end
end
end
diff --git a/railties/lib/rails/tasks/restart.rake b/railties/lib/rails/tasks/restart.rake
index f36c86d81b..3f98cbe51f 100644
--- a/railties/lib/rails/tasks/restart.rake
+++ b/railties/lib/rails/tasks/restart.rake
@@ -1,5 +1,8 @@
-desc "Restart app by touching tmp/restart.txt"
+desc 'Restart app by touching tmp/restart.txt'
task :restart do
- FileUtils.mkdir_p('tmp')
- FileUtils.touch('tmp/restart.txt')
+ verbose(false) do
+ mkdir_p 'tmp'
+ touch 'tmp/restart.txt'
+ rm_f 'tmp/pids/server.pid'
+ end
end
diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake
index 9162ef234a..c74a17a0ca 100644
--- a/railties/lib/rails/tasks/tmp.rake
+++ b/railties/lib/rails/tasks/tmp.rake
@@ -5,9 +5,7 @@ namespace :tmp do
tmp_dirs = [ 'tmp/cache',
'tmp/sockets',
'tmp/pids',
- 'tmp/cache/assets/development',
- 'tmp/cache/assets/test',
- 'tmp/cache/assets/production' ]
+ 'tmp/cache/assets' ]
tmp_dirs.each { |d| directory d }
@@ -17,21 +15,21 @@ namespace :tmp do
namespace :cache do
# desc "Clears all files and directories in tmp/cache"
task :clear do
- FileUtils.rm_rf(Dir['tmp/cache/[^.]*'])
+ rm_rf Dir['tmp/cache/[^.]*'], verbose: false
end
end
namespace :sockets do
# desc "Clears all files in tmp/sockets"
task :clear do
- FileUtils.rm(Dir['tmp/sockets/[^.]*'])
+ rm Dir['tmp/sockets/[^.]*'], verbose: false
end
end
namespace :pids do
# desc "Clears all files in tmp/pids"
task :clear do
- FileUtils.rm(Dir['tmp/pids/[^.]*'])
+ rm Dir['tmp/pids/[^.]*'], verbose: false
end
end
end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 11e19eec80..e32eea42b7 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -344,8 +344,7 @@ module ApplicationTests
clean_assets!
- files = Dir["#{app_path}/public/assets/**/*", "#{app_path}/tmp/cache/assets/development/*",
- "#{app_path}/tmp/cache/assets/test/*", "#{app_path}/tmp/cache/assets/production/*"]
+ files = Dir["#{app_path}/public/assets/**/*"]
assert_equal 0, files.length, "Expected no assets, but found #{files.join(', ')}"
end
diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb
index 59b46c6e79..deb9bc8dee 100644
--- a/railties/test/application/rake/dev_test.rb
+++ b/railties/test/application/rake/dev_test.rb
@@ -29,6 +29,15 @@ module ApplicationTests
assert_match(/Development mode is no longer being cached/, output)
end
end
+
+ test 'dev:cache removes server.pid also' do
+ Dir.chdir(app_path) do
+ FileUtils.mkdir_p("tmp/pids")
+ FileUtils.touch("tmp/pids/server.pid")
+ `rake dev:cache`
+ assert_not File.exist?("tmp/pids/server.pid")
+ end
+ end
end
end
end
diff --git a/railties/test/application/rake/restart_test.rb b/railties/test/application/rake/restart_test.rb
index 4cae199e6b..30f662a9be 100644
--- a/railties/test/application/rake/restart_test.rb
+++ b/railties/test/application/rake/restart_test.rb
@@ -34,6 +34,15 @@ module ApplicationTests
assert File.exist?('tmp/restart.txt')
end
end
+
+ test 'rake restart removes server.pid also' do
+ Dir.chdir(app_path) do
+ FileUtils.mkdir_p("tmp/pids")
+ FileUtils.touch("tmp/pids/server.pid")
+ `rake restart`
+ assert_not File.exist?("tmp/pids/server.pid")
+ end
+ end
end
end
end
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index 964b9a44de..38a1605d1f 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -118,4 +118,18 @@ class Rails::ServerTest < ActiveSupport::TestCase
assert_equal old_default_options, server.default_options
end
end
+
+ def test_restart_command_contains_customized_options
+ original_args = ARGV.dup
+ args = ["-p", "4567"]
+ ARGV.replace args
+
+ options = Rails::Server::Options.new.parse! args
+ server = Rails::Server.new options
+ expected = "bin/rails server -p 4567"
+
+ assert_equal expected, server.default_options[:restart_cmd]
+ ensure
+ ARGV.replace original_args
+ end
end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 0572a23df9..2d9867fa9d 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -65,8 +65,8 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_assets
run_generator
- assert_file("app/views/layouts/application.html.erb", /stylesheet_link_tag\s+'application', media: 'all', 'data-turbolinks-track' => 'reload'/)
- assert_file("app/views/layouts/application.html.erb", /javascript_include_tag\s+'application', 'data-turbolinks-track' => 'reload'/)
+ assert_file("app/views/layouts/application.html.erb", /stylesheet_link_tag\s+'application', media: 'all', 'data-turbolinks-track': 'reload'/)
+ assert_file("app/views/layouts/application.html.erb", /javascript_include_tag\s+'application', 'data-turbolinks-track': 'reload'/)
assert_file("app/assets/stylesheets/application.css")
assert_file("app/assets/javascripts/application.js")
end
diff --git a/railties/test/generators/channel_generator_test.rb b/railties/test/generators/channel_generator_test.rb
index cda9e8b910..23d0c7b4a4 100644
--- a/railties/test/generators/channel_generator_test.rb
+++ b/railties/test/generators/channel_generator_test.rb
@@ -38,4 +38,12 @@ class ChannelGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/assets/javascripts/channels/chat.coffee"
end
+
+ def test_cable_js_is_created_if_not_present_already
+ run_generator ['chat']
+ FileUtils.rm("#{destination_root}/app/assets/javascripts/cable.js")
+ run_generator ['camp']
+
+ assert_file "app/assets/javascripts/cable.js"
+ end
end
diff --git a/railties/test/generators/job_generator_test.rb b/railties/test/generators/job_generator_test.rb
index 7fd8f2062f..dbff0ab704 100644
--- a/railties/test/generators/job_generator_test.rb
+++ b/railties/test/generators/job_generator_test.rb
@@ -26,4 +26,11 @@ class JobGeneratorTest < Rails::Generators::TestCase
assert_match(/queue_as :admin/, job)
end
end
+
+ def test_application_job_skeleton_is_created
+ run_generator ["refresh_counters"]
+ assert_file "app/jobs/application_job.rb" do |job|
+ assert_match(/class ApplicationJob < ActiveJob::Base/, job)
+ end
+ end
end
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 17a2c6a327..3cc8e1de55 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -669,6 +669,19 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_generate_application_job_when_does_not_exist_in_mountable_engine
+ run_generator [destination_root, '--mountable']
+ FileUtils.rm "#{destination_root}/app/jobs/bukkits/application_job.rb"
+ capture(:stdout) do
+ `#{destination_root}/bin/rails g job refresh_counters`
+ end
+
+ assert_file "#{destination_root}/app/jobs/bukkits/application_job.rb" do |record|
+ assert_match(/module Bukkits/, record)
+ assert_match(/class ApplicationJob < ActiveJob::Base/, record)
+ end
+ end
+
def test_after_bundle_callback
path = 'http://example.org/rails_template'
template = %{ after_bundle { run 'echo ran after_bundle' } }