diff options
68 files changed, 738 insertions, 139 deletions
@@ -10,7 +10,7 @@ gem "bcrypt-ruby", "~> 3.0.0" gem "jquery-rails" # This needs to be with require false to avoid # it being automatically loaded by sprockets -gem "uglifier", ">= 1.0.0", :require => false +gem "uglifier", ">= 1.0.3", :require => false # Temp fix until rake 0.9.3 is out if RUBY_VERSION >= "1.9.3" diff --git a/README.rdoc b/README.rdoc index 9f017cd05f..ac43962d97 100644 --- a/README.rdoc +++ b/README.rdoc @@ -61,13 +61,14 @@ can read more about Action Pack in its {README}[link:/rails/rails/blob/master/ac * The {Ruby on Rails Guides}[http://guides.rubyonrails.org]. * The {API Documentation}[http://api.rubyonrails.org]. - -== Contributing http://travis-ci.org/rails/rails.png +== Contributing We encourage you to contribute to Ruby on Rails! Please check out the {Contributing to Rails guide}[http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html] for guidelines about how to proceed. {Join us}[http://contributors.rubyonrails.org]! +== Travis Build Status {<img src="https://secure.travis-ci.org/rails/rails.png"/>}[https://secure.travis-ci.org/rails/rails.png] + == License Ruby on Rails is released under the MIT license. @@ -78,7 +78,8 @@ RDoc::Task.new do |rdoc| rdoc_main.gsub!(%r{link:/rails/rails/blob/master/(\w+)/README\.rdoc}, "link:files/\\1/README_rdoc.html") # Remove Travis build status image from API pages. Only GitHub README page gets this image - rdoc_main.gsub!("http://travis-ci.org/rails/rails.png", "") + # https build image is used to avoid GitHub caching: http://about.travis-ci.org/docs/user/status-images + rdoc_main.gsub!(%r{^== Travis.*}, '') File.open(RDOC_MAIN, 'w') do |f| f.write(rdoc_main) diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 285ab05103..82dfc625a6 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -46,6 +46,9 @@ *Rails 3.1.1 (unreleased)* +* Fixed the behavior of asset pipeline when config.assets.digest and config.assets.compile are false and requested asset isn't precompiled. + Before the requested asset were compiled anyway ignoring that the config.assets.compile flag is false. [Guillermo Iguaran] + * CookieJar is now Enumerable. Fixes #2795 * Fixed AssetNotPrecompiled error raised when rake assets:precompile is compiling certain .erb files. See GH #2763 #2765 #2805 [Guillermo Iguaran] diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index 938a6ae81c..49cf70ec21 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -88,7 +88,7 @@ module ActionController #:nodoc: end def method_missing(method, *arguments, &block) - return if @controller.nil? + return unless @controller @controller.__send__(method, *arguments, &block) end end diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 7420a5e7e9..264806cd36 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -145,7 +145,7 @@ module ActionController end def encode_credentials(user_name, password) - "Basic #{ActiveSupport::Base64.encode64("#{user_name}:#{password}")}" + "Basic #{ActiveSupport::Base64.encode64s("#{user_name}:#{password}")}" end def authentication_request(controller, realm) diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 4f311a1cf5..f2dfb3833b 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -57,7 +57,7 @@ module ActionController # When using <tt>redirect_to :back</tt>, if there is no referrer, RedirectBackError will be raised. You may specify some fallback # behavior for this case by rescuing RedirectBackError. def redirect_to(options = {}, response_status = {}) #:doc: - raise ActionControllerError.new("Cannot redirect to nil!") if options.nil? + raise ActionControllerError.new("Cannot redirect to nil!") unless options raise AbstractController::DoubleRenderError if response_body self.status = _extract_redirect_to_status(options, response_status) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 40332da321..a83fa74795 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -79,10 +79,10 @@ module ActionController "expecting <?> but rendering with <?>", options, rendered.keys.join(', ')) assert_block(msg) do - if options.nil? - @templates.blank? - else + if options rendered.any? { |t,num| t.match(options) } + else + @templates.blank? end end when Hash diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 0c43954d81..4d65173f61 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -452,7 +452,9 @@ module ActionDispatch prefix_options = options.slice(*_route.segment_keys) # we must actually delete prefix segment keys to avoid passing them to next url_for _route.segment_keys.each { |k| options.delete(k) } - _routes.url_helpers.send("#{name}_path", prefix_options) + prefix = _routes.url_helpers.send("#{name}_path", prefix_options) + prefix = '' if prefix == '/' + prefix end end end @@ -1436,7 +1438,7 @@ module ActionDispatch name_prefix = @scope[:as] if parent_resource - return nil if as.nil? && action.nil? + return nil unless as || action collection_name = parent_resource.collection_name member_name = parent_resource.member_name diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index 5fa91d1a76..b4555f4f59 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -415,9 +415,9 @@ module ActionDispatch assert !deliveries.empty?, "No e-mail in delivery list" for delivery in deliveries - for part in delivery.parts + for part in (delivery.parts.empty? ? [delivery] : delivery.parts) if part["Content-Type"].to_s =~ /^text\/html\W/ - root = HTML::Document.new(part.body).root + root = HTML::Document.new(part.body.to_s).root assert_select root, ":root", &block end end diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb index aae8377f8a..73f4f8ee5f 100644 --- a/actionpack/lib/action_view/asset_paths.rb +++ b/actionpack/lib/action_view/asset_paths.rb @@ -69,7 +69,7 @@ module ActionView host = "#{compute_protocol(protocol)}#{host}" end end - host.nil? ? source : "#{host}#{source}" + host ? "#{host}#{source}" : source end def compute_protocol(protocol) diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 62f95379cd..8abd85c3a3 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -134,9 +134,9 @@ module ActionView # WARNING: content_for is ignored in caches. So you shouldn't use it # for elements that will be fragment cached. def content_for(name, content = nil, &block) - content = capture(&block) if block_given? - if content - @view_flow.append(name, content) + if content || block_given? + content = capture(&block) if block_given? + @view_flow.append(name, content) if content nil else @view_flow.get(name) diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 1ceb53fe9c..13b9dc8553 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -656,7 +656,7 @@ module ActionView if token == false || !protect_against_forgery? '' else - token = form_authenticity_token if token.nil? + token ||= form_authenticity_token tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token) end end diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index 1395400159..b351302d01 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -84,7 +84,7 @@ module ActionView if single_or_multiple_records.respond_to?(:to_ary) single_or_multiple_records.to_ary.map do |single_record| capture { content_tag_for_single_record(tag_name, single_record, prefix, options, &block) } - end.join("\n") + end.join("\n").html_safe else content_tag_for_single_record(tag_name, single_or_multiple_records, prefix, options, &block) end diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 4dbb0135f6..acd5e46e33 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -569,6 +569,12 @@ module ActionView # # current_page?(:controller => 'library', :action => 'checkout') # # => false + # + # Let's say we're in the <tt>/products</tt> action with method POST in case of invalid product. + # + # current_page?(:controller => 'product', :action => 'index') + # # => false + # def current_page?(options) unless request raise "You cannot use helpers that need to determine the current " \ @@ -576,6 +582,8 @@ module ActionView "in a #request method" end + return false unless request.get? + url_string = url_for(options) # We ignore any extra parameters in the request_uri if the @@ -596,9 +604,7 @@ module ActionView private def convert_options_to_data_attributes(options, html_options) - if html_options.nil? - link_to_remote_options?(options) ? {'data-remote' => 'true'} : {} - else + if html_options html_options = html_options.stringify_keys html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options) @@ -611,6 +617,8 @@ module ActionView add_method_to_attributes!(html_options, method) if method html_options + else + link_to_remote_options?(options) ? {'data-remote' => 'true'} : {} end end diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb index 2dde2e9cc9..3987e6e17f 100644 --- a/actionpack/lib/sprockets/helpers/rails_helper.rb +++ b/actionpack/lib/sprockets/helpers/rails_helper.rb @@ -61,11 +61,9 @@ module Sprockets private def debug_assets? - begin - compile_assets? && (Rails.application.config.assets.debug || params[:debug_assets]) - rescue NoMethodError - false - end + compile_assets? && (Rails.application.config.assets.debug || params[:debug_assets]) + rescue NoMethodError + false end # Override to specify an alternative prefix for asset path generation. @@ -124,7 +122,7 @@ module Sprockets end if compile_assets - if asset = asset_environment[logical_path] + if digest_assets && asset = asset_environment[logical_path] return asset.digest_path end return logical_path @@ -137,7 +135,7 @@ module Sprockets if source[0] == ?/ source else - source = digest_for(source) if digest_assets + source = digest_for(source) source = File.join(dir, source) source = "/#{source}" unless source =~ /^\// source diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb index 7927b7bc2c..dc991636a1 100644 --- a/actionpack/lib/sprockets/railtie.rb +++ b/actionpack/lib/sprockets/railtie.rb @@ -71,7 +71,7 @@ module Sprockets mount app.assets => config.assets.prefix end - if config.action_controller.perform_caching + if config.assets.digest app.assets = app.assets.index end end diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb index 878484eb57..5eef8a32d7 100644 --- a/actionpack/test/controller/assert_select_test.rb +++ b/actionpack/test/controller/assert_select_test.rb @@ -20,6 +20,15 @@ class AssertSelectTest < ActionController::TestCase end end + class AssertMultipartSelectMailer < ActionMailer::Base + def test(options) + mail :subject => "Test e-mail", :from => "test@test.host", :to => "test <test@test.host>" do |format| + format.text { render :text => options[:text] } + format.html { render :text => options[:html] } + end + end + end + class AssertSelectController < ActionController::Base def response_with=(content) @content = content @@ -313,6 +322,16 @@ EOF end end + def test_assert_select_email_multipart + AssertMultipartSelectMailer.test(:html => "<div><p>foo</p><p>bar</p></div>", :text => 'foo bar').deliver + assert_select_email do + assert_select "div:root" do + assert_select "p:first-child", "foo" + assert_select "p:last-child", "bar" + end + end + end + protected def render_html(html) @controller.response_with = html diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb index bd3e13e6fa..364e96d4f6 100644 --- a/actionpack/test/controller/http_basic_authentication_test.rb +++ b/actionpack/test/controller/http_basic_authentication_test.rb @@ -85,6 +85,14 @@ class HttpBasicAuthenticationTest < ActionController::TestCase end end + def test_encode_credentials_has_no_newline + username = 'laskjdfhalksdjfhalkjdsfhalksdjfhklsdjhalksdjfhalksdjfhlakdsjfh' + password = 'kjfhueyt9485osdfasdkljfh4lkjhakldjfhalkdsjf' + result = ActionController::HttpAuthentication::Basic.encode_credentials( + username, password) + assert_no_match(/\n/, result) + end + test "authentication request without credential" do get :display diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 6bcd606bf4..c46755417f 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -410,7 +410,7 @@ class TestController < ActionController::Base end def render_with_explicit_escaped_template - render :template => "test/hello_w*rld" + render :template => "test/hello,world" end def render_with_explicit_string_template diff --git a/actionpack/test/fixtures/test/hello_w*rld.erb b/actionpack/test/fixtures/test/hello,world.erb index bc8fa5e0ca..bc8fa5e0ca 100644 --- a/actionpack/test/fixtures/test/hello_w*rld.erb +++ b/actionpack/test/fixtures/test/hello,world.erb diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb index a9157e711c..13e2d5b595 100644 --- a/actionpack/test/template/capture_helper_test.rb +++ b/actionpack/test/template/capture_helper_test.rb @@ -46,6 +46,42 @@ class CaptureHelperTest < ActionView::TestCase assert_equal "bar", content_for(:bar) end + def test_content_for_with_multiple_calls + assert ! content_for?(:title) + content_for :title, 'foo' + content_for :title, 'bar' + assert_equal 'foobar', content_for(:title) + end + + def test_content_for_with_block + assert ! content_for?(:title) + content_for :title do + output_buffer << 'foo' + output_buffer << 'bar' + nil + end + assert_equal 'foobar', content_for(:title) + end + + def test_content_for_with_whitespace_block + assert ! content_for?(:title) + content_for :title, 'foo' + content_for :title do + output_buffer << " \n " + nil + end + content_for :title, 'bar' + assert_equal 'foobar', content_for(:title) + end + + def test_content_for_returns_nil_when_writing + assert ! content_for?(:title) + assert_equal nil, content_for(:title, 'foo') + assert_equal nil, content_for(:title) { output_buffer << 'bar'; nil } + assert_equal nil, content_for(:title) { output_buffer << " \n "; nil } + assert_equal 'foobar', content_for(:title) + end + def test_content_for_question_mark assert ! content_for?(:title) content_for :title, 'title' diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb index edc2689896..7f23629e05 100644 --- a/actionpack/test/template/record_tag_helper_test.rb +++ b/actionpack/test/template/record_tag_helper_test.rb @@ -5,9 +5,17 @@ class Post extend ActiveModel::Naming include ActiveModel::Conversion attr_writer :id, :body + + def initialize + @id = nil + @body = nil + super + end + def id @id || 45 end + def body super || @body || "What a wonderful world!" end @@ -68,9 +76,6 @@ class RecordTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end - def test_content_tag_for_collection_is_html_safe - end - def test_div_for_collection post_1 = Post.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true } post_2 = Post.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true } @@ -78,4 +83,16 @@ class RecordTagHelperTest < ActionView::TestCase actual = div_for([post_1, post_2]) { |post| concat post.body } assert_dom_equal expected, actual end + + def test_content_tag_for_single_record_is_html_safe + result = div_for(@post, :class => "bar") { concat @post.body } + assert result.html_safe? + end + + def test_content_tag_for_collection_is_html_safe + post_1 = Post.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true } + post_2 = Post.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true } + result = content_tag_for(:li, [post_1, post_2]) { |post| concat post.body } + assert result.html_safe? + end end diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 78245c1f95..dbac2e1fc0 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -304,8 +304,8 @@ class UrlHelperTest < ActiveSupport::TestCase assert_equal "Showing", link_to_if(false, "Showing", url_hash) end - def request_for_url(url) - env = Rack::MockRequest.env_for("http://www.example.com#{url}") + def request_for_url(url, opts = {}) + env = Rack::MockRequest.env_for("http://www.example.com#{url}", opts) ActionDispatch::Request.new(env) end @@ -329,6 +329,12 @@ class UrlHelperTest < ActiveSupport::TestCase assert current_page?("http://www.example.com/?order=desc&page=1") end + def test_current_page_with_not_get_verb + @request = request_for_url("/events", :method => :post) + + assert !current_page?('/events') + end + def test_link_unless_current @request = request_for_url("/") diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 843c0c3cb5..7828434927 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -88,6 +88,7 @@ module ActiveModel def include?(error) (v = messages[error]) && v.any? end + alias :has_key? :include? # Get messages for +key+ def get(key) diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index 85ca8ca835..da109a8738 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -33,6 +33,12 @@ class ErrorsTest < ActiveModel::TestCase assert errors.include?(:foo), 'errors should include :foo' end + def test_has_key? + errors = ActiveModel::Errors.new(self) + errors[:foo] = 'omg' + assert errors.has_key?(:foo), 'errors should have key :foo' + end + test "should return true if no errors" do person = Person.new person.errors[:foo] diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 700e11ff94..143de81925 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,4 +1,8 @@ -*Rails 3.2.0 (unreleased)* +Wed Sep 7 15:25:02 2011 Aaron Patterson <aaron@tenderlovemaking.com> + + * lib/active_record/connection_adapters/mysql_adapter.rb: LRU cache + keys are per process id. + * lib/active_record/connection_adapters/sqlite_adapter.rb: ditto * Support bulk change_table in mysql2 adapter, as well as the mysql one. [Jon Leighton] diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index adb6af7165..58c9648ce8 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -20,6 +20,10 @@ module ActiveRecord private + def find_target? + !loaded? && foreign_key_present? && klass + end + def update_counters(record) counter_cache_name = reflection.counter_cache_column diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index 81172179e0..b347a94978 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -16,7 +16,7 @@ module ActiveRecord chain[1..-1].each do |reflection| scope = scope.merge( reflection.klass.scoped.with_default_scope. - except(:select, :create_with, :includes) + except(:select, :create_with, :includes, :preload, :joins, :eager_load) ) end scope diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 61994d4a47..20863e73aa 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -421,7 +421,7 @@ module ActiveRecord # can be used as an argument for establish_connection, for easily # re-establishing the connection. def remove_connection(klass) - pool = @connection_pools[klass.name] + pool = @connection_pools.delete(klass.name) return nil unless pool pool.automatic_reconnect = false diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index 7312e34f01..c08c0263b9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -126,7 +126,7 @@ module ActiveRecord end def connection_pool - connection_handler.retrieve_connection_pool(self) + connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished end def retrieve_connection diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 3de850ec9e..f93c7cd74a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -102,10 +102,13 @@ module ActiveRecord def quoted_date(value) if value.acts_like?(:time) zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal - value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value - else - value - end.to_s(:db) + + if value.respond_to?(zone_conversion_method) + value = value.send(zone_conversion_method) + end + end + + value.to_s(:db) end 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 e9bdcc2104..a1824fe396 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -1,4 +1,5 @@ require 'active_record/connection_adapters/abstract_mysql_adapter' +require 'active_record/connection_adapters/statement_pool' require 'active_support/core_ext/hash/keys' gem 'mysql', '~> 2.8.1' @@ -90,9 +91,42 @@ module ActiveRecord ADAPTER_NAME = 'MySQL' + class StatementPool < ConnectionAdapters::StatementPool + def initialize(connection, max = 1000) + super + @cache = Hash.new { |h,pid| h[pid] = {} } + end + + def each(&block); cache.each(&block); end + def key?(key); cache.key?(key); end + def [](key); cache[key]; end + def length; cache.length; end + def delete(key); cache.delete(key); end + + def []=(sql, key) + while @max <= cache.size + cache.shift.last[:stmt].close + end + cache[sql] = key + end + + def clear + cache.values.each do |hash| + hash[:stmt].close + end + cache.clear + end + + private + def cache + @cache[$$] + end + end + def initialize(connection, logger, connection_options, config) super - @statements = {} + @statements = StatementPool.new(@connection, + config.fetch(:statement_limit) { 1000 }) @client_encoding = nil connect end @@ -187,9 +221,6 @@ module ActiveRecord # Clears the prepared statements cache. def clear_cache! - @statements.values.each do |cache| - cache[:stmt].close - end @statements.clear end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index ba4a6c7a78..5402918b1d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1,5 +1,6 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_support/core_ext/object/blank' +require 'active_record/connection_adapters/statement_pool' # Make sure we're using pg high enough for PGResult#values gem 'pg', '~> 0.11' @@ -246,6 +247,47 @@ module ActiveRecord true end + class StatementPool < ConnectionAdapters::StatementPool + def initialize(connection, max) + super + @counter = 0 + @cache = Hash.new { |h,pid| h[pid] = {} } + end + + def each(&block); cache.each(&block); end + def key?(key); cache.key?(key); end + def [](key); cache[key]; end + def length; cache.length; end + + def next_key + "a#{@counter + 1}" + end + + def []=(sql, key) + while @max <= cache.size + dealloc(cache.shift.last) + end + @counter += 1 + cache[sql] = key + end + + def clear + cache.each_value do |stmt_key| + dealloc stmt_key + end + cache.clear + end + + private + def cache + @cache[$$] + end + + def dealloc(key) + @connection.query "DEALLOCATE #{key}" + end + end + # Initializes and connects a PostgreSQL adapter. def initialize(connection, logger, connection_parameters, config) super(connection, logger) @@ -254,9 +296,10 @@ module ActiveRecord # @local_tz is initialized as nil to avoid warnings when connect tries to use it @local_tz = nil @table_alias_length = nil - @statements = {} connect + @statements = StatementPool.new @connection, + config.fetch(:statement_limit) { 1000 } if postgresql_version < 80200 raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!" @@ -271,9 +314,6 @@ module ActiveRecord # Clears the prepared statements cache. def clear_cache! - @statements.each_value do |value| - @connection.query "DEALLOCATE #{value}" - end @statements.clear end @@ -683,12 +723,12 @@ module ActiveRecord binds << [nil, schema] if schema exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0 - SELECT COUNT(*) - FROM pg_class c - LEFT JOIN pg_namespace n ON n.oid = c.relnamespace - WHERE c.relkind in ('v','r') - AND c.relname = $1 - AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'} + SELECT COUNT(*) + FROM pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind in ('v','r') + AND c.relname = $1 + AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'} SQL end @@ -996,7 +1036,7 @@ module ActiveRecord def exec_cache(sql, binds) unless @statements.key? sql - nextkey = "a#{@statements.length + 1}" + nextkey = @statements.next_key @connection.prepare nextkey, sql @statements[sql] = nextkey end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index a90c675bf6..1932a849ee 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -1,4 +1,5 @@ require 'active_record/connection_adapters/abstract_adapter' +require 'active_record/connection_adapters/statement_pool' require 'active_support/core_ext/string/encoding' module ActiveRecord @@ -48,9 +49,45 @@ module ActiveRecord end end + class StatementPool < ConnectionAdapters::StatementPool + def initialize(connection, max) + super + @cache = Hash.new { |h,pid| h[pid] = {} } + end + + def each(&block); cache.each(&block); end + def key?(key); cache.key?(key); end + def [](key); cache[key]; end + def length; cache.length; end + + def []=(sql, key) + while @max <= cache.size + dealloc(cache.shift.last[:stmt]) + end + cache[sql] = key + end + + def clear + cache.values.each do |hash| + dealloc hash[:stmt] + end + cache.clear + end + + private + def cache + @cache[$$] + end + + def dealloc(stmt) + stmt.close unless stmt.closed? + end + end + def initialize(connection, logger, config) super(connection, logger) - @statements = {} + @statements = StatementPool.new(@connection, + config.fetch(:statement_limit) { 1000 }) @config = config end @@ -107,10 +144,6 @@ module ActiveRecord # Clears the prepared statements cache. def clear_cache! - @statements.values.map { |hash| hash[:stmt] }.each { |stmt| - stmt.close unless stmt.closed? - } - @statements.clear end diff --git a/activerecord/lib/active_record/connection_adapters/statement_pool.rb b/activerecord/lib/active_record/connection_adapters/statement_pool.rb new file mode 100644 index 0000000000..c6b1bc8b5b --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/statement_pool.rb @@ -0,0 +1,40 @@ +module ActiveRecord + module ConnectionAdapters + class StatementPool + include Enumerable + + def initialize(connection, max = 1000) + @connection = connection + @max = max + end + + def each + raise NotImplementedError + end + + def key?(key) + raise NotImplementedError + end + + def [](key) + raise NotImplementedError + end + + def length + raise NotImplementedError + end + + def []=(sql, key) + raise NotImplementedError + end + + def clear + raise NotImplementedError + end + + def delete(key) + raise NotImplementedError + end + end + end +end diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index 2eeff7e36f..ffee5a081a 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -71,7 +71,7 @@ module ActiveRecord end def invert_rename_index(args) - [:rename_index, args.reverse] + [:rename_index, [args.first] + args.last(2).reverse] end def invert_rename_column(args) diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index a2324039cf..89179779e3 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -226,7 +226,7 @@ module ActiveRecord if options[:counter_cache] == true "#{active_record.name.demodulize.underscore.pluralize}_count" elsif options[:counter_cache] - options[:counter_cache] + options[:counter_cache].to_s end end diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index ec1176e3dd..2fd89882ff 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -62,7 +62,7 @@ module ActiveRecord start = options.delete(:start).to_i batch_size = options.delete(:batch_size) || 1000 - relation = relation.except(:order).order(batch_order).limit(batch_size) + relation = relation.reorder(batch_order).limit(batch_size) records = relation.where(table[primary_key].gteq(start)).all while records.any? diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 73368aed18..7eeb3dde70 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -114,7 +114,7 @@ module ActiveRecord def first(*args) if args.any? if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash)) - to_a.first(*args) + limit(*args).to_a else apply_finder_options(args.first).first end @@ -134,7 +134,11 @@ module ActiveRecord def last(*args) if args.any? if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash)) - to_a.last(*args) + if order_values.empty? && reorder_value.nil? + order("#{primary_key} DESC").limit(*args).reverse + else + to_a.last(*args) + end else apply_finder_options(args.first).last end @@ -180,7 +184,9 @@ module ActiveRecord # Person.exists?(:name => "David") # Person.exists?(['name LIKE ?', "%#{query}%"]) # Person.exists? - def exists?(id = nil) + def exists?(id = false) + return false if id.nil? + id = id.id if ActiveRecord::Base === id join_dependency = construct_join_dependency_for_association_find diff --git a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb new file mode 100644 index 0000000000..83de90f179 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb @@ -0,0 +1,23 @@ +require 'cases/helper' + +module ActiveRecord::ConnectionAdapters + class MysqlAdapter + class StatementPoolTest < ActiveRecord::TestCase + def test_cache_is_per_pid + return skip('must support fork') unless Process.respond_to?(:fork) + + cache = StatementPool.new nil, 10 + cache['foo'] = 'bar' + assert_equal 'bar', cache['foo'] + + pid = fork { + lookup = cache['foo']; + exit!(!lookup) + } + + Process.waitpid pid + assert $?.success?, 'process should exit successfully' + end + end + end +end diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 4c6d865d59..76c73e9dfa 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -91,6 +91,12 @@ class SchemaTest < ActiveRecord::TestCase end end + def test_table_exists_quoted_table + with_schema_search_path(SCHEMA_NAME) do + assert(@connection.table_exists?('"things.table"'), "table should exist") + end + end + def test_with_schema_prefixed_table_name assert_nothing_raised do assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{TABLE_NAME}") diff --git a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb new file mode 100644 index 0000000000..a82c6f67d6 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb @@ -0,0 +1,23 @@ +require 'cases/helper' + +module ActiveRecord::ConnectionAdapters + class PostgreSQLAdapter < AbstractAdapter + class StatementPoolTest < ActiveRecord::TestCase + def test_cache_is_per_pid + return skip('must support fork') unless Process.respond_to?(:fork) + + cache = StatementPool.new nil, 10 + cache['foo'] = 'bar' + assert_equal 'bar', cache['foo'] + + pid = fork { + lookup = cache['foo']; + exit!(!lookup) + } + + Process.waitpid pid + assert $?.success?, 'process should exit successfully' + end + end + end +end diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb new file mode 100644 index 0000000000..ae272e2c4b --- /dev/null +++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb @@ -0,0 +1,24 @@ +require 'cases/helper' + +module ActiveRecord::ConnectionAdapters + class SQLiteAdapter + class StatementPoolTest < ActiveRecord::TestCase + def test_cache_is_per_pid + return skip('must support fork') unless Process.respond_to?(:fork) + + cache = StatementPool.new nil, 10 + cache['foo'] = 'bar' + assert_equal 'bar', cache['foo'] + + pid = fork { + lookup = cache['foo']; + exit!(!lookup) + } + + Process.waitpid pid + assert $?.success?, 'process should exit successfully' + end + end + end +end + diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 818902beb5..866a3cca10 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -352,6 +352,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal members(:groucho), sponsor.sponsorable end + def test_dont_find_target_when_foreign_key_is_null + tagging = taggings(:thinking_general) + queries = assert_sql { tagging.super_tag } + assert_equal 0, queries.length + end + def test_field_name_same_as_foreign_key computer = Computer.find(1) assert_not_nil computer.developer, ":foreign key == attribute didn't lock up" # ' diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb index 490fc5177e..8dc1423375 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -36,6 +36,11 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase end def test_marshalling_extensions + if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7" + return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \ + "to be a Ruby bug.") + end + david = developers(:david) assert_equal projects(:action_controller), david.projects.find_most_recent @@ -46,6 +51,11 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase end def test_marshalling_named_extensions + if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7" + return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \ + "to be a Ruby bug.") + end + david = developers(:david) assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index a2764f3e3b..1e59931963 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -17,6 +17,7 @@ require 'models/invoice' require 'models/line_item' require 'models/car' require 'models/bulb' +require 'models/engine' class HasManyAssociationsTestForCountWithFinderSql < ActiveRecord::TestCase class Invoice < ActiveRecord::Base @@ -850,6 +851,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end + def test_clearing_updates_counter_cache_when_inverse_counter_cache_is_a_symbol_with_dependent_destroy + car = Car.first + car.engines.create! + + assert_difference 'car.reload.engines_count', -1 do + car.engines.clear + end + end + def test_clearing_a_dependent_association_collection firm = companies(:first_firm) client_id = firm.dependent_clients_of_firm.first.id diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index 80c6e41169..530f5212a2 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -356,6 +356,17 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase assert_equal categories(:general), members(:groucho).club_category end + def test_joins_and_includes_from_through_models_not_included_in_association + prev_default_scope = Club.default_scopes + + [:includes, :preload, :joins, :eager_load].each do |q| + Club.default_scopes = [Club.send(q, :category)] + assert_equal categories(:general), members(:groucho).reload.club_category + end + ensure + Club.default_scopes = prev_default_scope + end + def test_has_one_through_has_one_through_with_belongs_to_source_reflection_preload members = assert_queries(4) { Member.includes(:club_category).to_a.sort_by(&:id) } general = categories(:general) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 8b95eb958b..87f5b5ee81 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1835,6 +1835,11 @@ class BasicsTest < ActiveRecord::TestCase end def test_marshal_round_trip + if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7" + return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \ + "to be a Ruby bug.") + end + expected = posts(:welcome) marshalled = Marshal.dump(expected) actual = Marshal.load(marshalled) @@ -1843,6 +1848,11 @@ class BasicsTest < ActiveRecord::TestCase end def test_marshal_new_record_round_trip + if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7" + return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \ + "to be a Ruby bug.") + end + marshalled = Marshal.dump(Post.new) post = Marshal.load(marshalled) @@ -1850,6 +1860,11 @@ class BasicsTest < ActiveRecord::TestCase end def test_marshalling_with_associations + if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7" + return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \ + "to be a Ruby bug.") + end + post = Post.new post.comments.build diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index a35baee4ed..660098b9ad 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -113,7 +113,27 @@ class EachTest < ActiveRecord::TestCase batch.map! { not_a_post } end end + end + def test_find_in_batches_should_ignore_the_order_default_scope + # First post is with title scope + first_post = PostWithDefaultScope.first + posts = [] + PostWithDefaultScope.find_in_batches do |batch| + posts.concat(batch) + end + # posts.first will be ordered using id only. Title order scope should not apply here + assert_not_equal first_post, posts.first + assert_equal posts(:welcome), posts.first + end + + def test_find_in_batches_should_not_ignore_the_default_scope_if_it_is_other_then_order + special_posts_ids = SpecialPostWithDefaultScope.all.map(&:id).sort + posts = [] + SpecialPostWithDefaultScope.find_in_batches do |batch| + posts.concat(batch) + end + assert_equal special_posts_ids, posts.map(&:id) end end diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index abf317768f..bd0d161838 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -6,7 +6,12 @@ module ActiveRecord def setup @handler = ConnectionHandler.new @handler.establish_connection 'america', Base.connection_pool.spec - @klass = Struct.new(:name).new('america') + @klass = Class.new do + def self.name; 'america'; end + end + @subklass = Class.new(@klass) do + def self.name; 'north america'; end + end end def test_retrieve_connection @@ -28,6 +33,20 @@ module ActiveRecord def test_retrieve_connection_pool assert_not_nil @handler.retrieve_connection_pool(@klass) end + + def test_retrieve_connection_pool_uses_superclass_when_no_subclass_connection + assert_not_nil @handler.retrieve_connection_pool(@subklass) + end + + def test_retrieve_connection_pool_uses_superclass_pool_after_subclass_establish_and_remove + @handler.establish_connection 'north america', Base.connection_pool.spec + assert_not_same @handler.retrieve_connection_pool(@klass), + @handler.retrieve_connection_pool(@subklass) + + @handler.remove_connection @subklass + assert_same @handler.retrieve_connection_pool(@klass), + @handler.retrieve_connection_pool(@subklass) + end end end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 5dc5f99582..3088ab012f 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -48,6 +48,15 @@ class FinderTest < ActiveRecord::TestCase assert Topic.exists? end + # exists? should handle nil for id's that come from URLs and always return false + # (example: Topic.exists?(params[:id])) where params[:id] is nil + def test_exists_with_nil_arg + assert !Topic.exists?(nil) + assert Topic.exists? + assert !Topic.first.replies.exists?(nil) + assert Topic.first.replies.exists? + end + def test_does_not_exist_with_empty_table_and_no_args_given Topic.delete_all assert !Topic.exists? @@ -243,6 +252,32 @@ class FinderTest < ActiveRecord::TestCase end end + def test_first_and_last_with_integer_should_use_sql_limit + assert_sql(/LIMIT 2|ROWNUM <= 2/) { Topic.first(2).entries } + assert_sql(/LIMIT 5|ROWNUM <= 5/) { Topic.last(5).entries } + end + + def test_last_with_integer_and_order_should_keep_the_order + assert_equal Topic.order("title").to_a.last(2), Topic.order("title").last(2) + end + + def test_last_with_integer_and_order_should_not_use_sql_limit + query = assert_sql { Topic.order("title").last(5).entries } + assert_equal 1, query.length + assert_no_match(/LIMIT/, query.first) + end + + def test_last_with_integer_and_reorder_should_not_use_sql_limit + query = assert_sql { Topic.reorder("title").last(5).entries } + assert_equal 1, query.length + assert_no_match(/LIMIT/, query.first) + end + + def test_first_and_last_with_integer_should_return_an_array + assert_kind_of Array, Topic.first(5) + assert_kind_of Array, Topic.last(5) + end + def test_unexisting_record_exception_handling assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1).parent diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 36007255fa..d108b456f0 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -104,9 +104,9 @@ module ActiveRecord end def test_invert_rename_index - @recorder.record :rename_index, [:old, :new] + @recorder.record :rename_index, [:table, :old, :new] rename = @recorder.inverse.first - assert_equal [:rename_index, [:new, :old]], rename + assert_equal [:rename_index, [:table, :new, :old]], rename end def test_invert_add_timestamps diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index ed0240cada..4a09a87322 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -182,7 +182,7 @@ class NamedScopeTest < ActiveRecord::TestCase def test_first_and_last_should_allow_integers_for_limit assert_equal Topic.base.first(2), Topic.base.to_a.first(2) - assert_equal Topic.base.last(2), Topic.base.to_a.last(2) + assert_equal Topic.base.last(2), Topic.base.order("id").to_a.last(2) end def test_first_and_last_should_not_use_query_when_results_are_loaded diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index 76f20b1061..b9c2e8ec9a 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -8,7 +8,7 @@ class Car < ActiveRecord::Base has_one :frickinawesome_bulb, :class_name => "Bulb", :conditions => { :frickinawesome => true } has_many :tyres - has_many :engines + has_many :engines, :dependent => :destroy has_many :wheels, :as => :wheelable scope :incl_tyres, includes(:tyres) diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index affa37b02d..198a963cbc 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -171,4 +171,14 @@ class PostWithDefaultInclude < ActiveRecord::Base self.table_name = 'posts' default_scope includes(:comments) has_many :comments, :foreign_key => :post_id +end + +class PostWithDefaultScope < ActiveRecord::Base + self.table_name = 'posts' + default_scope :order => :title +end + +class SpecialPostWithDefaultScope < ActiveRecord::Base + self.table_name = 'posts' + default_scope where(:id => [1, 5,6]) end
\ No newline at end of file diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 18164234a5..038fe63141 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.2.0 (unreleased)* +* Safe strings no longer respond to sub, sub!, gsub, or gsub!. See issue #1555. [Damien Mathieu] + * ActiveSupport::OrderedHash is now marked as extractable when using Array#extract_options! [Prem Sichanugrist] * Added Array#prepend as an alias for Array#unshift and Array#append as an alias for Array#<< [DHH] 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 f111c8e5a3..f364a42354 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -75,7 +75,8 @@ 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 + UNSAFE_STRING_METHODS = ["capitalize", "chomp", "chop", "delete", "downcase", "lstrip", "next", "reverse", "rstrip", "slice", "squeeze", "strip", "succ", "swapcase", "tr", "tr_s", "upcase"].freeze + UNAVAILABLE_STRING_METHODS = ["gsub", "sub"] alias_method :original_concat, :concat private :original_concat @@ -143,17 +144,35 @@ module ActiveSupport #:nodoc: UNSAFE_STRING_METHODS.each do |unsafe_method| class_eval <<-EOT, __FILE__, __LINE__ - def #{unsafe_method}(*args, &block) # def gsub(*args, &block) + def #{unsafe_method}(*args, &block) # def capitalize(*args, &block) to_str.#{unsafe_method}(*args, &block) # to_str.gsub(*args, &block) end # end - def #{unsafe_method}!(*args) # def gsub!(*args) + def #{unsafe_method}!(*args) # def capitalize!(*args) @dirty = true # @dirty = true super # super end # end EOT end + UNAVAILABLE_STRING_METHODS.each do |unavailable_method| + class_eval <<-EOT, __FILE__, __LINE__ + # def gsub(*args) + # raise NoMethodError, "gsub cannot be used with a safe string. You should use object.to_str.gsub" + # end + def #{unavailable_method}(*args) + raise NoMethodError, "#{unavailable_method} cannot be used with a safe string. You should use object.to_str.#{unavailable_method}" + end + + # def gsub!(*args) + # raise NoMethodError, "gsub! cannot be used with a safe string. You should use object.to_str.gsub!" + # end + def #{unavailable_method}!(*args) + raise NoMethodError, "#{unavailable_method}! cannot be used with a safe string. You should use object.to_str.#{unavailable_method}!" + end + EOT + end + protected def dirty? diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 423b5abd20..e1aba44813 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -21,7 +21,8 @@ module ActiveSupport # "words".pluralize # => "words" # "CamelOctopus".pluralize # => "CamelOctopi" def pluralize(word) - result = word.to_s.dup + word = deprecate_symbol(word) + result = word.to_str.dup if word.empty? || inflections.uncountables.include?(result.downcase) result @@ -40,7 +41,8 @@ module ActiveSupport # "word".singularize # => "word" # "CamelOctopi".singularize # => "CamelOctopus" def singularize(word) - result = word.to_s.dup + word = deprecate_symbol(word) + result = word.to_str.dup if inflections.uncountables.any? { |inflection| result =~ /\b(#{inflection})\Z/i } result @@ -66,7 +68,8 @@ module ActiveSupport # # "SSLError".underscore.camelize # => "SslError" def camelize(term, uppercase_first_letter = true) - string = term.to_s + term = deprecate_symbol(term) + string = term.to_str if uppercase_first_letter string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize } else @@ -88,7 +91,8 @@ module ActiveSupport # # "SSLError".underscore.camelize # => "SslError" def underscore(camel_cased_word) - word = camel_cased_word.to_s.dup + camel_cased_word = deprecate_symbol(camel_cased_word) + word = camel_cased_word.to_str.dup word.gsub!(/::/, '/') word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" } word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2') @@ -105,7 +109,8 @@ module ActiveSupport # "employee_salary" # => "Employee salary" # "author_id" # => "Author" def humanize(lower_case_and_underscored_word) - result = lower_case_and_underscored_word.to_s.dup + lower_case_and_underscored_word = deprecate_symbol(lower_case_and_underscored_word) + result = lower_case_and_underscored_word.to_str.dup inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } result.gsub!(/_id$/, "") result.gsub(/(_)?([a-z\d]*)/i) { "#{$1 && ' '}#{inflections.acronyms[$2] || $2.downcase}" }.gsub(/^\w/) { $&.upcase } @@ -148,8 +153,9 @@ module ActiveSupport # Singular names are not handled correctly: # "business".classify # => "Busines" def classify(table_name) + table_name = deprecate_symbol(table_name) # strip out any leading schema name - camelize(singularize(table_name.to_s.sub(/.*\./, ''))) + camelize(singularize(table_name.to_str.sub(/.*\./, ''))) end # Replaces underscores with dashes in the string. @@ -157,7 +163,8 @@ module ActiveSupport # Example: # "puni_puni" # => "puni-puni" def dasherize(underscored_word) - underscored_word.gsub(/_/, '-') + underscored_word = deprecate_symbol(underscored_word) + underscored_word.to_str.gsub(/_/, '-') end # Removes the module part from the expression in the string. @@ -166,7 +173,8 @@ module ActiveSupport # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections" # "Inflections".demodulize # => "Inflections" def demodulize(class_name_in_module) - class_name_in_module.to_s.gsub(/^.*::/, '') + class_name_in_module = deprecate_symbol(class_name_in_module) + class_name_in_module.to_str.gsub(/^.*::/, '') end # Creates a foreign key name from a class name. @@ -246,5 +254,13 @@ module ActiveSupport end end end + + def deprecate_symbol(symbol) + if symbol.is_a?(Symbol) + symbol = symbol.to_s + ActiveSupport::Deprecation.warn("Using symbols in inflections is deprecated. Please use to_s to have a string.") + end + symbol + end end end diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 4fb487ade1..35f400f9df 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -371,7 +371,7 @@ module ActiveSupport protected def require_tzinfo - require 'tzinfo' + require 'tzinfo' unless defined?(::TZInfo) rescue LoadError $stderr.puts "You don't have tzinfo installed in your application. Please add it to your Gemfile and run bundle install" raise diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index b9e299af75..6484811d34 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -10,7 +10,7 @@ module Ace end end -class InflectorTest < Test::Unit::TestCase +class InflectorTest < ActiveSupport::TestCase include InflectorTestCases def test_pluralize_plurals @@ -248,12 +248,6 @@ class InflectorTest < Test::Unit::TestCase end end - def test_classify_with_symbol - assert_nothing_raised do - assert_equal 'FooBar', ActiveSupport::Inflector.classify(:foo_bars) - end - end - def test_classify_with_leading_schema_name assert_equal 'FooBar', ActiveSupport::Inflector.classify('schema.foo_bar') end @@ -319,12 +313,6 @@ class InflectorTest < Test::Unit::TestCase end end - def test_symbol_to_lower_camel - SymbolToLowerCamel.each do |symbol, lower_camel| - assert_equal(lower_camel, ActiveSupport::Inflector.camelize(symbol, false)) - end - end - %w{plurals singulars uncountables humans}.each do |inflection_type| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def test_clear_#{inflection_type} @@ -380,6 +368,14 @@ class InflectorTest < Test::Unit::TestCase ActiveSupport::Inflector.inflections.instance_variable_set :@humans, cached_values[3] end + [:pluralize, :singularize, :camelize, :underscore, :humanize, :titleize, :tableize, :classify, :dasherize, :demodulize].each do |method| + test "should deprecate symbols on #{method}" do + assert_deprecated(/Using symbols in inflections is deprecated/) do + ActiveSupport::Inflector.send(method, :foo) + end + end + end + Irregularities.each do |irregularity| singular, plural = *irregularity ActiveSupport::Inflector.inflections do |inflect| diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index 0cb1f70657..62e0ccd355 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -120,13 +120,6 @@ module InflectorTestCases "area51_controller" => "area51Controller" } - SymbolToLowerCamel = { - :product => 'product', - :special_guest => 'specialGuest', - :application_controller => 'applicationController', - :area51_controller => 'area51Controller' - } - CamelToUnderscoreWithoutReverse = { "HTMLTidy" => "html_tidy", "HTMLTidyGenerator" => "html_tidy_generator", diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index 8f77999d25..f2cd67749a 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -67,42 +67,41 @@ class SafeBufferTest < ActiveSupport::TestCase assert_equal "my_test", str end - test "Should not return safe buffer from gsub" do - altered_buffer = @buffer.gsub('', 'asdf') - assert_equal 'asdf', altered_buffer + test "Should not return safe buffer from capitalize" do + altered_buffer = "asdf".html_safe.capitalize + assert_equal 'Asdf', altered_buffer assert !altered_buffer.html_safe? end test "Should not return safe buffer from gsub!" do - @buffer.gsub!('', 'asdf') - assert_equal 'asdf', @buffer - assert !@buffer.html_safe? + string = "asdf" + string.capitalize! + assert_equal 'Asdf', string + assert !string.html_safe? end test "Should escape dirty buffers on add" do clean = "hello".html_safe - @buffer.gsub!('', '<>') - assert_equal "hello<>", clean + @buffer + assert_equal "hello<>", clean + '<>' end test "Should concat as a normal string when dirty" do clean = "hello".html_safe - @buffer.gsub!('', '<>') - assert_equal "<>hello", @buffer + clean + assert_equal "<>hello", '<>' + clean end test "Should preserve dirty? status on copy" do - @buffer.gsub!('', '<>') - assert !@buffer.dup.html_safe? + dirty = "<>" + assert !dirty.dup.html_safe? end test "Should raise an error when safe_concat is called on dirty buffers" do - @buffer.gsub!('', '<>') + @buffer.capitalize! assert_raise ActiveSupport::SafeBuffer::SafeConcatError do @buffer.safe_concat "BUSTED" end end - + test "should not fail if the returned object is not a string" do assert_kind_of NilClass, @buffer.slice("chipchop") end @@ -112,4 +111,18 @@ class SafeBufferTest < ActiveSupport::TestCase assert_not_nil dirty assert !dirty end + + ["gsub", "sub"].each do |unavailable_method| + test "should raise on #{unavailable_method}" do + assert_raise NoMethodError, "#{unavailable_method} cannot be used with a safe string. You should use object.to_str.#{unavailable_method}" do + @buffer.send(unavailable_method, '', '<>') + end + end + + test "should raise on #{unavailable_method}!" do + assert_raise NoMethodError, "#{unavailable_method}! cannot be used with a safe string. You should use object.to_str.#{unavailable_method}!" do + @buffer.send("#{unavailable_method}!", '', '<>') + end + end + end end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 21a2ae4e28..93f5023a7a 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -9,7 +9,7 @@ require 'uri' module Rails module Generators class AppBase < Base - DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) + DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db sqlserver ) JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc ) DATABASES.concat(JDBC_DATABASES) @@ -154,12 +154,13 @@ module Rails end def gem_for_database - # %w( mysql oracle postgresql sqlite3 frontbase ibm_db jdbcmysql jdbcsqlite3 jdbcpostgresql ) + # %w( mysql oracle postgresql sqlite3 frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql ) case options[:database] when "oracle" then "ruby-oci8" when "postgresql" then "pg" when "frontbase" then "ruby-frontbase" when "mysql" then "mysql2" + when "sqlserver" then "activerecord-sqlserver-adapter" when "jdbcmysql" then "activerecord-jdbcmysql-adapter" when "jdbcsqlite3" then "activerecord-jdbcsqlite3-adapter" when "jdbcpostgresql" then "activerecord-jdbcpostgresql-adapter" @@ -205,7 +206,7 @@ module Rails group :assets do gem 'sass-rails', :git => 'git://github.com/rails/sass-rails.git' gem 'coffee-rails', :git => 'git://github.com/rails/coffee-rails.git' - gem 'uglifier' + gem 'uglifier', '>= 1.0.3' end GEMFILE end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index 8e33a65b2d..80198cc21e 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -41,7 +41,4 @@ # Print deprecation notices to the stderr config.active_support.deprecation = :stderr - - # Allow pass debug_assets=true as a query parameter to load pages with unpackaged assets - config.assets.allow_debugging = true end diff --git a/railties/lib/rails/generators/rails/app/templates/public/500.html b/railties/lib/rails/generators/rails/app/templates/public/500.html index b80307fc16..f3648a0dbc 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/500.html +++ b/railties/lib/rails/generators/rails/app/templates/public/500.html @@ -20,7 +20,6 @@ <!-- This file lives in public/500.html --> <div class="dialog"> <h1>We're sorry, but something went wrong.</h1> - <p>We've been notified about this issue and we'll take a look at it shortly.</p> </div> </body> </html> diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 1e0a26918b..b9d8c002d7 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -37,7 +37,7 @@ module ApplicationTests test "assets do not require compressors until it is used" do app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();" - app_file "config/initializers/compile.rb", "Rails.application.config.assets.compile = true" + add_to_config "config.assets.compile = true" ENV["RAILS_ENV"] = "production" require "#{app_path}/config/environment" @@ -64,11 +64,21 @@ module ApplicationTests end end + test "asset pipeline should use a Sprockets::Index when config.assets.digest is true" do + add_to_config "config.assets.digest = true" + add_to_config "config.action_controller.perform_caching = false" + + ENV["RAILS_ENV"] = "production" + require "#{app_path}/config/environment" + + assert_equal Sprockets::Index, Rails.application.assets.class + end + test "precompile creates a manifest file with all the assets listed" do app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>" app_file "app/assets/javascripts/application.js", "alert();" # digest is default in false, we must enable it for test environment - app_file "config/initializers/compile.rb", "Rails.application.config.assets.digest = true" + add_to_config "config.assets.digest = true" capture(:stdout) do Dir.chdir(app_path){ `bundle exec rake assets:precompile` } @@ -84,10 +94,10 @@ module ApplicationTests test "precompile creates a manifest file in a custom path with all the assets listed" do app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>" app_file "app/assets/javascripts/application.js", "alert();" - FileUtils.mkdir "#{app_path}/shared" - app_file "config/initializers/manifest.rb", "Rails.application.config.assets.manifest = '#{app_path}/shared'" # digest is default in false, we must enable it for test environment - app_file "config/initializers/compile.rb", "Rails.application.config.assets.digest = true" + add_to_config "config.assets.digest = true" + add_to_config "config.assets.manifest = '#{app_path}/shared'" + FileUtils.mkdir "#{app_path}/shared" capture(:stdout) do Dir.chdir(app_path){ `bundle exec rake assets:precompile` } @@ -103,9 +113,9 @@ module ApplicationTests test "the manifest file should be saved by default in the same assets folder" do app_file "app/assets/javascripts/application.js", "alert();" - app_file "config/initializers/manifest.rb", "Rails.application.config.assets.prefix = '/x'" # digest is default in false, we must enable it for test environment - app_file "config/initializers/compile.rb", "Rails.application.config.assets.digest = true" + add_to_config "config.assets.digest = true" + add_to_config "config.assets.prefix = '/x'" capture(:stdout) do Dir.chdir(app_path){ `bundle exec rake assets:precompile` } @@ -119,7 +129,7 @@ module ApplicationTests test "precompile does not append asset digests when config.assets.digest is false" do app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>" app_file "app/assets/javascripts/application.js", "alert();" - app_file "config/initializers/compile.rb", "Rails.application.config.assets.digest = false" + add_to_config "config.assets.digest = false" capture(:stdout) do Dir.chdir(app_path){ `bundle exec rake assets:precompile` } @@ -180,10 +190,36 @@ module ApplicationTests assert_match(/app.js isn't precompiled/, last_response.body) end + test "assets raise AssetNotPrecompiledError when manifest file is present and requested file isn't precompiled if digest is disabled" do + app_file "app/views/posts/index.html.erb", "<%= javascript_include_tag 'app' %>" + add_to_config "config.assets.compile = false" + + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + match '/posts', :to => "posts#index" + end + RUBY + + ENV["RAILS_ENV"] = "development" + capture(:stdout) do + Dir.chdir(app_path){ `bundle exec rake assets:precompile` } + end + + # Create file after of precompile + app_file "app/assets/javascripts/app.js", "alert();" + + require "#{app_path}/config/environment" + class ::PostsController < ActionController::Base ; end + + get '/posts' + assert_match(/AssetNotPrecompiledError/, last_response.body) + assert_match(/app.js isn't precompiled/, last_response.body) + end + test "precompile appends the md5 hash to files referenced with asset_path and run in the provided RAILS_ENV" do app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>" # digest is default in false, we must enable it for test environment - app_file "config/initializers/compile.rb", "Rails.application.config.assets.digest = true" + add_to_config "config.assets.digest = true" # capture(:stdout) do Dir.chdir(app_path){ `bundle exec rake assets:precompile RAILS_ENV=test` } @@ -194,7 +230,7 @@ module ApplicationTests test "precompile appends the md5 hash to files referenced with asset_path and run in production as default even using RAILS_GROUPS=assets" do app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>" - app_file "config/initializers/compile.rb", "Rails.application.config.assets.compile = true" + add_to_config "config.assets.compile = true" ENV["RAILS_ENV"] = nil capture(:stdout) do diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb index 94dec405a7..253e61259b 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -11,13 +11,17 @@ module ApplicationTests add_to_config("config.action_dispatch.show_exceptions = false") + @simple_plugin = engine "weblog" @plugin = engine "blog" app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do + mount Weblog::Engine, :at => '/', :as => 'weblog' resources :posts match "/engine_route" => "application_generating#engine_route" match "/engine_route_in_view" => "application_generating#engine_route_in_view" + match "/weblog_engine_route" => "application_generating#weblog_engine_route" + match "/weblog_engine_route_in_view" => "application_generating#weblog_engine_route_in_view" match "/url_for_engine_route" => "application_generating#url_for_engine_route" match "/polymorphic_route" => "application_generating#polymorphic_route" match "/application_polymorphic_path" => "application_generating#application_polymorphic_path" @@ -28,6 +32,29 @@ module ApplicationTests end RUBY + + @simple_plugin.write "lib/weblog.rb", <<-RUBY + module Weblog + class Engine < ::Rails::Engine + end + end + RUBY + + @simple_plugin.write "config/routes.rb", <<-RUBY + Weblog::Engine.routes.draw do + match '/weblog' => "weblogs#index", :as => 'weblogs' + end + RUBY + + @simple_plugin.write "app/controllers/weblogs_controller.rb", <<-RUBY + class WeblogsController < ActionController::Base + def index + render :text => request.url + end + end + RUBY + + @plugin.write "app/models/blog/post.rb", <<-RUBY module Blog class Post @@ -100,6 +127,14 @@ module ApplicationTests render :inline => "<%= blog.posts_path %>" end + def weblog_engine_route + render :text => weblog.weblogs_path + end + + def weblog_engine_route_in_view + render :inline => "<%= weblog.weblogs_path %>" + end + def url_for_engine_route render :text => blog.url_for(:controller => "blog/posts", :action => "index", :user => "john", :only_path => true) end @@ -192,5 +227,18 @@ module ApplicationTests get "/application_polymorphic_path" assert_equal "/posts/44", last_response.body end + + test "route path for controller action when engine is mounted at root" do + get "/weblog_engine_route" + assert_equal "/weblog", last_response.body + + get "/weblog_engine_route_in_view" + assert_equal "/weblog", last_response.body + end + + test "request url for controller action when engine is mounted at root" do + get "/weblog" + assert_equal "http://example.org/weblog", last_response.body + end end end |