aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
authorkp <keith.j.payne@gmail.com>2016-02-10 17:44:54 +0000
committerkp <keith.j.payne@gmail.com>2016-02-10 17:44:54 +0000
commitec4ae308a76bd86fb6eaf7fa8ee025af0063ee30 (patch)
tree741019b3fbf474ff4cdeefdbeb6ff1f598ffcdfc /actionpack
parent8641de93eb98d4ebdb0db2530c8c79c0c4e2f95e (diff)
parent688996da7b25080a1a2ef74f5b4789f3e5eb670d (diff)
downloadrails-ec4ae308a76bd86fb6eaf7fa8ee025af0063ee30.tar.gz
rails-ec4ae308a76bd86fb6eaf7fa8ee025af0063ee30.tar.bz2
rails-ec4ae308a76bd86fb6eaf7fa8ee025af0063ee30.zip
Merge remote-tracking branch 'origin/master' into actioncable_logging
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/CHANGELOG.md20
-rw-r--r--actionpack/Rakefile3
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb10
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb4
-rw-r--r--actionpack/lib/action_controller/metal.rb1
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb4
-rw-r--r--actionpack/lib/action_controller/metal/live.rb66
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb3
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb4
-rw-r--r--actionpack/lib/action_controller/test_case.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb16
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb85
-rw-r--r--actionpack/lib/action_dispatch/http/mime_types.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb1
-rw-r--r--actionpack/lib/action_dispatch/routing.rb3
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb27
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb1
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb85
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb1
-rw-r--r--actionpack/lib/action_pack/gem_version.rb2
-rw-r--r--actionpack/test/controller/caching_test.rb12
-rw-r--r--actionpack/test/controller/integration_test.rb43
-rw-r--r--actionpack/test/controller/live_stream_test.rb9
-rw-r--r--actionpack/test/controller/new_base/bare_metal_test.rb16
-rw-r--r--actionpack/test/controller/render_test.rb33
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb34
-rw-r--r--actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb3
28 files changed, 389 insertions, 117 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index f6ffe45490..93e598e493 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,19 @@
+* Add image/svg+xml as a default mime type.
+
+ *DHH*
+
+## Rails 5.0.0.beta2 (February 01, 2016) ##
+
+* Add `-g` and `-c` (short for _grep_ and _controller_ respectively) options
+ to `bin/rake routes`. These options return the url `name`, `verb` and
+ `path` field that match the pattern or match a specific controller.
+
+ Deprecate `CONTROLLER` env variable in `bin/rake routes`.
+
+ See #18902.
+
+ *Anton Davydov* & *Vipul A M*
+
* Response etags to always be weak: Prefixes 'W/' to value returned by
`ActionDispatch::Http::Cache::Response#etag=`, such that etags set in
`fresh_when` and `stale?` are weak.
@@ -171,11 +187,11 @@
* Accessing mime types via constants like `Mime::HTML` is deprecated. Please
change code like this:
- Mime::HTML
+ Mime::HTML
To this:
- Mime[:html]
+ Mime[:html]
This change is so that Rails will not manage a list of constants, and fixes
an issue where if a type isn't registered you could possibly get the wrong
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 601263bfac..37a269cffd 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -5,6 +5,9 @@ test_files = Dir.glob('test/**/*_test.rb')
desc "Default Task"
task :default => :test
+task :package
+task "package:clean"
+
# Run the unit tests
Rake::TestTask.new do |t|
t.libs << 'test'
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 63fd76d9b7..e765d73ce4 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -82,13 +82,13 @@ module AbstractController
# <tt>render :file => "foo/bar"</tt>.
# :api: plugin
def _normalize_args(action=nil, options={})
- case action
- when ActionController::Parameters
- unless action.permitted?
+ if action.respond_to?(:permitted?)
+ if action.permitted?
+ action
+ else
raise ArgumentError, "render parameters are not permitted"
end
- action
- when Hash
+ elsif action.is_a?(Hash)
action
else
options
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 4c9f14e409..a0917b4fdb 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -25,7 +25,9 @@ module ActionController
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
- message << " (#{additions.join(" | ".freeze)})" unless additions.blank?
+ message << " (#{additions.join(" | ".freeze)})" unless additions.empty?
+ message << "\n\n" if defined?(Rails.env) && Rails.env.development?
+
message
end
end
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index f6a93a8940..1641d01c30 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -174,6 +174,7 @@ module ActionController
def response_body=(body)
body = [body] unless body.nil? || body.respond_to?(:each)
response.reset_body!
+ return unless body
body.each { |part|
next if part.empty?
response.write part
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 3dbf34eb2a..bf74b39ac4 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -19,9 +19,9 @@ module ActionController
:controller => self.class.name,
:action => self.action_name,
:params => request.filtered_parameters,
- :format => request.format.try(:ref),
+ :format => request.format.ref,
:method => request.request_method,
- :path => (request.fullpath rescue "unknown")
+ :path => request.fullpath
}
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index e3c540bf5f..fc20e7a421 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -237,39 +237,55 @@ module ActionController
# This processes the action in a child thread. It lets us return the
# response code and headers back up the rack stack, and still process
# the body in parallel with sending data to the client
- Thread.new {
- t2 = Thread.current
- t2.abort_on_exception = true
-
- # Since we're processing the view in a different thread, copy the
- # thread locals from the main thread to the child thread. :'(
- locals.each { |k,v| t2[k] = v }
-
- begin
- super(name)
- rescue => e
- if @_response.committed?
- begin
- @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
- @_response.stream.call_on_error
- rescue => exception
- log_error(exception)
- ensure
- log_error(e)
- @_response.stream.close
+ new_controller_thread {
+ ActiveSupport::Dependencies.interlock.running do
+ t2 = Thread.current
+
+ # Since we're processing the view in a different thread, copy the
+ # thread locals from the main thread to the child thread. :'(
+ locals.each { |k,v| t2[k] = v }
+
+ begin
+ super(name)
+ rescue => e
+ if @_response.committed?
+ begin
+ @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
+ @_response.stream.call_on_error
+ rescue => exception
+ log_error(exception)
+ ensure
+ log_error(e)
+ @_response.stream.close
+ end
+ else
+ error = e
end
- else
- error = e
+ ensure
+ @_response.commit!
end
- ensure
- @_response.commit!
end
}
- @_response.await_commit
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ @_response.await_commit
+ end
+
raise error if error
end
+ # Spawn a new thread to serve up the controller in. This is to get
+ # around the fact that Rack isn't based around IOs and we need to use
+ # a thread to stream data from the response bodies. Nobody should call
+ # this method except in Rails internals. Seriously!
+ def new_controller_thread # :nodoc:
+ Thread.new {
+ t2 = Thread.current
+ t2.abort_on_exception = true
+ yield
+ }
+ end
+
def log_error(exception)
logger = ActionController::Base.logger
return unless logger
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 91b3403ad5..6586985ff5 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -378,7 +378,8 @@ module ActionController #:nodoc:
end
def xor_byte_strings(s1, s2)
- s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*')
+ s2_bytes = s2.bytes
+ s1.bytes.map.with_index { |c1, i| c1 ^ s2_bytes[i] }.pack('c*')
end
# The form's authenticity parameter. Override to provide your own.
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index ba03430930..d3382ef296 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -587,11 +587,11 @@ module ActionController
def method_missing(method_sym, *args, &block)
if @parameters.respond_to?(method_sym)
message = <<-DEPRECATE.squish
- Method #{ method_sym } is deprecated and will be removed in Rails 5.1,
+ Method #{method_sym} is deprecated and will be removed in Rails 5.1,
as `ActionController::Parameters` no longer inherits from
hash. Using this deprecated behavior exposes potential security
problems. If you continue to use this method you may be creating
- a security vulunerability in your app that can be exploited. Instead,
+ a security vulnerability in your app that can be exploited. Instead,
consider using one of these documented methods which are not
deprecated: http://api.rubyonrails.org/v#{ActionPack.version}/classes/ActionController/Parameters.html
DEPRECATE
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index b43bb9dc17..0c4b661214 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -12,6 +12,17 @@ module ActionController
include Testing::Functional
end
+ module Live
+ # Disable controller / rendering threads in tests. User tests can access
+ # the database on the main thread, so they could open a txn, then the
+ # controller thread will open a new connection and try to access data
+ # that's only visible to the main thread's txn. This is the problem in #23483
+ remove_method :new_controller_thread
+ def new_controller_thread # :nodoc:
+ yield
+ end
+ end
+
# ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
# Please use ActionDispatch::IntegrationTest going forward.
class TestRequest < ActionDispatch::TestRequest #:nodoc:
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 0f7898a3f8..4bd727c14e 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -80,6 +80,14 @@ module ActionDispatch
set_header DATE, utc_time.httpdate
end
+ # This method allows you to set the ETag for cached content, which
+ # will be returned to the end user.
+ #
+ # By default, Action Dispatch sets all ETags to be weak.
+ # This ensures that if the content changes only semantically,
+ # the whole page doesn't have to be regenerated from scratch
+ # by the web server. With strong ETags, pages are compared
+ # byte by byte, and are regenerated only if they are not exactly equal.
def etag=(etag)
key = ActiveSupport::Cache.expand_cache_key(etag)
super %(W/"#{Digest::MD5.hexdigest(key)}")
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index 12f81dc1a5..8e899174c6 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -2,9 +2,23 @@ module ActionDispatch
module Http
# Provides access to the request's HTTP headers from the environment.
#
- # env = { "CONTENT_TYPE" => "text/plain" }
+ # env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
# headers = ActionDispatch::Http::Headers.new(env)
# headers["Content-Type"] # => "text/plain"
+ # headers["User-Agent"] # => "curl/7/43/0"
+ #
+ # Also note that when headers are mapped to CGI-like variables by the Rack
+ # server, both dashes and underscores are converted to underscores. This
+ # ambiguity cannot be resolved at this stage anymore. Both underscores and
+ # dashes have to be interpreted as if they were originally sent as dashes.
+ #
+ # # GET / HTTP/1.1
+ # # ...
+ # # User-Agent: curl/7.43.0
+ # # X_Custom_Header: token
+ #
+ # headers["X_Custom_Header"] # => nil
+ # headers["X-Custom-Header"] # => "token"
class Headers
CGI_VARIABLES = Set.new(%W[
AUTH_TYPE
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 6abbf9c754..4672ea7199 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -1,3 +1,5 @@
+# -*- frozen-string-literal: true -*-
+
require 'singleton'
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/string/starts_ends_with'
@@ -106,70 +108,58 @@ module Mime
result = @index <=> item.index if result == 0
result
end
-
- def ==(item)
- @name == item.to_s
- end
end
- class AcceptList < Array #:nodoc:
- def assort!
- sort!
+ class AcceptList #:nodoc:
+ def self.sort!(list)
+ list.sort!
+
+ text_xml_idx = find_item_by_name list, 'text/xml'
+ app_xml_idx = find_item_by_name list, Mime[:xml].to_s
# Take care of the broken text/xml entry by renaming or deleting it
if text_xml_idx && app_xml_idx
+ app_xml = list[app_xml_idx]
+ text_xml = list[text_xml_idx]
+
app_xml.q = [text_xml.q, app_xml.q].max # set the q value to the max of the two
- exchange_xml_items if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list
- delete_at(text_xml_idx) # delete text_xml from the list
+ if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list
+ list[app_xml_idx], list[text_xml_idx] = text_xml, app_xml
+ app_xml_idx, text_xml_idx = text_xml_idx, app_xml_idx
+ end
+ list.delete_at(text_xml_idx) # delete text_xml from the list
elsif text_xml_idx
- text_xml.name = Mime[:xml].to_s
+ list[text_xml_idx].name = Mime[:xml].to_s
end
# Look for more specific XML-based types and sort them ahead of app/xml
if app_xml_idx
+ app_xml = list[app_xml_idx]
idx = app_xml_idx
- while idx < length
- type = self[idx]
+ while idx < list.length
+ type = list[idx]
break if type.q < app_xml.q
if type.name.ends_with? '+xml'
- self[app_xml_idx], self[idx] = self[idx], app_xml
- @app_xml_idx = idx
+ list[app_xml_idx], list[idx] = list[idx], app_xml
+ app_xml_idx = idx
end
idx += 1
end
end
- map! { |i| Mime::Type.lookup(i.name) }.uniq!
- to_a
+ list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
+ list
end
- private
- def text_xml_idx
- @text_xml_idx ||= index('text/xml')
- end
-
- def app_xml_idx
- @app_xml_idx ||= index(Mime[:xml].to_s)
- end
-
- def text_xml
- self[text_xml_idx]
- end
-
- def app_xml
- self[app_xml_idx]
- end
-
- def exchange_xml_items
- self[app_xml_idx], self[text_xml_idx] = text_xml, app_xml
- @app_xml_idx, @text_xml_idx = text_xml_idx, app_xml_idx
- end
+ def self.find_item_by_name(array, name)
+ array.index { |item| item.name == name }
+ end
end
class << self
- TRAILING_STAR_REGEXP = /(text|application)\/\*/
+ TRAILING_STAR_REGEXP = /^(text|application)\/\*/
PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/
def register_callback(&block)
@@ -209,21 +199,22 @@ module Mime
accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
else
- list, index = AcceptList.new, 0
+ list, index = [], 0
accept_header.split(',').each do |header|
params, q = header.split(PARAMETER_SEPARATOR_REGEXP)
- if params.present?
- params.strip!
- params = parse_trailing_star(params) || [params]
+ next unless params
+ params.strip!
+ next if params.empty?
+
+ params = parse_trailing_star(params) || [params]
- params.each do |m|
- list << AcceptItem.new(index, m.to_s, q)
- index += 1
- end
+ params.each do |m|
+ list << AcceptItem.new(index, m.to_s, q)
+ index += 1
end
end
- list.assort!
+ AcceptList.sort! list
end
end
diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb
index 87715205d9..8356d1a238 100644
--- a/actionpack/lib/action_dispatch/http/mime_types.rb
+++ b/actionpack/lib/action_dispatch/http/mime_types.rb
@@ -14,6 +14,7 @@ Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
Mime::Type.register "image/gif", :gif, [], %w(gif)
Mime::Type.register "image/bmp", :bmp, [], %w(bmp)
Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff)
+Mime::Type.register "image/svg+xml", :svg
Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe)
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 14f86c7c07..fa4c54701a 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/module/attribute_accessors'
require 'action_dispatch/http/filter_redirect'
+require 'action_dispatch/http/cache'
require 'monitor'
module ActionDispatch # :nodoc:
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index d00b2c3eb5..6cde5b2900 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -239,7 +239,8 @@ module ActionDispatch
#
# rails routes
#
- # Target specific controllers by prefixing the command with <tt>CONTROLLER=x</tt>.
+ # Target specific controllers by prefixing the command with <tt>--controller</tt> option
+ # - or its <tt>-c</tt> shorthand.
#
module Routing
extend ActiveSupport::Autoload
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index 69e6dd5215..b806ee015b 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -60,12 +60,10 @@ module ActionDispatch
end
def format(formatter, filter = nil)
- routes_to_display = filter_routes(filter)
-
+ routes_to_display = filter_routes(normalize_filter(filter))
routes = collect_routes(routes_to_display)
-
if routes.none?
- formatter.no_routes(collect_routes(@routes), filter)
+ formatter.no_routes(collect_routes(@routes))
return formatter.result
end
@@ -82,10 +80,19 @@ module ActionDispatch
private
+ def normalize_filter(filter)
+ if filter.is_a?(Hash) && filter[:controller]
+ { controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ }
+ elsif filter
+ { controller: /#{filter}/, action: /#{filter}/ }
+ end
+ end
+
def filter_routes(filter)
if filter
- filter_name = filter.underscore.sub(/_controller$/, '')
- @routes.select { |route| route.defaults[:controller] == filter_name }
+ @routes.select do |route|
+ filter.any? { |default, value| route.defaults[default] =~ value }
+ end
else
@routes
end
@@ -137,7 +144,7 @@ module ActionDispatch
@buffer << draw_header(routes)
end
- def no_routes(routes, filter)
+ def no_routes(routes)
@buffer <<
if routes.none?
<<-MESSAGE.strip_heredoc
@@ -145,8 +152,6 @@ module ActionDispatch
Please add some routes in config/routes.rb.
MESSAGE
- elsif missing_controller?(filter)
- "The controller #{filter} does not exist!"
else
"No routes were found for this controller"
end
@@ -154,10 +159,6 @@ module ActionDispatch
end
private
- def missing_controller?(controller_name)
- [ controller_name.camelize, "#{controller_name.camelize}Controller" ].none?(&:safe_constantize)
- end
-
def draw_section(routes)
header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length)
name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 78ef860548..e7af27463c 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -86,6 +86,7 @@ module ActionDispatch
end
# Load routes.rb if it hasn't been loaded.
+ options = options.clone
generated_path, query_string_keys = @routes.generate_extras(options, defaults)
found_extras = options.reject { |k, _| ! query_string_keys.include? k }
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 711ca10419..61bd39c186 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -71,7 +71,7 @@ module ActionDispatch
end
# Performs an XMLHttpRequest request with the given parameters, mirroring
- # a request from the Prototype library.
+ # an AJAX request made from JavaScript.
#
# The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
# +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
@@ -321,7 +321,9 @@ module ActionDispatch
end
# Performs the actual request.
- def process(method, path, params: nil, headers: nil, env: nil, xhr: false)
+ def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil)
+ request_encoder = RequestEncoder.encoder(as)
+
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
@@ -330,14 +332,17 @@ module ActionDispatch
url_host += ":#{location.port}" if default != location.port
host! url_host
end
- path = location.query ? "#{location.path}?#{location.query}" : location.path
+ path = request_encoder.append_format_to location.path
+ path = location.query ? "#{path}?#{location.query}" : path
+ else
+ path = request_encoder.append_format_to path
end
hostname, port = host.split(':')
request_env = {
:method => method,
- :params => params,
+ :params => request_encoder.encode_params(params),
"SERVER_NAME" => hostname,
"SERVER_PORT" => port || (https? ? "443" : "80"),
@@ -347,7 +352,7 @@ module ActionDispatch
"REQUEST_URI" => path,
"HTTP_HOST" => host,
"REMOTE_ADDR" => remote_addr,
- "CONTENT_TYPE" => "application/x-www-form-urlencoded",
+ "CONTENT_TYPE" => request_encoder.content_type,
"HTTP_ACCEPT" => accept
}
@@ -387,6 +392,48 @@ module ActionDispatch
def build_full_uri(path, env)
"#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}"
end
+
+ class RequestEncoder # :nodoc:
+ @encoders = {}
+
+ def initialize(mime_name, param_encoder, url_encoded_form = false)
+ @mime = Mime[mime_name]
+
+ unless @mime
+ raise ArgumentError, "Can't register a request encoder for " \
+ "unregistered MIME Type: #{mime_name}. See `Mime::Type.register`."
+ end
+
+ @url_encoded_form = url_encoded_form
+ @path_format = ".#{@mime.symbol}" unless @url_encoded_form
+ @param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc
+ end
+
+ def append_format_to(path)
+ path << @path_format unless @url_encoded_form
+ path
+ end
+
+ def content_type
+ @mime.to_s
+ end
+
+ def encode_params(params)
+ @param_encoder.call(params)
+ end
+
+ def self.encoder(name)
+ @encoders[name] || WWWFormEncoder
+ end
+
+ def self.register_encoder(mime_name, &param_encoder)
+ @encoders[mime_name] = new(mime_name, param_encoder)
+ end
+
+ register_encoder :json
+
+ WWWFormEncoder = new(:url_encoded_form, -> params { params }, true)
+ end
end
module Runner
@@ -643,6 +690,30 @@ module ActionDispatch
# end
# end
#
+ # You can also test your JSON API easily by setting what the request should
+ # be encoded as:
+ #
+ # require 'test_helper'
+ #
+ # class ApiTest < ActionDispatch::IntegrationTest
+ # test "creates articles" do
+ # assert_difference -> { Article.count } do
+ # post articles_path, params: { article: { title: 'Ahoy!' } }, as: :json
+ # end
+ #
+ # assert_response :success
+ # end
+ # end
+ #
+ # The `as` option sets the format to JSON, sets the content type to
+ # 'application/json' and encodes the parameters as JSON.
+ #
+ # For any custom MIME Types you've registered, you can even add your own encoders with:
+ #
+ # ActionDispatch::IntegrationTest.register_encoder :wibble do |params|
+ # params.to_wibble
+ # end
+ #
# Consult the Rails Testing Guide for more.
class IntegrationTest < ActiveSupport::TestCase
@@ -671,5 +742,9 @@ module ActionDispatch
def document_root_element
html_document.root
end
+
+ def self.register_encoder(*args, &param_encoder)
+ Integration::Session::RequestEncoder.register_encoder(*args, &param_encoder)
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index eca0439909..1ecd7d14a7 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -1,6 +1,5 @@
require 'action_dispatch/middleware/cookies'
require 'action_dispatch/middleware/flash'
-require 'active_support/core_ext/hash/indifferent_access'
module ActionDispatch
module TestProcess
diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb
index 3a1410d492..778c5482d3 100644
--- a/actionpack/lib/action_pack/gem_version.rb
+++ b/actionpack/lib/action_pack/gem_version.rb
@@ -8,7 +8,7 @@ module ActionPack
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "beta1.1"
+ PRE = "beta2"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index d19b3810c2..74c78dfa8e 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -172,6 +172,9 @@ class FunctionalCachingController < CachingController
def fragment_cached_without_digest
end
+
+ def fragment_cached_with_options
+ end
end
class FunctionalFragmentCachingTest < ActionController::TestCase
@@ -215,6 +218,15 @@ CACHED
assert_equal "<p>ERB</p>", @store.read("views/nodigest")
end
+ def test_fragment_caching_with_options
+ get :fragment_cached_with_options
+ assert_response :success
+ expected_body = "<body>\n<p>ERB</p>\n</body>\n"
+
+ assert_equal expected_body, @response.body
+ assert_equal "<p>ERB</p>", @store.read("views/with_options")
+ end
+
def test_render_inline_before_fragment_caching
get :inline_fragment_cached
assert_response :success
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index d0a1d1285f..296bc1baad 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -1126,3 +1126,46 @@ class IntegrationRequestsWithSessionSetup < ActionDispatch::IntegrationTest
assert_equal({"user_name"=>"david"}, cookies.to_hash)
end
end
+
+class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest
+ class FooController < ActionController::Base
+ def foos
+ render plain: 'ok'
+ end
+ end
+
+ def test_encoding_as_json
+ assert_encoded_as :json, content_type: 'application/json'
+ end
+
+ def test_encoding_as_without_mime_registration
+ assert_raise ArgumentError do
+ ActionDispatch::IntegrationTest.register_encoder :wibble
+ end
+ end
+
+ def test_registering_custom_encoder
+ Mime::Type.register 'text/wibble', :wibble
+
+ ActionDispatch::IntegrationTest.register_encoder(:wibble, &:itself)
+
+ assert_encoded_as :wibble, content_type: 'text/wibble',
+ parsed_parameters: Hash.new # Unregistered MIME Type can't be parsed
+ ensure
+ Mime::Type.unregister :wibble
+ end
+
+ private
+ def assert_encoded_as(format, content_type:, parsed_parameters: { 'foo' => 'fighters' })
+ with_routing do |routes|
+ routes.draw { post ':action' => FooController }
+
+ post '/foos', params: { foo: 'fighters' }, as: format
+
+ assert_response :success
+ assert_match "foos.#{format}", request.path
+ assert_equal content_type, request.content_type
+ assert_equal parsed_parameters, request.request_parameters
+ end
+ end
+end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 2ef9734269..0c3884cd38 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -152,7 +152,6 @@ module ActionController
def thread_locals
tc.assert_equal 'aaron', Thread.current[:setting]
- tc.assert_not_equal Thread.current.object_id, Thread.current[:originating_thread]
response.headers['Content-Type'] = 'text/event-stream'
%w{ hello world }.each do |word|
@@ -261,6 +260,14 @@ module ActionController
end
end
+ def setup
+ super
+
+ def @controller.new_controller_thread
+ Thread.new { yield }
+ end
+ end
+
def test_set_cookie
get :set_cookie
assert_equal({'hello' => 'world'}, @response.cookies)
diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb
index c226fa57ee..ee3c498b1c 100644
--- a/actionpack/test/controller/new_base/bare_metal_test.rb
+++ b/actionpack/test/controller/new_base/bare_metal_test.rb
@@ -40,6 +40,22 @@ module BareMetalTest
end
end
+ class BareEmptyController < ActionController::Metal
+ def index
+ self.response_body = nil
+ end
+ end
+
+ class BareEmptyTest < ActiveSupport::TestCase
+ test "response body is nil" do
+ controller = BareEmptyController.new
+ controller.set_request!(ActionDispatch::Request.empty)
+ controller.set_response!(BareController.make_response!(controller.request))
+ controller.index
+ assert_equal nil, controller.response_body
+ end
+ end
+
class HeadController < ActionController::Metal
include ActionController::Head
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 3f569230c2..c814d4ea54 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -63,12 +63,16 @@ class TestController < ActionController::Base
end
def dynamic_render
- render params[:id] # => String, AC:Params
+ render params[:id] # => String, AC::Params
+ end
+
+ def dynamic_render_permit
+ render params[:id].permit(:file)
end
def dynamic_render_with_file
# This is extremely bad, but should be possible to do.
- file = params[:id] # => String, AC:Params
+ file = params[:id] # => String, AC::Params
render file: file
end
@@ -253,6 +257,11 @@ end
class ExpiresInRenderTest < ActionController::TestCase
tests TestController
+ def setup
+ super
+ ActionController::Base.view_paths.paths.each(&:clear_cache)
+ end
+
def test_dynamic_render_with_file
# This is extremely bad, but should be possible to do.
assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb'))
@@ -261,6 +270,18 @@ class ExpiresInRenderTest < ActionController::TestCase
response.body
end
+ def test_dynamic_render_with_absolute_path
+ file = Tempfile.new('name')
+ file.write "secrets!"
+ file.flush
+ assert_raises ActionView::MissingTemplate do
+ get :dynamic_render, params: { id: file.path }
+ end
+ ensure
+ file.close
+ file.unlink
+ end
+
def test_dynamic_render
assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb'))
assert_raises ActionView::MissingTemplate do
@@ -268,6 +289,14 @@ class ExpiresInRenderTest < ActionController::TestCase
end
end
+ def test_permitted_dynamic_render_file_hash
+ skip "FIXME: this test passes on 4-2-stable but not master. Why?"
+ assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb'))
+ response = get :dynamic_render_permit, params: { id: { file: '../\\../test/abstract_unit.rb' } }
+ assert_equal File.read(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')),
+ response.body
+ end
+
def test_dynamic_render_file_hash
assert_raises ArgumentError do
get :dynamic_render, params: { id: { file: '../\\../test/abstract_unit.rb' } }
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
index 7382c267c7..f72a87b994 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -17,10 +17,10 @@ module ActionDispatch
@set = ActionDispatch::Routing::RouteSet.new
end
- def draw(options = {}, &block)
+ def draw(options = nil, &block)
@set.draw(&block)
inspector = ActionDispatch::Routing::RoutesInspector.new(@set.routes)
- inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options[:filter]).split("\n")
+ inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options).split("\n")
end
def test_displaying_routes_for_engines
@@ -297,7 +297,7 @@ module ActionDispatch
end
def test_routes_can_be_filtered
- output = draw(filter: 'posts') do
+ output = draw('posts') do
resources :articles
resources :posts
end
@@ -313,6 +313,26 @@ module ActionDispatch
" DELETE /posts/:id(.:format) posts#destroy"], output
end
+ def test_routes_can_be_filtered_with_namespaced_controllers
+ output = draw('admin/posts') do
+ resources :articles
+ namespace :admin do
+ resources :posts
+ end
+ end
+
+ assert_equal [" Prefix Verb URI Pattern Controller#Action",
+ " admin_posts GET /admin/posts(.:format) admin/posts#index",
+ " POST /admin/posts(.:format) admin/posts#create",
+ " new_admin_post GET /admin/posts/new(.:format) admin/posts#new",
+ "edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit",
+ " admin_post GET /admin/posts/:id(.:format) admin/posts#show",
+ " PATCH /admin/posts/:id(.:format) admin/posts#update",
+ " PUT /admin/posts/:id(.:format) admin/posts#update",
+ " DELETE /admin/posts/:id(.:format) admin/posts#destroy"], output
+ end
+
+
def test_regression_route_with_controller_regexp
output = draw do
get ':controller(/:action)', controller: /api\/[^\/]+/, format: false
@@ -336,18 +356,18 @@ module ActionDispatch
end
def test_routes_with_undefined_filter
- output = draw(:filter => 'Rails::MissingController') do
+ output = draw(controller: 'Rails::MissingController') do
get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
end
assert_equal [
- "The controller Rails::MissingController does not exist!",
+ "No routes were found for this controller",
"For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
], output
end
def test_no_routes_matched_filter
- output = draw(:filter => 'rails/dummy') do
+ output = draw('rails/dummy') do
get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
end
@@ -358,7 +378,7 @@ module ActionDispatch
end
def test_no_routes_were_defined
- output = draw(:filter => 'Rails::DummyController') { }
+ output = draw('Rails::DummyController') {}
assert_equal [
"You don't have any routes defined!",
diff --git a/actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb b/actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb
new file mode 100644
index 0000000000..01453323ef
--- /dev/null
+++ b/actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb
@@ -0,0 +1,3 @@
+<body>
+<%= cache 'with_options', skip_digest: true, expires_in: 1.minute do %><p>ERB</p><% end %>
+</body>