diff options
78 files changed, 480 insertions, 193 deletions
@@ -44,7 +44,8 @@ platforms :ruby do gem "nokogiri", ">= 1.4.4" group :test do - gem "ruby-prof" + gem "ruby-prof" if RUBY_VERSION < "1.9.3" + end # AR gem "sqlite3", "~> 1.3.3" diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 479e8246c5..5314dcc193 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,36 @@ *Rails 3.2.0 (unreleased)* +* Make sure escape_js returns SafeBuffer string if it receives SafeBuffer string [Prem Sichanugrist] + +* Fix escape_js to work correctly with the new SafeBuffer restriction [Paul Gallagher] + +* Brought back alternative convention for namespaced models in i18n [thoefer] + + Now the key can be either "namespace.model" or "namespace/model" until further deprecation. + +* It is prohibited to perform a in-place SafeBuffer mutation [tenderlove] + + The old behavior of SafeBuffer allowed you to mutate string in place via + method like `sub!`. These methods can add unsafe strings to a safe buffer, + and the safe buffer will continue to be marked as safe. + + An example problem would be something like this: + + <%= link_to('hello world', @user).sub!(/hello/, params[:xss]) %> + + In the above example, an untrusted string (`params[:xss]`) is added to the + safe buffer returned by `link_to`, and the untrusted content is successfully + sent to the client without being escaped. To prevent this from happening + `sub!` and other similar methods will now raise an exception when they are called on a safe buffer. + + In addition to the in-place versions, some of the versions of these methods which return a copy of the string will incorrectly mark strings as safe. For example: + + <%= link_to('hello world', @user).sub(/hello/, params[:xss]) %> + + The new versions will now ensure that *all* strings returned by these methods on safe buffers are marked unsafe. + + You can read more about this change in http://groups.google.com/group/rubyonrails-security/browse_thread/thread/2e516e7acc96c4fb + * Refactor ActionController::TestCase cookies [Andrew White] Assigning cookies for test cases should now use cookies[], e.g: diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 0be04b70a1..2bdb23c4d7 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -109,7 +109,6 @@ module ActionController #:nodoc: def expire_fragment(key, options = nil) return unless cache_configured? key = fragment_cache_key(key) unless key.is_a?(Regexp) - message = nil instrument_fragment_cache :expire_fragment, key do if key.is_a?(Regexp) diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 6d0c163a9e..7420a5e7e9 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -423,7 +423,7 @@ module ActionController # Returns an Array of [String, Hash] if a token is present. # Returns nil if no token is found. def token_and_options(request) - if header = request.authorization.to_s[/^Token (.*)/] + if request.authorization.to_s[/^Token (.*)/] values = Hash[$1.split(',').map do |value| value.strip! # remove any spaces between commas and values key, value = value.split(/\=\"?/) # split key=value pairs diff --git a/actionpack/lib/action_dispatch/middleware/head.rb b/actionpack/lib/action_dispatch/middleware/head.rb index 56e2d2f2a8..f1906a3ab3 100644 --- a/actionpack/lib/action_dispatch/middleware/head.rb +++ b/actionpack/lib/action_dispatch/middleware/head.rb @@ -8,7 +8,7 @@ module ActionDispatch if env["REQUEST_METHOD"] == "HEAD" env["REQUEST_METHOD"] = "GET" env["rack.methodoverride.original_method"] = "HEAD" - status, headers, body = @app.call(env) + status, headers, _ = @app.call(env) [status, headers, []] else @app.call(env) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 6d6bde1449..70ea419e81 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -49,6 +49,9 @@ module ActionDispatch class Mapping #:nodoc: IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix] + ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} + SHORTHAND_REGEX = %r{^/[\w/]+$} + WILDCARD_PATH = %r{\*([^/]+)$} def initialize(set, scope, path, options) @set, @scope = set, scope @@ -77,18 +80,18 @@ module ActionDispatch # segment_keys.include?(k.to_s) || k == :controller next unless Regexp === requirement && !constraints[name] - if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} + if requirement.source =~ ANCHOR_CHARACTERS_REGEX raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" end if requirement.multiline? raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}" end end - end + end # match "account/overview" def using_match_shorthand?(path, options) - path && options.except(:via, :anchor, :to, :as).empty? && path =~ %r{^/[\w\/]+$} + path && options.except(:via, :anchor, :to, :as).empty? && path =~ SHORTHAND_REGEX end def normalize_path(path) @@ -107,7 +110,7 @@ module ActionDispatch # Add a constraint for wildcard route to make it non-greedy and match the # optional format part of the route by default - if path.match(/\*([^\/]+)$/) && @options[:format] != false + if path.match(WILDCARD_PATH) && @options[:format] != false @options.reverse_merge!(:"#{$1}" => /.+?/) end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 9791ba58e2..aae5752c93 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -207,9 +207,6 @@ module ActionDispatch "*/*;q=0.5" unless defined? @named_routes_configured - # install the named routes in this session instance. - klass = singleton_class - # the helpers are made protected by default--we make them public for # easier access during testing and troubleshooting. @named_routes_configured = true diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index e81d03b537..b57617b3d1 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -51,7 +51,13 @@ module ActionView # This dance is needed because Builder can't use capture pos = output_buffer.length yield - fragment = output_buffer.slice!(pos..-1) + if output_buffer.is_a?(ActionView::OutputBuffer) + safe_output_buffer = output_buffer.to_str + fragment = safe_output_buffer.slice!(pos..-1) + self.output_buffer = ActionView::OutputBuffer.new(safe_output_buffer) + else + fragment = output_buffer.slice!(pos..-1) + end controller.write_fragment(name, fragment, options) end end diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb index cd67851642..c0cc7d347c 100644 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ b/actionpack/lib/action_view/helpers/debug_helper.rb @@ -30,7 +30,7 @@ module ActionView begin Marshal::dump(object) "<pre class='debug_dump'>#{h(object.to_yaml).gsub(" ", " ")}</pre>".html_safe - rescue Exception => e # errors from Marshal or YAML + rescue Exception # errors from Marshal or YAML # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback "<code class='debug_dump'>#{h(object.inspect)}</code>".html_safe end diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index d7228bab67..4484390fde 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -18,7 +18,8 @@ module ActionView # $('some_element').replaceWith('<%=j render 'some/element_template' %>'); def escape_javascript(javascript) if javascript - javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] } + result = javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) {|match| JS_ESCAPE_MAP[match] } + javascript.html_safe? ? result.html_safe : result else '' end diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 4dafb7739a..fe0288521f 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -211,7 +211,7 @@ module ActionView defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {}) options = options.reverse_merge(defaults) - parts = number.to_s.split('.') + parts = number.to_s.to_str.split('.') parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}") parts.join(options[:separator]).html_safe diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 75422a343d..8f97eb7d75 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -258,11 +258,12 @@ module ActionView text = ''.html_safe if text.nil? start_tag = tag('p', html_options, true) text = sanitize(text) unless options[:sanitize] == false + text = text.to_str text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br text.insert 0, start_tag - text.html_safe.safe_concat("</p>") + text = ActiveSupport::SafeBuffer.new(text).safe_concat("</p>") end # Creates a Cycle object whose _to_s_ method cycles through elements of an diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 25411856cb..489b96856d 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -497,14 +497,14 @@ module ActionView }.compact extras = extras.empty? ? '' : '?' + ERB::Util.html_escape(extras.join('&')) - email_address_obfuscated = email_address.dup + email_address_obfuscated = email_address.to_str email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.key?("replace_at") email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.key?("replace_dot") case encode when "javascript" string = '' html = content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)) - html = escape_javascript(html) + html = escape_javascript(html.to_str) "document.write('#{html}');".each_byte do |c| string << sprintf("%%%x", c) end diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index a09cef8fef..d04c00fd40 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -64,7 +64,7 @@ module ActionView layout =~ /^\// ? with_fallbacks { find_template(layout, nil, false, keys) } : find_template(layout, nil, false, keys) end - rescue ActionView::MissingTemplate => e + rescue ActionView::MissingTemplate update_details(:formats => nil) do raise unless template_exists?(layout) end diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index d4448a7b33..587e37a84f 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -31,7 +31,6 @@ module ActionView def initialize(paths, path, prefixes, partial, details, *) @path = path prefixes = Array.wrap(prefixes) - display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ") template_type = if partial "partial" elsif path =~ /layouts/i diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb index 7b8a7ad3bb..4b497d142d 100644 --- a/actionpack/lib/sprockets/railtie.rb +++ b/actionpack/lib/sprockets/railtie.rb @@ -46,43 +46,48 @@ module Sprockets end protected + def asset_environment(app) + require "sprockets" - def asset_environment(app) - require "sprockets" - assets = app.config.assets - env = Sprockets::Environment.new(app.root.to_s) - env.static_root = File.join(app.root.join("public"), assets.prefix) - env.paths.concat assets.paths - env.logger = Rails.logger - env.js_compressor = expand_js_compressor(assets.js_compressor) if assets.compress - env.css_compressor = expand_css_compressor(assets.css_compressor) if assets.compress - env - end + assets = app.config.assets + + env = Sprockets::Environment.new(app.root.to_s) + + env.static_root = File.join(app.root.join("public"), assets.prefix) + env.paths.concat assets.paths + + env.logger = Rails.logger + + env.js_compressor = expand_js_compressor(assets.js_compressor) + env.css_compressor = expand_css_compressor(assets.css_compressor) - def expand_js_compressor(sym) - case sym - when :closure - require 'closure-compiler' - Closure::Compiler.new - when :uglifier - require 'uglifier' - Uglifier.new - when :yui - require 'yui/compressor' - YUI::JavaScriptCompressor.new - else - sym + env end - end - def expand_css_compressor(sym) - case sym - when :yui - require 'yui/compressor' - YUI::CssCompressor.new - else - sym + def expand_js_compressor(sym) + case sym + when :closure + require 'closure-compiler' + Closure::Compiler.new + when :uglifier + require 'uglifier' + Uglifier.new + when :yui + require 'yui/compressor' + YUI::JavaScriptCompressor.new + else + sym + end + end + + def expand_css_compressor(sym) + case sym + when :yui + require 'yui/compressor' + YUI::CssCompressor.new + else + sym + end end - end end end diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb index 981c023d38..53712a60ec 100644 --- a/actionpack/test/abstract/abstract_controller_test.rb +++ b/actionpack/test/abstract/abstract_controller_test.rb @@ -178,12 +178,6 @@ module AbstractController end end - class Me5 < WithLayouts - def index - render - end - end - class TestLayouts < ActiveSupport::TestCase test "layouts are included" do controller = Me4.new @@ -241,11 +235,11 @@ module AbstractController assert_dispatch ActionMissingRespondToActionController, "success", :ohai end - test "a method is available as an action if respond_to_action? returns true" do + test "a method is available as an action if method_for_action returns true" do assert_dispatch RespondToActionController, "success", :index end - test "raises ActionNotFound if method is defined but respond_to_action? returns false" do + test "raises ActionNotFound if method is defined but method_for_action returns false" do assert_raise(ActionNotFound) { RespondToActionController.new.process(:fail) } end end diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb index 3bdde86291..5d1a703c55 100644 --- a/actionpack/test/abstract/callbacks_test.rb +++ b/actionpack/test/abstract/callbacks_test.rb @@ -131,7 +131,7 @@ module AbstractController end def authenticate - @list = [] + @list ||= [] @authenticated = "true" end end diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb index 5ed6aa68b5..86208899f8 100644 --- a/actionpack/test/abstract/layouts_test.rb +++ b/actionpack/test/abstract/layouts_test.rb @@ -87,18 +87,6 @@ module AbstractControllerTests end end - class WithSymbolReturningString < Base - layout :no_hello - - def index - render :template => ActionView::Template::Text.new("Hello missing symbol!") - end - private - def no_hello - nil - end - end - class WithSymbolReturningNil < Base layout :nilz diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 60534a9746..24d071df39 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -151,6 +151,7 @@ class BasicController config.assets_dir = public_dir config.javascripts_dir = "#{public_dir}/javascripts" config.stylesheets_dir = "#{public_dir}/stylesheets" + config.assets = ActiveSupport::InheritableOptions.new({ :prefix => "assets" }) config end end diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 7f3d943bba..a714e8bbcc 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -100,9 +100,6 @@ class AssertResponseWithUnexpectedErrorController < ActionController::Base end end -class UserController < ActionController::Base -end - module Admin class InnerModuleController < ActionController::Base def index diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 82c2c23607..da3314fe6d 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -127,7 +127,7 @@ class PageCachingTest < ActionController::TestCase assert_equal 'I am xml', @response.body end - def test_should_cache_with_trailing_slash_on_url + def test_cached_page_should_not_have_trailing_slash_even_if_url_has_trailing_slash @controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash/' assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html") end @@ -141,7 +141,7 @@ class PageCachingTest < ActionController::TestCase [:ok, :no_content, :found, :not_found].each do |status| [:get, :post, :put, :delete].each do |method| - unless method == :get and status == :ok + unless method == :get && status == :ok define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do send(method, status) assert_response status diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb index b12c798302..d51882066d 100644 --- a/actionpack/test/controller/content_type_test.rb +++ b/actionpack/test/controller/content_type_test.rb @@ -74,6 +74,7 @@ class ContentTypeTest < ActionController::TestCase get :render_defaults assert_equal "utf-16", @response.charset assert_equal Mime::HTML, @response.content_type + ensure OldContentTypeController.default_charset = "utf-8" end diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb index 31ce97a25b..904398f563 100644 --- a/actionpack/test/dispatch/session/test_session_test.rb +++ b/actionpack/test/dispatch/session/test_session_test.rb @@ -29,7 +29,6 @@ class ActionController::TestSessionTest < ActiveSupport::TestCase end def test_clear_emptys_session - params = {:one => 'one', :two => 'two'} session = ActionController::TestSession.new({:one => 'one', :two => 'two'}) session.clear assert_nil(session[:one]) diff --git a/actionpack/test/template/erb_util_test.rb b/actionpack/test/template/erb_util_test.rb index 30f6d1a213..790ab1c74c 100644 --- a/actionpack/test/template/erb_util_test.rb +++ b/actionpack/test/template/erb_util_test.rb @@ -16,6 +16,16 @@ class ErbUtilTest < Test::Unit::TestCase end end + def test_json_escape_returns_unsafe_strings_when_passed_unsafe_strings + value = json_escape("asdf") + assert !value.html_safe? + end + + def test_json_escape_returns_safe_strings_when_passed_safe_strings + value = json_escape("asdf".html_safe) + assert value.html_safe? + end + def test_html_escape_is_html_safe escaped = h("<p>") assert_equal "<p>", escaped diff --git a/actionpack/test/template/html-scanner/document_test.rb b/actionpack/test/template/html-scanner/document_test.rb index ddfb351595..3db2fba783 100644 --- a/actionpack/test/template/html-scanner/document_test.rb +++ b/actionpack/test/template/html-scanner/document_test.rb @@ -123,7 +123,7 @@ HTML def test_parse_invalid_document assert_nothing_raised do - doc = HTML::Document.new("<html> + HTML::Document.new("<html> <table> <tr> <td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td> @@ -135,7 +135,7 @@ HTML def test_invalid_document_raises_exception_when_strict assert_raise RuntimeError do - doc = HTML::Document.new("<html> + HTML::Document.new("<html> <table> <tr> <td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td> diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index 538e0e9874..dd8b7b7cd5 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -30,6 +30,15 @@ class JavaScriptHelperTest < ActionView::TestCase assert_equal %(dont <\\/close> tags), j(%(dont </close> tags)) end + def test_escape_javascript_with_safebuffer + given = %('quoted' "double-quoted" new-line:\n </closed>) + expect = %(\\'quoted\\' \\"double-quoted\\" new-line:\\n <\\/closed>) + assert_equal expect, escape_javascript(given) + assert_equal expect, escape_javascript(ActiveSupport::SafeBuffer.new(given)) + assert_instance_of String, escape_javascript(given) + assert_instance_of ActiveSupport::SafeBuffer, escape_javascript(ActiveSupport::SafeBuffer.new(given)) + end + def test_button_to_function assert_dom_equal %(<input type="button" onclick="alert('Hello world!');" value="Greeting" />), button_to_function("Greeting", "alert('Hello world!')") diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 0104c20bc7..0e3475d98b 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -283,33 +283,40 @@ class NumberHelperTest < ActionView::TestCase assert number_to_human(1).html_safe? assert !number_to_human("<script></script>").html_safe? assert number_to_human("asdf".html_safe).html_safe? + assert number_to_human("1".html_safe).html_safe? assert number_to_human_size(1).html_safe? assert number_to_human_size(1000000).html_safe? assert !number_to_human_size("<script></script>").html_safe? assert number_to_human_size("asdf".html_safe).html_safe? + assert number_to_human_size("1".html_safe).html_safe? assert number_with_precision(1, :strip_insignificant_zeros => false).html_safe? assert number_with_precision(1, :strip_insignificant_zeros => true).html_safe? assert !number_with_precision("<script></script>").html_safe? assert number_with_precision("asdf".html_safe).html_safe? + assert number_with_precision("1".html_safe).html_safe? assert number_to_currency(1).html_safe? assert !number_to_currency("<script></script>").html_safe? assert number_to_currency("asdf".html_safe).html_safe? + assert number_to_currency("1".html_safe).html_safe? assert number_to_percentage(1).html_safe? assert !number_to_percentage("<script></script>").html_safe? assert number_to_percentage("asdf".html_safe).html_safe? + assert number_to_percentage("1".html_safe).html_safe? assert number_to_phone(1).html_safe? assert_equal "<script></script>", number_to_phone("<script></script>") assert number_to_phone("<script></script>").html_safe? assert number_to_phone("asdf".html_safe).html_safe? + assert number_to_phone("1".html_safe).html_safe? assert number_with_delimiter(1).html_safe? assert !number_with_delimiter("<script></script>").html_safe? assert number_with_delimiter("asdf".html_safe).html_safe? + assert number_with_delimiter("1".html_safe).html_safe? end def test_number_helpers_should_raise_error_if_invalid_when_specified diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb index 74d7bba4fe..1ba14e8bc9 100644 --- a/actionpack/test/template/record_tag_helper_test.rb +++ b/actionpack/test/template/record_tag_helper_test.rb @@ -48,14 +48,12 @@ class RecordTagHelperTest < ActionView::TestCase end def test_block_works_with_content_tag_for_in_erb - __in_erb_template = '' expected = %(<tr class="post" id="post_45">#{@post.body}</tr>) actual = content_tag_for(:tr, @post) { concat @post.body } assert_dom_equal expected, actual end def test_div_for_in_erb - __in_erb_template = '' expected = %(<div class="post bar" id="post_45">#{@post.body}</div>) actual = div_for(@post, :class => "bar") { concat @post.body } assert_dom_equal expected, actual diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 86d08a43a5..4187a0ac78 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -370,7 +370,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase def test_render_utf8_template_with_incompatible_external_encoding with_external_encoding Encoding::SHIFT_JIS do begin - result = @view.render(:file => "test/utf8.html.erb", :layouts => "layouts/yield") + @view.render(:file => "test/utf8.html.erb", :layouts => "layouts/yield") flunk 'Should have raised incompatible encoding error' rescue ActionView::Template::Error => error assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message @@ -381,7 +381,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase def test_render_utf8_template_with_partial_with_incompatible_encoding with_external_encoding Encoding::SHIFT_JIS do begin - result = @view.render(:file => "test/utf8_magic_with_bare_partial.html.erb", :layouts => "layouts/yield") + @view.render(:file => "test/utf8_magic_with_bare_partial.html.erb", :layouts => "layouts/yield") flunk 'Should have raised incompatible encoding error' rescue ActionView::Template::Error => error assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb index 81fb34b80f..b0ca7de0b6 100644 --- a/actionpack/test/template/template_test.rb +++ b/actionpack/test/template/template_test.rb @@ -153,7 +153,6 @@ class TestERBTemplate < ActiveSupport::TestCase def test_encoding_can_be_specified_with_magic_comment_in_erb with_external_encoding Encoding::UTF_8 do @template = new_template("<%# encoding: ISO-8859-1 %>hello \xFCmlat", :virtual_path => nil) - result = render assert_equal Encoding::UTF_8, render.encoding assert_equal "hello \u{fc}mlat", render end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 740f577a6e..5a43b5f864 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -291,7 +291,7 @@ class TextHelperTest < ActionView::TestCase end def test_cycle_class_with_no_arguments - assert_raise(ArgumentError) { value = Cycle.new() } + assert_raise(ArgumentError) { Cycle.new } end def test_cycle @@ -304,7 +304,7 @@ class TextHelperTest < ActionView::TestCase end def test_cycle_with_no_arguments - assert_raise(ArgumentError) { value = cycle() } + assert_raise(ArgumentError) { cycle } end def test_cycle_resets_with_new_values diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 4c1a82f413..c7b9c41f46 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -21,7 +21,7 @@ module ActiveModel @partial_path = "#{@collection}/#{@element}".freeze @param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural).freeze - @i18n_key = self.underscore.to_sym + @i18n_key = self.underscore.tr('/', '.').to_sym end # Transform the model name into a more humane format, using I18n. By default, @@ -35,8 +35,9 @@ module ActiveModel @klass.respond_to?(:i18n_scope) defaults = @klass.lookup_ancestors.map do |klass| - klass.model_name.i18n_key - end + [klass.model_name.i18n_key, + klass.model_name.i18n_key.to_s.tr('.', '/').to_sym] + end.flatten defaults << options[:default] if options[:default] defaults << @human diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb index 6d64c81b5f..c615311692 100644 --- a/activemodel/lib/active_model/translation.rb +++ b/activemodel/lib/active_model/translation.rb @@ -44,8 +44,9 @@ module ActiveModel # Specify +options+ with additional translating options. def human_attribute_name(attribute, options = {}) defaults = lookup_ancestors.map do |klass| - :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}" - end + [:"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}", + :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key.to_s.tr('.', '/')}.#{attribute}"] + end.flatten defaults << :"attributes.#{attribute}" defaults << options.delete(:default) if options[:default] diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb index 1b1d972d5c..838956bc98 100644 --- a/activemodel/test/cases/translation_test.rb +++ b/activemodel/test/cases/translation_test.rb @@ -76,5 +76,15 @@ class ActiveModelI18nTests < ActiveModel::TestCase Person.model_name.human(options) assert_equal({:default => 'person model'}, options) end + + def test_alternate_namespaced_model_attribute_translation + I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:gender => {:attribute => 'person gender attribute'}}}} + assert_equal 'person gender attribute', Person::Gender.human_attribute_name('attribute') + end + + def test_alternate_namespaced_model_translation + I18n.backend.store_translations 'en', :activemodel => {:models => {:person => {:gender => 'person gender model'}}} + assert_equal 'person gender model', Person::Gender.model_name.human + end end diff --git a/activerecord/RUNNING_UNIT_TESTS b/activerecord/RUNNING_UNIT_TESTS index 16444249cc..8fe9a357b4 100644 --- a/activerecord/RUNNING_UNIT_TESTS +++ b/activerecord/RUNNING_UNIT_TESTS @@ -9,24 +9,26 @@ You can build postgres and mysql databases using the build_postgresql and build_ You can run a particular test file from the command line, e.g. - $ ruby test/cases/base_test.rb + $ ruby -Itest test/cases/base_test.rb To run a specific test: - $ ruby test/cases/base_test.rb -n test_something_works + $ ruby -Itest test/cases/base_test.rb -n test_something_works You can run with a database other than the default you set in test/config.yml, using the ARCONN environment variable: - $ ARCONN=postgresql ruby test/cases/base_test.rb + $ ARCONN=postgresql ruby -Itest test/cases/base_test.rb You can run all the tests for a given database via rake: -To setup the testing environment for PostgreSQL use this command: + $ rake test_mysql - rake postgresql:build_databases +The 'rake test' task will run all the tests for mysql, mysql2, sqlite3 and postgresql. -The incantation for running a particular test looks like this +== Identity Map - rake test TEST=test/cases/datatype_test_postgresql.rb TESTOPTS="--name=test_timestamp_with_zone_values_without_rails_time_zone_support" +By default the tests run with the Identity Map turned off. But all tests should pass whether or +not the identity map is on or off. You can turn it on using the IM env variable: + $ IM=true ruby -Itest test/case/base_test.rb diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 687b668634..c3aee90124 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -141,7 +141,7 @@ module ActiveRecord @target ||= find_target end end - loaded! + loaded! unless loaded? target rescue ActiveRecord::RecordNotFound reset @@ -177,9 +177,7 @@ module ActiveRecord # Sets the owner attributes on the given record def set_owner_attributes(record) - if owner.persisted? - creation_attributes.each { |key, value| record[key] = value } - end + creation_attributes.each { |key, value| record[key] = value } end # Should be true if there is a foreign key present on the owner which diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 81b4a26b04..5267116c42 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -67,7 +67,7 @@ module ActiveRecord def method_missing(method, *args, &block) match = DynamicFinderMatch.match(method) if match && match.instantiator? - record = send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r| + send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r| @association.send :set_owner_attributes, r @association.send :add_to_target, r yield(r) if block_given? diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 2f3a6e71f1..6b8201973a 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -14,8 +14,8 @@ module ActiveRecord end if record - set_inverse_instance(record) set_owner_attributes(record) + set_inverse_instance(record) if owner.persisted? && save && !record.save nullify_owner_attributes(record) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 08cc282d09..2f283ff6bc 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -659,8 +659,8 @@ module ActiveRecord #:nodoc: def set_table_name(value = nil, &block) @quoted_table_name = nil define_attr_method :table_name, value, &block + @arel_table = nil - @arel_table = Arel::Table.new(table_name, arel_engine) @relation = Relation.new(self, arel_table) end alias :table_name= :set_table_name @@ -892,7 +892,7 @@ module ActiveRecord #:nodoc: end def arel_table - Arel::Table.new(table_name, arel_engine) + @arel_table ||= Arel::Table.new(table_name, arel_engine) end def arel_engine @@ -2006,7 +2006,7 @@ MSG set_values = (1..3).collect{|position| values_hash_from_param[position].blank? ? 1 : values_hash_from_param[position]} begin Date.new(*set_values) - rescue ArgumentError => ex # if Date.new raises an exception on an invalid date + rescue ArgumentError # if Date.new raises an exception on an invalid date instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 8d7dd8bacf..9e6cb13cca 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -707,7 +707,7 @@ module ActiveRecord def quoted_columns_for_index(column_names, options = {}) length = options[:length] if options.is_a?(Hash) - quoted_column_names = case length + case length when Hash column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) } when Fixnum diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 6f8f84d50b..6ef24a4eaf 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -498,7 +498,7 @@ namespace :railties do # desc "Copies missing migrations from Railties (e.g. plugins, engines). You can specify Railties to use with FROM=railtie1,railtie2" task :migrations => :'db:load_config' do to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip } - railties = {} + railties = ActiveSupport::OrderedHash.new Rails.application.railties.all do |railtie| next unless to_load == :all || to_load.include?(railtie.railtie_name) diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index 752b864818..3a9744e78f 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -87,8 +87,8 @@ class MysqlReservedWordTest < ActiveRecord::TestCase assert_nothing_raised { x.save } x.order = 'y' assert_nothing_raised { x.save } - assert_nothing_raised { y = Group.find_by_order('y') } - assert_nothing_raised { y = Group.find(1) } + assert_nothing_raised { Group.find_by_order('y') } + assert_nothing_raised { Group.find(1) } x = Group.find(1) end diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index b59ce4efeb..4ce8b85098 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -139,7 +139,21 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_set_polymorphic_has_one tagging = tags(:misc).taggings.create posts(:thinking).tagging = tagging - assert_equal "Post", tagging.taggable_type + + assert_equal "Post", tagging.taggable_type + assert_equal posts(:thinking).id, tagging.taggable_id + assert_equal posts(:thinking), tagging.taggable + end + + def test_set_polymorphic_has_one_on_new_record + tagging = tags(:misc).taggings.create + post = Post.new :title => "foo", :body => "bar" + post.tagging = tagging + post.save! + + assert_equal "Post", tagging.taggable_type + assert_equal post.id, tagging.taggable_id + assert_equal post, tagging.taggable end def test_create_polymorphic_has_many_with_scope @@ -708,8 +722,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_with_pluralize_table_names_false - engine = Engine.create!(:car_id => 1) - aircraft = Aircraft.create!(:name => "Airbus 380", :id => 1) + aircraft = Aircraft.create!(:name => "Airbus 380") + engine = Engine.create!(:car_id => aircraft.id) assert_equal aircraft.engines, [engine] end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 110f6b97c6..d0dc9cb03d 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -12,19 +12,20 @@ require 'active_support/dependencies' require 'support/config' require 'support/connection' -ARTest.connect - # TODO: Move all these random hacks into the ARTest namespace and into the support/ dir # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true -# Quote "type" if it's a reserved word for the current connection. -QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') - # Enable Identity Map only when ENV['IM'] is set to "true" ActiveRecord::IdentityMap.enabled = (ENV['IM'] == "true") +# Connect to the database +ARTest.connect + +# Quote "type" if it's a reserved word for the current connection. +QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') + def current_adapter?(*types) types.any? do |type| ActiveRecord::ConnectionAdapters.const_defined?(type) && diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb index fcd2e4dee5..a39794fa39 100644 --- a/activerecord/test/support/connection.rb +++ b/activerecord/test/support/connection.rb @@ -11,7 +11,7 @@ module ARTest end def self.connect - puts "Using #{connection_name}" + puts "Using #{connection_name} with Identity Map #{ActiveRecord::IdentityMap.enabled? ? 'on' : 'off'}" ActiveRecord::Base.logger = Logger.new("debug.log") ActiveRecord::Base.configurations = connection_config ActiveRecord::Base.establish_connection 'arunit' diff --git a/activeresource/test/cases/base/load_test.rb b/activeresource/test/cases/base/load_test.rb index d6b04cfaa8..0d030148d0 100644 --- a/activeresource/test/cases/base/load_test.rb +++ b/activeresource/test/cases/base/load_test.rb @@ -72,7 +72,6 @@ class BaseLoadTest < Test::Unit::TestCase def test_after_load_attributes_are_accessible_via_indifferent_access assert_equal Hash.new, @person.attributes - matz_attributes = @matz.values.first assert_equal @matz.stringify_keys, @person.load(@matz).attributes assert_equal @matz[:name], @person.attributes['name'] assert_equal @matz[:name], @person.attributes[:name] diff --git a/activeresource/test/cases/finder_test.rb b/activeresource/test/cases/finder_test.rb index 9c51f2a390..5fbbfeef6e 100644 --- a/activeresource/test/cases/finder_test.rb +++ b/activeresource/test/cases/finder_test.rb @@ -95,7 +95,7 @@ class FinderTest < Test::Unit::TestCase def test_find_all_sub_objects_not_found assert_nothing_raised do - addys = StreetAddress.find(:all, :params => { :person_id => 2 }) + StreetAddress.find(:all, :params => { :person_id => 2 }) end end diff --git a/activeresource/test/cases/log_subscriber_test.rb b/activeresource/test/cases/log_subscriber_test.rb index b9143f5b67..ab5c22a783 100644 --- a/activeresource/test/cases/log_subscriber_test.rb +++ b/activeresource/test/cases/log_subscriber_test.rb @@ -23,7 +23,7 @@ class LogSubscriberTest < ActiveSupport::TestCase end def test_request_notification - matz = Person.find(1) + Person.find(1) wait assert_equal 2, @logger.logged(:info).size assert_equal "GET http://37s.sunrise.i:3000/people/1.json", @logger.logged(:info)[0] diff --git a/activeresource/test/cases/observing_test.rb b/activeresource/test/cases/observing_test.rb index ca3ab5d03d..58d3d389ff 100644 --- a/activeresource/test/cases/observing_test.rb +++ b/activeresource/test/cases/observing_test.rb @@ -37,7 +37,7 @@ class ObservingTest < Test::Unit::TestCase end def test_create_fires_save_and_create_notifications - rick = Person.create(:name => 'Rick') + Person.create(:name => 'Rick') assert_equal [:before_save, :before_create, :after_create, :after_save], self.history end diff --git a/activesupport/lib/active_support/core_ext/date/freeze.rb b/activesupport/lib/active_support/core_ext/date/freeze.rb index 4edd715ba2..a731f8345e 100644 --- a/activesupport/lib/active_support/core_ext/date/freeze.rb +++ b/activesupport/lib/active_support/core_ext/date/freeze.rb @@ -5,7 +5,7 @@ # first call will result in a frozen object error since the memo # instance variable is uninitialized. # -# Work around by eagerly memoizing before freezing. +# Work around by eagerly memoizing before the first freeze. # # Ruby 1.9 uses a preinitialized instance variable so it's unaffected. # This hack is as close as we can get to feature detection: @@ -17,9 +17,11 @@ if RUBY_VERSION < '1.9' if frozen_object_error.message =~ /frozen/ class Date #:nodoc: def freeze - self.class.private_instance_methods(false).each do |m| - if m.to_s =~ /\A__\d+__\Z/ - instance_variable_set(:"@#{m}", [send(m)]) + unless frozen? + self.class.private_instance_methods(false).each do |m| + if m.to_s =~ /\A__\d+__\Z/ + instance_variable_set(:"@#{m}", [send(m)]) + end end end diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 3a7652f5bf..1777a4b32d 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -113,7 +113,7 @@ class Module raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method." end - prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_" || '' + prefix = options[:prefix] ? "#{options[:prefix] == true ? to : options[:prefix]}_" : '' file, line = caller.first.split(':', 2) line = line.to_i diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index c27cbc37c5..20e40fe40f 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -51,7 +51,8 @@ class ERB # <%=j @person.to_json %> # def json_escape(s) - s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] } + result = s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] } + s.html_safe? ? result.html_safe : result end alias j json_escape @@ -74,6 +75,7 @@ end module ActiveSupport #:nodoc: class SafeBuffer < String + UNSAFE_STRING_METHODS = ["capitalize", "chomp", "chop", "delete", "downcase", "gsub", "lstrip", "next", "reverse", "rstrip", "slice", "squeeze", "strip", "sub", "succ", "swapcase", "tr", "tr_s", "upcase"].freeze alias safe_concat concat def concat(value) @@ -110,6 +112,18 @@ module ActiveSupport #:nodoc: to_str.to_yaml(*args) end + + UNSAFE_STRING_METHODS.each do |unsafe_method| + class_eval <<-EOT, __FILE__, __LINE__ + def #{unsafe_method}(*args) + super.to_str + end + + def #{unsafe_method}!(*args) + raise TypeError, "Cannot modify SafeBuffer in place" + end + EOT + end end end diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index d81693209f..b4f848cd44 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -454,4 +454,10 @@ class DateExtBehaviorTest < Test::Unit::TestCase Date.today.freeze.inspect end end + + def test_can_freeze_twice + assert_nothing_raised do + Date.today.freeze.freeze + end + end end diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index a4e2acbb32..3a9854358c 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -60,4 +60,16 @@ class SafeBufferTest < ActiveSupport::TestCase yaml = YAML.dump data assert_equal({'str' => str}, YAML.load(yaml)) end + + test "Should not return safe buffer from gsub" do + altered_buffer = @buffer.gsub('', 'asdf') + assert_equal 'asdf', altered_buffer + assert !altered_buffer.html_safe? + end + + test "Should not allow gsub! on safe buffers" do + assert_raise TypeError do + @buffer.gsub!('', 'asdf') + end + end end diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index ee5a20c789..5bd995aa32 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -50,7 +50,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase def test_expression_is_evaluated_in_the_appropriate_scope silence_warnings do - local_scope = 'foo' + local_scope = local_scope = 'foo' assert_difference('local_scope; @object.num') { @object.increment } end end diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 5a42ac0411..a2b2af98a6 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -105,14 +105,6 @@ module Rails self end - def load_generators(app=self) - initialize_generators - railties.all { |r| r.load_generators(app) } - Rails::Generators.configure!(app.config.generators) - super - self - end - def load_console(app=self) initialize_console railties.all { |r| r.load_console(app) } @@ -199,10 +191,6 @@ module Rails end end - def initialize_generators - require "rails/generators" - end - def initialize_console require "pp" require "rails/console/app" diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index f22b4ef1f1..5e55aeada9 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -23,11 +23,7 @@ when 'generate', 'destroy', 'plugin' require APP_PATH Rails.application.require_environment! - if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) - Rails.application.load_generators(engine) - else - Rails.application.load_generators - end + Rails.application.load_generators require "rails/commands/#{command}" end diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb index b6f9a003d1..1fb2d98834 100644 --- a/railties/lib/rails/commands/generate.rb +++ b/railties/lib/rails/commands/generate.rb @@ -7,4 +7,6 @@ if ARGV.first.in?([nil, "-h", "--help"]) end name = ARGV.shift -Rails::Generators.invoke name, ARGV, :behavior => :invoke, :destination_root => Rails.root + +root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root +Rails::Generators.invoke name, ARGV, :behavior => :invoke, :destination_root => root diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index f8b00e7249..e8cc5d9e3b 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -4,6 +4,10 @@ require 'rbconfig' options = { :environment => (ENV['RAILS_ENV'] || "development").dup } code_or_file = nil +if ARGV.first.nil? + ARGV.push "-h" +end + ARGV.clone.options do |opts| script_name = File.basename($0) opts.banner = "Usage: runner [options] ('Some.ruby(code)' or a filename)" diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 383be1802f..b358de89d0 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -330,6 +330,14 @@ module Rails autoload :Configuration, "rails/engine/configuration" autoload :Railties, "rails/engine/railties" + def load_generators(app=self) + initialize_generators + railties.all { |r| r.load_generators(app) } + Rails::Generators.configure!(app.config.generators) + super + self + end + class << self attr_accessor :called_from, :isolated alias :isolated? :isolated @@ -567,6 +575,10 @@ module Rails protected + def initialize_generators + require "rails/generators" + end + def routes? defined?(@routes) end diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb new file mode 100644 index 0000000000..3b0920e213 --- /dev/null +++ b/railties/lib/rails/engine/commands.rb @@ -0,0 +1,38 @@ +require 'active_support/core_ext/object/inclusion' + +ARGV << '--help' if ARGV.empty? + +aliases = { + "g" => "generate" +} + +command = ARGV.shift +command = aliases[command] || command + +require ENGINE_PATH +engine = ::Rails::Engine.find(ENGINE_ROOT) + +case command +when 'generate', 'destroy' + require 'rails/generators' + Rails::Generators.namespace = engine.railtie_namespace + engine.load_generators + require "rails/commands/#{command}" + +when '--version', '-v' + ARGV.unshift '--version' + require 'rails/commands/application' + +else + puts "Error: Command not recognized" unless command.in?(['-h', '--help']) + puts <<-EOT +Usage: rails COMMAND [ARGS] + +The common rails commands available for engines are: + generate Generate new code (short-cut alias: "g") + destroy Undo code generated with "generate" + +All commands can be run with -h for more information. + EOT + exit(1) +end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 09e505a75b..355b05ce0b 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -20,6 +20,8 @@ module Rails autoload :ResourceHelpers, 'rails/generators/resource_helpers' autoload :TestCase, 'rails/generators/test_case' + mattr_accessor :namespace + DEFAULT_ALIASES = { :rails => { :actions => '-a', diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 7972c72c1e..f07e090c25 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -188,13 +188,19 @@ module Rails end def bundle_command(command) - require 'bundler' - require 'bundler/cli' - say_status :run, "bundle #{command}" - Bundler::CLI.new.send(command) - rescue - say_status :failure, "bundler raised an exception, are you offline?", :red + + # We are going to shell out rather than invoking Bundle::CLI.new(command) + # because `rails new` loads the Thor gem and on the other hand bundler uses + # its own vendored Thor, which could be a different version. Running both + # things in the same process is a recipe for a night with paracetamol. + # + # We use backticks and #print here instead of vanilla #system because it + # is easier to silence stdout in the existing test suite this way. The + # end-user gets the bundler commands called anyway, so no big deal. + # + # Thanks to James Tucker for the Gem tricks involved in this call. + print `"#{Gem.ruby}" -rubygems "#{Gem.bin_path('bundler', 'bundle')}" #{command}` end def run_bundle diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 7e7f8d2d08..c6c0392f43 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -63,9 +63,7 @@ module Rails end def namespace - @namespace ||= if defined?(Rails) && Rails.application - Rails.application.class.parents.detect { |n| n.respond_to?(:_railtie) } - end + Rails::Generators.namespace end def namespaced? diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 11867a4cd7..807350316c 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -126,6 +126,8 @@ task :default => :test end def script(force = false) + return unless full? + directory "script", :force => force do |content| "#{shebang}\n" + content end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt b/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt index 65d82abf6d..aa87d1b50c 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt +++ b/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt @@ -1,4 +1,7 @@ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. -ENGINE_PATH = File.expand_path('../..', __FILE__) -load File.expand_path('../../<%= dummy_path %>/script/rails', __FILE__) +ENGINE_ROOT = File.expand_path('../..', __FILE__) +ENGINE_PATH = File.expand_path('../../lib/<%= name -%>/engine', __FILE__) + +require 'rails/all' +require 'rails/engine/commands' diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 65c567d72f..5f50943626 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -1,6 +1,7 @@ require 'rails/initializable' require 'rails/configuration' require 'active_support/inflector' +require 'active_support/core_ext/module/introspection' module Rails # Railtie is the core of the Rails framework and provides several hooks to extend @@ -192,5 +193,9 @@ module Rails def load_generators(app) self.class.generators.each { |block| block.call(app) } end + + def railtie_namespace + @railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:_railtie) } + end end end diff --git a/railties/railties.gemspec b/railties/railties.gemspec index f1c92bd5d4..9c535f4f90 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -20,6 +20,7 @@ Gem::Specification.new do |s| s.add_dependency('rake', '>= 0.8.7') s.add_dependency('thor', '~> 0.14.6') s.add_dependency('rack-ssl', '~> 1.3.2') + s.add_dependency('rdoc', '~> 3.4') s.add_dependency('activesupport', version) s.add_dependency('actionpack', version) end diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb index 8b38081667..1c3f8a701a 100644 --- a/railties/test/abstract_unit.rb +++ b/railties/test/abstract_unit.rb @@ -10,7 +10,6 @@ require 'active_support/core_ext/logger' require 'action_controller' require 'rails/all' -# TODO: Remove these hacks module TestApp class Application < Rails::Application config.root = File.dirname(__FILE__) diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb index 4104bacae5..4468fa295e 100644 --- a/railties/test/application/runner_test.rb +++ b/railties/test/application/runner_test.rb @@ -22,6 +22,10 @@ module ApplicationTests teardown_app end + def test_should_include_runner_in_shebang_line_in_help_without_option + assert_match "/rails runner", Dir.chdir(app_path) { `bundle exec rails runner` } + end + def test_should_include_runner_in_shebang_line_in_help assert_match "/rails runner", Dir.chdir(app_path) { `bundle exec rails runner --help` } end diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index 17cbac0912..dd1e4bdac1 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -7,15 +7,7 @@ require 'rails/generators/rails/scaffold/scaffold_generator' class NamespacedGeneratorTestCase < Rails::Generators::TestCase def setup - TestApp::Application.isolate_namespace(TestApp) - end - - def teardown - if TestApp.respond_to?(:_railtie) - TestApp.singleton_class.send(:undef_method, :_railtie) - TestApp.singleton_class.send(:undef_method, :table_name_prefix) - TestApp::Application.isolated = false - end + Rails::Generators.namespace = TestApp end end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index b272638026..283d99dd9e 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -12,7 +12,6 @@ DEFAULT_PLUGIN_FILES = %w( lib lib/bukkits.rb lib/tasks/bukkits_tasks.rake - script/rails test/bukkits_test.rb test/test_helper.rb test/dummy @@ -172,6 +171,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "config/routes.rb", /Rails.application.routes.draw do/ assert_file "lib/bukkits/engine.rb", /module Bukkits\n class Engine < ::Rails::Engine\n end\nend/ assert_file "lib/bukkits.rb", /require "bukkits\/engine"/ + assert_file "script/rails" end def test_being_quiet_while_creating_dummy_application @@ -199,8 +199,16 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "bukkits.gemspec", /s.version = "0.0.1"/ end + def test_usage_of_engine_commands + run_generator [destination_root, "--full"] + assert_file "script/rails", /ENGINE_PATH = File.expand_path\('..\/..\/lib\/bukkits\/engine', __FILE__\)/ + assert_file "script/rails", /ENGINE_ROOT = File.expand_path\('..\/..', __FILE__\)/ + assert_file "script/rails", /require 'rails\/all'/ + assert_file "script/rails", /require 'rails\/engine\/commands'/ + end + def test_shebang - run_generator + run_generator [destination_root, "--full"] assert_file "script/rails", /#!\/usr\/bin\/env ruby/ end @@ -254,7 +262,6 @@ class CustomPluginGeneratorTest < Rails::Generators::TestCase assert_file 'spec/dummy' assert_file 'Rakefile', /task :default => :spec/ assert_file 'Rakefile', /# spec tasks in rakefile/ - assert_file 'script/rails', %r{spec/dummy} end protected diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index be9aef8a41..d3074afd91 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -67,12 +67,12 @@ module SharedGeneratorTests end def test_shebang_is_added_to_rails_file - run_generator [destination_root, "--ruby", "foo/bar/baz"] + run_generator [destination_root, "--ruby", "foo/bar/baz", "--full"] assert_file "script/rails", /#!foo\/bar\/baz/ end def test_shebang_when_is_the_same_as_default_use_env - run_generator [destination_root, "--ruby", Thor::Util.ruby_command] + run_generator [destination_root, "--ruby", Thor::Util.ruby_command, "--full"] assert_file "script/rails", /#!\/usr\/bin\/env/ end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 335e639692..0a203fd4d0 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -287,4 +287,4 @@ Module.new do end f.puts "require 'rails/all'" end -end +end unless defined?(RAILS_ISOLATED_ENGINE) diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 44cee2b164..0ff1e0f180 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -15,7 +15,7 @@ module RailtiesTest @plugin = engine "bukkits" do |plugin| plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine railtie_name "bukkits" end @@ -36,7 +36,7 @@ module RailtiesTest test "initializers are executed after application configuration initializers" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine initializer "dummy_initializer" do end @@ -77,7 +77,7 @@ module RailtiesTest add_to_config("config.action_dispatch.show_exceptions = false") @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine endpoint lambda { |env| [200, {'Content-Type' => 'text/html'}, ['Hello World']] } config.middleware.use ::RailtiesTest::EngineTest::Upcaser @@ -127,7 +127,7 @@ module RailtiesTest test "it provides routes as default endpoint" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine end end @@ -153,7 +153,7 @@ module RailtiesTest test "engine can load its own plugins" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine end end @@ -170,7 +170,7 @@ module RailtiesTest test "engine does not load plugins that already exists in application" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine end end @@ -193,7 +193,7 @@ module RailtiesTest test "it loads its environment file" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine end end @@ -212,7 +212,7 @@ module RailtiesTest test "it passes router in env" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine endpoint lambda { |env| [200, {'Content-Type' => 'text/html'}, 'hello'] } end diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb new file mode 100644 index 0000000000..fe59dcd52b --- /dev/null +++ b/railties/test/railties/generators_test.rb @@ -0,0 +1,115 @@ +RAILS_ISOLATED_ENGINE = true +require "isolation/abstract_unit" + +require "#{RAILS_FRAMEWORK_ROOT}/railties/lib/rails/generators/test_case" + +module RailtiesTests + class GeneratorTest < Rails::Generators::TestCase + include ActiveSupport::Testing::Isolation + + TMP_PATH = File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. tmp])) + self.destination_root = File.join(TMP_PATH, "foo_bar") + + def tmp_path(*args) + File.join(TMP_PATH, *args) + end + + def engine_path + tmp_path('foo_bar') + end + + def bundled_rails(cmd) + `bundle exec rails #{cmd}` + end + + def rails(cmd) + environment = File.expand_path('../../../../load_paths', __FILE__) + if File.exist?("#{environment}.rb") + require_environment = "-r #{environment}" + end + `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/bin/rails #{cmd}` + end + + def build_engine(is_mountable=false) + FileUtils.mkdir_p(engine_path) + FileUtils.rm_r(engine_path) + + mountable = is_mountable ? "--mountable" : "" + + rails("plugin new #{engine_path} --full #{mountable}") + + Dir.chdir(engine_path) do + File.open("Gemfile", "w") do |f| + f.write <<-GEMFILE.gsub(/^ {12}/, '') + source "http://rubygems.org" + + gem 'rails', :path => '#{RAILS_FRAMEWORK_ROOT}' + gem 'sqlite3' + + if RUBY_VERSION < '1.9' + gem "ruby-debug", ">= 0.10.3" + end + GEMFILE + end + end + end + + def build_mountable_engine + build_engine(true) + end + + def test_controllers_are_correctly_namespaced_when_engine_is_mountable + build_mountable_engine + Dir.chdir(engine_path) do + bundled_rails("g controller topics") + assert_file "app/controllers/foo_bar/topics_controller.rb", /module FooBar\n class TopicsController/ + assert_no_file "app/controllers/topics_controller.rb" + end + end + + def test_models_are_correctly_namespaced_when_engine_is_mountable + build_mountable_engine + Dir.chdir(engine_path) do + bundled_rails("g model topic") + assert_file "app/models/foo_bar/topic.rb", /module FooBar\n class Topic/ + assert_no_file "app/models/topic.rb" + end + end + + def test_helpers_are_correctly_namespaced_when_engine_is_mountable + build_mountable_engine + Dir.chdir(engine_path) do + bundled_rails("g helper topics") + assert_file "app/helpers/foo_bar/topics_helper.rb", /module FooBar\n module TopicsHelper/ + assert_no_file "app/helpers/topics_helper.rb" + end + end + + def test_controllers_are_not_namespaced_when_engine_is_not_mountable + build_engine + Dir.chdir(engine_path) do + bundled_rails("g controller topics") + assert_file "app/controllers/topics_controller.rb", /class TopicsController/ + assert_no_file "app/controllers/foo_bar/topics_controller.rb" + end + end + + def test_models_are_not_namespaced_when_engine_is_not_mountable + build_engine + Dir.chdir(engine_path) do + bundled_rails("g model topic") + assert_file "app/models/topic.rb", /class Topic/ + assert_no_file "app/models/foo_bar/topic.rb" + end + end + + def test_helpers_are_not_namespaced_when_engine_is_not_mountable + build_engine + Dir.chdir(engine_path) do + bundled_rails("g helper topics") + assert_file "app/helpers/topics_helper.rb", /module TopicsHelper/ + assert_no_file "app/helpers/foo_bar/topics_helper.rb" + end + end + end +end diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb index 659551d08a..8cbc76db71 100644 --- a/railties/test/railties/shared_tests.rb +++ b/railties/test/railties/shared_tests.rb @@ -53,8 +53,11 @@ module RailtiesTest add_to_config "ActiveRecord::Base.timestamped_migrations = false" + boot_rails + railties = Rails.application.railties.all.map(&:railtie_name) + Dir.chdir(app_path) do - output = `rake bukkits:install:migrations` + output = `bundle exec rake bukkits:install:migrations` assert File.exists?("#{app_path}/db/migrate/2_create_users.rb") assert File.exists?("#{app_path}/db/migrate/3_add_last_name_to_users.rb") @@ -63,15 +66,19 @@ module RailtiesTest assert_match /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/, output assert_equal 3, Dir["#{app_path}/db/migrate/*.rb"].length - output = `rake railties:install:migrations` + output = `bundle exec rake railties:install:migrations`.split("\n") assert File.exists?("#{app_path}/db/migrate/4_create_yaffles.rb") - assert_match /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/, output - assert_match /Copied migration 4_create_yaffles.rb from acts_as_yaffle/, output - assert_no_match /2_create_users/, output + assert_no_match /2_create_users/, output.join("\n") + + yaffle_migration_order = output.index(output.detect{|o| /Copied migration 4_create_yaffles.rb from acts_as_yaffle/ =~ o }) + bukkits_migration_order = output.index(output.detect{|o| /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/ =~ o }) + assert_not_nil yaffle_migration_order, "Expected migration to be copied" + assert_not_nil bukkits_migration_order, "Expected migration to be skipped" + assert_equal (railties.index('acts_as_yaffle') > railties.index('bukkits')) , (yaffle_migration_order > bukkits_migration_order) migrations_count = Dir["#{app_path}/db/migrate/*.rb"].length - output = `rake railties:install:migrations` + output = `bundle exec rake railties:install:migrations` assert_equal migrations_count, Dir["#{app_path}/db/migrate/*.rb"].length end |