aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/issue_template.md15
-rw-r--r--.github/pull_request_template.md18
-rw-r--r--README.md2
-rw-r--r--actioncable/README.md10
-rw-r--r--actioncable/app/assets/javascripts/action_cable.coffee.erb2
-rw-r--r--actioncable/app/assets/javascripts/action_cable/connection.coffee4
-rw-r--r--actioncable/lib/action_cable/channel/base.rb2
-rw-r--r--actioncable/lib/action_cable/channel/streams.rb2
-rw-r--r--actioncable/lib/action_cable/connection/client_socket.rb8
-rw-r--r--actioncable/lib/action_cable/connection/stream.rb18
-rw-r--r--actioncable/lib/action_cable/engine.rb13
-rw-r--r--actioncable/lib/action_cable/helpers/action_cable_helper.rb27
-rw-r--r--actioncable/lib/action_cable/server/configuration.rb2
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/redis.rb8
-rw-r--r--actioncable/test/connection/base_test.rb20
-rw-r--r--actionpack/CHANGELOG.md32
-rw-r--r--actionpack/lib/action_controller/metal/basic_implicit_render.rb2
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb91
-rw-r--r--actionpack/lib/action_controller/test_case.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb15
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb26
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb2
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb93
-rw-r--r--actionpack/test/controller/render_test.rb25
-rw-r--r--actionpack/test/dispatch/request/session_test.rb10
-rw-r--r--actionpack/test/dispatch/response_test.rb8
-rw-r--r--actionpack/test/dispatch/session/abstract_store_test.rb16
-rw-r--r--actionpack/test/dispatch/session/cache_store_test.rb29
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb34
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb31
-rw-r--r--actionpack/test/dispatch/session/test_session_test.rb7
-rw-r--r--actionpack/test/dispatch/ssl_test.rb23
-rw-r--r--actionpack/test/fixtures/implicit_render_test/empty_action_with_mobile_variant.html+mobile.erb1
-rw-r--r--actionpack/test/fixtures/implicit_render_test/empty_action_with_template.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/variant_with_implicit_template_rendering.html+mobile.erb (renamed from actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb)0
-rw-r--r--actionview/CHANGELOG.md6
-rw-r--r--actionview/lib/action_view/digestor.rb147
-rw-r--r--actionview/lib/action_view/log_subscriber.rb18
-rw-r--r--actionview/lib/action_view/lookup_context.rb33
-rw-r--r--actionview/lib/action_view/renderer/abstract_renderer.rb2
-rw-r--r--actionview/lib/action_view/tasks/dependencies.rake4
-rw-r--r--actionview/lib/action_view/template/resolver.rb8
-rw-r--r--actionview/lib/action_view/view_paths.rb2
-rw-r--r--actionview/test/template/digestor_test.rb14
-rw-r--r--actionview/test/template/log_subscriber_test.rb9
-rw-r--r--activerecord/CHANGELOG.md7
-rw-r--r--activerecord/lib/active_record/suppressor.rb6
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb26
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/application_record.rb2
-rw-r--r--activerecord/test/cases/suppressor_test.rb13
-rw-r--r--activerecord/test/cases/validations/presence_validation_test.rb2
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb18
-rw-r--r--activerecord/test/models/notification.rb1
-rw-r--r--guides/source/5_0_release_notes.md6
-rw-r--r--guides/source/api_app.md17
-rw-r--r--guides/source/configuring.md17
-rw-r--r--guides/source/debugging_rails_applications.md10
-rw-r--r--guides/source/getting_started.md36
-rw-r--r--guides/source/rails_application_templates.md6
-rw-r--r--guides/source/upgrading_ruby_on_rails.md6
-rw-r--r--railties/CHANGELOG.md9
-rw-r--r--railties/lib/rails/app_loader.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb10
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/README.md2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/ssl_options.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb3
-rw-r--r--railties/lib/rails/tasks/dev.rake2
-rw-r--r--railties/lib/rails/tasks/framework.rake16
-rw-r--r--railties/test/application/rake_test.rb6
-rw-r--r--railties/test/generators/app_generator_test.rb62
-rw-r--r--railties/test/generators/plugin_generator_test.rb14
-rw-r--r--tools/README.md4
79 files changed, 769 insertions, 403 deletions
diff --git a/.github/issue_template.md b/.github/issue_template.md
new file mode 100644
index 0000000000..2d071d4a71
--- /dev/null
+++ b/.github/issue_template.md
@@ -0,0 +1,15 @@
+### Steps to reproduce
+
+(Guidelines for creating a bug report are [available
+here](http://guides.rubyonrails.org/contributing_to_ruby_on_rails.html#creating-a-bug-report))
+
+### Expected behavior
+Tell us what should happen
+
+### Actual behavior
+Tell us what happens instead
+
+### System configuration
+**Rails version**:
+
+**Ruby version**:
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000000..48f7b0e214
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,18 @@
+### Summary
+
+Provide a general description of the code changes in your pull
+request... were there any bugs you had fixed? If so, mention them. If
+these bugs have open GitHub issues, be sure to tag them here as well,
+to keep the conversation linked together.
+
+### Other Information
+
+If there's anything else that's important and relevant to your pull
+request, mention that information here. This could include
+benchmarks, or other information.
+
+Finally, if your pull request affects documentation or any non-code
+changes, guidelines for those changes are [available
+here](http://guides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation)
+
+Thanks for contributing to Rails!
diff --git a/README.md b/README.md
index fb9e683bc3..a2b726ea6c 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-## Welcome to Rails
+# Welcome to Rails
Rails is a web-application framework that includes everything needed to
create database-backed web applications according to the
diff --git a/actioncable/README.md b/actioncable/README.md
index 334c75c79c..bb15ad3c70 100644
--- a/actioncable/README.md
+++ b/actioncable/README.md
@@ -339,21 +339,21 @@ Rails.application.config.action_cable.disable_request_forgery_protection = true
### Consumer Configuration
-Once you have decided how to run your cable server (see below), you must provide the server url (or path) to your client-side setup.
+Once you have decided how to run your cable server (see below), you must provide the server URL (or path) to your client-side setup.
There are two ways you can do this.
The first is to simply pass it in when creating your consumer. For a standalone server,
this would be something like: `App.cable = ActionCable.createConsumer("ws://example.com:28080")`, and for an in-app server,
something like: `App.cable = ActionCable.createConsumer("/cable")`.
-The second option is to pass the server url through the `action_cable_meta_tag` in your layout.
-This uses a url or path typically set via `config.action_cable.url` in the environment configuration files, or defaults to "/cable".
+The second option is to pass the server URL through the `action_cable_meta_tag` in your layout.
+This uses a URL or path typically set via `config.action_cable.url` in the environment configuration files, or defaults to "/cable".
-This method is especially useful if your WebSocket url might change between environments. If you host your production server via https, you will need to use the wss scheme
+This method is especially useful if your WebSocket URL might change between environments. If you host your production server via https, you will need to use the wss scheme
for your Action Cable server, but development might remain http and use the ws scheme. You might use localhost in development and your
domain in production.
-In any case, to vary the WebSocket url between environments, add the following configuration to each environment:
+In any case, to vary the WebSocket URL between environments, add the following configuration to each environment:
```ruby
config.action_cable.url = "ws://example.com:28080"
diff --git a/actioncable/app/assets/javascripts/action_cable.coffee.erb b/actioncable/app/assets/javascripts/action_cable.coffee.erb
index d95fe78ac5..6a8b4eeb85 100644
--- a/actioncable/app/assets/javascripts/action_cable.coffee.erb
+++ b/actioncable/app/assets/javascripts/action_cable.coffee.erb
@@ -9,7 +9,7 @@
getConfig: (name) ->
element = document.head.querySelector("meta[name='action-cable-#{name}']")
- element?.getAttribute("content")
+ element?.getAttribute("content") ? '/cable'
createWebSocketURL: (url) ->
if url and not /^wss?:/i.test(url)
diff --git a/actioncable/app/assets/javascripts/action_cable/connection.coffee b/actioncable/app/assets/javascripts/action_cable/connection.coffee
index ee888f567b..4244322a1e 100644
--- a/actioncable/app/assets/javascripts/action_cable/connection.coffee
+++ b/actioncable/app/assets/javascripts/action_cable/connection.coffee
@@ -6,9 +6,11 @@ class ActionCable.Connection
@reopenDelay: 500
constructor: (@consumer) ->
- @open()
send: (data) ->
+ unless @isOpen()
+ @open()
+
if @isOpen()
@webSocket.send(JSON.stringify(data))
true
diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb
index 05764fe107..714d9887d4 100644
--- a/actioncable/lib/action_cable/channel/base.rb
+++ b/actioncable/lib/action_cable/channel/base.rb
@@ -166,7 +166,7 @@ module ActionCable
end
end
- # Called by the cable connection when its cut, so the channel has a chance to cleanup with callbacks.
+ # Called by the cable connection when it's cut, so the channel has a chance to cleanup with callbacks.
# This method is not intended to be called directly by the user. Instead, overwrite the #unsubscribed callback.
def unsubscribe_from_channel # :nodoc:
run_callbacks :unsubscribe do
diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb
index 28092a8b2b..431a5c1063 100644
--- a/actioncable/lib/action_cable/channel/streams.rb
+++ b/actioncable/lib/action_cable/channel/streams.rb
@@ -1,6 +1,6 @@
module ActionCable
module Channel
- # Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a pub/sub queue where any data
+ # Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a pubsub queue where any data
# placed into it is automatically sent to the clients that are connected at that time. It's purely an online queue, though. If you're not
# streaming a broadcasting at the very moment it sends out an update, you will not get that update, if you connect after it has been sent.
#
diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb
index 95e1ac4c16..f6b11e93f0 100644
--- a/actioncable/lib/action_cable/connection/client_socket.rb
+++ b/actioncable/lib/action_cable/connection/client_socket.rb
@@ -50,14 +50,16 @@ module ActionCable
@driver.on(:error) { |e| emit_error(e.message) }
@stream = ActionCable::Connection::Stream.new(@stream_event_loop, self)
+ end
+
+ def start_driver
+ return if @driver.nil? || @driver_started
+ @stream.hijack_rack_socket
if callback = @env['async.callback']
callback.call([101, {}, @stream])
end
- end
- def start_driver
- return if @driver.nil? || @driver_started
@driver_started = true
@driver.start
end
diff --git a/actioncable/lib/action_cable/connection/stream.rb b/actioncable/lib/action_cable/connection/stream.rb
index ace250cd16..2d97b28c09 100644
--- a/actioncable/lib/action_cable/connection/stream.rb
+++ b/actioncable/lib/action_cable/connection/stream.rb
@@ -4,15 +4,13 @@ module ActionCable
# This class is heavily based on faye-websocket-ruby
#
# Copyright (c) 2010-2015 James Coglan
- class Stream
+ class Stream # :nodoc:
def initialize(event_loop, socket)
@event_loop = event_loop
@socket_object = socket
@stream_send = socket.env['stream.send']
@rack_hijack_io = nil
-
- hijack_rack_socket
end
def each(&callback)
@@ -39,16 +37,16 @@ module ActionCable
@socket_object.parse(data)
end
- private
- def hijack_rack_socket
- return unless @socket_object.env['rack.hijack']
+ def hijack_rack_socket
+ return unless @socket_object.env['rack.hijack']
- @socket_object.env['rack.hijack'].call
- @rack_hijack_io = @socket_object.env['rack.hijack_io']
+ @socket_object.env['rack.hijack'].call
+ @rack_hijack_io = @socket_object.env['rack.hijack_io']
- @event_loop.attach(@rack_hijack_io, self)
- end
+ @event_loop.attach(@rack_hijack_io, self)
+ end
+ private
def clean_rack_hijack
return unless @rack_hijack_io
@event_loop.detach(@rack_hijack_io, self)
diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb
index f5f1cb59e0..ae0c59dccd 100644
--- a/actioncable/lib/action_cable/engine.rb
+++ b/actioncable/lib/action_cable/engine.rb
@@ -6,7 +6,7 @@ require "active_support/core_ext/hash/indifferent_access"
module ActionCable
class Railtie < Rails::Engine # :nodoc:
config.action_cable = ActiveSupport::OrderedOptions.new
- config.action_cable.url = '/cable'
+ config.action_cable.mount_path = '/cable'
config.eager_load_namespaces << ActionCable
@@ -40,5 +40,16 @@ module ActionCable
options.each { |k,v| send("#{k}=", v) }
end
end
+
+ initializer "action_cable.routes" do
+ config.after_initialize do |app|
+ config = app.config
+ unless config.action_cable.mount_path.nil?
+ app.routes.prepend do
+ mount ActionCable.server => config.action_cable.mount_path, internal: true
+ end
+ end
+ end
+ end
end
end
diff --git a/actioncable/lib/action_cable/helpers/action_cable_helper.rb b/actioncable/lib/action_cable/helpers/action_cable_helper.rb
index 3067542b33..2081a37db6 100644
--- a/actioncable/lib/action_cable/helpers/action_cable_helper.rb
+++ b/actioncable/lib/action_cable/helpers/action_cable_helper.rb
@@ -1,28 +1,39 @@
module ActionCable
module Helpers
module ActionCableHelper
- # Returns an "action-cable-url" meta tag with the value of the url specified in your
- # configuration. Ensure this is above your javascript tag:
+ # Returns an "action-cable-url" meta tag with the value of the URL specified in your
+ # configuration. Ensure this is above your JavaScript tag:
#
# <head>
# <%= action_cable_meta_tag %>
# <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
# </head>
#
- # This is then used by Action Cable to determine the url of your WebSocket server.
+ # This is then used by Action Cable to determine the URL of your WebSocket server.
# Your CoffeeScript can then connect to the server without needing to specify the
- # url directly:
+ # URL directly:
#
# #= require cable
# @App = {}
# App.cable = Cable.createConsumer()
#
- # Make sure to specify the correct server location in each of your environments
- # config file:
+ # Make sure to specify the correct server location in each of your environment
+ # config files:
+ #
+ # config.action_cable.mount_path = "/cable123"
+ # <%= action_cable_meta_tag %> would render:
+ # => <meta name="action-cable-url" content="/cable123" />
+ #
+ # config.action_cable.url = "ws://actioncable.com"
+ # <%= action_cable_meta_tag %> would render:
+ # => <meta name="action-cable-url" content="ws://actioncable.com" />
#
- # config.action_cable.url = "ws://example.com:28080"
def action_cable_meta_tag
- tag "meta", name: "action-cable-url", content: Rails.application.config.action_cable.url
+ tag "meta", name: "action-cable-url", content: (
+ ActionCable.server.config.url ||
+ ActionCable.server.config.mount_path ||
+ raise("No Action Cable URL configured -- please configure this at config.action_cable.url")
+ )
end
end
end
diff --git a/actioncable/lib/action_cable/server/configuration.rb b/actioncable/lib/action_cable/server/configuration.rb
index ee17bff13b..9a7301287c 100644
--- a/actioncable/lib/action_cable/server/configuration.rb
+++ b/actioncable/lib/action_cable/server/configuration.rb
@@ -6,7 +6,7 @@ module ActionCable
attr_accessor :logger, :log_tags
attr_accessor :connection_class, :worker_pool_size
attr_accessor :disable_request_forgery_protection, :allowed_request_origins
- attr_accessor :cable, :url
+ attr_accessor :cable, :url, :mount_path
attr_accessor :channel_paths # :nodoc:
diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb
index ba4934a264..6b4236e7d3 100644
--- a/actioncable/lib/action_cable/subscription_adapter/redis.rb
+++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb
@@ -33,7 +33,7 @@ module ActionCable
end
def redis_connection_for_subscriptions
- ::Redis.new(@server.config.cable)
+ redis_connection
end
private
@@ -43,10 +43,14 @@ module ActionCable
def redis_connection_for_broadcasts
@redis_connection_for_broadcasts || @server.mutex.synchronize do
- @redis_connection_for_broadcasts ||= self.class.redis_connector.call(@server.config.cable)
+ @redis_connection_for_broadcasts ||= redis_connection
end
end
+ def redis_connection
+ self.class.redis_connector.call(@server.config.cable)
+ end
+
class Listener < SubscriberMap
def initialize(adapter)
super()
diff --git a/actioncable/test/connection/base_test.rb b/actioncable/test/connection/base_test.rb
index 3bef9e95a1..fb11f9be64 100644
--- a/actioncable/test/connection/base_test.rb
+++ b/actioncable/test/connection/base_test.rb
@@ -108,6 +108,26 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase
end
end
+ test "rejecting a connection causes a 404" do
+ run_in_eventmachine do
+ class CallMeMaybe
+ def call(*)
+ raise 'Do not call me!'
+ end
+ end
+
+ env = Rack::MockRequest.env_for(
+ "/test",
+ { 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket',
+ 'HTTP_ORIGIN' => 'http://rubyonrails.org', 'rack.hijack' => CallMeMaybe.new }
+ )
+
+ connection = ActionCable::Connection::Base.new(@server, env)
+ response = connection.process
+ assert_equal 404, response[0]
+ end
+ end
+
private
def open_connection
env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket',
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index da96aef98b..6b73b29ace 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,13 +1,33 @@
-## Rails 5.0.0.beta3 (February 24, 2016) ##
+* Update default rendering policies when the controller action did
+ not explicitly indicate a response.
+
+ For API controllers, the implicit render always renders "204 No Content"
+ and does not account for any templates.
+
+ For other controllers, the following conditions are checked:
-* Update session to have indifferent access across multiple requests.
+ First, if a template exists for the controller action, it is rendered.
+ This template lookup takes into account the action name, locales, format,
+ variant, template handlers, etc. (see +render+ for details).
- session[:deep][:hash] = "Magic"
+ Second, if other templates exist for the controller action but is not in
+ the right format (or variant, etc.), an <tt>ActionController::UnknownFormat</tt>
+ is raised. The list of available templates is assumed to be a complete
+ enumeration of all the possible formats (or variants, etc.); that is,
+ having only HTML and JSON templates indicate that the controller action is
+ not meant to handle XML requests.
- session[:deep][:hash] == "Magic"
- session[:deep]["hash"] == "Magic"
+ Third, if the current request is an "interactive" browser request (the user
+ navigated here by entering the URL in the address bar, submiting a form,
+ clicking on a link, etc. as opposed to an XHR or non-browser API request),
+ <tt>ActionView::UnknownFormat</tt> is raised to display a helpful error
+ message.
- *Tom Prats*
+ Finally, it falls back to the same "204 No Content" behavior as API controllers.
+
+ *Godfrey Chan*, *Jon Moss*, *Kasper Timm Hansen*, *Mike Clark*, *Matthew Draper*
+
+## Rails 5.0.0.beta3 (February 24, 2016) ##
* Add application/gzip as a default mime type.
diff --git a/actionpack/lib/action_controller/metal/basic_implicit_render.rb b/actionpack/lib/action_controller/metal/basic_implicit_render.rb
index 6c6f8381ff..cef65a362c 100644
--- a/actionpack/lib/action_controller/metal/basic_implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/basic_implicit_render.rb
@@ -1,5 +1,5 @@
module ActionController
- module BasicImplicitRender
+ module BasicImplicitRender # :nodoc:
def send_action(method, *args)
super.tap { default_render unless performed? }
end
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index 17fcc2fa02..6b540d42c7 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -1,29 +1,80 @@
+require 'active_support/core_ext/string/strip'
+
module ActionController
+ # Handles implicit rendering for a controller action when it did not
+ # explicitly indicate an appropiate response via methods such as +render+,
+ # +respond_to+, +redirect+ or +head+.
+ #
+ # For API controllers, the implicit render always renders "204 No Content"
+ # and does not account for any templates.
+ #
+ # For other controllers, the following conditions are checked:
+ #
+ # First, if a template exists for the controller action, it is rendered.
+ # This template lookup takes into account the action name, locales, format,
+ # variant, template handlers, etc. (see +render+ for details).
+ #
+ # Second, if other templates exist for the controller action but is not in
+ # the right format (or variant, etc.), an <tt>ActionController::UnknownFormat</tt>
+ # is raised. The list of available templates is assumed to be a complete
+ # enumeration of all the possible formats (or variants, etc.); that is,
+ # having only HTML and JSON templates indicate that the controller action is
+ # not meant to handle XML requests.
+ #
+ # Third, if the current request is an "interactive" browser request (the user
+ # navigated here by entering the URL in the address bar, submiting a form,
+ # clicking on a link, etc. as opposed to an XHR or non-browser API request),
+ # <tt>ActionView::UnknownFormat</tt> is raised to display a helpful error
+ # message.
+ #
+ # Finally, it falls back to the same "204 No Content" behavior as API controllers.
module ImplicitRender
+ # :stopdoc:
include BasicImplicitRender
- # Renders the template corresponding to the controller action, if it exists.
- # The action name, format, and variant are all taken into account.
- # For example, the "new" action with an HTML format and variant "phone"
- # would try to render the <tt>new.html+phone.erb</tt> template.
- #
- # If no template is found <tt>ActionController::BasicImplicitRender</tt>'s implementation is called, unless
- # a block is passed. In that case, it will override the super implementation.
- #
- # default_render do
- # head 404 # No template was found
- # end
def default_render(*args)
if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
render(*args)
- else
- if block_given?
- yield(*args)
- else
- logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
- super
+ elsif any_templates?(action_name.to_s, _prefixes)
+ message = "#{self.class.name}\##{action_name} does not know how to respond " \
+ "to this request. There are other templates available for this controller " \
+ "action but none of them were suitable for this request.\n\n" \
+ "This usually happens when the client requested an unsupported format " \
+ "(e.g. requesting HTML content from a JSON endpoint or vice versa), but " \
+ "it might also be failing due to other constraints, such as locales or" \
+ "variants.\n"
+
+ if request.formats.any?
+ message << "\nRequested format(s): #{request.formats.join(", ")}"
end
+
+ if request.variant.any?
+ message << "\nRequested variant(s): #{request.variant.join(", ")}"
+ end
+
+ raise ActionController::UnknownFormat, message
+ elsif interactive_browser_request?
+ message = "You did not define any templates for #{self.class.name}\##{action_name}. " \
+ "This is not necessarily a problem (e.g. you might be building an API endpoint " \
+ "that does not require any templates), and the controller would usually respond " \
+ "with `head :no_content` for your convenience.\n\n" \
+ "However, you appear to have navigated here from an interactive browser request – " \
+ "such as by navigating to this URL directly, clicking on a link or submitting a form. " \
+ "Rendering a `head :no_content` in this case could have resulted in unexpected UI " \
+ "behavior in the browser.\n\n" \
+ "If you expected the `head :no_content` response, you do not need to take any " \
+ "actions – requests coming from an XHR (AJAX) request or other non-browser clients " \
+ "will receive the \"204 No Content\" response as expected.\n\n" \
+ "If you did not expect this behavior, you can resolve this error by adding a " \
+ "template for this controller action (usually `#{action_name}.html.erb`) or " \
+ "otherwise indicate the appropriate response in the action using `render`, " \
+ "`redirect_to`, `head`, etc.\n"
+
+ raise ActionController::UnknownFormat, message
+ else
+ logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
+ super
end
end
@@ -32,5 +83,11 @@ module ActionController
"default_render"
end
end
+
+ private
+
+ def interactive_browser_request?
+ request.format == Mime[:html] && !request.xhr?
+ end
end
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 6548ce326b..700317614f 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -176,7 +176,7 @@ module ActionController
def initialize(session = {})
super(nil, nil)
@id = SecureRandom.hex(16)
- @data = session.with_indifferent_access
+ @data = stringify_keys(session)
@loaded = true
end
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index fee08fc3db..cfd6681dd1 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -82,7 +82,7 @@ module ActionDispatch
end
def requirements # :nodoc:
- # needed for rails `rake routes`
+ # needed for rails `rails routes`
@defaults.merge(path.requirements).delete_if { |_,v|
/.+?/ == v
}
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 735b5939dd..711d8b016a 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -23,7 +23,7 @@ module ActionDispatch
# preload lists is `18.weeks`.
# * `subdomains`: Set to `true` to tell the browser to apply these settings
# to all subdomains. This protects your cookies from interception by a
- # vulnerable site on a subdomain. Defaults to `false`.
+ # vulnerable site on a subdomain. Defaults to `true`.
# * `preload`: Advertise that this site may be included in browsers'
# preloaded HSTS lists. HSTS protects your site on every visit *except the
# first visit* since it hasn't seen your HSTS header yet. To close this
@@ -49,7 +49,7 @@ module ActionDispatch
if options[:host] || options[:port]
ActiveSupport::Deprecation.warn <<-end_warning.strip_heredoc
The `:host` and `:port` options are moving within `:redirect`:
- `config.ssl_options = { redirect: { host: …, port: … }}`.
+ `config.ssl_options = { redirect: { host: …, port: … } }`.
end_warning
@redirect = options.slice(:host, :port)
else
@@ -57,6 +57,17 @@ module ActionDispatch
end
@secure_cookies = secure_cookies
+
+ if hsts != true && hsts != false && hsts[:subdomains].nil?
+ hsts[:subdomains] = false
+
+ ActiveSupport::Deprecation.warn <<-end_warning.strip_heredoc
+ In Rails 5.1, The `:subdomains` option of HSTS config will be treated as true if
+ unspecified. Set `config.ssl_options = { hsts: { subdomains: false } }` to opt out
+ of this behavior.
+ end_warning
+ end
+
@hsts_header = build_hsts_header(normalize_hsts_options(hsts))
end
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 38d0da3e67..42890225fa 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -9,7 +9,7 @@ module ActionDispatch
# Singleton object used to determine if an optional param wasn't specified
Unspecified = Object.new
-
+
# Creates a session hash, merging the properties of the previous session if any
def self.create(store, req, default_options)
session_was = find req
@@ -61,7 +61,7 @@ module ActionDispatch
def initialize(by, req)
@by = by
@req = req
- @delegate = {}.with_indifferent_access
+ @delegate = {}
@loaded = false
@exists = nil # we haven't checked yet
end
@@ -88,13 +88,13 @@ module ActionDispatch
# nil if the given key is not found in the session.
def [](key)
load_for_read!
- @delegate[key]
+ @delegate[key.to_s]
end
# Returns true if the session has the given key or false.
def has_key?(key)
load_for_read!
- @delegate.key?(key)
+ @delegate.key?(key.to_s)
end
alias :key? :has_key?
alias :include? :has_key?
@@ -112,7 +112,7 @@ module ActionDispatch
# Writes given value to given key of the session.
def []=(key, value)
load_for_write!
- @delegate[key] = value
+ @delegate[key.to_s] = value
end
# Clears the session.
@@ -139,13 +139,13 @@ module ActionDispatch
# # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"}
def update(hash)
load_for_write!
- @delegate.update hash
+ @delegate.update stringify_keys(hash)
end
# Deletes given key from the session.
def delete(key)
load_for_write!
- @delegate.delete key
+ @delegate.delete key.to_s
end
# Returns value of the given key from the session, or raises +KeyError+
@@ -165,9 +165,9 @@ module ActionDispatch
def fetch(key, default=Unspecified, &block)
load_for_read!
if default == Unspecified
- @delegate.fetch(key, &block)
+ @delegate.fetch(key.to_s, &block)
else
- @delegate.fetch(key, default, &block)
+ @delegate.fetch(key.to_s, default, &block)
end
end
@@ -211,9 +211,15 @@ module ActionDispatch
def load!
id, session = @by.load_session @req
options[:id] = id
- @delegate.replace(session)
+ @delegate.replace(stringify_keys(session))
@loaded = true
end
+
+ def stringify_keys(other)
+ other.each_with_object({}) { |(key, value), hash|
+ hash[key.to_s] = value
+ }
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index 6f651a5689..5d30a545a2 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -51,7 +51,7 @@ module ActionDispatch
##
# This class is just used for displaying route information when someone
- # executes `rake routes` or looks at the RoutingError page.
+ # executes `rails routes` or looks at the RoutingError page.
# People should not use this class.
class RoutesInspector # :nodoc:
def initialize(routes)
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
index 76e2d3ff43..d0c7b2e06a 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -160,7 +160,14 @@ class RespondToController < ActionController::Base
end
end
- def variant_with_implicit_rendering
+ def variant_with_implicit_template_rendering
+ # This has exactly one variant template defined in the file system (+mobile.html.erb),
+ # which raises the regular MissingTemplate error for other variants.
+ end
+
+ def variant_without_implicit_template_rendering
+ # This differs from the above in that it does not have any templates defined in the file
+ # system, which triggers the ImplicitRender (204 No Content) behavior.
end
def variant_with_format_and_custom_render
@@ -272,6 +279,8 @@ class RespondToController < ActionController::Base
end
class RespondToControllerTest < ActionController::TestCase
+ NO_CONTENT_WARNING = "No template found for RespondToController#variant_without_implicit_template_rendering, rendering head :no_content"
+
def setup
super
@request.host = "www.example.com"
@@ -616,30 +625,69 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_invalid_variant
+ assert_raises(ActionController::UnknownFormat) do
+ get :variant_with_implicit_template_rendering, params: { v: :invalid }
+ end
+ end
+
+ def test_variant_not_set_regular_unknown_format
+ assert_raises(ActionController::UnknownFormat) do
+ get :variant_with_implicit_template_rendering
+ end
+ end
+
+ def test_variant_with_implicit_template_rendering
+ get :variant_with_implicit_template_rendering, params: { v: :mobile }
+ assert_equal "text/html", @response.content_type
+ assert_equal "mobile", @response.body
+ end
+
+ def test_variant_without_implicit_rendering_from_browser
+ assert_raises(ActionController::UnknownFormat) do
+ get :variant_without_implicit_template_rendering, params: { v: :does_not_matter }
+ end
+ end
+
+ def test_variant_variant_not_set_and_without_implicit_rendering_from_browser
+ assert_raises(ActionController::UnknownFormat) do
+ get :variant_without_implicit_template_rendering
+ end
+ end
+
+ def test_variant_without_implicit_rendering_from_xhr
logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
old_logger, ActionController::Base.logger = ActionController::Base.logger, logger
- get :variant_with_implicit_rendering, params: { v: :invalid }
+ get :variant_without_implicit_template_rendering, xhr: true, params: { v: :does_not_matter }
assert_response :no_content
- assert_equal 1, logger.logged(:info).select{ |s| s =~ /No template found/ }.size, "Implicit head :no_content not logged"
+
+ assert_equal 1, logger.logged(:info).select{ |s| s == NO_CONTENT_WARNING }.size, "Implicit head :no_content not logged"
ensure
ActionController::Base.logger = old_logger
end
- def test_variant_not_set_regular_template_missing
- get :variant_with_implicit_rendering
+ def test_variant_without_implicit_rendering_from_api
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ old_logger, ActionController::Base.logger = ActionController::Base.logger, logger
+
+ get :variant_without_implicit_template_rendering, format: 'json', params: { v: :does_not_matter }
assert_response :no_content
+
+ assert_equal 1, logger.logged(:info).select{ |s| s == NO_CONTENT_WARNING }.size, "Implicit head :no_content not logged"
+ ensure
+ ActionController::Base.logger = old_logger
end
- def test_variant_with_implicit_rendering
- get :variant_with_implicit_rendering, params: { v: :implicit }
+ def test_variant_variant_not_set_and_without_implicit_rendering_from_xhr
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ old_logger, ActionController::Base.logger = ActionController::Base.logger, logger
+
+ get :variant_without_implicit_template_rendering, xhr: true
assert_response :no_content
- end
- def test_variant_with_implicit_template_rendering
- get :variant_with_implicit_rendering, params: { v: :mobile }
- assert_equal "text/html", @response.content_type
- assert_equal "mobile", @response.body
+ assert_equal 1, logger.logged(:info).select { |s| s == NO_CONTENT_WARNING }.size, "Implicit head :no_content not logged"
+ ensure
+ ActionController::Base.logger = old_logger
end
def test_variant_with_format_and_custom_render
@@ -778,24 +826,3 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "phone", @response.body
end
end
-
-class RespondToWithBlockOnDefaultRenderController < ActionController::Base
- def show
- default_render do
- render body: 'default_render yielded'
- end
- end
-end
-
-class RespondToWithBlockOnDefaultRenderControllerTest < ActionController::TestCase
- def setup
- super
- @request.host = "www.example.com"
- end
-
- def test_default_render_uses_block_when_no_template_exists
- get :show
- assert_equal "default_render yielded", @response.body
- assert_equal "text/plain", @response.content_type
- end
-end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 60c6518c62..83d7405e4d 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -26,6 +26,9 @@ end
class ImplicitRenderTestController < ActionController::Base
def empty_action
end
+
+ def empty_action_with_template
+ end
end
class TestController < ActionController::Base
@@ -537,10 +540,28 @@ end
class ImplicitRenderTest < ActionController::TestCase
tests ImplicitRenderTestController
- def test_implicit_no_content_response
- get :empty_action
+ def test_implicit_no_content_response_as_browser
+ assert_raises(ActionController::UnknownFormat) do
+ get :empty_action
+ end
+ end
+
+ def test_implicit_no_content_response_as_xhr
+ get :empty_action, xhr: true
assert_response :no_content
end
+
+ def test_implicit_success_response_with_right_format
+ get :empty_action_with_template
+ assert_equal "<h1>Empty action rendered this implicitly.</h1>\n", @response.body
+ assert_response :success
+ end
+
+ def test_implicit_unknown_format_response
+ assert_raises(ActionController::UnknownFormat) do
+ get :empty_action_with_template, format: 'json'
+ end
+ end
end
class HeadRenderTest < ActionController::TestCase
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
index 3433d82791..7dcbcc5c21 100644
--- a/actionpack/test/dispatch/request/session_test.rb
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -105,16 +105,6 @@ module ActionDispatch
end
end
- def test_indifferent_access
- s = Session.create(store, req, {})
-
- s[:one] = { test: "deep" }
- s[:two] = { "test" => "deep" }
-
- assert_equal 'deep', s[:one]["test"]
- assert_equal 'deep', s[:two][:test]
- end
-
private
def store
Class.new {
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 8b3849cb7a..cd385982d9 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -445,4 +445,12 @@ 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"'
+
+ assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag)
+ assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
+ end
end
diff --git a/actionpack/test/dispatch/session/abstract_store_test.rb b/actionpack/test/dispatch/session/abstract_store_test.rb
index c9ce5cad42..d38d1bbce6 100644
--- a/actionpack/test/dispatch/session/abstract_store_test.rb
+++ b/actionpack/test/dispatch/session/abstract_store_test.rb
@@ -46,22 +46,6 @@ module ActionDispatch
assert_equal session.to_hash, session1.to_hash
end
- def test_previous_session_has_indifferent_access
- env = {}
- as = MemoryStore.new app
- as.call(env)
-
- assert @env
- session = Request::Session.find ActionDispatch::Request.new @env
- session[:foo] = { bar: "baz" }
-
- as.call(@env)
- session = Request::Session.find ActionDispatch::Request.new @env
-
- assert_equal session[:foo][:bar], "baz"
- assert_equal session[:foo]["bar"], "baz"
- end
-
private
def app(&block)
@env = nil
diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb
index b911392cf1..dbb996973d 100644
--- a/actionpack/test/dispatch/session/cache_store_test.rb
+++ b/actionpack/test/dispatch/session/cache_store_test.rb
@@ -12,11 +12,6 @@ class CacheStoreTest < ActionDispatch::IntegrationTest
head :ok
end
- def set_deep_session_value
- session[:foo] = { bar: "baz" }
- head :ok
- end
-
def set_serialized_session_value
session[:foo] = SessionAutoloadTest::Foo.new
head :ok
@@ -26,14 +21,6 @@ class CacheStoreTest < ActionDispatch::IntegrationTest
render plain: "foo: #{session[:foo].inspect}"
end
- def get_deep_session_value_with_symbol
- render plain: "foo: { bar: #{session[:foo][:bar].inspect} }"
- end
-
- def get_deep_session_value_with_string
- render plain: "foo: { \"bar\" => #{session[:foo]["bar"].inspect} }"
- end
-
def get_session_id
render plain: "#{request.session.id}"
end
@@ -173,22 +160,6 @@ class CacheStoreTest < ActionDispatch::IntegrationTest
end
end
- def test_previous_session_has_indifferent_access
- with_test_route_set do
- get '/set_deep_session_value'
- assert_response :success
- assert cookies['_session_id']
-
- get '/get_deep_session_value_with_symbol'
- assert_response :success
- assert_equal 'foo: { bar: "baz" }', response.body
-
- get '/get_deep_session_value_with_string'
- assert_response :success
- assert_equal 'foo: { "bar" => "baz" }', response.body
- end
- end
-
private
def with_test_route_set
with_routing do |set|
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index 71402b021a..f07e215e3a 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -24,23 +24,10 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
render plain: Rack::Utils.escape(Verifier.generate(session.to_hash))
end
- def set_deep_session_value
- session[:foo] = { bar: "baz" }
- render plain: Rack::Utils.escape(Verifier.generate(session.to_hash))
- end
-
def get_session_value
render plain: "foo: #{session[:foo].inspect}"
end
- def get_deep_session_value_with_symbol
- render plain: "foo: { bar: #{session[:foo][:bar].inspect} }"
- end
-
- def get_deep_session_value_with_string
- render plain: "foo: { \"bar\" => #{session[:foo]["bar"].inspect} }"
- end
-
def get_session_id
render plain: "id: #{request.session.id}"
end
@@ -94,15 +81,6 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
end
end
- def test_session_indifferent_access
- with_test_route_set do
- cookies[SessionKey] = SignedBar
- get '/get_session_value'
- assert_response :success
- assert_equal 'foo: "bar"', response.body
- end
- end
-
def test_getting_session_id
with_test_route_set do
cookies[SessionKey] = SignedBar
@@ -354,18 +332,6 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
end
end
- def test_previous_session_has_indifferent_access
- with_test_route_set do
- get '/set_deep_session_value'
-
- get '/get_deep_session_value_with_symbol'
- assert_equal 'foo: { bar: "baz" }', response.body
-
- get '/get_deep_session_value_with_string'
- assert_equal 'foo: { "bar" => "baz" }', response.body
- end
- end
-
private
# Overwrite get to send SessionSecret in env hash
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index 2e6b42856f..3fed9bad4f 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -13,11 +13,6 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
head :ok
end
- def set_deep_session_value
- session[:foo] = { bar: "baz" }
- head :ok
- end
-
def set_serialized_session_value
session[:foo] = SessionAutoloadTest::Foo.new
head :ok
@@ -27,14 +22,6 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
render plain: "foo: #{session[:foo].inspect}"
end
- def get_deep_session_value_with_symbol
- render plain: "foo: { bar: #{session[:foo][:bar].inspect} }"
- end
-
- def get_deep_session_value_with_string
- render plain: "foo: { \"bar\" => #{session[:foo]["bar"].inspect} }"
- end
-
def get_session_id
render plain: "#{request.session.id}"
end
@@ -192,24 +179,6 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
rescue Dalli::RingError => ex
skip ex.message, ex.backtrace
end
-
- def test_previous_session_has_indifferent_access
- with_test_route_set do
- get '/set_deep_session_value'
- assert_response :success
- assert cookies['_session_id']
-
- get '/get_deep_session_value_with_symbol'
- assert_response :success
- assert_equal 'foo: { bar: "baz" }', response.body
-
- get '/get_deep_session_value_with_string'
- assert_response :success
- assert_equal 'foo: { "bar" => "baz" }', response.body
- end
- rescue Dalli::RingError => ex
- skip ex.message, ex.backtrace
- end
rescue LoadError, RuntimeError, Dalli::DalliError
$stderr.puts "Skipping MemCacheStoreTest tests. Start memcached and try again."
end
diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb
index 332c2ae3c8..3e61d123e3 100644
--- a/actionpack/test/dispatch/session/test_session_test.rb
+++ b/actionpack/test/dispatch/session/test_session_test.rb
@@ -60,11 +60,4 @@ class ActionController::TestSessionTest < ActiveSupport::TestCase
session = ActionController::TestSession.new(one: '1')
assert_equal(2, session.fetch('2') { |key| key.to_i })
end
-
- def test_fetch_returns_indifferent_access
- session = ActionController::TestSession.new(three: { two: '1' })
- three = session.fetch(:three)
- assert_equal('1', three[:two])
- assert_equal('1', three["two"])
- end
end
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb
index c66a0e6a7a..18ff894b31 100644
--- a/actionpack/test/dispatch/ssl_test.rb
+++ b/actionpack/test/dispatch/ssl_test.rb
@@ -7,7 +7,7 @@ class SSLTest < ActionDispatch::IntegrationTest
def build_app(headers: {}, ssl_options: {})
headers = HEADERS.merge(headers)
- ActionDispatch::SSL.new lambda { |env| [200, headers, []] }, ssl_options
+ ActionDispatch::SSL.new lambda { |env| [200, headers, []] }, ssl_options.reverse_merge(hsts: { subdomains: true })
end
end
@@ -98,15 +98,16 @@ end
class StrictTransportSecurityTest < SSLTest
EXPECTED = 'max-age=15552000'
+ EXPECTED_WITH_SUBDOMAINS = 'max-age=15552000; includeSubDomains'
- def assert_hsts(expected, url: 'https://example.org', hsts: {}, headers: {})
+ def assert_hsts(expected, url: 'https://example.org', hsts: { subdomains: true }, headers: {})
self.app = build_app ssl_options: { hsts: hsts }, headers: headers
get url
assert_equal expected, response.headers['Strict-Transport-Security']
end
test 'enabled by default' do
- assert_hsts EXPECTED
+ assert_hsts EXPECTED_WITH_SUBDOMAINS
end
test 'not sent with http:// responses' do
@@ -126,11 +127,15 @@ class StrictTransportSecurityTest < SSLTest
end
test ':expires sets max-age' do
- assert_hsts 'max-age=500', hsts: { expires: 500 }
+ assert_deprecated do
+ assert_hsts 'max-age=500', hsts: { expires: 500 }
+ end
end
test ':expires supports AS::Duration arguments' do
- assert_hsts 'max-age=31557600', hsts: { expires: 1.year }
+ assert_deprecated do
+ assert_hsts 'max-age=31557600', hsts: { expires: 1.year }
+ end
end
test 'include subdomains' do
@@ -142,11 +147,15 @@ class StrictTransportSecurityTest < SSLTest
end
test 'opt in to browser preload lists' do
- assert_hsts "#{EXPECTED}; preload", hsts: { preload: true }
+ assert_deprecated do
+ assert_hsts "#{EXPECTED}; preload", hsts: { preload: true }
+ end
end
test 'opt out of browser preload lists' do
- assert_hsts EXPECTED, hsts: { preload: false }
+ assert_deprecated do
+ assert_hsts EXPECTED, hsts: { preload: false }
+ end
end
end
diff --git a/actionpack/test/fixtures/implicit_render_test/empty_action_with_mobile_variant.html+mobile.erb b/actionpack/test/fixtures/implicit_render_test/empty_action_with_mobile_variant.html+mobile.erb
new file mode 100644
index 0000000000..ded99ba52d
--- /dev/null
+++ b/actionpack/test/fixtures/implicit_render_test/empty_action_with_mobile_variant.html+mobile.erb
@@ -0,0 +1 @@
+mobile
diff --git a/actionpack/test/fixtures/implicit_render_test/empty_action_with_template.html.erb b/actionpack/test/fixtures/implicit_render_test/empty_action_with_template.html.erb
new file mode 100644
index 0000000000..dd294f8cf6
--- /dev/null
+++ b/actionpack/test/fixtures/implicit_render_test/empty_action_with_template.html.erb
@@ -0,0 +1 @@
+<h1>Empty action rendered this implicitly.</h1>
diff --git a/actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb b/actionpack/test/fixtures/respond_to/variant_with_implicit_template_rendering.html+mobile.erb
index 317801ad30..317801ad30 100644
--- a/actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb
+++ b/actionpack/test/fixtures/respond_to/variant_with_implicit_template_rendering.html+mobile.erb
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index d084f2b1e0..6b77fd44f1 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,9 @@
+* 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.
+
+ *Vipul A M and Prem Sichanugrist*
+
## Rails 5.0.0.beta3 (February 24, 2016) ##
* Collection rendering can cache and fetch multiple partials at once.
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index f3c5d6c8df..b99d1af998 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -4,7 +4,7 @@ require 'monitor'
module ActionView
class Digestor
- @@digest_monitor = Monitor.new
+ @@digest_mutex = Mutex.new
class PerRequestDigestCacheExpiry < Struct.new(:app) # :nodoc:
def call(env)
@@ -20,111 +20,104 @@ module ActionView
# * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt>
# * <tt>dependencies</tt> - An array of dependent views
# * <tt>partial</tt> - Specifies whether the template is a partial
- def digest(name:, finder:, **options)
- options.assert_valid_keys(:dependencies, :partial)
-
- cache_key = ([ name ].compact + Array.wrap(options[:dependencies])).join('.')
+ def digest(name:, finder:, dependencies: [])
+ dependencies ||= []
+ cache_key = ([ name ].compact + dependencies).join('.')
# this is a correctly done double-checked locking idiom
# (Concurrent::Map's lookups have volatile semantics)
- finder.digest_cache[cache_key] || @@digest_monitor.synchronize do
+ finder.digest_cache[cache_key] || @@digest_mutex.synchronize do
finder.digest_cache.fetch(cache_key) do # re-check under lock
- compute_and_store_digest(cache_key, name, finder, options)
+ partial = name.include?("/_")
+ root = tree(name, finder, partial)
+ dependencies.each do |injected_dep|
+ root.children << Injected.new(injected_dep, nil, nil)
+ end
+ finder.digest_cache[cache_key] = root.digest(finder)
end
end
end
- private
- def compute_and_store_digest(cache_key, name, finder, options) # called under @@digest_monitor lock
- klass = if options[:partial] || name.include?("/_")
- # Prevent re-entry or else recursive templates will blow the stack.
- # There is no need to worry about other threads seeing the +false+ value,
- # as they will then have to wait for this thread to let go of the @@digest_monitor lock.
- pre_stored = finder.digest_cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion
- PartialDigestor
- else
- Digestor
- end
-
- finder.digest_cache[cache_key] = stored_digest = klass.new(name, finder, options).digest
- ensure
- # something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache
- finder.digest_cache.delete_pair(cache_key, false) if pre_stored && !stored_digest
- end
- end
-
- attr_reader :name, :finder, :options
+ def logger
+ ActionView::Base.logger || NullLogger
+ end
- def initialize(name, finder, options = {})
- @name, @finder = name, finder
- @options = options
- end
+ # Create a dependency tree for template named +name+.
+ def tree(name, finder, partial = false, seen = {})
+ logical_name = name.gsub(%r|/_|, "/")
- def digest
- Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest|
- logger.debug " Cache digest for #{template.inspect}: #{digest}"
- end
- rescue ActionView::MissingTemplate
- logger.error " Couldn't find template for digesting: #{name}"
- ''
- end
+ if finder.disable_cache { finder.exists?(logical_name, [], partial) }
+ template = finder.disable_cache { finder.find(logical_name, [], partial) }
- def dependencies
- DependencyTracker.find_dependencies(name, template, finder.view_paths)
- rescue ActionView::MissingTemplate
- logger.error " '#{name}' file doesn't exist, so no dependencies"
- []
- end
+ if node = seen[template.identifier] # handle cycles in the tree
+ node
+ else
+ node = seen[template.identifier] = Node.create(name, logical_name, template, partial)
- def nested_dependencies
- dependencies.collect do |dependency|
- dependencies = PartialDigestor.new(dependency, finder).nested_dependencies
- dependencies.any? ? { dependency => dependencies } : dependency
+ deps = DependencyTracker.find_dependencies(name, template, finder.view_paths)
+ deps.uniq { |n| n.gsub(%r|/_|, "/") }.each do |dep_file|
+ node.children << tree(dep_file, finder, true, seen)
+ end
+ node
+ end
+ else
+ logger.error " '#{name}' file doesn't exist, so no dependencies"
+ logger.error " Couldn't find template for digesting: #{name}"
+ seen[name] ||= Missing.new(name, logical_name, nil)
+ end
end
end
- private
- class NullLogger
- def self.debug(_); end
- def self.error(_); end
- end
+ class Node
+ attr_reader :name, :logical_name, :template, :children
- def logger
- ActionView::Base.logger || NullLogger
+ def self.create(name, logical_name, template, partial)
+ klass = partial ? Partial : Node
+ klass.new(name, logical_name, template, [])
end
- def logical_name
- name.gsub(%r|/_|, "/")
+ def initialize(name, logical_name, template, children = [])
+ @name = name
+ @logical_name = logical_name
+ @template = template
+ @children = children
end
- def partial?
- false
+ def digest(finder, stack = [])
+ Digest::MD5.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}")
end
- def template
- @template ||= finder.disable_cache { finder.find(logical_name, [], partial?) }
+ def dependency_digest(finder, stack)
+ children.map do |node|
+ if stack.include?(node)
+ false
+ else
+ finder.digest_cache[node.name] ||= begin
+ stack.push node
+ node.digest(finder, stack).tap { stack.pop }
+ end
+ end
+ end.join("-")
end
- def source
- template.source
+ def to_dep_map
+ children.any? ? { name => children.map(&:to_dep_map) } : name
end
+ end
- def dependency_digest
- template_digests = dependencies.collect do |template_name|
- Digestor.digest(name: template_name, finder: finder, partial: true)
- end
+ class Partial < Node; end
- (template_digests + injected_dependencies).join("-")
- end
+ class Missing < Node
+ def digest(finder, _ = []) '' end
+ end
- def injected_dependencies
- Array.wrap(options[:dependencies])
- end
- end
+ class Injected < Node
+ def digest(finder, _ = []) name end
+ end
- class PartialDigestor < Digestor # :nodoc:
- def partial?
- true
+ class NullLogger
+ def self.debug(_); end
+ def self.error(_); end
end
end
end
diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb
index aa38db2a3a..5a29c68214 100644
--- a/actionview/lib/action_view/log_subscriber.rb
+++ b/actionview/lib/action_view/log_subscriber.rb
@@ -30,6 +30,14 @@ module ActionView
end
end
+ def start(name, id, payload)
+ if name == "render_template.action_view"
+ log_rendering_start(payload)
+ end
+
+ super
+ end
+
def logger
ActionView::Base.logger
end
@@ -54,6 +62,16 @@ module ActionView
"[#{payload[:count]} times]"
end
end
+
+ private
+
+ def log_rendering_start(payload)
+ info do
+ message = " Rendering #{from_rails_root(payload[:identifier])}"
+ message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
+ message
+ end
+ end
end
end
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index 86afedaa2d..626c4b8f5e 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -70,8 +70,6 @@ module ActionView
@details_keys.clear
end
- def self.empty?; @details_keys.empty?; end
-
def self.digest_caches
@details_keys.values.map(&:digest_cache)
end
@@ -138,6 +136,11 @@ module ActionView
end
alias :template_exists? :exists?
+ def any?(name, prefixes = [], partial = false)
+ @view_paths.exists?(*args_for_any(name, prefixes, partial))
+ end
+ alias :any_templates? :any?
+
# Adds fallbacks to the view paths. Useful in cases when you are rendering
# a :file.
def with_fallbacks
@@ -174,6 +177,32 @@ module ActionView
[user_details, details_key]
end
+ def args_for_any(name, prefixes, partial) # :nodoc:
+ name, prefixes = normalize_name(name, prefixes)
+ details, details_key = detail_args_for_any
+ [name, prefixes, partial || false, details, details_key]
+ end
+
+ def detail_args_for_any # :nodoc:
+ @detail_args_for_any ||= begin
+ details = {}
+
+ registered_details.each do |k|
+ if k == :variants
+ details[k] = :any
+ else
+ details[k] = Accessors::DEFAULT_PROCS[k].call
+ end
+ end
+
+ if @cache
+ [details, DetailsKey.get(details)]
+ else
+ [details, nil]
+ end
+ end
+ end
+
# Support legacy foo.erb names even though we now ignore .erb
# as well as incorrectly putting part of the path in the template
# name instead of the prefix.
diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb
index 23e672a95f..1dddf53df0 100644
--- a/actionview/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionview/lib/action_view/renderer/abstract_renderer.rb
@@ -15,7 +15,7 @@ module ActionView
# that new object is called in turn. This abstracts the setup and rendering
# into a separate classes for partials and templates.
class AbstractRenderer #:nodoc:
- delegate :find_template, :find_file, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
+ delegate :find_template, :find_file, :template_exists?, :any_templates?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
def initialize(lookup_context)
@lookup_context = lookup_context
diff --git a/actionview/lib/action_view/tasks/dependencies.rake b/actionview/lib/action_view/tasks/dependencies.rake
index 9932ff8b6d..045bdf5691 100644
--- a/actionview/lib/action_view/tasks/dependencies.rake
+++ b/actionview/lib/action_view/tasks/dependencies.rake
@@ -2,13 +2,13 @@ namespace :cache_digests do
desc 'Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
task :nested_dependencies => :environment do
abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
- puts JSON.pretty_generate ActionView::Digestor.new(CacheDigests.template_name, CacheDigests.finder).nested_dependencies
+ puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:to_dep_map)
end
desc 'Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
task :dependencies => :environment do
abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
- puts JSON.pretty_generate ActionView::Digestor.new(CacheDigests.template_name, CacheDigests.finder).dependencies
+ puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:name)
end
class CacheDigests
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 8a675cd521..b6de0b03bf 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -222,7 +222,7 @@ module ActionView
end
def find_template_paths(query)
- Dir[query].reject do |filename|
+ Dir[query].uniq.reject do |filename|
File.directory?(filename) ||
# deals with case-insensitive file systems.
!File.fnmatch(query, filename, File::FNM_EXTGLOB)
@@ -340,7 +340,11 @@ module ActionView
query = escape_entry(File.join(@path, path))
exts = EXTENSIONS.map do |ext, prefix|
- "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}"
+ if ext == :variants && details[ext] == :any
+ "{#{prefix}*,}"
+ else
+ "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}"
+ end
end.join
query + exts
diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb
index 37722013ce..b46fe06b01 100644
--- a/actionview/lib/action_view/view_paths.rb
+++ b/actionview/lib/action_view/view_paths.rb
@@ -10,7 +10,7 @@ module ActionView
self._view_paths.freeze
end
- delegate :template_exists?, :view_paths, :formats, :formats=,
+ delegate :template_exists?, :any_templates?, :view_paths, :formats, :formats=,
:locale, :locale=, :to => :lookup_context
module ClassMethods
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index a9a6de250c..d4c5048bde 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -146,6 +146,11 @@ class TemplateDigestorTest < ActionView::TestCase
end
end
+ def test_nested_template_deps
+ nested_deps = ["messages/header", {"comments/comments"=>["comments/comment"]}, "messages/actions/move", "events/event", "messages/something_missing", "messages/something_missing_1", "messages/message", "messages/form"]
+ assert_equal nested_deps, nested_dependencies("messages/show")
+ end
+
def test_recursion_in_renders
assert digest("level/recursion") # assert recursion is possible
assert_not_nil digest("level/recursion") # assert digest is stored
@@ -313,16 +318,17 @@ class TemplateDigestorTest < ActionView::TestCase
options = options.dup
finder.variants = options.delete(:variants) || []
-
- ActionView::Digestor.digest({ name: template_name, finder: finder }.merge(options))
+ ActionView::Digestor.digest(name: template_name, finder: finder, dependencies: (options[:dependencies] || []))
end
def dependencies(template_name)
- ActionView::Digestor.new(template_name, finder).dependencies
+ tree = ActionView::Digestor.tree(template_name, finder)
+ tree.children.map(&:name)
end
def nested_dependencies(template_name)
- ActionView::Digestor.new(template_name, finder).nested_dependencies
+ tree = ActionView::Digestor.tree(template_name, finder)
+ tree.children.map(&:to_dep_map)
end
def finder
diff --git a/actionview/test/template/log_subscriber_test.rb b/actionview/test/template/log_subscriber_test.rb
index 93a0701dcc..7683444bf0 100644
--- a/actionview/test/template/log_subscriber_test.rb
+++ b/actionview/test/template/log_subscriber_test.rb
@@ -35,7 +35,8 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
@view.render(:file => "test/hello_world")
wait
- assert_equal 1, @logger.logged(:info).size
+ assert_equal 2, @logger.logged(:info).size
+ assert_match(/Rendering test\/hello_world\.erb/, @logger.logged(:info).first)
assert_match(/Rendered test\/hello_world\.erb/, @logger.logged(:info).last)
end
end
@@ -45,7 +46,8 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
@view.render(:text => "TEXT")
wait
- assert_equal 1, @logger.logged(:info).size
+ assert_equal 2, @logger.logged(:info).size
+ assert_match(/Rendering text template/, @logger.logged(:info).first)
assert_match(/Rendered text template/, @logger.logged(:info).last)
end
end
@@ -55,7 +57,8 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
@view.render(:inline => "<%= 'TEXT' %>")
wait
- assert_equal 1, @logger.logged(:info).size
+ assert_equal 2, @logger.logged(:info).size
+ assert_match(/Rendering inline template/, @logger.logged(:info).first)
assert_match(/Rendered inline template/, @logger.logged(:info).last)
end
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index c1a8803457..39fe217777 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,10 @@
+* Ensure that the Suppressor runs before validations.
+
+ This moves the suppressor up to be run before validations rather than after
+ validations. There's no reason to validate a record you aren't planning on saving.
+
+ *Eileen M. Uchitelle*
+
## Rails 5.0.0.beta3 (February 24, 2016) ##
* Ensure that mutations of the array returned from `ActiveRecord::Relation#to_a`
diff --git a/activerecord/lib/active_record/suppressor.rb b/activerecord/lib/active_record/suppressor.rb
index b3644bf569..8ec4b48d31 100644
--- a/activerecord/lib/active_record/suppressor.rb
+++ b/activerecord/lib/active_record/suppressor.rb
@@ -37,7 +37,11 @@ module ActiveRecord
end
end
- def create_or_update(*args) # :nodoc:
+ def save(*) # :nodoc:
+ SuppressorRegistry.suppressed[self.class.name] ? true : super
+ end
+
+ def save!(*) # :nodoc:
SuppressorRegistry.suppressed[self.class.name] ? true : super
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 13053beb78..4a80cda0b8 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -18,7 +18,7 @@ module ActiveRecord
relation = build_relation(finder_class, table, attribute, value)
if record.persisted?
if finder_class.primary_key
- relation = relation.where.not(finder_class.primary_key => record.id_was)
+ relation = relation.where.not(finder_class.primary_key => record.id_was || record.id)
else
raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
end
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index 179a4dc91e..f191eff5bf 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -41,8 +41,8 @@ module ActiveRecord
# FIXME: Change this file to a symlink once RubyGems 2.5.0 is required.
def generate_application_record
- if self.behavior == :invoke && !File.exist?('app/models/application_record.rb')
- template 'application_record.rb', 'app/models/application_record.rb'
+ if self.behavior == :invoke && !application_record_exist?
+ template 'application_record.rb', application_record_file_name
end
end
@@ -51,18 +51,22 @@ module ActiveRecord
options[:parent] || determine_default_parent_class
end
- def determine_default_parent_class
- application_record = nil
+ def application_record_exist?
+ file_exist = nil
+ in_root { file_exist = File.exist?(application_record_file_name) }
+ file_exist
+ end
- in_root do
- application_record = if mountable_engine?
- File.exist?("app/models/#{namespaced_path}/application_record.rb")
- else
- File.exist?('app/models/application_record.rb')
- end
+ def application_record_file_name
+ @application_record_file_name ||= if mountable_engine?
+ "app/models/#{namespaced_path}/application_record.rb"
+ else
+ 'app/models/application_record.rb'
end
+ end
- if application_record
+ def determine_default_parent_class
+ if application_record_exist?
"ApplicationRecord"
else
"ActiveRecord::Base"
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb b/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb
index 10a4cba84d..60050e0bf8 100644
--- a/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb
+++ b/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb
@@ -1,3 +1,5 @@
+<% module_namespacing do -%>
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
+<% end -%>
diff --git a/activerecord/test/cases/suppressor_test.rb b/activerecord/test/cases/suppressor_test.rb
index 72c5c16555..7d44e36419 100644
--- a/activerecord/test/cases/suppressor_test.rb
+++ b/activerecord/test/cases/suppressor_test.rb
@@ -46,7 +46,18 @@ class SuppressorTest < ActiveRecord::TestCase
Notification.suppress { UserWithNotification.create! }
assert_difference -> { Notification.count } do
- Notification.create!
+ Notification.create!(message: "New Comment")
+ end
+ end
+
+ def test_suppresses_validations_on_create
+ assert_no_difference -> { Notification.count } do
+ Notification.suppress do
+ User.create
+ User.create!
+ User.new.save
+ User.new.save!
+ end
end
end
end
diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb
index 2ae30c7fd3..868d111b8c 100644
--- a/activerecord/test/cases/validations/presence_validation_test.rb
+++ b/activerecord/test/cases/validations/presence_validation_test.rb
@@ -92,7 +92,7 @@ class PresenceValidationTest < ActiveRecord::TestCase
end
end
- def test_validates_prescence_with_on_context
+ def test_validates_presence_with_on_context
repair_validations(Interest) do
Interest.validates_presence_of(:topic, on: :required_name)
interest = Interest.new
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 8abb6c9844..4c14d93c66 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -53,6 +53,14 @@ class CoolTopic < Topic
validates_uniqueness_of :id
end
+class TopicWithAfterCreate < Topic
+ after_create :set_author
+
+ def set_author
+ update_attributes!(:author_name => "#{title} #{id}")
+ end
+end
+
class UniquenessValidationTest < ActiveRecord::TestCase
INT_MAX_VALUE = 2147483647
@@ -469,6 +477,16 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert t.save, "Should still save t as unique"
end
+ def test_validate_uniqueness_with_after_create_performing_save
+ TopicWithAfterCreate.validates_uniqueness_of(:title)
+ topic = TopicWithAfterCreate.create!(:title => "Title1")
+ assert topic.author_name.start_with?("Title1")
+
+ topic2 = TopicWithAfterCreate.new(:title => "Title1")
+ refute topic2.valid?
+ assert_equal(["has already been taken"], topic2.errors[:title])
+ end
+
def test_validate_uniqueness_uuid
skip unless current_adapter?(:PostgreSQLAdapter)
item = UuidItem.create!(uuid: SecureRandom.uuid, title: 'item1')
diff --git a/activerecord/test/models/notification.rb b/activerecord/test/models/notification.rb
index b4b4b8f1b6..82edc64b68 100644
--- a/activerecord/test/models/notification.rb
+++ b/activerecord/test/models/notification.rb
@@ -1,2 +1,3 @@
class Notification < ActiveRecord::Base
+ validates_presence_of :message
end
diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md
index 91bca16356..dc631e5cb9 100644
--- a/guides/source/5_0_release_notes.md
+++ b/guides/source/5_0_release_notes.md
@@ -256,6 +256,12 @@ Please refer to the [Changelog][action-pack] for detailed changes.
* Rails will only generate "weak", instead of strong ETags.
([Pull Request](https://github.com/rails/rails/pull/17573))
+* Controller actions without an explicit `render` call and with no
+ corresponding templates will render `head :no_content` implicitly
+ instead of raising an error.
+ (Pull Request [1](https://github.com/rails/rails/pull/19377),
+ [2](https://github.com/rails/rails/pull/23827))
+
Action View
-------------
diff --git a/guides/source/api_app.md b/guides/source/api_app.md
index 0598b9c7fa..8dba914923 100644
--- a/guides/source/api_app.md
+++ b/guides/source/api_app.md
@@ -166,6 +166,23 @@ class definition:
config.api_only = true
```
+In `config/environments/development.rb`, set `config.debug_exception_response_format`
+to configure the format used in responses when errors occur in development mode.
+
+To render an HTML page with debugging information, use the value `:default`.
+
+```ruby
+config.debug_exception_response_format = :default
+```
+
+To render debugging information preserving the response format, use the value `:api`.
+
+```ruby
+config.debug_exception_response_format = :api
+```
+
+By default, `config.debug_exception_response_format` is set to `:api`.
+
Finally, inside `app/controllers/application_controller.rb`, instead of:
```ruby
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 23866b1800..41985c3661 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -98,13 +98,15 @@ application. Accepts a valid week day symbol (e.g. `:monday`).
* `config.exceptions_app` sets the exceptions application invoked by the ShowException middleware when an exception happens. Defaults to `ActionDispatch::PublicExceptions.new(Rails.public_path)`.
+* `config.debug_exception_response_format` sets the format used in responses when errors occur in development mode.
+
* `config.file_watcher` is the class used to detect file updates in the file system when `config.reload_classes_only_on_change` is true. Rails ships with `ActiveSupport::FileUpdateChecker`, the default, and `ActiveSupport::EventedFileUpdateChecker` (this one depends on the [listen](https://github.com/guard/listen) gem). Custom classes must conform to the `ActiveSupport::FileUpdateChecker` API.
* `config.filter_parameters` used for filtering out the parameters that
you don't want shown in the logs, such as passwords or credit card
numbers. New applications filter out passwords by adding the following `config.filter_parameters+=[:password]` in `config/initializers/filter_parameter_logging.rb`.
-* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](http://edgeapi.rubyonrails.org/classes/ActionDispatch/SSL.html) for details.
+* `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.
* `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes except production, where it defaults to `Logger::Formatter`.
@@ -548,7 +550,7 @@ There are a few configuration options available in Active Support:
* `config.active_support.time_precision` sets the precision of JSON encoded time values. Defaults to `3`.
-* `ActiveSupport.halt_callback_chains_on_return_false` specifies whether Active Record and Active Model callback chains can be halted by returning `false` in a 'before' callback. When set to `false`, callback chains are halted only when explicitly done so with `throw(:abort)`. When set to `true`, callback chains are halted when a callback returns false (the previous behavior before Rails 5) and a deprecation warning is given. Defaults to `true` during the deprecation period. New Rails 5 apps generate an initializer file called `callback_terminator.rb` which sets the value to `false`. This file is *not* added when running `rake rails:update`, so returning `false` will still work on older apps ported to Rails 5 and display a deprecation warning to prompt users to update their code.
+* `ActiveSupport.halt_callback_chains_on_return_false` specifies whether Active Record and Active Model callback chains can be halted by returning `false` in a 'before' callback. When set to `false`, callback chains are halted only when explicitly done so with `throw(:abort)`. When set to `true`, callback chains are halted when a callback returns false (the previous behavior before Rails 5) and a deprecation warning is given. Defaults to `true` during the deprecation period. New Rails 5 apps generate an initializer file called `callback_terminator.rb` which sets the value to `false`. This file is *not* added when running `rails app:update`, so returning `false` will still work on older apps ported to Rails 5 and display a deprecation warning to prompt users to update their code.
* `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`.
@@ -613,6 +615,17 @@ There are a few configuration options available in Active Support:
* `config.active_job.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Active Job. You can retrieve this logger by calling `logger` on either an Active Job class or an Active Job instance. Set to `nil` to disable logging.
+### Configuring Action Cable
+
+* `config.action_cable.url` accepts a string for the URL for where
+ you are hosting your Action Cable server. You would use this option
+if you are running Action Cable servers that are separated from your
+main application.
+* `config.action_cable.mount_path` accepts a string for where to mount Action
+ Cable, as part of the main server process. Defaults to `/cable`.
+You can set this as nil to not mount Action Cable as part of your
+normal Rails server.
+
### Configuring a Database
Just about every Rails application will interact with a database. You can connect to the database by setting an environment variable `ENV['DATABASE_URL']` or by using a configuration file called `config/database.yml`.
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 35ad6eb705..faf475c294 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -109,18 +109,18 @@ It can also be useful to save information to log files at runtime. Rails maintai
Rails makes use of the `ActiveSupport::Logger` class to write log information. Other loggers, such as `Log4r`, may also be substituted.
-You can specify an alternative logger in `environment.rb` or any other environment file, for example:
+You can specify an alternative logger in `config/application.rb` or any other environment file, for example:
```ruby
-Rails.logger = Logger.new(STDOUT)
-Rails.logger = Log4r::Logger.new("Application Log")
+config.logger = Logger.new(STDOUT)
+config.logger = Log4r::Logger.new("Application Log")
```
Or in the `Initializer` section, add _any_ of the following
```ruby
-config.logger = Logger.new(STDOUT)
-config.logger = Log4r::Logger.new("Application Log")
+Rails.logger = Logger.new(STDOUT)
+Rails.logger = Log4r::Logger.new("Application Log")
```
TIP: By default, each log is created under `Rails.root/log/` and the log file is named after the environment in which the application is running.
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 8eb3b6190f..4431512eda 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -298,26 +298,30 @@ Open the file `config/routes.rb` in your editor.
Rails.application.routes.draw do
get 'welcome/index'
- # The priority is based upon order of creation:
- # first created -> highest priority.
- # See how all your routes lay out with "bin/rails routes".
- #
- # You can have the root of your site routed with "root"
- # root 'welcome#index'
- #
- # ...
+ # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
+
+ # Serve websocket cable requests in-process
+ # mount ActionCable.server => '/cable'
+end
```
This is your application's _routing file_ which holds entries in a special
[DSL (domain-specific language)](http://en.wikipedia.org/wiki/Domain-specific_language)
that tells Rails how to connect incoming requests to
-controllers and actions. This file contains many sample routes on commented
-lines, and one of them actually shows you how to connect the root of your site
-to a specific controller and action. Find the line beginning with `root` and
-uncomment it. It should look something like the following:
+controllers and actions.
+Edit this file by adding the line of code `root 'welcome#index'`.
+It should look something like the following:
```ruby
-root 'welcome#index'
+Rails.application.routes.draw do
+ get 'welcome/index'
+
+ # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
+
+ # Serve websocket cable requests in-process
+ # mount ActionCable.server => '/cable'
+ root 'welcome#index'
+end
```
`root 'welcome#index'` tells Rails to map requests to the root of the
@@ -348,7 +352,7 @@ operations are referred to as _CRUD_ operations.
Rails provides a `resources` method which can be used to declare a standard REST
resource. You need to add the _article resource_ to the
-`config/routes.rb` as follows:
+`config/routes.rb` so the file will look as follows:
```ruby
Rails.application.routes.draw do
@@ -625,7 +629,7 @@ end
The `render` method here is taking a very simple hash with a key of `:plain` and
value of `params[:article].inspect`. The `params` method is the object which
represents the parameters (or fields) coming in from the form. The `params`
-method returns an `ActiveSupport::HashWithIndifferentAccess` object, which
+method returns an `ActionController::Parameters` object, which
allows you to access the keys of the hash using either strings or symbols. In
this situation, the only parameters that matter are the ones from the form.
@@ -635,7 +639,7 @@ If you re-submit the form one more time you'll now no longer get the missing
template error. Instead, you'll see something that looks like the following:
```ruby
-{"title"=>"First article!", "text"=>"This is my first article."}
+<ActionController::Parameters {"title"=>"First Article!", "text"=>"This is my first article."} permitted: false>
```
This action is now displaying the parameters for the article that are coming in
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index 3bb5d3c8a6..3b773d84f8 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -22,11 +22,11 @@ $ rails new blog -m ~/template.rb
$ rails new blog -m http://example.com/template.rb
```
-You can use the task `rails:template` to apply templates to an existing Rails application. The location of the template needs to be passed in to an environment variable named LOCATION. Again, this can either be path to a file or a URL.
+You can use the task `app:template` to apply templates to an existing Rails application. The location of the template needs to be passed in to an environment variable named LOCATION. Again, this can either be path to a file or a URL.
```bash
-$ bin/rails rails:template LOCATION=~/template.rb
-$ bin/rails rails:template LOCATION=http://example.com/template.rb
+$ bin/rails app:template LOCATION=~/template.rb
+$ bin/rails app:template LOCATION=http://example.com/template.rb
```
Template API
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 0dfa4f1cb8..9d0fec5ab1 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -42,15 +42,15 @@ Rails generally stays close to the latest released Ruby version when it's releas
TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump straight to 1.9.3 for smooth sailing.
-### The Rake Task
+### The Task
-Rails provides the `rails:update` rake task. After updating the Rails version
+Rails provides the `app:update` rake task. After updating the Rails version
in the Gemfile, run this rake task.
This will help you with the creation of new files and changes of old files in an
interactive session.
```bash
-$ rake rails:update
+$ rails app:update
identical config/boot.rb
exist config
conflict config/routes.rb
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 40140c49f6..f3da431b09 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,12 @@
+* The tasks in the rails task namespace is deprecated in favor of app namespace.
+ (e.g. `rails:update` and `rails:template` tasks is renamed to `app:update` and `app:template`.)
+
+ *Ryo Hashimoto*
+
+* Enable HSTS with IncludeSudomains header for new applications.
+
+ *Egor Homakov*, *Prathamesh Sonpatki*
+
## Rails 5.0.0.beta3 (February 24, 2016) ##
* Alias `rake` with `rails_command` in the Rails Application Templates API
diff --git a/railties/lib/rails/app_loader.rb b/railties/lib/rails/app_loader.rb
index a9fe21824e..af004d85bf 100644
--- a/railties/lib/rails/app_loader.rb
+++ b/railties/lib/rails/app_loader.rb
@@ -16,7 +16,7 @@ like any other source code, rather than stubs that are generated on demand.
Here's how to upgrade:
bundle config --delete bin # Turn off Bundler's stub generator
- rake rails:update:bin # Use the new Rails 4 executables
+ rails app:update:bin # Use the new Rails 5 executables
git add bin # Add bin/ to source control
You may need to remove bin/ from your .gitignore as well.
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index ed4e73fb90..89341e6fa2 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -181,7 +181,7 @@ module Rails
def webserver_gemfile_entry
return [] if options[:skip_puma]
comment = 'Use Puma as the app server'
- GemfileEntry.new('puma', nil, comment)
+ GemfileEntry.new('puma', '~> 3.0', comment)
end
def include_all_railties?
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 07d38605a2..7bab3fdf74 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -92,6 +92,8 @@ module Rails
callback_terminator_config_exist = File.exist?('config/initializers/callback_terminator.rb')
active_record_belongs_to_required_by_default_config_exist = File.exist?('config/initializers/active_record_belongs_to_required_by_default.rb')
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')
config
@@ -110,6 +112,14 @@ module Rails
unless action_cable_config_exist
template 'config/cable.yml'
end
+
+ unless ssl_options_exist
+ remove_file 'config/initializers/ssl_options.rb'
+ end
+
+ unless rack_cors_config_exist
+ remove_file 'config/initializers/cors.rb'
+ end
end
def database_yml
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index f3bc9d9734..e8ec214b28 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -26,7 +26,7 @@ source 'https://rubygems.org'
<% if RUBY_ENGINE == 'ruby' -%>
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
- gem 'byebug'
+ gem 'byebug', platform: :mri
end
group :development do
diff --git a/railties/lib/rails/generators/rails/app/templates/README.md b/railties/lib/rails/generators/rails/app/templates/README.md
index 55e144da18..7db80e4ca1 100644
--- a/railties/lib/rails/generators/rails/app/templates/README.md
+++ b/railties/lib/rails/generators/rails/app/templates/README.md
@@ -1,4 +1,4 @@
-## README
+# README
This README would normally document whatever steps are necessary to get the
application up and running.
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee
index 07934d026f..af08f58e34 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee
@@ -1,11 +1,9 @@
# 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.
#
-# Turn on the cable connection by removing the comments after the require statements (and ensure it's also on in config/routes.rb).
-#
#= require action_cable
#= require_self
#= require_tree ./channels
-#
-# @App ||= {}
-# App.cable = ActionCable.createConsumer()
+
+@App ||= {}
+App.cable = ActionCable.createConsumer()
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 ec781721cb..72258cc96b 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
@@ -3,9 +3,6 @@
<head>
<title><%= camelized %></title>
<%%= csrf_meta_tags %>
- <%- unless options[:skip_action_cable] -%>
- <%%= action_cable_meta_tag %>
- <%- end -%>
<%- if options[:skip_javascript] -%>
<%%= stylesheet_link_tag 'application', media: 'all' %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 236e42fcd7..0fc1179339 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -91,5 +91,9 @@ Rails.application.configure do
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
+
+ # Don't mount Action Cable in the main server process.
+ # config.action_cable.mount_path = nil
+ # config.action_cable.url = "ws://example.com"
<%- end -%>
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/ssl_options.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/ssl_options.rb
new file mode 100644
index 0000000000..1775dea1e7
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/ssl_options.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Configure SSL options to enable HSTS with subdomains.
+Rails.application.config.ssl_options = { hsts: { subdomains: true } }
diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
index 8293c8a483..787824f888 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
@@ -1,6 +1,3 @@
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
-
- # Serve websocket cable requests in-process
- # mount ActionCable.server => '/cable'
end
diff --git a/railties/lib/rails/tasks/dev.rake b/railties/lib/rails/tasks/dev.rake
index 4593100465..ff2de264ce 100644
--- a/railties/lib/rails/tasks/dev.rake
+++ b/railties/lib/rails/tasks/dev.rake
@@ -1,6 +1,8 @@
namespace :dev do
desc 'Toggle development mode caching on/off'
task :cache do
+ FileUtils.mkdir_p('tmp')
+
if File.exist? 'tmp/caching-dev.txt'
File.delete 'tmp/caching-dev.txt'
puts 'Development mode is no longer being cached.'
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index 7601836809..ae92836407 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -1,4 +1,6 @@
-namespace :rails do
+require 'active_support/deprecation'
+
+namespace :app do
desc "Update configs and some other initially generated files (or use just update:configs or update:bin)"
task update: [ "update:configs", "update:bin" ]
@@ -66,3 +68,15 @@ namespace :rails do
end
end
end
+
+namespace :rails do
+ %i(update template templates:copy update:configs update:bin).each do |task_name|
+ task "#{task_name}" do
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Running #{task_name} with the rails: namespace is deprecated in favor of app.
+ Run e.g. bin/rails app:#{task_name} instead."
+ MSG
+ Rake.application.invoke_task("app:#{task_name}")
+ end
+ end
+end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index d1c828b509..92ae3edc08 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -118,7 +118,7 @@ module ApplicationTests
end
def test_code_statistics_sanity
- assert_match "Code LOC: 14 Test LOC: 0 Code to Test Ratio: 1:0.0",
+ assert_match "Code LOC: 16 Test LOC: 0 Code to Test Ratio: 1:0.0",
Dir.chdir(app_path){ `bin/rails stats` }
end
@@ -369,7 +369,7 @@ module ApplicationTests
def test_copy_templates
Dir.chdir(app_path) do
- `bin/rails rails:templates:copy`
+ `bin/rails app:templates:copy`
%w(controller mailer scaffold).each do |dir|
assert File.exist?(File.join(app_path, 'lib', 'templates', 'erb', dir))
end
@@ -384,7 +384,7 @@ module ApplicationTests
app_file "template.rb", ""
output = Dir.chdir(app_path) do
- `bin/rails rails:template LOCATION=template.rb`
+ `bin/rails app:template LOCATION=template.rb`
end
assert_match(/Hello, World!/, output)
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index c2c3638f07..63655044da 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -241,6 +241,60 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_rails_update_does_not_create_ssl_options_by_default
+ app_root = File.join(destination_root, 'myapp')
+ run_generator [app_root]
+
+ FileUtils.rm("#{app_root}/config/initializers/ssl_options.rb")
+
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_no_file "#{app_root}/config/initializers/ssl_options.rb"
+ end
+ end
+
+ def test_rails_update_does_not_remove_ssl_options_if_already_present
+ app_root = File.join(destination_root, 'myapp')
+ run_generator [app_root]
+
+ FileUtils.touch("#{app_root}/config/initializers/ssl_options.rb")
+
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file "#{app_root}/config/initializers/ssl_options.rb"
+ end
+ end
+
+ def test_rails_update_does_not_create_rack_cors
+ app_root = File.join(destination_root, 'myapp')
+ run_generator [app_root]
+
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_no_file "#{app_root}/config/initializers/cors.rb"
+ end
+ end
+
+ def test_rails_update_does_not_remove_rack_cors_if_already_present
+ app_root = File.join(destination_root, 'myapp')
+ run_generator [app_root]
+
+ FileUtils.touch("#{app_root}/config/initializers/cors.rb")
+
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file "#{app_root}/config/initializers/cors.rb"
+ end
+ end
+
def test_application_names_are_not_singularized
run_generator [File.join(destination_root, "hats")]
assert_file "hats/config/environment.rb", /Rails\.application\.initialize!/
@@ -338,6 +392,11 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_generator_defaults_to_puma_version
+ run_generator [destination_root]
+ assert_gem "puma", "'~> 3.0'"
+ end
+
def test_generator_if_skip_puma_is_given
run_generator [destination_root, "--skip-puma"]
assert_no_file "config/puma.rb"
@@ -406,9 +465,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_file "config/cable.yml"
assert_no_file "app/assets/javascripts/cable.coffee"
assert_no_file "app/channels"
- assert_file "app/views/layouts/application.html.erb" do |content|
- assert_no_match(/action_cable_meta_tag/, content)
- end
assert_file "Gemfile" do |content|
assert_no_match(/redis/, content)
end
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index d7d27e6b2e..cf3ed8405d 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -642,6 +642,20 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "app/models/bukkits/article.rb", /class Article < ApplicationRecord/
end
+ def test_generate_application_record_when_does_not_exist_in_mountable_engine
+ run_generator [destination_root, '--mountable']
+ FileUtils.rm "#{destination_root}/app/models/bukkits/application_record.rb"
+ capture(:stdout) do
+ `#{destination_root}/bin/rails g model article`
+ end
+
+ assert_file "#{destination_root}/app/models/bukkits/application_record.rb" do |record|
+ assert_match(/module Bukkits/, record)
+ assert_match(/class ApplicationRecord < ActiveRecord::Base/, record)
+ assert_match(/self.abstract_class = true/, record)
+ end
+ end
+
def test_after_bundle_callback
path = 'http://example.org/rails_template'
template = %{ after_bundle { run 'echo ran after_bundle' } }
diff --git a/tools/README.md b/tools/README.md
index 1f3d6c59d9..b2e7e4b0ae 100644
--- a/tools/README.md
+++ b/tools/README.md
@@ -1,8 +1,8 @@
-## Rails dev tools
+# Rails dev tools
This is a collection of utilities used for Rails internal development.
They aren't used by Rails apps directly.
* `console` drops you in irb and loads local Rails repos
* `profile` profiles `Kernel#require` to help reduce startup time
- * `line_statistics` provides CodeTools module and LineStatistics class to count lines \ No newline at end of file
+ * `line_statistics` provides CodeTools module and LineStatistics class to count lines