diff options
author | Aaron Patterson <aaron.patterson@gmail.com> | 2014-02-18 11:46:12 -0800 |
---|---|---|
committer | Aaron Patterson <aaron.patterson@gmail.com> | 2014-02-18 11:46:12 -0800 |
commit | e5e440f477a0b5e06b008ee77e3c635049405957 (patch) | |
tree | 3e26bbc11329439e75495fafa5688e9f814bdf31 | |
parent | 20fd254a5bdf35347d231dcc44d7b94cc5c00c1e (diff) | |
parent | 5dc6bf5fbcb70b330edff8da257607acd1760805 (diff) | |
download | rails-e5e440f477a0b5e06b008ee77e3c635049405957.tar.gz rails-e5e440f477a0b5e06b008ee77e3c635049405957.tar.bz2 rails-e5e440f477a0b5e06b008ee77e3c635049405957.zip |
Merge branch 'master' into adequaterecord
* master: (32 commits)
Typo fix for unscope
Use the reference for the mime type to get the format
Preparing for 4.1.0.beta2 release
Correctly escape PostgreSQL arrays.
Escape format, negative_format and units options of number helpers
Sync 4.1 release notes with changes since 7f648bc7 [ci skip]
Update upgrading guide regarding `render :text`
Add `#no_content_type` attribute to `AD::Response`
Add missing CHANGELOG entry to Action View
Update guides for new rendering options
Cleanup `ActionController::Rendering`
Fix a fragile test on `action_view/render`
Introduce `render :html` for render HTML string
Introduce `render :plain` for render plain text
Update hash format for render_text_test
Introduce `render :body` for render raw content
Don't use `# =>` when it is not the expression values
Fix the column name [ci skip]
Document the default scopes change on the release notes, CHANGELOG and upgrating guides
Move changelog entry to the top, fix examples indent [ci skip]
...
49 files changed, 1153 insertions, 85 deletions
diff --git a/RAILS_VERSION b/RAILS_VERSION index 78dae579e8..ee00187eb3 100644 --- a/RAILS_VERSION +++ b/RAILS_VERSION @@ -1 +1 @@ -4.1.0.beta1 +4.1.0.beta2 diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb index 46eb763c26..60732c593b 100644 --- a/actionmailer/lib/action_mailer/version.rb +++ b/actionmailer/lib/action_mailer/version.rb @@ -1,7 +1,7 @@ module ActionMailer # Returns the version of the currently loaded ActionMailer as a Gem::Version def self.version - Gem::Version.new "4.1.0.beta1" + Gem::Version.new "4.1.0.beta2" end module VERSION #:nodoc: diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 342f670e78..b05aa21f95 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,39 @@ +* Introduce `render :html` as an option to render HTML content with a content + type of `text/html`. This rendering option calls `ERB::Util.html_escape` + internally to escape unsafe HTML string, so you will have to mark your + string as html safe if you have any HTML tag in it. + + Please see #12374 for more detail. + + *Prem Sichanugrist* + +* Introduce `render :plain` as an option to render content with a content type + of `text/plain`. This is the preferred option if you are planning to render + a plain text content. + + Please see #12374 for more detail. + + *Prem Sichanugrist* + +* Introduce `render :body` as an option for sending a raw content back to + browser. Note that this rendering option will unset the default content type + and does not include "Content-Type" header back in the response. + + You should only use this option if you are expecting the "Content-Type" + header to not be set. More information on "Content-Type" header can be found + on RFC 2616, section 7.2.1. + + Please see #12374 for more detail. + + *Prem Sichanugrist* + +* Set stream status to 500 (or 400 on BadRequest) when an error is thrown + before commiting. + + Fixes #12552. + + *Kevin Casey* + * Add new config option `config.action_dispatch.cookies_serializer` for specifying a serializer for the signed and encrypted cookie jars. diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index f24b03ad16..349bbf4ee7 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -23,7 +23,7 @@ module AbstractController def render(*args, &block) options = _normalize_render(*args, &block) self.response_body = render_to_body(options) - _process_format(rendered_format) if rendered_format + _process_format(rendered_format, options) if rendered_format self.response_body end @@ -98,7 +98,7 @@ module AbstractController # Process the rendered format. # :api: private - def _process_format(format) + def _process_format(format, options = {}) end # Normalize args and options. diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index 33014b97ca..fdf4ef293d 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -205,6 +205,8 @@ module ActionController begin super(name) rescue => e + @_response.status = 500 unless @_response.committed? + @_response.status = 400 if e.class == ActionController::BadRequest begin @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html @_response.stream.call_on_error diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb index bdf6e88699..e1bee9e60c 100644 --- a/actionpack/lib/action_controller/metal/rack_delegation.rb +++ b/actionpack/lib/action_controller/metal/rack_delegation.rb @@ -5,8 +5,8 @@ module ActionController module RackDelegation extend ActiveSupport::Concern - delegate :headers, :status=, :location=, :content_type=, - :status, :location, :content_type, :to => "@_response" + delegate :headers, :status=, :location=, :content_type=, :no_content_type=, + :status, :location, :content_type, :no_content_type, :to => "@_response" def dispatch(action, request) set_response!(request) diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 5c48b4ab98..3c4ef596c7 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -2,6 +2,8 @@ module ActionController module Rendering extend ActiveSupport::Concern + RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html] + # Before processing, set the request formats in current controller formats. def process_action(*) #:nodoc: self.formats = request.formats.map(&:ref).compact @@ -27,14 +29,29 @@ module ActionController end def render_to_body(options = {}) - super || options[:text].presence || ' ' + super || _render_in_priorities(options) || ' ' end private - def _process_format(format) + def _render_in_priorities(options) + RENDER_FORMATS_IN_PRIORITY.each do |format| + return options[format] if options.key?(format) + end + + nil + end + + def _process_format(format, options = {}) super - self.content_type ||= format.to_s + + if options[:body] + self.headers.delete "Content-Type" + elsif options[:plain] + self.content_type = Mime::TEXT + else + self.content_type ||= format.to_s + end end # Normalize arguments by catching blocks and setting them on :update. @@ -46,12 +63,14 @@ module ActionController # Normalize both text and status options. def _normalize_options(options) #:nodoc: - if options.key?(:text) && options[:text].respond_to?(:to_text) - options[:text] = options[:text].to_text + _normalize_text(options) + + if options[:html] + options[:html] = ERB::Util.html_escape(options[:html]) end - if options.delete(:nothing) || (options.key?(:text) && options[:text].nil?) - options[:text] = " " + if options.delete(:nothing) || _any_render_format_is_nil?(options) + options[:body] = " " end if options[:status] @@ -61,6 +80,18 @@ module ActionController super end + def _normalize_text(options) + RENDER_FORMATS_IN_PRIORITY.each do |format| + if options.key?(format) && options[format].respond_to?(:to_text) + options[format] = options[format].to_text + end + end + end + + def _any_render_format_is_nil?(options) + RENDER_FORMATS_IN_PRIORITY.any? { |format| options.key?(format) && options[format].nil? } + end + # Process controller specific options, as status, content-type and location. def _process_options(options) #:nodoc: status, content_type, location = options.values_at(:status, :content_type, :location) diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 2c6bcf7b7b..f14ca1ea44 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -63,6 +63,8 @@ module ActionDispatch # :nodoc: # content you're giving them, so we need to send that along. attr_accessor :charset + attr_accessor :no_content_type # :nodoc: + CONTENT_TYPE = "Content-Type".freeze SET_COOKIE = "Set-Cookie".freeze LOCATION = "Location".freeze @@ -303,8 +305,17 @@ module ActionDispatch # :nodoc: !@sending_file && @charset != false end + def remove_content_type! + headers.delete CONTENT_TYPE + end + def rack_response(status, header) - assign_default_content_type_and_charset!(header) + if no_content_type + remove_content_type! + else + assign_default_content_type_and_charset!(header) + end + handle_conditional_get! header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join) diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index 57bc6d5cd0..c1df518b14 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -47,12 +47,12 @@ module ActionDispatch # clients (like WAP devices), or behind proxies that set headers in an # incorrect or confusing way (like AWS ELB). # - # The +custom_trusted+ argument can take a regex, which will be used + # The +custom_proxies+ argument can take a regex, which will be used # instead of +TRUSTED_PROXIES+, or a string, which will be used in addition # to +TRUSTED_PROXIES+. Any proxy setup will put the value you want in the # middle (or at the beginning) of the X-Forwarded-For list, with your proxy # servers after it. If your proxies aren't removed, pass them in via the - # +custom_trusted+ parameter. That way, the middleware will ignore those + # +custom_proxies+ parameter. That way, the middleware will ignore those # IP addresses, and return the one that you want. def initialize(app, check_ip_spoofing = true, custom_proxies = nil) @app = app diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index a51f6a434a..8da3069c8b 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -1,7 +1,7 @@ module ActionPack # Returns the version of the currently loaded ActionPack as a Gem::Version def self.version - Gem::Version.new "4.1.0.beta1" + Gem::Version.new "4.1.0.beta2" end module VERSION #:nodoc: diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb index 0a431270b5..fb6a750089 100644 --- a/actionpack/test/controller/live_stream_test.rb +++ b/actionpack/test/controller/live_stream_test.rb @@ -156,6 +156,14 @@ module ActionController raise 'An exception occurred...' end + def exception_in_controller + raise 'Exception in controller' + end + + def bad_request_error + raise ActionController::BadRequest + end + def exception_in_exception_callback response.headers['Content-Type'] = 'text/event-stream' response.stream.on_error do @@ -275,6 +283,16 @@ module ActionController end end + def test_exception_in_controller_before_streaming + response = get :exception_in_controller, format: 'text/event-stream' + assert_equal 500, response.status + end + + def test_bad_request_in_controller_before_streaming + response = get :bad_request_error, format: 'text/event-stream' + assert_equal 400, response.status + end + def test_exceptions_raised_handling_exceptions capture_log_output do |output| get :exception_in_exception_callback, format: 'text/event-stream' diff --git a/actionpack/test/controller/new_base/render_body_test.rb b/actionpack/test/controller/new_base/render_body_test.rb new file mode 100644 index 0000000000..a7e4f87bd9 --- /dev/null +++ b/actionpack/test/controller/new_base/render_body_test.rb @@ -0,0 +1,175 @@ +require 'abstract_unit' + +module RenderBody + class MinimalController < ActionController::Metal + include AbstractController::Rendering + include ActionController::Rendering + + def index + render body: "Hello World!" + end + end + + class SimpleController < ActionController::Base + self.view_paths = [ActionView::FixtureResolver.new] + + def index + render body: "hello david" + end + end + + class WithLayoutController < ::ApplicationController + self.view_paths = [ActionView::FixtureResolver.new( + "layouts/application.erb" => "<%= yield %>, I'm here!", + "layouts/greetings.erb" => "<%= yield %>, I wish thee well.", + "layouts/ivar.erb" => "<%= yield %>, <%= @ivar %>" + )] + + def index + render body: "hello david" + end + + def custom_code + render body: "hello world", status: 404 + end + + def with_custom_code_as_string + render body: "hello world", status: "404 Not Found" + end + + def with_nil + render body: nil + end + + def with_nil_and_status + render body: nil, status: 403 + end + + def with_false + render body: false + end + + def with_layout_true + render body: "hello world", layout: true + end + + def with_layout_false + render body: "hello world", layout: false + end + + def with_layout_nil + render body: "hello world", layout: nil + end + + def with_custom_layout + render body: "hello world", layout: "greetings" + end + + def with_ivar_in_layout + @ivar = "hello world" + render body: "hello world", layout: "ivar" + end + end + + class RenderBodyTest < Rack::TestCase + test "rendering body from a minimal controller" do + get "/render_body/minimal/index" + assert_body "Hello World!" + assert_status 200 + end + + test "rendering body from an action with default options renders the body with the layout" do + with_routing do |set| + set.draw { get ':controller', action: 'index' } + + get "/render_body/simple" + assert_body "hello david" + assert_status 200 + end + end + + test "rendering body from an action with default options renders the body without the layout" do + with_routing do |set| + set.draw { get ':controller', action: 'index' } + + get "/render_body/with_layout" + + assert_body "hello david" + assert_status 200 + end + end + + test "rendering body, while also providing a custom status code" do + get "/render_body/with_layout/custom_code" + + assert_body "hello world" + assert_status 404 + end + + test "rendering body with nil returns an empty body padded for Safari" do + get "/render_body/with_layout/with_nil" + + assert_body " " + assert_status 200 + end + + test "Rendering body with nil and custom status code returns an empty body padded for Safari and the status" do + get "/render_body/with_layout/with_nil_and_status" + + assert_body " " + assert_status 403 + end + + test "rendering body with false returns the string 'false'" do + get "/render_body/with_layout/with_false" + + assert_body "false" + assert_status 200 + end + + test "rendering body with layout: true" do + get "/render_body/with_layout/with_layout_true" + + assert_body "hello world, I'm here!" + assert_status 200 + end + + test "rendering body with layout: 'greetings'" do + get "/render_body/with_layout/with_custom_layout" + + assert_body "hello world, I wish thee well." + assert_status 200 + end + + test "rendering body with layout: false" do + get "/render_body/with_layout/with_layout_false" + + assert_body "hello world" + assert_status 200 + end + + test "rendering body with layout: nil" do + get "/render_body/with_layout/with_layout_nil" + + assert_body "hello world" + assert_status 200 + end + + test "rendering from minimal controller returns response with no content type" do + get "/render_body/minimal/index" + + assert_header_no_content_type + end + + test "rendering from normal controller returns response with no content type" do + get "/render_body/simple/index" + + assert_header_no_content_type + end + + def assert_header_no_content_type + assert_not response.headers.has_key?("Content-Type"), + %(Expect response not to have Content-Type header, got "#{response.headers["Content-Type"]}") + end + end +end diff --git a/actionpack/test/controller/new_base/render_html_test.rb b/actionpack/test/controller/new_base/render_html_test.rb new file mode 100644 index 0000000000..bfe0271df7 --- /dev/null +++ b/actionpack/test/controller/new_base/render_html_test.rb @@ -0,0 +1,190 @@ +require 'abstract_unit' + +module RenderHtml + class MinimalController < ActionController::Metal + include AbstractController::Rendering + include ActionController::Rendering + + def index + render html: "Hello World!" + end + end + + class SimpleController < ActionController::Base + self.view_paths = [ActionView::FixtureResolver.new] + + def index + render html: "hello david" + end + end + + class WithLayoutController < ::ApplicationController + self.view_paths = [ActionView::FixtureResolver.new( + "layouts/application.html.erb" => "<%= yield %>, I'm here!", + "layouts/greetings.html.erb" => "<%= yield %>, I wish thee well.", + "layouts/ivar.html.erb" => "<%= yield %>, <%= @ivar %>" + )] + + def index + render html: "hello david" + end + + def custom_code + render html: "hello world", status: 404 + end + + def with_custom_code_as_string + render html: "hello world", status: "404 Not Found" + end + + def with_nil + render html: nil + end + + def with_nil_and_status + render html: nil, status: 403 + end + + def with_false + render html: false + end + + def with_layout_true + render html: "hello world", layout: true + end + + def with_layout_false + render html: "hello world", layout: false + end + + def with_layout_nil + render html: "hello world", layout: nil + end + + def with_custom_layout + render html: "hello world", layout: "greetings" + end + + def with_ivar_in_layout + @ivar = "hello world" + render html: "hello world", layout: "ivar" + end + + def with_unsafe_html_tag + render html: "<p>hello world</p>", layout: nil + end + + def with_safe_html_tag + render html: "<p>hello world</p>".html_safe, layout: nil + end + end + + class RenderHtmlTest < Rack::TestCase + test "rendering text from a minimal controller" do + get "/render_html/minimal/index" + assert_body "Hello World!" + assert_status 200 + end + + test "rendering text from an action with default options renders the text with the layout" do + with_routing do |set| + set.draw { get ':controller', action: 'index' } + + get "/render_html/simple" + assert_body "hello david" + assert_status 200 + end + end + + test "rendering text from an action with default options renders the text without the layout" do + with_routing do |set| + set.draw { get ':controller', action: 'index' } + + get "/render_html/with_layout" + + assert_body "hello david" + assert_status 200 + end + end + + test "rendering text, while also providing a custom status code" do + get "/render_html/with_layout/custom_code" + + assert_body "hello world" + assert_status 404 + end + + test "rendering text with nil returns an empty body padded for Safari" do + get "/render_html/with_layout/with_nil" + + assert_body " " + assert_status 200 + end + + test "Rendering text with nil and custom status code returns an empty body padded for Safari and the status" do + get "/render_html/with_layout/with_nil_and_status" + + assert_body " " + assert_status 403 + end + + test "rendering text with false returns the string 'false'" do + get "/render_html/with_layout/with_false" + + assert_body "false" + assert_status 200 + end + + test "rendering text with layout: true" do + get "/render_html/with_layout/with_layout_true" + + assert_body "hello world, I'm here!" + assert_status 200 + end + + test "rendering text with layout: 'greetings'" do + get "/render_html/with_layout/with_custom_layout" + + assert_body "hello world, I wish thee well." + assert_status 200 + end + + test "rendering text with layout: false" do + get "/render_html/with_layout/with_layout_false" + + assert_body "hello world" + assert_status 200 + end + + test "rendering text with layout: nil" do + get "/render_html/with_layout/with_layout_nil" + + assert_body "hello world" + assert_status 200 + end + + test "rendering html should escape the string if it is not html safe" do + get "/render_html/with_layout/with_unsafe_html_tag" + + assert_body "<p>hello world</p>" + assert_status 200 + end + + test "rendering html should not escape the string if it is html safe" do + get "/render_html/with_layout/with_safe_html_tag" + + assert_body "<p>hello world</p>" + assert_status 200 + end + + test "rendering from minimal controller returns response with text/html content type" do + get "/render_html/minimal/index" + assert_content_type "text/html" + end + + test "rendering from normal controller returns response with text/html content type" do + get "/render_html/simple/index" + assert_content_type "text/html; charset=utf-8" + end + end +end diff --git a/actionpack/test/controller/new_base/render_plain_test.rb b/actionpack/test/controller/new_base/render_plain_test.rb new file mode 100644 index 0000000000..dba2e9f13e --- /dev/null +++ b/actionpack/test/controller/new_base/render_plain_test.rb @@ -0,0 +1,168 @@ +require 'abstract_unit' + +module RenderPlain + class MinimalController < ActionController::Metal + include AbstractController::Rendering + include ActionController::Rendering + + def index + render plain: "Hello World!" + end + end + + class SimpleController < ActionController::Base + self.view_paths = [ActionView::FixtureResolver.new] + + def index + render plain: "hello david" + end + end + + class WithLayoutController < ::ApplicationController + self.view_paths = [ActionView::FixtureResolver.new( + "layouts/application.text.erb" => "<%= yield %>, I'm here!", + "layouts/greetings.text.erb" => "<%= yield %>, I wish thee well.", + "layouts/ivar.text.erb" => "<%= yield %>, <%= @ivar %>" + )] + + def index + render plain: "hello david" + end + + def custom_code + render plain: "hello world", status: 404 + end + + def with_custom_code_as_string + render plain: "hello world", status: "404 Not Found" + end + + def with_nil + render plain: nil + end + + def with_nil_and_status + render plain: nil, status: 403 + end + + def with_false + render plain: false + end + + def with_layout_true + render plain: "hello world", layout: true + end + + def with_layout_false + render plain: "hello world", layout: false + end + + def with_layout_nil + render plain: "hello world", layout: nil + end + + def with_custom_layout + render plain: "hello world", layout: "greetings" + end + + def with_ivar_in_layout + @ivar = "hello world" + render plain: "hello world", layout: "ivar" + end + end + + class RenderPlainTest < Rack::TestCase + test "rendering text from a minimal controller" do + get "/render_plain/minimal/index" + assert_body "Hello World!" + assert_status 200 + end + + test "rendering text from an action with default options renders the text with the layout" do + with_routing do |set| + set.draw { get ':controller', action: 'index' } + + get "/render_plain/simple" + assert_body "hello david" + assert_status 200 + end + end + + test "rendering text from an action with default options renders the text without the layout" do + with_routing do |set| + set.draw { get ':controller', action: 'index' } + + get "/render_plain/with_layout" + + assert_body "hello david" + assert_status 200 + end + end + + test "rendering text, while also providing a custom status code" do + get "/render_plain/with_layout/custom_code" + + assert_body "hello world" + assert_status 404 + end + + test "rendering text with nil returns an empty body padded for Safari" do + get "/render_plain/with_layout/with_nil" + + assert_body " " + assert_status 200 + end + + test "Rendering text with nil and custom status code returns an empty body padded for Safari and the status" do + get "/render_plain/with_layout/with_nil_and_status" + + assert_body " " + assert_status 403 + end + + test "rendering text with false returns the string 'false'" do + get "/render_plain/with_layout/with_false" + + assert_body "false" + assert_status 200 + end + + test "rendering text with layout: true" do + get "/render_plain/with_layout/with_layout_true" + + assert_body "hello world, I'm here!" + assert_status 200 + end + + test "rendering text with layout: 'greetings'" do + get "/render_plain/with_layout/with_custom_layout" + + assert_body "hello world, I wish thee well." + assert_status 200 + end + + test "rendering text with layout: false" do + get "/render_plain/with_layout/with_layout_false" + + assert_body "hello world" + assert_status 200 + end + + test "rendering text with layout: nil" do + get "/render_plain/with_layout/with_layout_nil" + + assert_body "hello world" + assert_status 200 + end + + test "rendering from minimal controller returns response with text/plain content type" do + get "/render_plain/minimal/index" + assert_content_type "text/plain" + end + + test "rendering from normal controller returns response with text/plain content type" do + get "/render_plain/simple/index" + assert_content_type "text/plain; charset=utf-8" + end + end +end diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb index 2a253799f3..abb81d7e71 100644 --- a/actionpack/test/controller/new_base/render_text_test.rb +++ b/actionpack/test/controller/new_base/render_text_test.rb @@ -14,7 +14,7 @@ module RenderText self.view_paths = [ActionView::FixtureResolver.new] def index - render :text => "hello david" + render text: "hello david" end end @@ -26,48 +26,48 @@ module RenderText )] def index - render :text => "hello david" + render text: "hello david" end def custom_code - render :text => "hello world", :status => 404 + render text: "hello world", status: 404 end def with_custom_code_as_string - render :text => "hello world", :status => "404 Not Found" + render text: "hello world", status: "404 Not Found" end def with_nil - render :text => nil + render text: nil end def with_nil_and_status - render :text => nil, :status => 403 + render text: nil, status: 403 end def with_false - render :text => false + render text: false end def with_layout_true - render :text => "hello world", :layout => true + render text: "hello world", layout: true end def with_layout_false - render :text => "hello world", :layout => false + render text: "hello world", layout: false end def with_layout_nil - render :text => "hello world", :layout => nil + render text: "hello world", layout: nil end def with_custom_layout - render :text => "hello world", :layout => "greetings" + render text: "hello world", layout: "greetings" end def with_ivar_in_layout @ivar = "hello world" - render :text => "hello world", :layout => "ivar" + render text: "hello world", layout: "ivar" end end @@ -80,7 +80,7 @@ module RenderText test "rendering text from an action with default options renders the text with the layout" do with_routing do |set| - set.draw { get ':controller', :action => 'index' } + set.draw { get ':controller', action: 'index' } get "/render_text/simple" assert_body "hello david" @@ -90,7 +90,7 @@ module RenderText test "rendering text from an action with default options renders the text without the layout" do with_routing do |set| - set.draw { get ':controller', :action => 'index' } + set.draw { get ':controller', action: 'index' } get "/render_text/with_layout" @@ -127,28 +127,28 @@ module RenderText assert_status 200 end - test "rendering text with :layout => true" do + test "rendering text with layout: true" do get "/render_text/with_layout/with_layout_true" assert_body "hello world, I'm here!" assert_status 200 end - test "rendering text with :layout => 'greetings'" do + test "rendering text with layout: 'greetings'" do get "/render_text/with_layout/with_custom_layout" assert_body "hello world, I wish thee well." assert_status 200 end - test "rendering text with :layout => false" do + test "rendering text with layout: false" do get "/render_text/with_layout/with_layout_false" assert_body "hello world" assert_status 200 end - test "rendering text with :layout => nil" do + test "rendering text with layout: nil" do get "/render_text/with_layout/with_layout_nil" assert_body "hello world" diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 959a3bc5cd..1360ede3f8 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -235,6 +235,14 @@ class ResponseTest < ActiveSupport::TestCase assert_equal @response.body, body.each.to_a.join end end + + test "does not add default content-type if Content-Type is none" do + resp = ActionDispatch::Response.new.tap { |response| + response.no_content_type = true + } + + assert_not resp.headers.has_key?('Content-Type') + end end class ResponseIntegrationTest < ActionDispatch::IntegrationTest diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index a0f298a6b1..c05ed10263 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,8 @@ +* Added `:plain`, `:html` and `:body` option for `render` method. Please see + Action Pack's release note for more detail. + + *Prem Sichanugrist* + * Date select helpers accept a format string for the months selector via the new option `:month_format_string`. @@ -40,6 +45,10 @@ *Kuldeep Aggarwal* +* Escape format, negative_format and units options of number helpers + + Fixes: CVE-2014-0081 + * A Cycle object should accept an array and cycle through it as it would with a set of comma-separated objects. diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb index ad825cd1f1..7157a95146 100644 --- a/actionview/lib/action_view/helpers/number_helper.rb +++ b/actionview/lib/action_view/helpers/number_helper.rb @@ -384,20 +384,29 @@ module ActionView def delegate_number_helper_method(method, number, options) return unless number - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) + options = escape_unsafe_options(options.symbolize_keys) wrap_with_output_safety_handling(number, options.delete(:raise)) { ActiveSupport::NumberHelper.public_send(method, number, options) } end - def escape_unsafe_delimiters_and_separators(options) - options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] && !options[:separator].html_safe? - options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] && !options[:delimiter].html_safe? - options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe? + def escape_unsafe_options(options) + options[:format] = ERB::Util.html_escape(options[:format]) if options[:format] + options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format] + options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] + options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] + options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe? + options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units] options end + def escape_units(units) + Hash[units.map do |k, v| + [k, ERB::Util.html_escape(v)] + end] + end + def wrap_with_output_safety_handling(number, raise_on_invalid, &block) valid_float = valid_float?(number) raise InvalidNumberError, number if raise_on_invalid && !valid_float diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb index 458086de96..15b88bcda6 100644 --- a/actionview/lib/action_view/helpers/rendering_helper.rb +++ b/actionview/lib/action_view/helpers/rendering_helper.rb @@ -12,6 +12,13 @@ module ActionView # * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those. # * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller. # * <tt>:text</tt> - Renders the text passed in out. + # * <tt>:plain</tt> - Renders the text passed in out. Setting the content + # type as <tt>text/plain</tt>. + # * <tt>:html</tt> - Renders the html safe string passed in out, otherwise + # performs html escape on the string first. Setting the content type as + # <tt>text/html</tt>. + # * <tt>:body</tt> - Renders the text passed in, and does not set content + # type in the response. # # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter # as the locals hash. diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb index ffa67649da..9ee05bd816 100644 --- a/actionview/lib/action_view/layouts.rb +++ b/actionview/lib/action_view/layouts.rb @@ -420,7 +420,7 @@ module ActionView end def _include_layout?(options) - (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout) + (options.keys & [:body, :text, :plain, :html, :inline, :partial]).empty? || options.key?(:layout) end end end diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index 668831dff3..be17097428 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -21,8 +21,14 @@ module ActionView def determine_template(options) #:nodoc: keys = options.fetch(:locals, {}).keys - if options.key?(:text) + if options.key?(:body) + Template::Text.new(options[:body]) + elsif options.key?(:text) Template::Text.new(options[:text], formats.first) + elsif options.key?(:plain) + Template::Text.new(options[:plain]) + elsif options.key?(:html) + Template::HTML.new(options[:html], formats.first) elsif options.key?(:file) with_fallbacks { find_template(options[:file], nil, false, keys, @details) } elsif options.key?(:inline) @@ -35,7 +41,7 @@ module ActionView find_template(options[:template], options[:prefixes], false, keys, @details) end else - raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file or :text option." + raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :text or :body option." end end diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index 7c17220d14..f96587c816 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -100,8 +100,13 @@ module ActionView end # Assign the rendered format to lookup context. - def _process_format(format) #:nodoc: + def _process_format(format, options = {}) #:nodoc: super + + if options[:body] + self.no_content_type = true + end + lookup_context.formats = [format.to_sym] lookup_context.rendered_format = lookup_context.formats.first end diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index 9b0619f1aa..961a969b6e 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -90,6 +90,7 @@ module ActionView eager_autoload do autoload :Error autoload :Handlers + autoload :HTML autoload :Text autoload :Types end diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb new file mode 100644 index 0000000000..0321f819b5 --- /dev/null +++ b/actionview/lib/action_view/template/html.rb @@ -0,0 +1,34 @@ +module ActionView #:nodoc: + # = Action View HTML Template + class Template + class HTML #:nodoc: + attr_accessor :type + + def initialize(string, type = nil) + @string = string.to_s + @type = Types[type] || type if type + @type ||= Types[:html] + end + + def identifier + 'html template' + end + + def inspect + 'html template' + end + + def to_str + ERB::Util.h(@string) + end + + def render(*args) + to_str + end + + def formats + [@type.respond_to?(:ref) ? @type.ref : @type.to_s] + end + end + end +end diff --git a/actionview/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb index 859c7bc3ce..04f5b8d17a 100644 --- a/actionview/lib/action_view/template/text.rb +++ b/actionview/lib/action_view/template/text.rb @@ -27,7 +27,7 @@ module ActionView #:nodoc: end def formats - [@type.to_sym] + [@type.respond_to?(:ref) ? @type.ref : @type.to_s] end end end diff --git a/actionview/lib/action_view/version.rb b/actionview/lib/action_view/version.rb index edb6d8f116..3d5d6c9be1 100644 --- a/actionview/lib/action_view/version.rb +++ b/actionview/lib/action_view/version.rb @@ -1,7 +1,7 @@ module ActionView # Returns the version of the currently loaded ActionView as a Gem::Version def self.version - Gem::Version.new "4.1.0.beta1" + Gem::Version.new "4.1.0.beta2" end module VERSION #:nodoc: diff --git a/actionview/test/template/html_test.rb b/actionview/test/template/html_test.rb new file mode 100644 index 0000000000..549c12c88c --- /dev/null +++ b/actionview/test/template/html_test.rb @@ -0,0 +1,17 @@ +require 'abstract_unit' + +class HTMLTest < ActiveSupport::TestCase + test 'formats returns symbol for recognized MIME type' do + assert_equal [:html], ActionView::Template::HTML.new('', :html).formats + end + + test 'formats returns string for recognized MIME type when MIME does not have symbol' do + foo = Mime::Type.lookup("foo") + assert_nil foo.to_sym + assert_equal ['foo'], ActionView::Template::HTML.new('', foo).formats + end + + test 'formats returns string for unknown MIME type' do + assert_equal ['foo'], ActionView::Template::HTML.new('', 'foo').formats + end +end diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb index be336ea3fb..11bc978324 100644 --- a/actionview/test/template/number_helper_test.rb +++ b/actionview/test/template/number_helper_test.rb @@ -8,6 +8,8 @@ class NumberHelperTest < ActionView::TestCase assert_equal "555-1234", number_to_phone(5551234) assert_equal "(800) 555-1212 x 123", number_to_phone(8005551212, area_code: true, extension: 123) assert_equal "+18005551212", number_to_phone(8005551212, country_code: 1, delimiter: "") + assert_equal "+<script></script>8005551212", number_to_phone(8005551212, country_code: "<script></script>", delimiter: "") + assert_equal "8005551212 x <script></script>", number_to_phone(8005551212, extension: "<script></script>", delimiter: "") end def test_number_to_currency @@ -16,11 +18,17 @@ class NumberHelperTest < ActionView::TestCase assert_equal "$1,234,567,892", number_to_currency(1234567891.50, precision: 0) assert_equal "1,234,567,890.50 - Kč", number_to_currency("-1234567890.50", unit: raw("Kč"), format: "%n %u", negative_format: "%n - %u") assert_equal "&pound;1,234,567,890.50", number_to_currency("1234567890.50", unit: "£") + assert_equal "<b>1,234,567,890.50</b> $", number_to_currency("1234567890.50", format: "<b>%n</b> %u") + assert_equal "<b>1,234,567,890.50</b> $", number_to_currency("-1234567890.50", negative_format: "<b>%n</b> %u") + assert_equal "<b>1,234,567,890.50</b> $", number_to_currency("-1234567890.50", 'negative_format' => "<b>%n</b> %u") end def test_number_to_percentage assert_equal nil, number_to_percentage(nil) assert_equal "100.000%", number_to_percentage(100) + assert_equal "100.000 %", number_to_percentage(100, format: '%n %') + assert_equal "<b>100.000</b> %", number_to_percentage(100, format: '<b>%n</b> %') + assert_equal "<b>100.000</b> %", number_to_percentage(100, format: raw('<b>%n</b> %')) assert_equal "100%", number_to_percentage(100, precision: 0) assert_equal "123.4%", number_to_percentage(123.400, precision: 3, strip_insignificant_zeros: true) assert_equal "1.000,000%", number_to_percentage(1000, delimiter: ".", separator: ",") @@ -52,6 +60,31 @@ class NumberHelperTest < ActionView::TestCase assert_equal "489.0 Thousand", number_to_human(489000, precision: 4, strip_insignificant_zeros: false) end + def test_number_to_human_escape_units + volume = { unit: "<b>ml</b>", thousand: "<b>lt</b>", million: "<b>m3</b>", trillion: "<b>km3</b>", quadrillion: "<b>Pl</b>" } + assert_equal '123 <b>lt</b>', number_to_human(123456, :units => volume) + assert_equal '12 <b>ml</b>', number_to_human(12, :units => volume) + assert_equal '1.23 <b>m3</b>', number_to_human(1234567, :units => volume) + assert_equal '1.23 <b>km3</b>', number_to_human(1_234_567_000_000, :units => volume) + assert_equal '1.23 <b>Pl</b>', number_to_human(1_234_567_000_000_000, :units => volume) + + #Including fractionals + distance = { mili: "<b>mm</b>", centi: "<b>cm</b>", deci: "<b>dm</b>", unit: "<b>m</b>", + ten: "<b>dam</b>", hundred: "<b>hm</b>", thousand: "<b>km</b>", + micro: "<b>um</b>", nano: "<b>nm</b>", pico: "<b>pm</b>", femto: "<b>fm</b>"} + assert_equal '1.23 <b>mm</b>', number_to_human(0.00123, :units => distance) + assert_equal '1.23 <b>cm</b>', number_to_human(0.0123, :units => distance) + assert_equal '1.23 <b>dm</b>', number_to_human(0.123, :units => distance) + assert_equal '1.23 <b>m</b>', number_to_human(1.23, :units => distance) + assert_equal '1.23 <b>dam</b>', number_to_human(12.3, :units => distance) + assert_equal '1.23 <b>hm</b>', number_to_human(123, :units => distance) + assert_equal '1.23 <b>km</b>', number_to_human(1230, :units => distance) + assert_equal '1.23 <b>um</b>', number_to_human(0.00000123, :units => distance) + assert_equal '1.23 <b>nm</b>', number_to_human(0.00000000123, :units => distance) + assert_equal '1.23 <b>pm</b>', number_to_human(0.00000000000123, :units => distance) + assert_equal '1.23 <b>fm</b>', number_to_human(0.00000000000000123, :units => distance) + end + def test_number_helpers_escape_delimiter_and_separator assert_equal "111<script></script>111<script></script>1111", number_to_phone(1111111111, delimiter: "<script></script>") @@ -73,6 +106,12 @@ class NumberHelperTest < ActionView::TestCase assert_equal "100<script></script>000 Quadrillion", number_to_human(10**20, delimiter: "<script></script>") end + def test_number_to_human_with_custom_translation_scope + I18n.backend.store_translations 'ts', + :custom_units_for_number_to_human => {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"} + assert_equal "1.01 cm", number_to_human(0.0101, :locale => 'ts', :units => :custom_units_for_number_to_human) + end + def test_number_helpers_outputs_are_html_safe assert number_to_human(1).html_safe? assert !number_to_human("<script></script>").html_safe? diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index db5d99755c..ca508abfb8 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -22,7 +22,7 @@ module RenderTestCases def test_render_without_options e = assert_raises(ArgumentError) { @view.render() } - assert_match "You invoked render but did not give any of :partial, :template, :inline, :file or :text option.", e.message + assert_match(/You invoked render but did not give any of (.+) option./, e.message) end def test_render_file diff --git a/actionview/test/template/text_test.rb b/actionview/test/template/text_test.rb new file mode 100644 index 0000000000..d899d54589 --- /dev/null +++ b/actionview/test/template/text_test.rb @@ -0,0 +1,17 @@ +require 'abstract_unit' + +class TextTest < ActiveSupport::TestCase + test 'formats returns symbol for recognized MIME type' do + assert_equal [:text], ActionView::Template::Text.new('', :text).formats + end + + test 'formats returns string for recognized MIME type when MIME does not have symbol' do + foo = Mime::Type.lookup("foo") + assert_nil foo.to_sym + assert_equal ['foo'], ActionView::Template::Text.new('', foo).formats + end + + test 'formats returns string for unknown MIME type' do + assert_equal ['foo'], ActionView::Template::Text.new('', 'foo').formats + end +end diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb index 58ba3ab9b2..f7c9534ffb 100644 --- a/activemodel/lib/active_model/version.rb +++ b/activemodel/lib/active_model/version.rb @@ -1,7 +1,7 @@ module ActiveModel # Returns the version of the currently loaded ActiveModel as a Gem::Version def self.version - Gem::Version.new "4.1.0.beta1" + Gem::Version.new "4.1.0.beta2" end module VERSION #:nodoc: diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 458b9d77c2..7efd75a239 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,64 @@ +* Default scopes are no longer overriden by chained conditions. + + Before this change when you defined a `default_scope` in a model + it was overriden by chained conditions in the same field. Now it + is merged like any other scope. + + Before: + + class User < ActiveRecord::Base + default_scope { where state: 'pending' } + scope :active, -> { where state: 'active' } + scope :inactive, -> { where state: 'inactive' } + end + + User.all + # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' + + User.active + # SELECT "users".* FROM "users" WHERE "users"."state" = 'active' + + User.where(state: 'inactive') + # SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive' + + After: + + class User < ActiveRecord::Base + default_scope { where state: 'pending' } + scope :active, -> { where state: 'active' } + scope :inactive, -> { where state: 'inactive' } + end + + User.all + # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' + + User.active + # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active' + + User.where(state: 'inactive') + # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive' + + To get the previous behavior it is needed to explicitly remove the + `default_scope` condition using `unscoped`, `unscope`, `rewhere` or + `except`. + + Example: + + class User < ActiveRecord::Base + default_scope { where state: 'pending' } + scope :active, -> { unscope(where: :state).where(state: 'active') } + scope :inactive, -> { rewhere state: 'inactive' } + end + + User.all + # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' + + User.active + # SELECT "users".* FROM "users" WHERE "users"."state" = 'active' + + User.inactive + # SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive' + * Perform necessary deeper encoding when hstore is inside an array. Fixes #11135. @@ -548,6 +609,10 @@ *Kuldeep Aggarwal* +* Correctly escape PostgreSQL arrays. + + Fixes: CVE-2014-0080 + * `Relation` no longer has mutator methods like `#map!` and `#delete_if`. Convert to an `Array` by calling `#to_a` before using these methods. diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index 3a3b500b1f..551a9289c3 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -142,12 +142,16 @@ module ActiveRecord end end + ARRAY_ESCAPE = "\\" * 2 * 2 # escape the backslash twice for PG arrays + def quote_and_escape(value) case value when "NULL", Numeric value else - "\"#{value.gsub(/"/,"\\\"")}\"" + value = value.gsub(/\\/, ARRAY_ESCAPE) + value.gsub!(/"/,"\\\"") + "\"#{value}\"" end end diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index 863c3ebe4d..7795561e51 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -1,7 +1,7 @@ module ActiveRecord # Returns the version of the currently loaded ActiveRecord as a Gem::Version def self.version - Gem::Version.new "4.1.0.beta1" + Gem::Version.new "4.1.0.beta2" end module VERSION #:nodoc: diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 04a458fbce..5c3a797c41 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -78,6 +78,14 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase PostgresqlBitString, PostgresqlOid, PostgresqlTimestampWithZone, PostgresqlUUID].each(&:delete_all) end + def test_array_escaping + unknown = %(foo\\",bar,baz,\\) + nicknames = ["hello_#{unknown}"] + ar = PostgresqlArray.create!(nicknames: nicknames, id: 100) + ar.reload + assert_equal nicknames, ar.nicknames + end + def test_data_type_of_array_types assert_equal :integer, @first_array.column_for_attribute(:commission_by_quarter).type assert_equal :text, @first_array.column_for_attribute(:nicknames).type diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 43bfeff079..713bb3c1e2 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,19 @@ +* Added `Object#present_in` to simplify value whitelisting. + + Before: + + params[:bucket_type].in?(%w( project calendar )) ? params[:bucket_type] : nil + + After: + + params[:bucket_type].present_in %w( project calendar ) + + *DHH* + +* Time helpers honor the application time zone when passed a date. + + *Xavier Noria* + * Fix the implementation of Multibyte::Unicode.tidy_bytes for JRuby The existing implementation caused JRuby to raise the error: diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb index b5671f66d0..141f19e7b3 100644 --- a/activesupport/lib/active_support/core_ext/object/inclusion.rb +++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb @@ -12,4 +12,16 @@ class Object rescue NoMethodError raise ArgumentError.new("The parameter passed to #in? must respond to #include?") end + + # Returns the receiver if it's included in the argument otherwise returns +nil+. + # Argument must be any object which responds to +#include?+. Usage: + # + # params[:bucket_type].present_in %w( project calendar ) + # + # This will throw an ArgumentError if the argument doesn't respond to +#include?+. + # + # @return [Object] + def present_in(another_object) + self.in?(another_object) ? self : nil + end end diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index 9e0a3d6345..eefa84262e 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -10,7 +10,7 @@ module ActiveSupport def stub_object(object, method_name, return_value) key = [object.object_id, method_name] - if (stub = @stubs[key]) + if stub = @stubs[key] unstub_object(stub) end @@ -61,14 +61,23 @@ module ActiveSupport travel_to Time.now + duration, &block end - # Changes current time to the given time by stubbing +Time.now+ and +Date.today+ to return the - # time or date passed into this method. + # Changes current time to the given time by stubbing +Time.now+ and + # +Date.today+ to return the time or date passed into this method. # # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 # travel_to Time.new(2004, 11, 24, 01, 04, 44) # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 # Date.current # => Wed, 24 Nov 2004 # + # Dates are taken as their timestamp at the beginning of the day in the + # application time zone. <tt>Time.current</tt> returns said timestamp, + # and <tt>Time.now</tt> its equivalent in the system time zone. Similarly, + # <tt>Date.current</tt> returns a date equal to the argument, and + # <tt>Date.today</tt> the date according to <tt>Time.now</tt>, which may + # be different. (Note that you rarely want to deal with <tt>Time.now</tt>, + # or <tt>Date.today</tt>, in order to honor the application time zone + # please always use <tt>Time.current</tt> and <tt>Date.current</tt>.) + # # This method also accepts a block, which will return the current time back to its original # state at the end of the block: # @@ -78,12 +87,21 @@ module ActiveSupport # end # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 def travel_to(date_or_time, &block) - simple_stubs.stub_object(Time, :now, date_or_time.to_time) - simple_stubs.stub_object(Date, :today, date_or_time.to_date) + if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime) + now = date_or_time.midnight.to_time + else + now = date_or_time.to_time + end + + simple_stubs.stub_object(Time, :now, now) + simple_stubs.stub_object(Date, :today, now.to_date) if block_given? - block.call - travel_back + begin + block.call + ensure + travel_back + end end end diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index b3f0e7198d..b9d6417b07 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -1,7 +1,7 @@ module ActiveSupport # Returns the version of the currently loaded ActiveSupport as a Gem::Version def self.version - Gem::Version.new "4.1.0.beta1" + Gem::Version.new "4.1.0.beta2" end module VERSION #:nodoc: diff --git a/activesupport/test/core_ext/object/inclusion_test.rb b/activesupport/test/core_ext/object/inclusion_test.rb index 478706eeae..c5e2cc693a 100644 --- a/activesupport/test/core_ext/object/inclusion_test.rb +++ b/activesupport/test/core_ext/object/inclusion_test.rb @@ -47,4 +47,9 @@ class InTest < ActiveSupport::TestCase def test_no_method_catching assert_raise(ArgumentError) { 1.in?(1) } end + + def test_present_in + assert_equal "stuff", "stuff".present_in(%w( lots of stuff )) + assert_nil "stuff".present_in(%w( lots of crap )) + end end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index cd79efbe8c..79ec57af2b 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -124,6 +124,32 @@ class TimeZoneTest < ActiveSupport::TestCase travel_back end + def test_travel_to_a_date + with_env_tz do + Time.use_zone('Hawaii') do + date = Date.new(2014, 2, 18) + time = date.midnight + + travel_to date do + assert_equal date, Date.current + assert_equal time, Time.current + end + end + end + end + + def test_travel_to_travels_back_and_reraises_if_the_block_raises + ts = Time.current - 1.second + + travel_to ts do + raise + end + + flunk # ensure travel_to re-raises + rescue + assert_not_equal ts, Time.current + end + def test_local time = ActiveSupport::TimeZone["Hawaii"].local(2007, 2, 5, 15, 30, 45) assert_equal Time.utc(2007, 2, 5, 15, 30, 45), time.time diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 8fcfc71351..a859553b1b 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -349,6 +349,10 @@ for detailed changes. * New config option `config.action_dispatch.cookies_serializer` for specifying a serializer for the signed and encrypted cookie jars. (Pull Requests [1](https://github.com/rails/rails/pull/13692), [2](https://github.com/rails/rails/pull/13945) / [More Details](upgrading_ruby_on_rails.html#cookies-serializer)) +* Added `render :plain`, `render :html` and `render :body`. ([Pull Request](https://github.com/rails/rails/pull/14062) / + [More Details](upgrading_ruby_on_rails.html#rendering-content-from-string)) + + Action Mailer ------------- @@ -453,6 +457,12 @@ for detailed changes. ### Notable changes +* Default scopes are no longer overriden by chained conditions. + + Before this change when you defined a `default_scope` in a model + it was overriden by chained conditions in the same field. Now it + is merged like any other scope. [More Details](upgrading_ruby_on_rails.html#changes-on-default-scopes). + * Added `ActiveRecord::Base.to_param` for convenient "pretty" URLs derived from a model's attribute or method. ([Pull Request](https://github.com/rails/rails/pull/12891)) @@ -539,6 +549,9 @@ for detailed changes. * Make `change_column_null` revertable. ([Commit](https://github.com/rails/rails/commit/724509a9d5322ff502aefa90dd282ba33a281a96)) +* Added a flag to disable schema dump after migration. This is set to `false` + by defualt in the production environment for new applications. ([Pull Request](https://github.com/rails/rails/pull/13948)) + Active Model ------------ @@ -656,9 +669,8 @@ for detailed changes. [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling)) * Added `ActiveSupport::Testing::TimeHelpers#travel` and `#travel_to`. These - methods change current time to the given time or time difference by stubbing - `Time.now` and - `Date.today`. ([Pull Request](https://github.com/rails/rails/pull/12824)) + methods change current time to the given time or duration by stubbing + `Time.now` and `Date.today`. * Added `ActiveSupport::Testing::TimeHelpers#travel_back`. This method returns the current time to the original state, by removing the stubs added by `travel` @@ -697,6 +709,8 @@ for detailed changes. responsibilities within a class. ([Commit](https://github.com/rails/rails/commit/1eee0ca6de975b42524105a59e0521d18b38ab81)) +* Added `Object#present_in` to simplify value whitelisting. ([Commit](https://github.com/rails/rails/commit/4edca106daacc5a159289eae255207d160f22396)) + Credits ------- diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 222d86afe9..5b5f53c9be 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -1088,7 +1088,7 @@ class ApplicationController < ActionController::Base private def record_not_found - render text: "404 Not Found", status: 404 + render plain: "404 Not Found", status: 404 end end ``` diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index d164b08d93..4900f176a6 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -707,7 +707,7 @@ You can additionally unscope specific where clauses. For example: ```ruby Post.where(id: 10, trashed: false).unscope(where: :id) -# => SELECT "posts".* FROM "posts" WHERE trashed = 0 +# SELECT "posts".* FROM "posts" WHERE trashed = 0 ``` A relation which has used `unscope` will affect any relation it is @@ -715,7 +715,7 @@ merged in to: ```ruby Post.order('id asc').merge(Post.unscope(:order)) -# => SELECT "posts".* FROM "posts" +# SELECT "posts".* FROM "posts" ``` ### `only` @@ -1242,26 +1242,26 @@ class User < ActiveRecord::Base end User.active.inactive -# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive' +# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive' ``` We can mix and match `scope` and `where` conditions and the final sql -will have all conditions joined with `AND` . +will have all conditions joined with `AND`. ```ruby User.active.where(state: 'finished') -# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished' +# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished' ``` If we do want the `last where clause` to win then `Relation#merge` can -be used . +be used. ```ruby User.active.merge(User.inactive) -# => SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive' +# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive' ``` -One important caveat is that `default_scope` will be overridden by +One important caveat is that `default_scope` will be prepended in `scope` and `where` conditions. ```ruby @@ -1272,16 +1272,16 @@ class User < ActiveRecord::Base end User.all -# => SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' User.active -# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active' User.where(state: 'inactive') -# => SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive' +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive' ``` -As you can see above the `default_scope` is being overridden by both +As you can see above the `default_scope` is being merged in both `scope` and `where` conditions. diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 53d2a9b55b..a16b9ac8da 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -608,7 +608,7 @@ look like, change the `create` action to this: ```ruby def create - render text: params[:article].inspect + render plain: params[:article].inspect end ``` diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index 93e25d619e..66ed6f2e08 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -236,15 +236,34 @@ render inline: "xml.p {'Horrid coding practice!'}", type: :builder #### Rendering Text -You can send plain text - with no markup at all - back to the browser by using the `:text` option to `render`: +You can send plain text - with no markup at all - back to the browser by using +the `:plain` option to `render`: ```ruby -render text: "OK" +render plain: "OK" ``` -TIP: Rendering pure text is most useful when you're responding to Ajax or web service requests that are expecting something other than proper HTML. +TIP: Rendering pure text is most useful when you're responding to Ajax or web +service requests that are expecting something other than proper HTML. -NOTE: By default, if you use the `:text` option, the text is rendered without using the current layout. If you want Rails to put the text into the current layout, you need to add the `layout: true` option. +NOTE: By default, if you use the `:plain` option, the text is rendered without +using the current layout. If you want Rails to put the text into the current +layout, you need to add the `layout: true` option. + +#### Rendering HTML + +You can send a HTML string back to the browser by using the `:html` option to +`render`: + +```ruby +render html: "<strong>Not Found</strong>".html_safe +``` + +TIP: This is useful when you're rendering a small snippet of HTML code. +However, you might want to consider moving it to a template file if the markup +is complex. + +NOTE: This option will escape HTML entities if the string is not html safe. #### Rendering JSON @@ -276,6 +295,19 @@ render js: "alert('Hello Rails');" This will send the supplied string to the browser with a MIME type of `text/javascript`. +#### Rendering raw body + +You can send a raw content back to the browser, without setting any content +type, by using the `:body` option to `render`: + +```ruby +render body: "raw" +``` + +TIP: This option should be used only if you explicitly want the content type to +be unset. Using `:plain` or `:html` might be more appropriate in most of the +time. + #### Options for `render` Calls to the `render` method generally accept four options: diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 8aae3bbc1a..af3580a85b 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -262,6 +262,92 @@ authors = Author.where(name: 'Hank Moody').to_a authors.compact! ``` +### Changes on Default Scopes + +Default scopes are no longer overriden by chained conditions. + +In previous versions when you defined a `default_scope` in a model +it was overriden by chained conditions in the same field. Now it +is merged like any other scope. + +Before: + +```ruby +class User < ActiveRecord::Base + default_scope { where state: 'pending' } + scope :active, -> { where state: 'active' } + scope :inactive, -> { where state: 'inactive' } +end + +User.all +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' + +User.active +# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' + +User.where(state: 'inactive') +# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive' +``` + +After: + +```ruby +class User < ActiveRecord::Base + default_scope { where state: 'pending' } + scope :active, -> { where state: 'active' } + scope :inactive, -> { where state: 'inactive' } +end + +User.all +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' + +User.active +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active' + +User.where(state: 'inactive') +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive' +``` + +To get the previous behavior it is needed to explicitly remove the +`default_scope` condition using `unscoped`, `unscope`, `rewhere` or +`except`. + +```ruby +class User < ActiveRecord::Base + default_scope { where state: 'pending' } + scope :active, -> { unscope(where: :state).where(state: 'active') } + scope :inactive, -> { rewhere state: 'inactive' } +end + +User.all +# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' + +User.active +# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' + +User.inactive +# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive' +``` + +### Rendering content from string + +Rails 4.1 introduces `:plain`, `:html`, and `:body` options to `render`. Those +options are now the preferred way to render string-based content, as it allows +you to specify which content type you want the response sent as. + +* `render :plain` will set the content type to `text/plain` +* `render :html` will set the content type to `text/html` +* `render :body` will *not* set the content type header. + +From the security standpoint, if you don't expect to have any markup in your +response body, you should be using `render :plain` as most browsers will escape +unsafe content in the response for you. + +We will be deprecating the use of `render :text` in a future version. So please +start using the more precise `:plain:`, `:html`, and `:body` options instead. +Using `render :text` may pose a security risk, as the content is sent as +`text/html`. + Upgrading from Rails 3.2 to Rails 4.0 ------------------------------------- @@ -567,9 +653,8 @@ Active Record Observer and Action Controller Sweeper have been extracted to the ### sprockets-rails -* `assets:precompile:primary` has been removed. Use `assets:precompile` instead. -* The `config.assets.compress` option should be changed to -`config.assets.js_compressor` like so for instance: +* `assets:precompile:primary` and `assets:precompile:all` have been removed. Use `assets:precompile` instead. +* The `config.assets.compress` option should be changed to `config.assets.js_compressor` like so for instance: ```ruby config.assets.js_compressor = :uglifier @@ -577,7 +662,7 @@ config.assets.js_compressor = :uglifier ### sass-rails -* `asset-url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")` +* `asset-url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")`. Upgrading from Rails 3.1 to Rails 3.2 ------------------------------------- diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb index 923cab4e2a..e4fd798d18 100644 --- a/railties/lib/rails/version.rb +++ b/railties/lib/rails/version.rb @@ -3,7 +3,7 @@ module Rails MAJOR = 4 MINOR = 1 TINY = 0 - PRE = "beta1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/version.rb b/version.rb index 923cab4e2a..e4fd798d18 100644 --- a/version.rb +++ b/version.rb @@ -3,7 +3,7 @@ module Rails MAJOR = 4 MINOR = 1 TINY = 0 - PRE = "beta1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end |