diff options
82 files changed, 1187 insertions, 201 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 91274f35ab..1d38e33874 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,85 @@ ## Rails 3.2.9 (unreleased) ## +* Accept :remote as symbolic option for `link_to` helper. *Riley Lynch* + +* Warn when the `:locals` option is passed to `assert_template` outside of a view test case + Fix #3415 + + *Yves Senn* + +* Rename internal variables on ActionController::TemplateAssertions to prevent + naming collisions. @partials, @templates and @layouts are now prefixed with an underscore. + Fix #7459 + + *Yves Senn* + +* `resource` and `resources` don't modify the passed options hash + Fix #7777 + + *Yves Senn* + +* Precompiled assets include aliases from foo.js to foo/index.js and vice versa. + + # Precompiles phone-<digest>.css and aliases phone/index.css to phone.css. + config.assets.precompile = [ 'phone.css' ] + + # Precompiles phone/index-<digest>.css and aliases phone.css to phone/index.css. + config.assets.precompile = [ 'phone/index.css' ] + + # Both of these work with either precompile thanks to their aliases. + <%= stylesheet_link_tag 'phone', media: 'all' %> + <%= stylesheet_link_tag 'phone/index', media: 'all' %> + + *Jeremy Kemper* + +* `assert_template` is no more passing with what ever string that matches + with the template name. + + Before when we have a template `/layout/hello.html.erb`, `assert_template` + was passing with any string that matches. This behavior allowed false + positive like: + + assert_template "layout" + assert_template "out/hello" + + Now it only passes with: + + assert_template "layout/hello" + assert_template "hello" + + Fixes #3849. + + *Hugolnx* + +* Handle `ActionDispatch::Http::UploadedFile` like `Rack::Test::UploadedFile`, don't call to_param on it. Since + `Rack::Test::UploadedFile` isn't API compatible this is needed to test file uploads that rely on `tempfile` + being available. + + *Tim Vandecasteele* + +* Fixed a bug with shorthand routes scoped with the `:module` option not + adding the module to the controller as described in issue #6497. + This should now work properly: + + scope :module => "engine" do + get "api/version" # routes to engine/api#version + end + + *Luiz Felipe Garcia Pereira* + +* Respect `config.digest = false` for `asset_path` + + Previously, the `asset_path` internals only respected the `:digest` + option, but ignored the global config setting. This meant that + `config.digest = false` could not be used in conjunction with + `config.compile = false` this corrects the behavior. + + *Peter Wagenet* + +* Fix #7646, the log now displays the correct status code when an exception is raised. + + *Yves Senn* + * Fix handling of date selects when using both disabled and discard options. Fixes #7431. diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 3d67541557..002351696d 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |s| s.add_dependency('rack', '~> 1.4.0') s.add_dependency('rack-test', '~> 0.6.1') s.add_dependency('journey', '~> 1.0.4') - s.add_dependency('sprockets', '~> 2.1') + s.add_dependency('sprockets', '~> 2.2') s.add_dependency('erubis', '~> 2.7.0') s.add_development_dependency('tzinfo', '~> 0.3.29') diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index c482062592..3b07e4cdba 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -309,7 +309,7 @@ module AbstractController # still does a dynamic lookup. In next Rails release, we should @_layout # to be inheritable so we can skip the child lookup if the parent explicitly # set the layout. - parent = self.superclass.instance_variable_get(:@_layout) + parent = self.superclass.instance_eval { @_layout if defined?(@_layout) } @_layout = nil inspect = parent.is_a?(Proc) ? parent.inspect : parent diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 4c76f4c43b..194f26aefc 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -20,7 +20,8 @@ module ActionController status = payload[:status] if status.nil? && payload[:exception].present? - status = Rack::Utils.status_code(ActionDispatch::ExceptionWrapper.new({}, payload[:exception]).status_code) + exception_class_name = payload[:exception].first + status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name) end message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration message << " (#{additions.join(" | ")})" unless additions.blank? diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 05e3cd40b5..d486f32c01 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -14,13 +14,13 @@ module ActionController end def setup_subscriptions - @partials = Hash.new(0) - @templates = Hash.new(0) - @layouts = Hash.new(0) + @_partials = Hash.new(0) + @_templates = Hash.new(0) + @_layouts = Hash.new(0) ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload| path = payload[:layout] - @layouts[path] += 1 + @_layouts[path] += 1 end ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload| @@ -28,11 +28,11 @@ module ActionController next unless path partial = path =~ /^.*\/_[^\/]*$/ if partial - @partials[path] += 1 - @partials[path.split("/").last] += 1 - @templates[path] += 1 + @_partials[path] += 1 + @_partials[path.split("/").last] += 1 + @_templates[path] += 1 else - @templates[path] += 1 + @_templates[path] += 1 end end end @@ -43,9 +43,9 @@ module ActionController end def process(*args) - @partials = Hash.new(0) - @templates = Hash.new(0) - @layouts = Hash.new(0) + @_partials = Hash.new(0) + @_templates = Hash.new(0) + @_layouts = Hash.new(0) super end @@ -72,43 +72,54 @@ module ActionController validate_request! case options - when NilClass, String, Symbol + when NilClass, Regexp, String, Symbol options = options.to_s if Symbol === options - rendered = @templates + rendered = @_templates msg = build_message(message, "expecting <?> but rendering with <?>", options, rendered.keys.join(', ')) - assert_block(msg) do - if options + matches_template = + case options + when String + rendered.any? do |t, num| + options_splited = options.split(File::SEPARATOR) + t_splited = t.split(File::SEPARATOR) + t_splited.last(options_splited.size) == options_splited + end + when Regexp rendered.any? { |t,num| t.match(options) } - else - @templates.blank? + when NilClass + rendered.blank? end - end + assert matches_template, msg when Hash if expected_layout = options[:layout] msg = build_message(message, "expecting layout <?> but action rendered <?>", - expected_layout, @layouts.keys) + expected_layout, @_layouts.keys) case expected_layout when String - assert(@layouts.keys.include?(expected_layout), msg) + assert(@_layouts.keys.include?(expected_layout), msg) when Regexp - assert(@layouts.keys.any? {|l| l =~ expected_layout }, msg) + assert(@_layouts.keys.any? {|l| l =~ expected_layout }, msg) when nil - assert(@layouts.empty?, msg) + assert(@_layouts.empty?, msg) end end if expected_partial = options[:partial] if expected_locals = options[:locals] - actual_locals = @locals[expected_partial.to_s.sub(/^_/,'')] - expected_locals.each_pair do |k,v| - assert_equal(v, actual_locals[k]) + if defined?(@locals) + actual_locals = @locals[expected_partial.to_s.sub(/^_/,'')] + expected_locals.each_pair do |k,v| + assert_equal(v, actual_locals[k]) + end + else + warn "the :locals option to #assert_template is only supported in a ActionView::TestCase" end elsif expected_count = options[:count] - actual_count = @partials[expected_partial] + actual_count = @_partials[expected_partial] msg = build_message(message, "expecting ? to be rendered ? time(s) but rendered ? time(s)", expected_partial, expected_count, actual_count) @@ -116,11 +127,11 @@ module ActionController else msg = build_message(message, "expecting partial <?> but action rendered <?>", - options[:partial], @partials.keys) - assert(@partials.include?(expected_partial), msg) + options[:partial], @_partials.keys) + assert(@_partials.include?(expected_partial), msg) end else - assert @partials.empty?, + assert @_partials.empty?, "Expected no partials to be rendered" end end @@ -422,7 +433,7 @@ module ActionController Hash[hash_or_array_or_value.map{|key, value| [key, paramify_values(value)] }] when Array hash_or_array_or_value.map {|i| paramify_values(i)} - when Rack::Test::UploadedFile + when Rack::Test::UploadedFile, ActionDispatch::Http::UploadedFile hash_or_array_or_value else hash_or_array_or_value.to_param diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 94fa747a79..eb95f79e0b 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -19,7 +19,7 @@ module ActionDispatch [:open, :path, :rewind, :size].each do |method| class_eval "def #{method}; @tempfile.#{method}; end" end - + private def encode_filename(filename) # Encode the filename in the utf8 encoding, unless it is nil or we're in 1.8 diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index c0532c80c4..0f49dc65bf 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -34,7 +34,7 @@ module ActionDispatch end def status_code - Rack::Utils.status_code(@@rescue_responses[@exception.class.name]) + self.class.status_code_for_exception(@exception.class.name) end def application_trace @@ -49,6 +49,10 @@ module ActionDispatch clean_backtrace(:all) end + def self.status_code_for_exception(class_name) + Rack::Utils.status_code(@@rescue_responses[class_name]) + end + private def original_exception(exception) @@ -75,4 +79,4 @@ module ActionDispatch @backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner'] end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 40ff69693b..cd2f464506 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -166,7 +166,7 @@ module ActionDispatch controller ||= default_controller action ||= default_action - unless controller.is_a?(Regexp) || to_shorthand + unless controller.is_a?(Regexp) controller = [@scope[:module], controller].compact.join("/").presence end @@ -982,7 +982,7 @@ module ActionDispatch # === Options # Takes same options as +resources+. def resource(*resources, &block) - options = resources.extract_options! + options = resources.extract_options!.dup if apply_common_behavior_for(:resource, resources, options, &block) return self @@ -1120,7 +1120,7 @@ module ActionDispatch # # resource actions are at /admin/posts. # resources :posts, :path => "admin/posts" def resources(*resources, &block) - options = resources.extract_options! + options = resources.extract_options!.dup if apply_common_behavior_for(:resources, resources, options, &block) return self diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 1f1cd3cee3..812bb4de9e 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -633,7 +633,9 @@ module ActionView end def link_to_remote_options?(options) - options.is_a?(Hash) && options.key?('remote') && options.delete('remote') + if options.is_a?(Hash) + options.delete('remote') || options.delete(:remote) + end end def add_method_to_attributes!(html_options, method) diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 658a503f7f..dfaa1d88e4 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -193,16 +193,16 @@ module ActionView @_result @_routes @controller - @layouts + @_layouts @locals @method_name @output_buffer - @partials + @_partials @passed @rendered @request @routes - @templates + @_templates @options @test_passed @view diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb index 8f0e0f8ee1..690c71b472 100644 --- a/actionpack/lib/sprockets/helpers/rails_helper.rb +++ b/actionpack/lib/sprockets/helpers/rails_helper.rb @@ -147,7 +147,9 @@ module Sprockets if source[0] == ?/ source else - source = digest_for(source) unless options[:digest] == false + if digest_assets && options[:digest] != false + source = digest_for(source) + end source = File.join(dir, source) source = "/#{source}" unless source =~ /^\// source diff --git a/actionpack/lib/sprockets/static_compiler.rb b/actionpack/lib/sprockets/static_compiler.rb index 2e2db4b760..4341a27d5d 100644 --- a/actionpack/lib/sprockets/static_compiler.rb +++ b/actionpack/lib/sprockets/static_compiler.rb @@ -15,13 +15,11 @@ module Sprockets def compile manifest = {} - env.each_logical_path do |logical_path| - if File.basename(logical_path)[/[^\.]+/, 0] == 'index' - logical_path.sub!(/\/index\./, '.') - end - next unless compile_path?(logical_path) + env.each_logical_path(paths) do |logical_path| if asset = env.find_asset(logical_path) - manifest[logical_path] = write_asset(asset) + digest_path = write_asset(asset) + manifest[asset.logical_path] = digest_path + manifest[aliased_path_for(asset.logical_path)] = digest_path end end write_manifest(manifest) if @manifest @@ -43,22 +41,16 @@ module Sprockets end end - def compile_path?(logical_path) - paths.each do |path| - case path - when Regexp - return true if path.match(logical_path) - when Proc - return true if path.call(logical_path) - else - return true if File.fnmatch(path.to_s, logical_path) - end - end - false - end - def path_for(asset) @digest ? asset.digest_path : asset.logical_path end + + def aliased_path_for(logical_path) + if File.basename(logical_path).start_with?('index') + logical_path.sub(/\/index([^\/]+)$/, '\1') + else + logical_path.sub(/\.([^\/]+)$/, '/index.\1') + end + end end end diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 5252e43c25..81cf096952 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -7,6 +7,7 @@ class ActionPackAssertionsController < ActionController::Base def nothing() head :ok end def hello_world() render :template => "test/hello_world"; end + def hello_repeating_in_path() render :template => "test/hello/hello"; end def hello_xml_world() render :template => "test/hello_xml_world"; end @@ -469,6 +470,20 @@ class AssertTemplateTest < ActionController::TestCase end end + def test_fails_with_incorrect_string_that_matches + get :hello_world + assert_raise(ActiveSupport::TestCase::Assertion) do + assert_template 'est/he' + end + end + + def test_fails_with_repeated_name_in_path + get :hello_repeating_in_path + assert_raise(ActiveSupport::TestCase::Assertion) do + assert_template 'test/hello' + end + end + def test_fails_with_incorrect_symbol get :hello_world assert_raise(ActiveSupport::TestCase::Assertion) do @@ -476,6 +491,13 @@ class AssertTemplateTest < ActionController::TestCase end end + def test_fails_with_incorrect_symbol_that_matches + get :hello_world + assert_raise(ActiveSupport::TestCase::Assertion) do + assert_template :"est/he" + end + end + def test_fails_with_wrong_layout get :render_with_layout assert_raise(ActiveSupport::TestCase::Assertion) do diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index 700fd788fa..a72b6dde1a 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -54,6 +54,10 @@ module Another def with_rescued_exception raise SpecialException end + + def with_action_not_found + raise AbstractController::ActionNotFound + end end end @@ -225,6 +229,17 @@ class ACLogSubscriberTest < ActionController::TestCase assert_match(/Completed 406/, logs.last) end + def test_process_action_with_with_action_not_found_logs_404 + begin + get :with_action_not_found + wait + rescue AbstractController::ActionNotFound + end + + assert_equal 2, logs.size + assert_match(/Completed 404/, logs.last) + end + def logs @logs ||= @logger.logged(:info) end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index afaf9e5ea9..8c631e218f 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -1405,6 +1405,17 @@ class RenderTest < ActionController::TestCase assert_equal "Bonjour: davidBonjour: mary", @response.body end + def test_locals_option_to_assert_template_is_not_supported + warning_buffer = StringIO.new + $stderr = warning_buffer + + get :partial_collection_with_locals + assert_template :partial => 'customer_greeting', :locals => { :greeting => 'Bonjour' } + assert_equal "the :locals option to #assert_template is only supported in a ActionView::TestCase\n", warning_buffer.string + ensure + $stderr = STDERR + end + def test_partial_collection_with_spacer get :partial_collection_with_spacer assert_equal "Hello: davidonly partialHello: mary", @response.body diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index 6a5843f9d7..1da5298900 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -766,6 +766,12 @@ XML assert_equal '159528', @response.body end + def test_action_dispatch_uploaded_file_upload + tf = Class.new { def size; 159528 end } + post :test_file_upload, :file => ActionDispatch::Http::UploadedFile.new(:tempfile => tf.new) + assert_equal '159528', @response.body + end + def test_test_uploaded_file_exception_when_file_doesnt_exist assert_raise(RuntimeError) { Rack::Test::UploadedFile.new('non_existent_file') } end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 00c71dc8be..f32b1b9c71 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -362,6 +362,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest resources :errors, :shallow => true do resources :notices end + get 'api/version' end scope :path => 'api' do @@ -1185,6 +1186,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_resource_does_not_modify_passed_options + options = {:id => /.+?/, :format => /json|xml/} + self.class.stub_controllers do |routes| + routes.draw do + resource :user, options + end + end + assert_equal({:id => /.+?/, :format => /json|xml/}, options) + end + + def test_resources_does_not_modify_passed_options + options = {:id => /.+?/, :format => /json|xml/} + self.class.stub_controllers do |routes| + routes.draw do + resources :users, options + end + end + assert_equal({:id => /.+?/, :format => /json|xml/}, options) + end + def test_path_names with_test_routes do get '/pt/projetos' @@ -1376,6 +1397,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_match_shorthand_with_module + assert_equal '/api/version', api_version_path + get '/api/version' + assert_equal 'api/api#version', @response.body + end + def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper with_test_routes do assert_equal '/replies', replies_path diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb index 7e4a1519fb..fae39a403e 100644 --- a/actionpack/test/dispatch/uploaded_file_test.rb +++ b/actionpack/test/dispatch/uploaded_file_test.rb @@ -12,7 +12,7 @@ module ActionDispatch uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new) assert_equal 'foo', uf.original_filename end - + if "ruby".encoding_aware? def test_filename_should_be_in_utf_8 uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new) diff --git a/actionpack/test/fixtures/test/hello/hello.erb b/actionpack/test/fixtures/test/hello/hello.erb new file mode 100644 index 0000000000..6769dd60bd --- /dev/null +++ b/actionpack/test/fixtures/test/hello/hello.erb @@ -0,0 +1 @@ +Hello world!
\ No newline at end of file diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb index b908b6777c..72d03e43e9 100644 --- a/actionpack/test/template/sprockets_helper_test.rb +++ b/actionpack/test/template/sprockets_helper_test.rb @@ -360,4 +360,12 @@ class SprocketsHelperTest < ActionView::TestCase assert_equal '/assets/logo.png', asset_path("logo.png") end + + test "`config.digest = false` works with `config.compile = false`" do + @config.assets.digest = false + @config.assets.compile = false + + assert_equal '/assets/logo.png', + asset_path("logo.png") + end end diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index d55b1fd2bd..38f77203e0 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -227,6 +227,13 @@ class UrlHelperTest < ActiveSupport::TestCase ) end + def test_link_to_with_symbolic_remote_in_non_html_options + assert_dom_equal( + "<a href=\"/\" data-remote=\"true\">Hello</a>", + link_to("Hello", hash_for([:remote, true]), {}) + ) + end + def test_link_tag_using_post_javascript assert_dom_equal( "<a href='http://www.example.com' data-method=\"post\" rel=\"nofollow\">Hello</a>", diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 2a3c51172b..d3056e73a2 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,16 @@ +## Rails 3.2.9 (unreleased) + +* Due to a change in builder, nil values and empty strings now generates + closed tags, so instead of this: + + <pseudonyms nil=\"true\"></pseudonyms> + + It generates this: + + <pseudonyms nil=\"true\"/> + + *Carlos Antonio da Silva* + ## Rails 3.2.8 (Aug 9, 2012) ## * No changes. diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index f38121c175..45d8677fa0 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -5,6 +5,7 @@ require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/hash/except' require 'active_model/errors' require 'active_model/validations/callbacks' +require 'active_model/validator' module ActiveModel diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb index 38aecf51ff..7f14b21cf4 100644 --- a/activemodel/test/cases/serializers/xml_serialization_test.rb +++ b/activemodel/test/cases/serializers/xml_serialization_test.rb @@ -132,7 +132,7 @@ class XmlSerializationTest < ActiveModel::TestCase end test "should serialize nil" do - assert_match %r{<pseudonyms nil=\"true\"></pseudonyms>}, @contact.to_xml(:methods => :pseudonyms) + assert_match %r{<pseudonyms nil=\"true\"/>}, @contact.to_xml(:methods => :pseudonyms) end test "should serialize integer" do diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index c836886c08..aafdbec25c 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,92 @@ ## Rails 3.2.9 (unreleased) +* Fix AR#dup to nullify the validation errors in the dup'ed object. Previously the original + and the dup'ed object shared the same errors. + + * Christian Seiler* + +* Synchronize around deleting from the reserved connections hash. + Fixes #7955 + +* PostgreSQL adapter correctly fetches default values when using + multiple schemas and domains in a db. Fixes #7914 + + *Arturo Pie* + +* Fix deprecation notice when loading a collection association that + selects columns from other tables, if a new record was previously + built using that association. + + *Ernie Miller* + +* The postgres adapter now supports tables with capital letters. + Fix #5920 + + *Yves Senn* + +* `CollectionAssociation#count` returns `0` without querying if the + parent record is not persisted. + + Before: + + person.pets.count + # SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" IS NULL + # => 0 + + After: + + person.pets.count + # fires without sql query + # => 0 + + *Francesco Rodriguez* + +* Fix `reset_counters` crashing on `has_many :through` associations. + Fix #7822. + + *lulalala* + +* ConnectionPool recognizes checkout_timeout spec key as taking + precedence over legacy wait_timeout spec key, can be used to avoid + conflict with mysql2 use of wait_timeout. Closes #7684. + + *jrochkind* + +* Rename field_changed? to _field_changed? so that users can create a field named field + + *Akira Matsuda*, backported by *Steve Klabnik* + +* Fix creation of through association models when using `collection=[]` + on a `has_many :through` association from an unsaved model. + Fix #7661. + + *Ernie Miller* + +* Explain only normal CRUD sql (select / update / insert / delete). + Fix problem that explains unexplainable sql. Closes #7544 #6458. + + *kennyj* + +* Backport test coverage to ensure that PostgreSQL auto-reconnect functionality + remains healthy. + + *Steve Jorgensen* + +* Use config['encoding'] instead of config['charset'] when executing + databases.rake in the mysql/mysql2. A correct option for a database.yml + is 'encoding'. + + *kennyj* + +* Fix ConnectionAdapters::Column.type_cast_code integer conversion, + to always convert values to integer calling #to_i. Fixes #7509. + + *Thiago Pradi* + +* Fix time column type casting for invalid time string values to correctly return nil. + + *Adam Meehan* + * Fix `becomes` when using a configured `inheritance_column`. *Yves Senn* diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 5aa9e26fa6..b74ff62c09 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -135,11 +135,13 @@ module ActiveRecord autoload :HasAndBelongsToMany, 'active_record/associations/builder/has_and_belongs_to_many' end - autoload :Preloader, 'active_record/associations/preloader' - autoload :JoinDependency, 'active_record/associations/join_dependency' - autoload :AssociationScope, 'active_record/associations/association_scope' - autoload :AliasTracker, 'active_record/associations/alias_tracker' - autoload :JoinHelper, 'active_record/associations/join_helper' + eager_autoload do + autoload :Preloader, 'active_record/associations/preloader' + autoload :JoinDependency, 'active_record/associations/join_dependency' + autoload :AssociationScope, 'active_record/associations/association_scope' + autoload :AliasTracker, 'active_record/associations/alias_tracker' + autoload :JoinHelper, 'active_record/associations/join_helper' + end # Clears out the association cache. def clear_association_cache #:nodoc: diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index d929a45c92..2c852f6efc 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -190,6 +190,8 @@ module ActiveRecord # association, it will be used for the query. Otherwise, construct options and pass them with # scope to the target class's +count+. def count(column_name = nil, count_options = {}) + return 0 if owner.new_record? + column_name, count_options = nil, column_name if column_name.is_a?(Hash) if options[:counter_sql] || options[:finder_sql] @@ -362,7 +364,7 @@ module ActiveRecord # replace the SELECT clause with COUNT(SELECTS), preserving any hints within /* ... */ interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) do count_with = $2.to_s - count_with = '*' if count_with.blank? || count_with =~ /,/ + count_with = '*' if count_with.blank? || count_with =~ /,/ || count_with =~ /\.\*/ "SELECT #{$1}COUNT(#{count_with}) FROM" end end @@ -407,7 +409,7 @@ module ActiveRecord if mem_index mem_record = memory.delete_at(mem_index) - (record.attribute_names - mem_record.changes.keys).each do |name| + ((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name| mem_record[name] = record[name] end diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 53d49fef2e..ce81333aa9 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -38,6 +38,20 @@ module ActiveRecord super end + def concat_records(records) + ensure_not_nested + + records = super + + if owner.new_record? && records + records.flatten.each do |record| + build_through_record(record) + end + end + + records + end + def insert_record(record, validate = true, raise = false) ensure_not_nested diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index fafed94ff2..12cfdd4622 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -30,17 +30,21 @@ module ActiveRecord # option references an association's column), it will fallback to the table # join strategy. class Preloader #:nodoc: - autoload :Association, 'active_record/associations/preloader/association' - autoload :SingularAssociation, 'active_record/associations/preloader/singular_association' - autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association' - autoload :ThroughAssociation, 'active_record/associations/preloader/through_association' + extend ActiveSupport::Autoload - autoload :HasMany, 'active_record/associations/preloader/has_many' - autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through' - autoload :HasOne, 'active_record/associations/preloader/has_one' - autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through' - autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many' - autoload :BelongsTo, 'active_record/associations/preloader/belongs_to' + eager_autoload do + autoload :Association, 'active_record/associations/preloader/association' + autoload :SingularAssociation, 'active_record/associations/preloader/singular_association' + autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association' + autoload :ThroughAssociation, 'active_record/associations/preloader/through_association' + + autoload :HasMany, 'active_record/associations/preloader/has_many' + autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through' + autoload :HasOne, 'active_record/associations/preloader/has_one' + autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through' + autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many' + autoload :BelongsTo, 'active_record/associations/preloader/belongs_to' + end attr_reader :records, :associations, :options, :model diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index ed863a9696..9cd8954496 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -55,12 +55,12 @@ module ActiveRecord # The attribute already has an unsaved change. if attribute_changed?(attr) old = @changed_attributes[attr] - @changed_attributes.delete(attr) unless field_changed?(attr, old, value) + @changed_attributes.delete(attr) unless _field_changed?(attr, old, value) else old = clone_attribute_value(:read_attribute, attr) # Save Time objects as TimeWithZone if time_zone_aware_attributes == true old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old) - @changed_attributes[attr] = old if field_changed?(attr, old, value) + @changed_attributes[attr] = old if _field_changed?(attr, old, value) end # Carry on. @@ -77,7 +77,7 @@ module ActiveRecord end end - def field_changed?(attr, old, value) + def _field_changed?(attr, old, value) if column = column_for_attribute(attr) if column.number? && (changes_from_nil_to_empty_string?(column, old, value) || changes_from_zero_to_string?(old, value)) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b51bb5cc5e..62c8110274 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -450,12 +450,12 @@ module ActiveRecord #:nodoc: private def relation #:nodoc: - @relation ||= Relation.new(self, arel_table) + relation = Relation.new(self, arel_table) if finder_needs_type_condition? - @relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) + relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) else - @relation + relation end end end @@ -489,7 +489,6 @@ module ActiveRecord #:nodoc: @marked_for_destruction = false @previously_changed = {} @changed_attributes = {} - @relation = nil ensure_proper_type @@ -544,7 +543,7 @@ module ActiveRecord #:nodoc: @changed_attributes = {} self.class.column_defaults.each do |attr, orig_value| - @changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr]) + @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr]) end @aggregation_cache = {} 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 d4649102df..6cb97bc550 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -54,8 +54,11 @@ module ActiveRecord # your database connection configuration: # # * +pool+: number indicating size of connection pool (default 5) - # * +wait_timeout+: number of seconds to block and wait for a connection - # before giving up and raising a timeout error (default 5 seconds). + # * +checkout _timeout+: number of seconds to block and wait for a + # connection before giving up and raising a timeout error + # (default 5 seconds). ('wait_timeout' supported for backwards + # compatibility, but conflicts with key used for different purpose + # by mysql2 adapter). class ConnectionPool include MonitorMixin @@ -77,7 +80,10 @@ module ActiveRecord @reserved_connections = {} @queue = new_cond - @timeout = spec.config[:wait_timeout] || 5 + # 'wait_timeout', the backward-compatible key, conflicts with spec key + # used by mysql2 for something entirely different, checkout_timeout + # preferred to avoid conflict and allow independent values. + @timeout = spec.config[:checkout_timeout] || spec.config[:wait_timeout] || 5 # default max pool size to 5 @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 @@ -110,7 +116,7 @@ module ActiveRecord # #release_connection releases the connection-thread association # and returns the connection to the pool. def release_connection(with_id = current_connection_id) - conn = @reserved_connections.delete(with_id) + conn = synchronize { @reserved_connections.delete(with_id) } checkin conn if conn end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 393a3b623a..d38e8464c5 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -75,7 +75,7 @@ module ActiveRecord case type when :string, :text then value - when :integer then value.to_i rescue value ? 1 : 0 + when :integer then value.to_i when :float then value.to_f when :decimal then klass.value_to_decimal(value) when :datetime, :timestamp then klass.string_to_time(value) @@ -92,7 +92,7 @@ module ActiveRecord case type when :string, :text then var_name - when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)" + when :integer then "(#{var_name}.to_i)" when :float then "#{var_name}.to_f" when :decimal then "#{klass}.value_to_decimal(#{var_name})" when :datetime, :timestamp then "#{klass}.string_to_time(#{var_name})" @@ -150,7 +150,13 @@ module ActiveRecord return string unless string.is_a?(String) return nil if string.empty? - string_to_time "2000-01-01 #{string}" + dummy_time_string = "2000-01-01 #{string}" + + fast_string_to_time(dummy_time_string) || begin + time_hash = Date._parse(dummy_time_string) + return nil if time_hash[:hour].nil? + new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction)) + end end # convert something to a boolean diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 3dfd150d5a..80a0c6d13c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -145,11 +145,8 @@ module ActiveRecord when /\A\(?(-?\d+(\.\d*)?\)?)\z/ $1 # Character types - when /\A'(.*)'::(?:character varying|bpchar|text)\z/m + when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m $1 - # Character types (8.1 formatting) - when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m - $1.gsub(/\\(\d\d\d)/) { $1.oct.chr } # Binary data types when /\A'(.*)'::bytea\z/m $1 @@ -354,6 +351,7 @@ module ActiveRecord def reconnect! clear_cache! @connection.reset + @open_transactions = 0 configure_connection end @@ -816,7 +814,7 @@ module ActiveRecord # Returns an array of indexes for the given table. def indexes(table_name, name = nil) - result = query(<<-SQL, name) + result = query(<<-SQL, 'SCHEMA') SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid FROM pg_class t INNER JOIN pg_index d ON t.oid = d.indrelid @@ -836,7 +834,7 @@ module ActiveRecord inddef = row[3] oid = row[4] - columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")] + columns = Hash[query(<<-SQL, "SCHEMA")] SELECT a.attnum, a.attname FROM pg_attribute a WHERE a.attrelid = #{oid} @@ -848,7 +846,7 @@ module ActiveRecord # add info on sort order for columns (only desc order is explicitly specified, asc is the default) desc_order_columns = inddef.scan(/(\w+) DESC/).flatten orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {} - + column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders) end.compact end @@ -863,7 +861,7 @@ module ActiveRecord # Returns the current database name. def current_database - query('select current_database()')[0][0] + query('select current_database()', 'SCHEMA')[0][0] end # Returns the current schema name. @@ -873,7 +871,7 @@ module ActiveRecord # Returns the current database encoding format. def encoding - query(<<-end_sql)[0][0] + query(<<-end_sql, 'SCHEMA')[0][0] SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database WHERE pg_database.datname LIKE '#{current_database}' end_sql @@ -936,7 +934,7 @@ module ActiveRecord if pk && sequence quoted_sequence = quote_table_name(sequence) - select_value <<-end_sql, 'Reset sequence' + select_value <<-end_sql, 'SCHEMA' SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false) end_sql end @@ -946,7 +944,7 @@ module ActiveRecord def pk_and_sequence_for(table) #:nodoc: # First try looking for a sequence with a dependency on the # given table's primary key. - result = query(<<-end_sql, 'PK and serial sequence')[0] + result = query(<<-end_sql, 'SCHEMA')[0] SELECT attr.attname, seq.relname FROM pg_class seq, pg_attribute attr, @@ -964,16 +962,13 @@ module ActiveRecord end_sql if result.nil? or result.empty? - # If that fails, try parsing the primary key's default value. - # Support the 7.x and 8.0 nextval('foo'::text) as well as - # the 8.1+ nextval('foo'::regclass). - result = query(<<-end_sql, 'PK and custom sequence')[0] + result = query(<<-end_sql, 'SCHEMA')[0] SELECT attr.attname, CASE - WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN - substr(split_part(def.adsrc, '''', 2), - strpos(split_part(def.adsrc, '''', 2), '.')+1) - ELSE split_part(def.adsrc, '''', 2) + WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN + substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), + strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1) + ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) END FROM pg_class t JOIN pg_attribute attr ON (t.oid = attrelid) @@ -981,7 +976,7 @@ module ActiveRecord JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1]) WHERE t.oid = '#{quote_table_name(table)}'::regclass AND cons.contype = 'p' - AND def.adsrc ~* 'nextval' + AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval' end_sql end @@ -998,7 +993,7 @@ module ActiveRecord INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1] WHERE cons.contype = 'p' - AND dep.refobjid = '#{table}'::regclass + AND dep.refobjid = '#{quote_table_name(table)}'::regclass end_sql row && row.first @@ -1085,7 +1080,7 @@ module ActiveRecord end when 'integer' return 'integer' unless limit - + case limit when 1, 2; 'smallint' when 3, 4; 'integer' @@ -1281,7 +1276,8 @@ module ActiveRecord # - ::regclass is a function that gives the id for a table name def column_definitions(table_name) #:nodoc: exec_query(<<-end_sql, 'SCHEMA').rows - SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull + SELECT a.attname, format_type(a.atttypid, a.atttypmod), + pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod FROM pg_attribute a LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 00f74318c0..731f7e2049 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -359,12 +359,12 @@ module ActiveRecord # Returns an array of indexes for the given table. def indexes(table_name, name = nil) #:nodoc: - exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row| + exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row| IndexDefinition.new( table_name, row['name'], row['unique'] != 0, - exec_query("PRAGMA index_info('#{row['name']}')").map { |col| + exec_query("PRAGMA index_info('#{row['name']}')", 'SCHEMA').map { |col| col['name'] }) end diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 0ba54e6248..1ee465ab21 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -25,6 +25,10 @@ module ActiveRecord self.name end + if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection + has_many_association = has_many_association.through_reflection + end + foreign_key = has_many_association.foreign_key.to_s child_class = has_many_association.klass belongs_to = child_class.reflect_on_all_associations(:belongs_to) diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb index 1f8c4fc203..859c8edfc5 100644 --- a/activerecord/lib/active_record/explain_subscriber.rb +++ b/activerecord/lib/active_record/explain_subscriber.rb @@ -15,8 +15,9 @@ module ActiveRecord # On the other hand, we want to monitor the performance of our real database # queries, not the performance of the access to the query cache. IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE) + EXPLAINED_SQLS = /\A\s*(select|update|delete|insert)/i def ignore_payload?(payload) - payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) + payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ EXPLAINED_SQLS end ActiveSupport::Notifications.subscribe("sql.active_record", new) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 83d9126ccb..987bb558e5 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -44,7 +44,7 @@ db_namespace = namespace :db do def mysql_creation_options(config) @charset = ENV['CHARSET'] || 'utf8' @collation = ENV['COLLATION'] || 'utf8_unicode_ci' - {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)} + {:charset => (config['encoding'] || @charset), :collation => (config['collation'] || @collation)} end def create_database(config) @@ -96,8 +96,8 @@ db_namespace = namespace :db do ActiveRecord::Base.establish_connection(config) else $stderr.puts sqlerr.error - $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['charset'] || @charset}, collation: #{config['collation'] || @collation}" - $stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['charset'] + $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['encoding'] || @charset}, collation: #{config['collation'] || @collation}" + $stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['encoding'] end end when /postgresql/ @@ -369,7 +369,7 @@ db_namespace = namespace :db do end end - task :load_if_ruby => [:environment, 'db:create'] do + task :load_if_ruby => ['db:create', :environment] do db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby end end @@ -412,7 +412,7 @@ db_namespace = namespace :db do # desc "Recreate the databases from the structure.sql file" task :load => [:environment, :load_config] do - env = ENV['RAILS_ENV'] || 'test' + env = Rails.env abcs = ActiveRecord::Base.configurations filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql") @@ -445,7 +445,7 @@ db_namespace = namespace :db do end end - task :load_if_sql => [:environment, 'db:create'] do + task :load_if_sql => ['db:create', :environment] do db_namespace["structure:load"].invoke if ActiveRecord::Base.schema_format == :sql end end diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 9c50baa647..d6b0265fb3 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -34,7 +34,7 @@ module ActiveRecord if current_scope current_scope.clone else - scope = relation.clone + scope = relation scope.default_scoped = true scope end @@ -48,7 +48,7 @@ module ActiveRecord if current_scope current_scope.scope_for_create else - scope = relation.clone + scope = relation scope.default_scoped = true scope.scope_for_create end diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 0c760e9850..ab25dc52f9 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -39,6 +39,7 @@ module ActiveRecord def initialize_dup(other) clear_timestamp_attributes + super end private diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 1b14df70e0..d0b51ef6a7 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -329,6 +329,7 @@ module ActiveRecord @_start_transaction_state[:destroyed] = @destroyed end @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1 + @_start_transaction_state[:frozen?] = @attributes.frozen? end # Clear the new record state and id of a record. @@ -345,8 +346,8 @@ module ActiveRecord @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 if @_start_transaction_state[:level] < 1 || force restore_state = remove_instance_variable(:@_start_transaction_state) - was_frozen = @attributes.frozen? - @attributes = @attributes.dup if was_frozen + was_frozen = restore_state[:frozen?] + @attributes = @attributes.dup if @attributes.frozen? @new_record = restore_state[:new_record] @destroyed = restore_state[:destroyed] if restore_state.has_key?(:id) diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 21b97b3b39..28cdd1d588 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -10,5 +10,79 @@ module ActiveRecord def test_encoding assert_not_nil @connection.encoding end + + def test_reconnection_after_simulated_disconnection_with_verify + assert @connection.active? + original_connection_pid = @connection.query('select pg_backend_pid()') + + # Fail with bad connection on next query attempt. + raw_connection = @connection.raw_connection + raw_connection_class = class << raw_connection ; self ; end + raw_connection_class.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def query_fake(*args) + if !( @called ||= false ) + self.stubs(:status).returns(PGconn::CONNECTION_BAD) + @called = true + raise PGError + else + self.unstub(:status) + query_unfake(*args) + end + end + + alias query_unfake query + alias query query_fake + CODE + + begin + @connection.verify! + new_connection_pid = @connection.query('select pg_backend_pid()') + ensure + raw_connection_class.class_eval <<-CODE + alias query query_unfake + undef query_fake + CODE + end + + assert_not_equal original_connection_pid, new_connection_pid, "Should have a new underlying connection pid" + end + + # Must have with_manual_interventions set to true for this + # test to run. + # When prompted, restart the PostgreSQL server with the + # "-m fast" option or kill the individual connection assuming + # you know the incantation to do that. + # To restart PostgreSQL 9.1 on OS X, installed via MacPorts, ... + # sudo su postgres -c "pg_ctl restart -D /opt/local/var/db/postgresql91/defaultdb/ -m fast" + def test_reconnection_after_actual_disconnection_with_verify + return skip "with_manual_interventions is false in configuration" unless ARTest.config['with_manual_interventions'] + + original_connection_pid = @connection.query('select pg_backend_pid()') + + # Sanity check. + assert @connection.active? + + puts 'Kill the connection now (e.g. by restarting the PostgreSQL ' + + 'server with the "-m fast" option) and then press enter.' + $stdin.gets + + @connection.verify! + + assert @connection.active? + + # If we get no exception here, then either we re-connected successfully, or + # we never actually got disconnected. + new_connection_pid = @connection.query('select pg_backend_pid()') + + assert_not_equal original_connection_pid, new_connection_pid, + "umm -- looks like you didn't break the connection, because we're still " + + "successfully querying with the same connection pid." + + # Repair all fixture connections so other tests won't break. + @fixture_connections.each do |c| + c.verify! + end + end + end end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 3eb73d3093..0de3786eb8 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -14,6 +14,10 @@ module ActiveRecord assert_equal 'id', @connection.primary_key('ex') end + def test_primary_key_works_tables_containing_capital_letters + assert_equal 'id', @connection.primary_key('CamelCase') + end + def test_non_standard_primary_key @connection.exec_query('drop table if exists ex') @connection.exec_query('create table ex(data character varying(255) primary key)') diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 1160d236c9..e7d1893ff3 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -63,6 +63,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal apple.id, citibank.firm_id end + def test_id_assignment + apple = Firm.create("name" => "Apple") + citibank = Account.create("credit_limit" => 10) + assert_raise(NoMethodError) { citibank.firm_id = apple } + end + def test_natural_assignment_with_primary_key apple = Firm.create("name" => "Apple") citibank = Client.create("name" => "Primary key client") diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index bacab8d2f2..cad4cc9bf9 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -812,6 +812,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal 1, developer.projects.count end + def test_counting_should_not_fire_sql_if_parent_is_unsaved + assert_no_queries do + assert_equal 0, Developer.new.projects.count + end + end + unless current_adapter?(:PostgreSQLAdapter) def test_count_with_finder_sql assert_equal 3, projects(:active_record).developers_with_finder_sql.count diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index a51ce26117..ed4475770b 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -42,9 +42,12 @@ class HasManyAssociationsTestForCountWithCountSql < ActiveRecord::TestCase end end -class HasManyAssociationsTestForCountDistinctWithFinderSql < ActiveRecord::TestCase +class HasManyAssociationsTestForCountWithVariousFinderSqls < ActiveRecord::TestCase class Invoice < ActiveRecord::Base has_many :custom_line_items, :class_name => 'LineItem', :finder_sql => "SELECT DISTINCT line_items.amount from line_items" + has_many :custom_full_line_items, :class_name => 'LineItem', :finder_sql => "SELECT line_items.invoice_id, line_items.amount from line_items" + has_many :custom_star_line_items, :class_name => 'LineItem', :finder_sql => "SELECT * from line_items" + has_many :custom_qualified_star_line_items, :class_name => 'LineItem', :finder_sql => "SELECT line_items.* from line_items" end def test_should_count_distinct_results @@ -55,6 +58,33 @@ class HasManyAssociationsTestForCountDistinctWithFinderSql < ActiveRecord::TestC assert_equal 1, invoice.custom_line_items.count end + + def test_should_count_results_with_multiple_fields + invoice = Invoice.new + invoice.custom_full_line_items << LineItem.new(:amount => 0) + invoice.custom_full_line_items << LineItem.new(:amount => 0) + invoice.save! + + assert_equal 2, invoice.custom_full_line_items.count + end + + def test_should_count_results_with_star + invoice = Invoice.new + invoice.custom_star_line_items << LineItem.new(:amount => 0) + invoice.custom_star_line_items << LineItem.new(:amount => 0) + invoice.save! + + assert_equal 2, invoice.custom_star_line_items.count + end + + def test_should_count_results_with_qualified_star + invoice = Invoice.new + invoice.custom_qualified_star_line_items << LineItem.new(:amount => 0) + invoice.custom_qualified_star_line_items << LineItem.new(:amount => 0) + invoice.save! + + assert_equal 2, invoice.custom_qualified_star_line_items.count + end end class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase @@ -271,6 +301,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal firm.limited_clients.length, firm.limited_clients.count end + def test_counting_should_not_fire_sql_if_parent_is_unsaved + assert_no_queries do + assert_equal 0, Person.new.readers.count + end + end + def test_finding assert_equal 2, Firm.find(:first, :order => "id").clients.length end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 4489c3e638..ffd7f4efa2 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -782,6 +782,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal 1, authors(:mary).categories.general.count end + def test_counting_should_not_fire_sql_if_parent_is_unsaved + assert_no_queries do + assert_equal 0, Person.new.posts.count + end + end + def test_has_many_through_belongs_to_should_update_when_the_through_foreign_key_changes post = posts(:eager_other) @@ -845,6 +851,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end + def test_assign_array_to_new_record_builds_join_records + c = Category.new(:name => 'Fishing', :authors => [Author.first]) + assert_equal 1, c.categorizations.size + end + def test_create_bang_should_raise_exception_when_join_record_has_errors repair_validations(Categorization) do Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' } diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index f4592f7d0e..8bd44e6444 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -236,6 +236,14 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase assert_equal "2", categories(:sti_test).authors_with_select.first.post_id.to_s end + def test_create_through_has_many_with_piggyback + category = categories(:sti_test) + ernie = category.authors_with_select.create(:name => 'Ernie') + assert_not_deprecated do + assert_equal ernie, category.authors_with_select.detect {|a| a.name == 'Ernie'} + end + end + def test_include_has_many_through posts = Post.find(:all, :order => 'posts.id') posts_with_authors = Post.find(:all, :include => :authors, :order => 'posts.id') diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index fc33438569..073e856e5e 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -919,6 +919,18 @@ class BasicsTest < ActiveRecord::TestCase assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time end + def test_attributes_on_dummy_time_with_invalid_time + # Oracle, and Sybase do not have a TIME datatype. + return true if current_adapter?(:OracleAdapter, :SybaseAdapter) + + attributes = { + "bonus_time" => "not a time" + } + topic = Topic.find(1) + topic.attributes = attributes + assert_nil topic.bonus_time + end + def test_boolean b_nil = Boolean.create({ "value" => nil }) nil_id = b_nil.id diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb index ccc57cb876..4fcf8a33a4 100644 --- a/activerecord/test/cases/column_test.rb +++ b/activerecord/test/cases/column_test.rb @@ -24,6 +24,30 @@ module ActiveRecord assert !column.type_cast('off') assert !column.type_cast('OFF') end + + def test_type_cast_integer + column = Column.new("field", nil, "integer") + assert_equal 1, column.type_cast(1) + assert_equal 1, column.type_cast('1') + assert_equal 1, column.type_cast('1ignore') + assert_equal 0, column.type_cast('bad1') + assert_equal 0, column.type_cast('bad') + assert_equal 1, column.type_cast(1.7) + assert_nil column.type_cast(nil) + end + + def test_type_cast_non_integer_to_integer + column = Column.new("field", nil, "integer") + assert_raises(NoMethodError) do + column.type_cast([]) + end + assert_raises(NoMethodError) do + column.type_cast(true) + end + assert_raises(NoMethodError) do + column.type_cast(false) + end + end end end end diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 5cecfa90e7..98759544b6 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -151,6 +151,28 @@ module ActiveRecord def test_pool_sets_connection_visitor assert @pool.connection.visitor.is_a?(Arel::Visitors::ToSql) end + + def test_timeout_spec_keys + # 'wait_timeout' is supported for backwards compat, + # 'checkout_timeout' is preferred to avoid conflicting + # with mysql2 adapters key of name 'wait_timeout' but + # different meaning. + config = ActiveRecord::Base.connection_pool.spec.config.merge(:wait_timeout => nil, :connection_timeout => nil) + method = ActiveRecord::Base.connection_pool.spec.adapter_method + + pool = ConnectionPool.new ActiveRecord::Base::ConnectionSpecification.new(config.merge(:wait_timeout => 1), method) + assert_equal 1, pool.instance_variable_get(:@timeout) + pool.disconnect! + + pool = ConnectionPool.new ActiveRecord::Base::ConnectionSpecification.new(config.merge(:checkout_timeout => 1), method) + assert_equal 1, pool.instance_variable_get(:@timeout) + pool.disconnect! + + pool = ConnectionPool.new ActiveRecord::Base::ConnectionSpecification.new(config.merge(:wait_timeout => 6000, :checkout_timeout => 1), method) + assert_equal 1, pool.instance_variable_get(:@timeout) + pool.disconnect! + end + end end end diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index ee443741ca..fc46a249c8 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -10,9 +10,12 @@ require 'models/dog' require 'models/dog_lover' require 'models/person' require 'models/friendship' +require 'models/subscriber' +require 'models/subscription' +require 'models/book' class CounterCacheTest < ActiveRecord::TestCase - fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers, :people, :friendships + fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers, :people, :friendships, :subscribers, :subscriptions, :books class ::SpecialTopic < ::Topic has_many :special_replies, :foreign_key => 'parent_id' @@ -118,4 +121,14 @@ class CounterCacheTest < ActiveRecord::TestCase Person.reset_counters(michael.id, :followers) end end + + test "reset counter of has_many :through association" do + subscriber = subscribers('second') + Subscriber.reset_counters(subscriber.id, 'books') + Subscriber.increment_counter('books_count', subscriber.id) + + assert_difference 'subscriber.reload.books_count', -1 do + Subscriber.reset_counters(subscriber.id, 'books') + end + end end diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index b3a281d960..c63553ed51 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -110,3 +110,43 @@ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) end end end + +if current_adapter?(:PostgreSQLAdapter) + class DefaultsUsingMultipleSchemasAndDomainTest < ActiveSupport::TestCase + def setup + @connection = ActiveRecord::Base.connection + + @old_search_path = @connection.schema_search_path + @connection.schema_search_path = "schema_1, pg_catalog" + @connection.create_table "defaults" do |t| + t.text "text_col", :default => "some value" + t.string "string_col", :default => "some value" + end + Default.reset_column_information + end + + def test_text_defaults_in_new_schema_when_overriding_domain + assert_equal "some value", Default.new.text_col, "Default of text column was not correctly parse" + end + + def test_string_defaults_in_new_schema_when_overriding_domain + assert_equal "some value", Default.new.string_col, "Default of string column was not correctly parse" + end + + def test_bpchar_defaults_in_new_schema_when_overriding_domain + @connection.execute "ALTER TABLE defaults ADD bpchar_col bpchar DEFAULT 'some value'" + Default.reset_column_information + assert_equal "some value", Default.new.bpchar_col, "Default of bpchar column was not correctly parse" + end + + def test_text_defaults_after_updating_column_default + @connection.execute "ALTER TABLE defaults ALTER COLUMN text_col SET DEFAULT 'some text'::schema_1.text" + assert_equal "some text", Default.new.text_col, "Default of text column was not correctly parse after updating default using '::text' since postgreSQL will add parens to the default in db" + end + + def teardown + @connection.schema_search_path = @old_search_path + Default.reset_column_information + end + end +end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 3bd2e909d7..4fc7e77786 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -521,6 +521,20 @@ class DirtyTest < ActiveRecord::TestCase assert !pirate.previous_changes.key?('created_on') end + if ActiveRecord::Base.connection.supports_migrations? + class Testings < ActiveRecord::Base; end + def test_field_named_field + ActiveRecord::Base.connection.create_table :testings do |t| + t.string :field + end + assert_nothing_raised do + Testings.new.attributes + end + ensure + ActiveRecord::Base.connection.drop_table :testings rescue nil + end + end + def test_setting_time_attributes_with_time_zone_field_to_same_time_should_not_be_marked_as_a_change in_time_zone 'Paris' do target = Class.new(ActiveRecord::Base) @@ -530,7 +544,7 @@ class DirtyTest < ActiveRecord::TestCase pirate = target.create(:created_on => created_on) pirate.reload # Here mysql truncate the usec value to 0 - + pirate.created_on = created_on assert !pirate.created_on_changed? end diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb index 303f616c61..b2a3cb5733 100644 --- a/activerecord/test/cases/dup_test.rb +++ b/activerecord/test/cases/dup_test.rb @@ -98,5 +98,20 @@ module ActiveRecord assert_not_nil new_topic.updated_at assert_not_nil new_topic.created_at end + + def test_dup_validity_is_independent + Topic.validates_presence_of :title + topic = Topic.new("title" => "Litterature") + topic.valid? + + duped = topic.dup + duped.title = nil + assert duped.invalid? + + topic.title = nil + duped.title = 'Mathematics' + assert topic.invalid? + assert duped.valid? + end end end diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb index e118add44c..7b852a625d 100644 --- a/activerecord/test/cases/explain_subscriber_test.rb +++ b/activerecord/test/cases/explain_subscriber_test.rb @@ -38,6 +38,13 @@ if ActiveRecord::Base.connection.supports_explain? end end + def test_collects_nothing_if_unexplained_sqls + with_queries([]) do |queries| + SUBSCRIBER.call(:name => 'SQL', :sql => 'SHOW max_identifier_length') + assert queries.empty? + end + end + def with_queries(queries) Thread.current[:available_queries_for_explain] = queries yield queries @@ -45,4 +52,4 @@ if ActiveRecord::Base.connection.supports_explain? Thread.current[:available_queries_for_explain] = nil end end -end
\ No newline at end of file +end diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index bc3dfb1078..e69243a537 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -17,7 +17,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase end def checkout_connections - ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :wait_timeout => 0.3})) + ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :checkout_timeout => 0.3})) @connections = [] @timed_out = 0 @@ -42,7 +42,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase end def checkout_checkin_connections(pool_size, threads) - ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :wait_timeout => 0.5})) + ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :checkout_timeout => 0.5})) @connection_count = 0 @timed_out = 0 threads.times do diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index cfcd11ca46..f33e765c59 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -548,4 +548,8 @@ class DefaultScopingTest < ActiveRecord::TestCase end threads.each(&:join) end + + def test_default_scope_unscoped_is_not_cached + assert_not_equal DeveloperCalledDavid.unscoped.object_id, DeveloperCalledDavid.unscoped.object_id + end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 7639585649..b24df47efd 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1093,6 +1093,10 @@ class RelationTest < ActiveRecord::TestCase assert_equal 'honda', FastCar.unscoped { FastCar.order_using_old_style.limit(1).first.name} end + def test_unscoped_relation_clones + assert_not_equal CoolCar.unscoped.object_id, CoolCar.unscoped.object_id + end + def test_intersection_with_array relation = Author.where(:name => "David") rails_author = relation.first diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index c2fc239167..45b1c66544 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -14,6 +14,21 @@ class TransactionTest < ActiveRecord::TestCase @first, @second = Topic.find(1, 2).sort_by { |t| t.id } end + def test_raise_after_destroy + assert !@first.frozen? + + assert_raises(RuntimeError) { + Topic.transaction do + @first.destroy + assert @first.frozen? + raise + end + } + + assert @first.reload + assert !@first.frozen? + end + def test_successful Topic.transaction do @first.approved = true diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb index 88751a72f9..33d510499b 100644 --- a/activerecord/test/cases/xml_serialization_test.rb +++ b/activerecord/test/cases/xml_serialization_test.rb @@ -130,18 +130,18 @@ class NilXmlSerializationTest < ActiveRecord::TestCase end def test_should_serialize_string - assert_match %r{<name nil="true"></name>}, @xml + assert_match %r{<name nil="true"/>}, @xml end def test_should_serialize_integer - assert %r{<age (.*)></age>}.match(@xml) + assert %r{<age (.*)/>}.match(@xml) attributes = $1 assert_match %r{nil="true"}, attributes assert_match %r{type="integer"}, attributes end def test_should_serialize_binary - assert %r{<avatar (.*)></avatar>}.match(@xml) + assert %r{<avatar (.*)/>}.match(@xml) attributes = $1 assert_match %r{type="binary"}, attributes assert_match %r{encoding="base64"}, attributes @@ -149,21 +149,21 @@ class NilXmlSerializationTest < ActiveRecord::TestCase end def test_should_serialize_datetime - assert %r{<created-at (.*)></created-at>}.match(@xml) + assert %r{<created-at (.*)/>}.match(@xml) attributes = $1 assert_match %r{nil="true"}, attributes assert_match %r{type="datetime"}, attributes end def test_should_serialize_boolean - assert %r{<awesome (.*)></awesome>}.match(@xml) + assert %r{<awesome (.*)/>}.match(@xml) attributes = $1 assert_match %r{type="boolean"}, attributes assert_match %r{nil="true"}, attributes end def test_should_serialize_yaml - assert_match %r{<preferences nil=\"true\"></preferences>}, @xml + assert_match %r{<preferences nil=\"true\"/>}, @xml end end diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml index f450efd839..479b8c050d 100644 --- a/activerecord/test/config.example.yml +++ b/activerecord/test/config.example.yml @@ -1,5 +1,7 @@ default_connection: <%= defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3' %> +with_manual_interventions: false + connections: jdbcderby: arunit: activerecord_unittest diff --git a/activerecord/test/models/subscription.rb b/activerecord/test/models/subscription.rb index 4bdb36ea46..bcac4738a3 100644 --- a/activerecord/test/models/subscription.rb +++ b/activerecord/test/models/subscription.rb @@ -1,4 +1,4 @@ class Subscription < ActiveRecord::Base - belongs_to :subscriber + belongs_to :subscriber, :counter_cache => :books_count belongs_to :book end diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 5cf9a207f3..34e24b5a2e 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -10,6 +10,8 @@ ActiveRecord::Schema.define do execute "ALTER TABLE companies ALTER COLUMN id SET DEFAULT nextval('companies_nonstd_seq')" execute 'DROP SEQUENCE IF EXISTS companies_id_seq' + execute "DROP SCHEMA IF EXISTS schema_1 CASCADE" + %w(accounts_id_seq developers_id_seq projects_id_seq topics_id_seq customers_id_seq orders_id_seq).each do |seq_name| execute "SELECT setval('#{seq_name}', 100)" end @@ -35,7 +37,12 @@ ActiveRecord::Schema.define do ); _SQL - execute <<_SQL + execute "CREATE SCHEMA schema_1" + execute "CREATE DOMAIN schema_1.text AS text" + execute "CREATE DOMAIN schema_1.varchar AS varchar" + execute "CREATE DOMAIN schema_1.bpchar AS bpchar" + + execute <<_SQL CREATE TABLE geometrics ( id serial primary key, a_point point, diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index b8150c50b9..8a3dfbb35a 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -596,6 +596,7 @@ ActiveRecord::Schema.define do create_table :subscribers, :force => true, :id => false do |t| t.string :nick, :null => false t.string :name + t.column :books_count, :integer, :null => false, :default => 0 end add_index :subscribers, :nick, :unique => true diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index f823118c8b..76a232665f 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,21 @@ +## Rails 3.2.9 (unreleased) + +* Add logger.push_tags and .pop_tags to complement logger.tagged: + + class Job + def before + Rails.logger.push_tags :jobs, self.class.name + end + + def after + Rails.logger.pop_tags 2 + end + end + + *Jeremy Kemper* + +* Add %:z and %::z format string support to ActiveSupport::TimeWithZone#strftime. [fixes #6962] *kennyj* + ## Rails 3.2.8 (Aug 9, 2012) ## * Fix ActiveSupport integration with Mocha > 0.12.1. *Mike Gunderloy* diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index 6af87e85e6..7e7f7ecfb2 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -15,16 +15,27 @@ module ActiveSupport class TaggedLogging def initialize(logger) @logger = logger - @tags = Hash.new { |h,k| h[k] = [] } end - def tagged(*new_tags) - tags = current_tags - new_tags = Array.wrap(new_tags).flatten.reject(&:blank?) - tags.concat new_tags - yield + def tagged(*tags) + new_tags = push_tags(*tags) + yield self ensure - new_tags.size.times { tags.pop } + pop_tags(new_tags.size) + end + + def push_tags(*tags) + tags.flatten.reject(&:blank?).tap do |new_tags| + current_tags.concat new_tags + end + end + + def pop_tags(size = 1) + current_tags.pop size + end + + def clear_tags! + current_tags.clear end def silence(temporary_level = Logger::ERROR, &block) @@ -46,7 +57,7 @@ module ActiveSupport end def flush - @tags.delete(Thread.current) + clear_tags! @logger.flush if @logger.respond_to?(:flush) end @@ -54,17 +65,16 @@ module ActiveSupport @logger.send(method, *args) end - protected - - def tags_text - tags = current_tags - if tags.any? - tags.collect { |tag| "[#{tag}]" }.join(" ") + " " + private + def tags_text + tags = current_tags + if tags.any? + tags.collect { |tag| "[#{tag}] " }.join + end end - end - def current_tags - @tags[Thread.current] - end + def current_tags + Thread.current[:activesupport_tagged_logging_tags] ||= [] + end end end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index d3adf671a0..a3c378f057 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -177,7 +177,11 @@ module ActiveSupport # Replaces <tt>%Z</tt> and <tt>%z</tt> directives with +zone+ and +formatted_offset+, respectively, before passing to # Time#strftime, so that zone information is correct def strftime(format) - format = format.gsub('%Z', zone).gsub('%z', formatted_offset(false)) + format = format.gsub('%Z', zone). + gsub('%z', formatted_offset(false)). + gsub('%:z', formatted_offset(true)). + gsub('%::z', formatted_offset(true) + ":00") + time.strftime(format) end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 56dfeb68cc..b5eb049ad4 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -614,7 +614,7 @@ class HashToXmlTest < Test::Unit::TestCase assert_equal "<person>", xml.first(8) assert xml.include?(%(<street>Paulina</street>)) assert xml.include?(%(<name>David</name>)) - assert xml.include?(%(<age nil="true"></age>)) + assert xml.include?(%(<age nil="true"/>)) end def test_one_level_with_skipping_types @@ -622,7 +622,7 @@ class HashToXmlTest < Test::Unit::TestCase assert_equal "<person>", xml.first(8) assert xml.include?(%(<street>Paulina</street>)) assert xml.include?(%(<name>David</name>)) - assert xml.include?(%(<age nil="true"></age>)) + assert xml.include?(%(<age nil="true"/>)) end def test_one_level_with_yielding diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb index c838c073e6..7253eb1222 100644 --- a/activesupport/test/tagged_logging_test.rb +++ b/activesupport/test/tagged_logging_test.rb @@ -29,6 +29,33 @@ class TaggedLoggingTest < ActiveSupport::TestCase assert_equal "[BCX] [Jason] [New] Funky time\n", @output.string end + test "tagged are flattened" do + @logger.tagged("BCX", %w(Jason New)) { @logger.info "Funky time" } + assert_equal "[BCX] [Jason] [New] Funky time\n", @output.string + end + + test "push and pop tags directly" do + assert_equal %w(A B C), @logger.push_tags('A', ['B', ' ', ['C']]) + @logger.info 'a' + assert_equal %w(C), @logger.pop_tags + @logger.info 'b' + assert_equal %w(B), @logger.pop_tags(1) + @logger.info 'c' + assert_equal [], @logger.clear_tags! + @logger.info 'd' + assert_equal "[A] [B] [C] a\n[A] [B] b\n[A] c\nd\n", @output.string + end + + test "does not strip message content" do + @logger.info " Hello" + assert_equal " Hello\n", @output.string + end + + test "provides access to the logger instance" do + @logger.tagged("BCX") { |logger| logger.info "Funky time" } + assert_equal "[BCX] Funky time\n", @output.string + end + test "tagged once with blank and nil" do @logger.tagged(nil, "", "New") { @logger.info "Funky time" } assert_equal "[New] Funky time\n", @output.string diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 3575175517..8ecfc1e47e 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -235,6 +235,14 @@ class TimeZoneTest < Test::Unit::TestCase assert_equal "-0500", zone.formatted_offset(false) end + def test_z_format_strings + zone = ActiveSupport::TimeZone['Tokyo'] + twz = zone.now + assert_equal '+0900', twz.strftime('%z') + assert_equal '+09:00', twz.strftime('%:z') + assert_equal '+09:00:00', twz.strftime('%::z') + end + def test_formatted_offset_zero zone = ActiveSupport::TimeZone['London'] assert_equal "+00:00", zone.formatted_offset diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index f41f91cf22..27f4fd6de7 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +## Rails 3.2.9 (unreleased) + +* Update supported ruby versions error message in ruby_version_check.rb *Lihan Li* + ## Rails 3.2.8 (Aug 9, 2012) ## * ERB scaffold generator use the `:data => { :confirm => "Text" }` syntax instead of `:confirm`. diff --git a/railties/guides/source/3_1_release_notes.textile b/railties/guides/source/3_1_release_notes.textile index ad4e2d8408..e2a1cbb99f 100644 --- a/railties/guides/source/3_1_release_notes.textile +++ b/railties/guides/source/3_1_release_notes.textile @@ -17,10 +17,132 @@ If you're upgrading an existing application, it's a great idea to have good test h4. Rails 3.1 requires at least Ruby 1.8.7 +If your application is currently on any version of Rails older than 3.0.x, you should upgrade to Rails 3.0 before attempting an update to Rails 3.1. + +The following changes are meant for upgrading your application to Rails 3.1.3, the latest 3.1.x version of Rails. + +h4. Ruby + Rails 3.1 requires Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially and you should upgrade as early as possible. Rails 3.1 is also compatible with Ruby 1.9.2. TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02 though. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x jump on 1.9.2 for smooth sailing. +h4. Gemfile + +Make the following changes to your +Gemfile+. + +<ruby> +gem 'rails', '= 3.1.3' +gem 'mysql2' + +# Needed for the new asset pipeline +group :assets do + gem 'sass-rails', "~> 3.1.5" + gem 'coffee-rails', "~> 3.1.1" + gem 'uglifier', ">= 1.0.3" +end + +# jQuery is the default JavaScript library in Rails 3.1 +gem 'jquery-rails' +</ruby> + +h4. config/application.rb + +The asset pipeline requires the following additions: + +<ruby> +config.assets.enabled = true +config.assets.version = '1.0' +</ruby> + +If your application is using an "/assets" route for a resource you may want change the prefix used for assets to avoid conflicts: + +<ruby> +# Defaults to '/assets' +config.assets.prefix = '/asset-files' +</ruby> + +h4. config/environments/development.rb + +Remove the RJS setting <tt>config.action_view.debug_rjs = true</tt>. + +Add these settings if you enable the asset pipeline: + +<ruby> +# Do not compress assets +config.assets.compress = false + +# Expands the lines which load the assets +config.assets.debug = true +</ruby> + +h4. config/environments/production.rb + +Again, most of the changes below are for the asset pipeline. You can read more about these in the "Asset Pipeline":asset_pipeline.html guide. + +<ruby> +# Compress JavaScripts and CSS +config.assets.compress = true + +# Don't fallback to assets pipeline if a precompiled asset is missed +config.assets.compile = false + +# Generate digests for assets URLs +config.assets.digest = true + +# Defaults to Rails.root.join("public/assets") +# config.assets.manifest = YOUR_PATH + +# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) +# config.assets.precompile += %w( search.js ) + +# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. +# config.force_ssl = true +</ruby> + +h4. config/environments/test.rb + +You can help test performance with these additions to your test environment: + +<ruby> +# Configure static asset server for tests with Cache-Control for performance +config.serve_static_assets = true +config.static_cache_control = "public, max-age=3600" +</ruby> + +h4. config/initializers/wrap_parameters.rb + +Add this file with the following contents, if you wish to wrap parameters into a nested hash. This is on by default in new applications. + +<ruby> +# Be sure to restart your server when you modify this file. +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters :format => [:json] +end + +# Disable root element in JSON by default. +ActiveSupport.on_load(:active_record) do + self.include_root_in_json = false +end +</ruby> + +h4. config/initializers/session_store.rb + +You need to change your session key to something new, or remove all sessions: + +<ruby> +# in config/initializers/session_store.rb +AppName::Application.config.session_store :cookie_store, :key => 'SOMETHINGNEW' +</ruby> + +or + +<tt>$ rake db:sessions:clear</tt> + h3. Creating a Rails 3.1 application <shell> diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 0bcd50a1c4..c32a23c50b 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -221,7 +221,7 @@ h3. Creating a New Rails Project The best way to use this guide is to follow each step as it happens, no code or step needed to make this example application has been left out, so you can -literally follow along step by step. You can get the complete code "here":https://github.com/lifo/docrails/tree/master/railties/guides/code/getting_started. +literally follow along step by step. You can get the complete code "here":https://github.com/rails/rails/tree/3-2-stable/railties/guides/code/getting_started. By following along with this guide, you'll create a Rails project called <tt>blog</tt>, a (very) simple weblog. Before you can start building the application, you need to diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index c0eb25a290..854ac2cbbc 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -232,6 +232,8 @@ module Rails end def default_middleware_stack + require 'action_controller/railtie' + ActionDispatch::MiddlewareStack.new.tap do |middleware| if rack_cache = config.action_controller.perform_caching && config.action_dispatch.rack_cache require "action_dispatch/http/rack_cache" diff --git a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb index 6a5d62803c..f33d56b564 100644 --- a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb +++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb @@ -1,13 +1,50 @@ module Rails module Generators class ResourceRouteGenerator < NamedBase + + # Properly nests namespaces passed into a generator + # + # $ rails generate resource admin/users/products + # + # should give you + # + # namespace :admin do + # namespace :users + # resources :products + # end + # end def add_resource_route return if options[:actions].present? - route_config = regular_class_path.collect{ |namespace| "namespace :#{namespace} do " }.join(" ") - route_config << "resources :#{file_name.pluralize}" - route_config << " end" * regular_class_path.size - route route_config + + # iterates over all namespaces and opens up blocks + regular_class_path.each_with_index do |namespace, index| + write("namespace :#{namespace} do", index + 1) + end + + # inserts the primary resource + write("resources :#{file_name.pluralize}", route_length + 1) + + # ends blocks + regular_class_path.each_index do |index| + write("end", route_length - index) + end + + # route prepends two spaces onto the front of the string that is passed, this corrects that + route route_string[2..-1] end + + private + def route_string + @route_string ||= "" + end + + def write(str, indent) + route_string << "#{" " * indent}#{str}\n" + end + + def route_length + regular_class_path.length + end end end end diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb index 89de10c83d..3f59bb8733 100644 --- a/railties/lib/rails/rack/logger.rb +++ b/railties/lib/rails/rack/logger.rb @@ -3,35 +3,48 @@ require 'active_support/core_ext/object/blank' module Rails module Rack - # Log the request started and flush all loggers after it. + # Sets log tags, logs the request, calls the app, and flushes the logs. class Logger < ActiveSupport::LogSubscriber - def initialize(app, tags=nil) - @app, @tags = app, tags.presence + def initialize(app, taggers = nil) + @app, @taggers = app, taggers || [] end def call(env) - if @tags - Rails.logger.tagged(compute_tags(env)) { call_app(env) } + request = ActionDispatch::Request.new(env) + + if Rails.logger.respond_to?(:tagged) + Rails.logger.tagged(compute_tags(request)) { call_app(request, env) } else - call_app(env) + call_app(request, env) end end protected - def call_app(env) - request = ActionDispatch::Request.new(env) - path = request.filtered_path - Rails.logger.info "\n\nStarted #{request.request_method} \"#{path}\" for #{request.ip} at #{Time.now.to_default_s}" + def call_app(request, env) + # Put some space between requests in development logs. + if Rails.env.development? + Rails.logger.info '' + Rails.logger.info '' + end + + Rails.logger.info started_request_message(request) @app.call(env) ensure ActiveSupport::LogSubscriber.flush_all! end - def compute_tags(env) - request = ActionDispatch::Request.new(env) + # Started GET "/session/new" for 127.0.0.1 at 2012-09-26 14:51:42 -0700 + def started_request_message(request) + 'Started %s "%s" for %s at %s' % [ + request.request_method, + request.filtered_path, + request.ip, + Time.now.to_default_s ] + end - @tags.collect do |tag| + def compute_tags(request) + @taggers.collect do |tag| case tag when Proc tag.call(request) diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb index 4d57c5973c..d6c7716c0d 100644 --- a/railties/lib/rails/ruby_version_check.rb +++ b/railties/lib/rails/ruby_version_check.rb @@ -2,7 +2,7 @@ if RUBY_VERSION < '1.8.7' desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})" abort <<-end_message - Rails 3 requires Ruby 1.8.7 or 1.9.2. + Rails 3 requires Ruby 1.8.7 or >= 1.9.2. You're running #{desc} @@ -14,7 +14,7 @@ elsif RUBY_VERSION > '1.9' and RUBY_VERSION < '1.9.2' $stderr.puts <<-end_message Rails 3 doesn't officially support Ruby 1.9.1 since recent stable - releases have segfaulted the test suite. Please upgrade to Ruby 1.9.2. + releases have segfaulted the test suite. Please upgrade to Ruby 1.9.2 or later. You're running #{RUBY_DESCRIPTION} diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 6e638ed85f..9e9702efb6 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -122,8 +122,23 @@ module ApplicationTests app_file "app/assets/javascripts/something/index.js.erb", "alert();" precompile! - assert File.exists?("#{app_path}/public/assets/something.js") + + assets = YAML.load_file("#{app_path}/public/assets/manifest.yml") + assert_not_nil assets['something/index.js'], "Expected something/index.js among #{assets.keys.inspect}" + assert_not_nil assets['something.js'], "Expected something.js among #{assets.keys.inspect}" + end + + test "precompile something/index.js for directory containing index file" do + add_to_config "config.assets.precompile = [ 'something/index.js' ]" + app_file "app/assets/javascripts/something/index.js.erb", "alert();" + + precompile! + assert File.exists?("#{app_path}/public/assets/something/index.js") + + assets = YAML.load_file("#{app_path}/public/assets/manifest.yml") + assert_not_nil assets['something/index.js'], "Expected something/index.js among #{assets.keys.inspect}" + assert_not_nil assets['something.js'], "Expected something.js among #{assets.keys.inspect}" end test "asset pipeline should use a Sprockets::Index when config.assets.digest is true" do @@ -243,6 +258,7 @@ module ApplicationTests 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" + add_to_config "config.assets.digest = false" app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do @@ -250,14 +266,16 @@ module ApplicationTests end RUBY - ENV["RAILS_ENV"] = "development" + ENV["RAILS_ENV"] = "production" precompile! # Create file after of precompile app_file "app/assets/javascripts/app.js", "alert();" require "#{app_path}/config/environment" - class ::PostsController < ActionController::Base ; end + class ::PostsController < ActionController::Base + def show_detailed_exceptions?() true end + end get '/posts' assert_match(/AssetNotPrecompiledError/, last_response.body) diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb index 387eb25525..c13f9a49e7 100644 --- a/railties/test/application/rack/logger_test.rb +++ b/railties/test/application/rack/logger_test.rb @@ -21,25 +21,25 @@ module ApplicationTests end def logs - @logs ||= @logger.logged(:info) + @logs ||= @logger.logged(:info).join("\n") end test "logger logs proper HTTP verb and path" do get "/blah" wait - assert_match(/^Started GET "\/blah"/, logs[0]) + assert_match 'Started GET "/blah"', logs end test "logger logs HTTP verb override" do - post "/", {:_method => 'put'} + post "/", :_method => 'put' wait - assert_match(/^Started PUT "\/"/, logs[0]) + assert_match 'Started PUT "/"', logs end test "logger logs HEAD requests" do - post "/", {:_method => 'head'} + post "/", :_method => 'head' wait - assert_match(/^Started HEAD "\/"/, logs[0]) + assert_match 'Started HEAD "/"', logs end end end diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index 88415b20e4..1cef42dafa 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -297,7 +297,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Route assert_file "config/routes.rb" do |route| - assert_match(/namespace :admin do resources :roles end$/, route) + assert_match(/^ namespace :admin do\n resources :roles\n end$/, route) end # Controller @@ -339,7 +339,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Route assert_file "config/routes.rb" do |route| - assert_no_match(/namespace :admin do resources :roles end$/, route) + assert_no_match(/^ namespace :admin do\n resources :roles\n end$$/, route) end # Controller @@ -357,4 +357,76 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Stylesheets (should not be removed) assert_file "app/assets/stylesheets/scaffold.css" end + + def test_scaffold_with_nested_namespace_on_invoke + run_generator [ "admin/user/special/role", "name:string", "description:string" ] + + # Model + assert_file "app/models/test_app/admin/user/special.rb", /module TestApp\n module Admin/ + assert_file "app/models/test_app/admin/user/special/role.rb", /module TestApp\n class Admin::User::Special::Role < ActiveRecord::Base/ + assert_file "test/unit/test_app/admin/user/special/role_test.rb", /module TestApp\n class Admin::User::Special::RoleTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/test_app/admin/user/special/roles.yml" + assert_migration "db/migrate/create_test_app_admin_user_special_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_match(/^ namespace :admin do\n namespace :user do\n namespace :special do\n resources :roles\n end\n end\n end$/, route) + end + + # Controller + assert_file "app/controllers/test_app/admin/user/special/roles_controller.rb" do |content| + assert_match(/module TestApp\n class Admin::User::Special::RolesController < ApplicationController/, content) + end + + assert_file "test/functional/test_app/admin/user/special/roles_controller_test.rb", + /module TestApp\n class Admin::User::Special::RolesControllerTest < ActionController::TestCase/ + + # Views + %w( + index + edit + new + show + _form + ).each { |view| assert_file "app/views/test_app/admin/user/special/roles/#{view}.html.erb" } + assert_no_file "app/views/layouts/admin/user/special/roles.html.erb" + + # Helpers + assert_file "app/helpers/test_app/admin/user/special/roles_helper.rb" + assert_file "test/unit/helpers/test_app/admin/user/special/roles_helper_test.rb" + + # Stylesheets + assert_file "app/assets/stylesheets/scaffold.css" + end + + def test_scaffold_with_nested_namespace_on_revoke + run_generator [ "admin/user/special/role", "name:string", "description:string" ] + run_generator [ "admin/user/special/role" ], :behavior => :revoke + + # Model + assert_file "app/models/test_app/admin/user/special.rb" # ( should not be remove ) + assert_no_file "app/models/test_app/admin/user/special/role.rb" + assert_no_file "test/unit/test_app/admin/user/special/role_test.rb" + assert_no_file "test/fixtures/test_app/admin/user/special/roles.yml" + assert_no_migration "db/migrate/create_test_app_admin_user_special_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_no_match(/^ namespace :admin do\n namespace :user do\n namespace :special do\n resources :roles\n end\n end\n end$/, route) + end + + # Controller + assert_no_file "app/controllers/test_app/admin/user/special/roles_controller.rb" + assert_no_file "test/functional/test_app/admin/user/special/roles_controller_test.rb" + + # Views + assert_no_file "app/views/test_app/admin/user/special/roles" + + # Helpers + assert_no_file "app/helpers/test_app/admin/user/special/roles_helper.rb" + assert_no_file "test/unit/helpers/test_app/admin/user/special/roles_helper_test.rb" + + # Stylesheets (should not be removed) + assert_file "app/assets/stylesheets/scaffold.css" + end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 2710ef7dfd..5891af505a 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -156,7 +156,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Route assert_file "config/routes.rb" do |route| - assert_match(/namespace :admin do resources :roles end$/, route) + assert_match(/^ namespace :admin do\n resources :roles\n end$/, route) end # Controller |