diff options
89 files changed, 917 insertions, 150 deletions
@@ -53,7 +53,7 @@ end platforms :ruby do gem 'yajl-ruby' - gem 'nokogiri', '>= 1.4.5' + gem 'nokogiri', '>= 1.4.5', '< 1.6' # AR gem 'sqlite3', '~> 1.3.5' diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index c2c8e9b8e2..8b2e754fde 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -17,5 +17,5 @@ Gem::Specification.new do |s| s.requirements << 'none' s.add_dependency('actionpack', version) - s.add_dependency('mail', '~> 2.5.3') + s.add_dependency('mail', '~> 2.5.4') end diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb index 50438ead2a..bd3f193fda 100644 --- a/actionmailer/test/mailers/base_mailer.rb +++ b/actionmailer/test/mailers/base_mailer.rb @@ -38,7 +38,7 @@ class BaseMailer < ActionMailer::Base end def attachment_with_hash - attachments['invoice.jpg'] = { :data => "\312\213\254\232)b", + attachments['invoice.jpg'] = { :data => ::Base64.encode64("\312\213\254\232)b"), :mime_type => "image/x-jpg", :transfer_encoding => "base64" } mail diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 1f4e356952..ac260a9592 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,28 +1,79 @@ ## unreleased ## -* Fixed assets loading performance in 3.2.13. +* Merge `:action` from routing scope and assign endpoint if both `:controller` + and `:action` are present. The endpoint assignment only occurs if there is + no `:to` present in the options hash so should only affect routes using the + shorthand syntax (i.e. endpoint is inferred from the the path). - #8756 uses Sprockets for resolving files that already exists on disk, for those files - their extensions don't need to be rewritten. + Fixes #9856 + + *Yves Senn*, *Andrew White* + +* Always escape the result of `link_to_unless` method. + + Before: + + link_to_unless(true, '<b>Showing</b>', 'github.com') + # => "<b>Showing</b>" + + After: + + link_to_unless(true, '<b>Showing</b>', 'github.com') + # => "<b>Showing</b>" + + *dtaniwaki* + +* Use a case insensitive URI Regexp for #asset_path. + + This fix a problem where the same asset path using different case are generating + different URIs. + + Before: + + image_tag("HTTP://google.com") + # => "<img alt=\"Google\" src=\"/assets/HTTP://google.com\" />" + image_tag("http://google.com") + # => "<img alt=\"Google\" src=\"http://google.com\" />" + + After: + + image_tag("HTTP://google.com") + # => "<img alt=\"Google\" src=\"HTTP://google.com\" />" + image_tag("http://google.com") + # => "<img alt=\"Google\" src=\"http://google.com\" />" + + *David Celis + Rafael Mendonça França* + +* Fix explicit names on multiple file fields. If a file field tag has + the multiple option, it is turned into an array field (appending `[]`), + but if an explicit name is passed to `file_field` the `[]` is not + appended. + Fixes #9830. + + *Ryan McGeary* + +* Fix assets loading performance in 3.2.13. + + Issue #8756 uses Sprockets for resolving files that already exist on disk, + for those files their extensions don't need to be rewritten. Fixes #9803. *Fred Wu* -* Fixed `ActionController#action_missing` not being called. - +* Fix `ActionController#action_missing` not being called. Fixes #9799. *Janko Luin* -* `ActiveSupport::NumberHelper#number_to_human` returns the number unaltered when +* `ActionView::Helpers::NumberHelper#number_to_human` returns the number unaltered when the units hash does not contain the needed key, e.g. when the number provided is less - than the largest key proivided. + than the largest key provided. Examples: - number_to_human(123, :units => {}) # => 123 - number_to_human(123, :units => {:thousand => 'k'}) # => 123 + number_to_human(123, units: {}) # => 123 + number_to_human(123, units: { thousand: 'k' }) # => 123 Fixes #9269. Backport #9347. diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index bc6828a805..e089feea87 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -20,7 +20,12 @@ module ActionController ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload| path = payload[:layout] - @layouts[path] += 1 + if path + @layouts[path] += 1 + if path =~ /^layouts\/(.*)/ + @layouts[$1] += 1 + end + end end ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload| @@ -56,6 +61,15 @@ module ActionController # # assert that the "new" view template was rendered # assert_template "new" # + # # assert that the layout 'admin' was rendered + # assert_template :layout => 'admin' + # assert_template :layout => 'layouts/admin' + # assert_template :layout => :admin + # + # # assert that no layout was rendered + # assert_template :layout => nil + # assert_template :layout => false + # # # assert that the "_customer" partial was rendered twice # assert_template :partial => '_customer', :count => 2 # @@ -88,17 +102,18 @@ module ActionController end end when Hash - if expected_layout = options[:layout] + if options.key?(:layout) + expected_layout = options[:layout] msg = build_message(message, "expecting layout <?> but action rendered <?>", expected_layout, @layouts.keys) case expected_layout - when String - assert(@layouts.keys.include?(expected_layout), msg) + when String, Symbol + assert(@layouts.keys.include?(expected_layout.to_s), msg) when Regexp assert(@layouts.keys.any? {|l| l =~ expected_layout }, msg) - when nil + when nil, false assert(@layouts.empty?, msg) end end @@ -125,7 +140,7 @@ module ActionController options[:partial], @partials.keys) assert(@partials.include?(expected_partial), msg) end - else + elsif options.key?(:partial) assert @partials.empty?, "Expected no partials to be rendered" end @@ -460,7 +475,7 @@ module ActionController parameters ||= {} controller_class_name = @controller.class.anonymous? ? "anonymous_controller" : - @controller.class.name.underscore.sub(/_controller$/, '') + @controller.class.controller_path @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index d71b21efc3..bfa816798d 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -779,6 +779,10 @@ module ActionDispatch child end + def merge_action_scope(parent, child) #:nodoc: + child + end + def merge_path_names_scope(parent, child) #:nodoc: merge_options_scope(parent, child) end @@ -1253,6 +1257,10 @@ module ActionDispatch paths = [path] + rest end + if @scope[:controller] && @scope[:action] + options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}" + end + path_without_format = path.to_s.sub(/\(\.:format\)$/, '') if using_match_shorthand?(path_without_format, options) options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1') diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index a993699e05..6e63f92ff3 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -97,9 +97,7 @@ module ActionDispatch @routes = {} @helpers = [] - @module = Module.new do - instance_methods.each { |selector| remove_method(selector) } - end + @module = Module.new end def helper_names @@ -108,13 +106,11 @@ module ActionDispatch def clear! @helpers.each do |helper| - @module.module_eval do - remove_possible_method helper - end + @module.remove_possible_method helper end - @routes = {} - @helpers = [] + @routes.clear + @helpers.clear end def add(name, route) diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb index c192d3704e..636a37b699 100644 --- a/actionpack/lib/action_view/asset_paths.rb +++ b/actionpack/lib/action_view/asset_paths.rb @@ -43,7 +43,7 @@ module ActionView end def is_uri?(path) - path =~ %r{^[-a-z]+://|^(?:cid|data):|^//} + path =~ %r{^[-a-z]+://|^(?:cid|data):|^//}i end private diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 99aa144d3a..39b9a8d27c 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -112,7 +112,7 @@ module ActionView # english it would read better as about 80 years. minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year remainder = (minutes_with_offset % 525600) - distance_in_years = (minutes_with_offset / 525600) + distance_in_years = (minutes_with_offset.div 525600) if remainder < 131400 locale.t(:about_x_years, :count => distance_in_years) elsif remainder < 394200 diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 920dc3f794..0c079256a3 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -1196,27 +1196,26 @@ module ActionView def add_default_name_and_id(options) if options.has_key?("index") - options["name"] ||= tag_name_with_index(options["index"]) + options["name"] ||= tag_name_with_index(options["index"], options["multiple"]) options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) } options.delete("index") elsif defined?(@auto_index) - options["name"] ||= tag_name_with_index(@auto_index) + options["name"] ||= tag_name_with_index(@auto_index, options["multiple"]) options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) } else - options["name"] ||= tag_name + options["name"] ||= tag_name(options["multiple"]) options["id"] = options.fetch("id"){ tag_id } end - options["name"] += "[]" if options["multiple"] && !options["name"].ends_with?("[]") options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence end - def tag_name - "#{@object_name}[#{sanitized_method_name}]" + def tag_name(multiple = false) + "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}" end - def tag_name_with_index(index) - "#{@object_name}[#{index}][#{sanitized_method_name}]" + def tag_name_with_index(index, multiple = false) + "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}" end def tag_id diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 812bb4de9e..51c3100dff 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -422,7 +422,7 @@ module ActionView if block_given? block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block) else - name + ERB::Util.html_escape(name) end else link_to(name, options, html_options) diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 9f617a9a53..1a656ed37f 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -103,7 +103,7 @@ module ActionView # Helpers related to template lookup using the lookup context information. module ViewPaths - attr_reader :view_paths + attr_reader :view_paths, :html_fallback_for_js # Whenever setting view paths, makes a copy so we can manipulate then in # instance objects as we wish. @@ -200,7 +200,10 @@ module ActionView def formats=(values) if values values.concat(default_formats) if values.delete "*/*" - values << :html if values == [:js] + if values == [:js] + values << :html + @html_fallback_for_js = true + end end super(values) end diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb index 0b5d3785d4..b79b89e142 100644 --- a/actionpack/lib/action_view/renderer/abstract_renderer.rb +++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb @@ -37,5 +37,11 @@ module ActionView def instrument(name, options={}) ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield } end + + def prepend_formats(formats) + formats = Array(formats) + return if formats.empty? || @lookup_context.html_fallback_for_js + @lookup_context.formats = formats | @lookup_context.formats + end end end diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 71fa05ab3e..f3300e470b 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -281,6 +281,8 @@ module ActionView @block = block @details = extract_details(options) + prepend_formats(options[:formats]) + if String === partial @object = options[:object] @path = partial diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index a27d5dd1b1..d15e75637a 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -10,9 +10,10 @@ module ActionView template = determine_template(options) context = @lookup_context + prepend_formats(template.formats) + unless context.rendered_format - context.formats = template.formats unless template.formats.empty? - context.rendered_format = context.formats.first + context.rendered_format = template.formats.first || formats.last end render_template(template, options[:layout], options[:locals]) diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 5252e43c25..3b5a515e84 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -76,6 +76,11 @@ class ActionPackAssertionsController < ActionController::Base render "test/hello_world", :layout => "layouts/standard" end + def render_with_layout_and_partial + @variable_for_layout = nil + render "test/hello_world_with_partial", :layout => "layouts/standard" + end + def session_stuffing session['xmas'] = 'turkey' render :text => "ho ho ho" @@ -483,11 +488,43 @@ class AssertTemplateTest < ActionController::TestCase end end + def test_fails_expecting_no_layout + get :render_with_layout + assert_raise(ActiveSupport::TestCase::Assertion) do + assert_template :layout => nil + end + end + def test_passes_with_correct_layout get :render_with_layout assert_template :layout => "layouts/standard" end + def test_passes_with_layout_and_partial + get :render_with_layout_and_partial + assert_template :layout => "layouts/standard" + end + + def test_passed_with_no_layout + get :hello_world + assert_template :layout => nil + end + + def test_passed_with_no_layout_false + get :hello_world + assert_template :layout => false + end + + def test_passes_with_correct_layout_without_layouts_prefix + get :render_with_layout + assert_template :layout => "standard" + end + + def test_passes_with_correct_layout_symbol + get :render_with_layout + assert_template :layout => :standard + end + def test_assert_template_reset_between_requests get :hello_world assert_template 'test/hello_world' diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 88a5c37c43..659c6c715e 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -529,6 +529,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end end + + scope '/job', :controller => 'job' do + scope ':id', :action => 'manage_applicant' do + get "/active" + end + end end end @@ -1444,6 +1450,13 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_controller_option_with_nesting_and_leading_slash + with_test_routes do + get '/job/5/active' + assert_equal 'job#manage_applicant', @response.body + end + 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/fixtures/test/_changing_priority.html.erb b/actionpack/test/fixtures/test/_changing_priority.html.erb new file mode 100644 index 0000000000..3225efc49a --- /dev/null +++ b/actionpack/test/fixtures/test/_changing_priority.html.erb @@ -0,0 +1 @@ +HTML
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_changing_priority.json.erb b/actionpack/test/fixtures/test/_changing_priority.json.erb new file mode 100644 index 0000000000..7fa41dce66 --- /dev/null +++ b/actionpack/test/fixtures/test/_changing_priority.json.erb @@ -0,0 +1 @@ +JSON
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_first_json_partial.json.erb b/actionpack/test/fixtures/test/_first_json_partial.json.erb new file mode 100644 index 0000000000..790ee896db --- /dev/null +++ b/actionpack/test/fixtures/test/_first_json_partial.json.erb @@ -0,0 +1 @@ +<%= render :partial => "test/second_json_partial" %>
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_json_change_priority.json.erb b/actionpack/test/fixtures/test/_json_change_priority.json.erb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/actionpack/test/fixtures/test/_json_change_priority.json.erb diff --git a/actionpack/test/fixtures/test/_second_json_partial.json.erb b/actionpack/test/fixtures/test/_second_json_partial.json.erb new file mode 100644 index 0000000000..5ebb7f1afd --- /dev/null +++ b/actionpack/test/fixtures/test/_second_json_partial.json.erb @@ -0,0 +1 @@ +Third level
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/change_priority.html.erb b/actionpack/test/fixtures/test/change_priority.html.erb new file mode 100644 index 0000000000..71ecef11c4 --- /dev/null +++ b/actionpack/test/fixtures/test/change_priority.html.erb @@ -0,0 +1,2 @@ +<%= render :partial => "test/json_change_priority", :formats => :json %> +HTML Template, but <%= render :partial => "test/changing_priority" %> partial
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/hello_world_with_partial.html.erb b/actionpack/test/fixtures/test/hello_world_with_partial.html.erb new file mode 100644 index 0000000000..ec31545356 --- /dev/null +++ b/actionpack/test/fixtures/test/hello_world_with_partial.html.erb @@ -0,0 +1,2 @@ +Hello world! +<%= render '/test/partial' %> diff --git a/actionpack/test/fixtures/test/html_template.html.erb b/actionpack/test/fixtures/test/html_template.html.erb new file mode 100644 index 0000000000..1b483357bd --- /dev/null +++ b/actionpack/test/fixtures/test/html_template.html.erb @@ -0,0 +1 @@ +<%= render :partial => "test/first_json_partial", :formats => :json %>
\ No newline at end of file diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index b1a01b53b1..6b1bc01f54 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -79,13 +79,17 @@ class AssetTagHelperTest < ActionView::TestCase JavascriptPathToTag = { %(javascript_path("xmlhr")) => %(/javascripts/xmlhr.js), %(javascript_path("super/xmlhr")) => %(/javascripts/super/xmlhr.js), - %(javascript_path("/super/xmlhr.js")) => %(/super/xmlhr.js) + %(javascript_path("/super/xmlhr.js")) => %(/super/xmlhr.js), + %(javascript_path("http://www.outside.com/foo.js")) => %(http://www.outside.com/foo.js), + %(javascript_path("HTTP://www.outside.com/foo.js")) => %(HTTP://www.outside.com/foo.js) } PathToJavascriptToTag = { %(path_to_javascript("xmlhr")) => %(/javascripts/xmlhr.js), %(path_to_javascript("super/xmlhr")) => %(/javascripts/super/xmlhr.js), - %(path_to_javascript("/super/xmlhr.js")) => %(/super/xmlhr.js) + %(path_to_javascript("/super/xmlhr.js")) => %(/super/xmlhr.js), + %(path_to_javascript("http://www.outside.com/foo.js")) => %(http://www.outside.com/foo.js), + %(path_to_javascript("HTTP://www.outside.com/foo.js")) => %(HTTP://www.outside.com/foo.js) } JavascriptIncludeToTag = { @@ -109,14 +113,18 @@ class AssetTagHelperTest < ActionView::TestCase %(stylesheet_path("bank")) => %(/stylesheets/bank.css), %(stylesheet_path("bank.css")) => %(/stylesheets/bank.css), %(stylesheet_path('subdir/subdir')) => %(/stylesheets/subdir/subdir.css), - %(stylesheet_path('/subdir/subdir.css')) => %(/subdir/subdir.css) + %(stylesheet_path('/subdir/subdir.css')) => %(/subdir/subdir.css), + %(stylesheet_path("http://www.outside.com/foo.css")) => %(http://www.outside.com/foo.css), + %(stylesheet_path("HTTP://www.outside.com/foo.css")) => %(HTTP://www.outside.com/foo.css) } PathToStyleToTag = { %(path_to_stylesheet("style")) => %(/stylesheets/style.css), %(path_to_stylesheet("style.css")) => %(/stylesheets/style.css), %(path_to_stylesheet('dir/file')) => %(/stylesheets/dir/file.css), - %(path_to_stylesheet('/dir/file.rcss')) => %(/dir/file.rcss) + %(path_to_stylesheet('/dir/file.rcss')) => %(/dir/file.rcss), + %(path_to_stylesheet("http://www.outside.com/foo.css")) => %(http://www.outside.com/foo.css), + %(path_to_stylesheet("HTTP://www.outside.com/foo.css")) => %(HTTP://www.outside.com/foo.css) } StyleLinkToTag = { @@ -139,14 +147,18 @@ class AssetTagHelperTest < ActionView::TestCase %(image_path("xml")) => %(/images/xml), %(image_path("xml.png")) => %(/images/xml.png), %(image_path("dir/xml.png")) => %(/images/dir/xml.png), - %(image_path("/dir/xml.png")) => %(/dir/xml.png) + %(image_path("/dir/xml.png")) => %(/dir/xml.png), + %(image_path("http://www.outside.com/foo.png")) => %(http://www.outside.com/foo.png), + %(image_path("HTTP://www.outside.com/foo.png")) => %(HTTP://www.outside.com/foo.png) } PathToImageToTag = { %(path_to_image("xml")) => %(/images/xml), %(path_to_image("xml.png")) => %(/images/xml.png), %(path_to_image("dir/xml.png")) => %(/images/dir/xml.png), - %(path_to_image("/dir/xml.png")) => %(/dir/xml.png) + %(path_to_image("/dir/xml.png")) => %(/dir/xml.png), + %(path_to_image("http://www.outside.com/foo.png")) => %(http://www.outside.com/foo.png), + %(path_to_image("HTTP://www.outside.com/foo.png")) => %(HTTP://www.outside.com/foo.png) } ImageLinkToTag = { @@ -181,14 +193,18 @@ class AssetTagHelperTest < ActionView::TestCase %(video_path("xml")) => %(/videos/xml), %(video_path("xml.ogg")) => %(/videos/xml.ogg), %(video_path("dir/xml.ogg")) => %(/videos/dir/xml.ogg), - %(video_path("/dir/xml.ogg")) => %(/dir/xml.ogg) + %(video_path("/dir/xml.ogg")) => %(/dir/xml.ogg), + %(video_path("http://www.outside.com/foo.ogg")) => %(http://www.outside.com/foo.ogg), + %(video_path("HTTP://www.outside.com/foo.ogg")) => %(HTTP://www.outside.com/foo.ogg) } PathToVideoToTag = { %(path_to_video("xml")) => %(/videos/xml), %(path_to_video("xml.ogg")) => %(/videos/xml.ogg), %(path_to_video("dir/xml.ogg")) => %(/videos/dir/xml.ogg), - %(path_to_video("/dir/xml.ogg")) => %(/dir/xml.ogg) + %(path_to_video("/dir/xml.ogg")) => %(/dir/xml.ogg), + %(path_to_video("http://www.outside.com/foo.ogg")) => %(http://www.outside.com/foo.ogg), + %(path_to_video("HTTP://www.outside.com/foo.ogg")) => %(HTTP://www.outside.com/foo.ogg) } VideoLinkToTag = { @@ -211,14 +227,18 @@ class AssetTagHelperTest < ActionView::TestCase %(audio_path("xml")) => %(/audios/xml), %(audio_path("xml.wav")) => %(/audios/xml.wav), %(audio_path("dir/xml.wav")) => %(/audios/dir/xml.wav), - %(audio_path("/dir/xml.wav")) => %(/dir/xml.wav) + %(audio_path("/dir/xml.wav")) => %(/dir/xml.wav), + %(audio_path("http://www.outside.com/foo.wav")) => %(http://www.outside.com/foo.wav), + %(audio_path("HTTP://www.outside.com/foo.wav")) => %(HTTP://www.outside.com/foo.wav) } PathToAudioToTag = { %(path_to_audio("xml")) => %(/audios/xml), %(path_to_audio("xml.wav")) => %(/audios/xml.wav), %(path_to_audio("dir/xml.wav")) => %(/audios/dir/xml.wav), - %(path_to_audio("/dir/xml.wav")) => %(/dir/xml.wav) + %(path_to_audio("/dir/xml.wav")) => %(/dir/xml.wav), + %(path_to_audio("http://www.outside.com/foo.wav")) => %(http://www.outside.com/foo.wav), + %(path_to_audio("HTTP://www.outside.com/foo.wav")) => %(HTTP://www.outside.com/foo.wav) } AudioLinkToTag = { diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index e4f84f8dd7..ad46ff4429 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -19,6 +19,8 @@ class DateHelperTest < ActionView::TestCase end def assert_distance_of_time_in_words(from, to=nil) + Fixnum.send(:private, :/) if RUBY_VERSION >= '1.9.3' # test we avoid Integer#/ (redefined by mathn) + to ||= from # 0..1 with include_seconds @@ -96,6 +98,8 @@ class DateHelperTest < ActionView::TestCase # test to < from assert_equal "about 4 hours", distance_of_time_in_words(from + 4.hours, to) assert_equal "less than 20 seconds", distance_of_time_in_words(from + 19.seconds, to, true) + ensure + Fixnum.send(:public, :/) if RUBY_VERSION >= '1.9.3' end def test_distance_in_words diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 7b35424ec7..22af39add4 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -301,6 +301,16 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, file_field("user", "avatar") end + def test_file_field_with_multiple_behavior + expected = '<input id="import_file" multiple="multiple" name="import[file][]" type="file" />' + assert_dom_equal expected, file_field("import", "file", :multiple => true) + end + + def test_file_field_with_multiple_behavior_and_explicit_name + expected = '<input id="import_file" multiple="multiple" name="custom" type="file" />' + assert_dom_equal expected, file_field("import", "file", :multiple => true, :name => "custom") + end + def test_hidden_field assert_dom_equal '<input id="post_title" name="post[title]" type="hidden" value="Hello World" />', hidden_field("post", "title") diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index b907e3297b..72f494c811 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -54,6 +54,16 @@ module RenderTestCases assert_equal "Hello world", @view.render(:template => "test/one", :formats => [:html]) end + def test_render_partial_implicitly_use_format_of_the_rendered_partial + @view.lookup_context.formats = [:html] + assert_equal "Third level", @view.render(:template => "test/html_template") + end + + def test_render_partial_use_last_prepended_format_for_partials_with_the_same_names + @view.lookup_context.formats = [:html] + assert_equal "\nHTML Template, but JSON partial", @view.render(:template => "test/change_priority") + end + def test_render_template_with_a_missing_partial_of_another_format @view.lookup_context.formats = [:html] assert_raise ActionView::Template::Error, "Missing partial /missing with {:locale=>[:en], :formats=>[:json], :handlers=>[:erb, :builder]}" do diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 38f77203e0..a00d71ff29 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -306,6 +306,11 @@ class UrlHelperTest < ActiveSupport::TestCase link_to_unless(true, "Showing", url_hash) { "test" } + + assert_equal %{<b>Showing</b>}, link_to_unless(true, "<b>Showing</b>", url_hash) + assert_equal %{<a href="/"><b>Showing</b></a>}, link_to_unless(false, "<b>Showing</b>", url_hash) + assert_equal %{<b>Showing</b>}, link_to_unless(true, "<b>Showing</b>".html_safe, url_hash) + assert_equal %{<a href="/"><b>Showing</b></a>}, link_to_unless(false, "<b>Showing</b>".html_safe, url_hash) end def test_link_to_if diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index c0e0bb1dbc..c673a278f6 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,98 @@ ## unreleased ## +* Fix `ActiveRecord::Store` incorrectly tracking changes of its attributes. + Fixes #10373. + + *Janko Marohnić* + +* Fix mysql2 adapter raises the correct exception when executing a query on a + closed connection. + + *Yves Senn* + +* Fixes bug where `Company.new.contract_ids` would incorrectly load + all non-associated contracts. + + Example: + + company = Company.new # Company has many :contracts + + # before + company.contract_ids # => SELECT ... WHERE `contracts`.`company_id` IS NULL + + # after + company.contract_ids # => [] + + *Jared Armstrong* + +* Fix the `:primary_key` option for `has_many` associations. + Fixes #10693. + + *Yves Senn* + +* fixes bug introduced by #3329. Now, when autosaving associations, + deletions happen before inserts and saves. This prevents a 'duplicate + unique value' database error that would occur if a record being created had + the same value on a unique indexed field as that of a record being destroyed. + + Backport of #10417 + + *Johnny Holton* + +* Fix that under some conditions, Active Record could produce invalid SQL of the sort: + "SELECT DISTINCT DISTINCT". + + Backport of #6792. + + *Ben Woosley* + +* Require `ActiveRecord::Base` in railtie hooks for rake_tasks, console and runner to + avoid circular constant loading issues. + + Backport #7695. + + Fixes #7683 and #882 + + *Ben Holley* + +* Maintain context for joins within ActiveRecord::Relation merges. + Backport #10164. + + *Neeraj Singh + Andrew Horner* + +* Make sure the `EXPLAIN` command is never triggered by a `select_db` call. + + *Daniel Schierbeck* + +* Revert changes on `pluck` that was ignoring the select clause when the relation already + has one. This caused a regression since it changed the behavior in a stable release. + + Fixes #9777. + + *Rafael Mendonça França* + +* Confirm a record has not already been destroyed before decrementing counter cache. + + *Ben Tucker* + +* Default values for PostgreSQL bigint types now get parsed and dumped to the + schema correctly. + Backport #10098. + + *Erik Peterson* + +* Removed warning when `auto_explain_threshold_in_seconds` is set and the + connection adapter doesn't support explain. + This is causing a regression since the Active Record Railtie is trying to + connect to the development database in the application boot. + + *Rafael Mendonça França* + +* Do not reset `inheritance_column` when it's set explicitly. + Backport of #5327. + + *kennyj + Fred Wu* + * Fix a problem wrong exception is occured when raising no translatable exception in PostgreSQL. @@ -27,6 +120,11 @@ ## Rails 3.2.13 (Mar 18, 2013) ## +* Chaining multiple preloaded scopes will correctly preload all the scopes + at the same time. + + *Chris Geihsler* + * Reverted 921a296a3390192a71abeec6d9a035cc6d1865c8, 'Quote numeric values compared to string columns.' This caused several regressions. diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 1759a41d93..d78717704c 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -34,7 +34,10 @@ module ActiveRecord::Associations::Builder method_name = "belongs_to_counter_cache_before_destroy_for_#{name}" mixin.redefine_method(method_name) do record = send(name) - record.class.decrement_counter(cache_column, record.id) unless record.nil? + + if record && !self.destroyed? + record.class.decrement_counter(cache_column, record.id) + end end model.before_destroy(method_name) diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 65e882867e..baddb9852f 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -43,7 +43,7 @@ module ActiveRecord # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items def ids_reader - if loaded? || options[:finder_sql] + if owner.new_record? || loaded? || options[:finder_sql] load_target.map do |record| record.send(reflection.association_primary_key) end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 059e6c77bc..5296cb7282 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -89,8 +89,7 @@ module ActiveRecord records.each { |r| r.destroy } update_counter(-records.length) unless inverse_updates_counter_cache? else - keys = records.map { |r| r[reflection.association_primary_key] } - scope = scoped.where(reflection.association_primary_key => keys) + scope = self.scoped.where(reflection.klass.primary_key => records) if method == :delete_all update_counter(-scope.delete_all) diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index cd366ac8b7..e3d8356f49 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -109,7 +109,7 @@ module ActiveRecord case associations when Symbol, String reflection = parent.reflections[associations.to_s.intern] or - raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?" + raise ConfigurationError, "Association named '#{ associations }' was not found on #{parent.active_record.name}; perhaps you misspelled it?" unless join_association = find_join_association(reflection, parent) @reflections << reflection join_association = build_join_association(reflection, parent) diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index 03963ab060..becf1a3f62 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -55,7 +55,12 @@ module ActiveRecord def find_parent_in(other_join_dependency) other_join_dependency.join_parts.detect do |join_part| - parent == join_part + case parent + when JoinBase + parent.active_record == join_part.active_record + else + parent == join_part + end end end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index 4cb7b56b57..e052b00403 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -56,8 +56,7 @@ module ActiveRecord through_options[:include] = options[:include] || options[:source] through_options[:conditions] = options[:conditions] end - - through_options[:order] = options[:order] + through_options[:order] = options[:order] if options.has_key?(:order) end through_options diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index e1499fc3b0..3fc9d307bc 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -332,16 +332,16 @@ module ActiveRecord if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave) begin - records_to_destroy = [] + if autosave + records_to_destroy = records.select(&:marked_for_destruction?) + records_to_destroy.each { |record| association.proxy.destroy(record) } + records -= records_to_destroy + end records.each do |record| - next if record.destroyed? - saved = true - if autosave && record.marked_for_destruction? - records_to_destroy << record - elsif autosave != false && (@new_record_before_save || record.new_record?) + if autosave != false && (@new_record_before_save || record.new_record?) if autosave saved = association.insert_record(record, false) else @@ -353,19 +353,14 @@ module ActiveRecord raise ActiveRecord::Rollback unless saved end - - records_to_destroy.each do |record| - association.proxy.destroy(record) - end rescue records.each {|x| IdentityMap.remove(x) } if IdentityMap.enabled? raise end - end # reconstruct the scope now that we know the owner's id - association.send(:reset_scope) if association.respond_to?(:reset_scope) + association.reset_scope if association.respond_to?(:reset_scope) end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 524a7d30fc..c690b982a1 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -204,9 +204,11 @@ module ActiveRecord # Executes the SQL statement in the context of this connection. def execute(sql, name = nil) - # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been - # made since we established the connection - @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + if @connection + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + end super end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index cbbb195458..04de08e876 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -142,7 +142,7 @@ module ActiveRecord when NilClass nil # Numeric types - when /\A\(?(-?\d+(\.\d*)?\)?)\z/ + when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/ $1 # Character types when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb index 859c8edfc5..1d861a57db 100644 --- a/activerecord/lib/active_record/explain_subscriber.rb +++ b/activerecord/lib/active_record/explain_subscriber.rb @@ -15,7 +15,7 @@ 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 + EXPLAINED_SQLS = /\A\s*(select|update|delete|insert)\b/i def ignore_payload?(payload) payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ EXPLAINED_SQLS end diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 1517e5ec17..e10e6b4aa8 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -173,6 +173,7 @@ module ActiveRecord def inheritance_column=(value) @original_inheritance_column = inheritance_column @inheritance_column = value.to_s + @explicit_inheritance_column = true end def set_inheritance_column(value = nil, &block) #:nodoc: @@ -300,7 +301,8 @@ module ActiveRecord connection.schema_cache.clear_table_cache!(table_name) if table_exists? @column_names = @content_columns = @column_defaults = @columns = @columns_hash = nil - @dynamic_methods_hash = @inheritance_column = nil + @dynamic_methods_hash = nil + @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column @arel_engine = @relation = nil end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 05091654c0..41b62f6037 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -92,8 +92,9 @@ module ActiveRecord # accepts_nested_attributes_for :posts # end # - # You can now set or update attributes on an associated post model through - # the attribute hash. + # You can now set or update attributes on the associated posts through + # an attribute hash for a member: include the key +:posts_attributes+ + # with an array of hashes of post attributes as a value. # # For each hash that does _not_ have an <tt>id</tt> key a new record will # be instantiated, unless the hash also contains a <tt>_destroy</tt> key @@ -116,10 +117,10 @@ module ActiveRecord # hashes if they fail to pass your criteria. For example, the previous # example could be rewritten as: # - # class Member < ActiveRecord::Base - # has_many :posts - # accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes['title'].blank? } - # end + # class Member < ActiveRecord::Base + # has_many :posts + # accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes['title'].blank? } + # end # # params = { :member => { # :name => 'joe', :posts_attributes => [ @@ -136,19 +137,19 @@ module ActiveRecord # # Alternatively, :reject_if also accepts a symbol for using methods: # - # class Member < ActiveRecord::Base - # has_many :posts - # accepts_nested_attributes_for :posts, :reject_if => :new_record? - # end + # class Member < ActiveRecord::Base + # has_many :posts + # accepts_nested_attributes_for :posts, :reject_if => :new_record? + # end # - # class Member < ActiveRecord::Base - # has_many :posts - # accepts_nested_attributes_for :posts, :reject_if => :reject_posts + # class Member < ActiveRecord::Base + # has_many :posts + # accepts_nested_attributes_for :posts, :reject_if => :reject_posts # - # def reject_posts(attributed) - # attributed['title'].blank? - # end - # end + # def reject_posts(attributed) + # attributed['title'].blank? + # end + # end # # If the hash contains an <tt>id</tt> key that matches an already # associated record, the matching record will be modified: @@ -185,6 +186,29 @@ module ActiveRecord # member.save # member.reload.posts.length # => 1 # + # Nested attributes for an associated collection can also be passed in + # the form of a hash of hashes instead of an array of hashes: + # + # Member.create(:name => 'joe', + # :posts_attributes => { :first => { :title => 'Foo' }, + # :second => { :title => 'Bar' } }) + # + # has the same effect as + # + # Member.create(:name => 'joe', + # :posts_attributes => [ { :title => 'Foo' }, + # { :title => 'Bar' } ]) + # + # The keys of the hash which is the value for +:posts_attributes+ are + # ignored in this case. + # However, it is not allowed to use +'id'+ or +:id+ for one of + # such keys, otherwise the hash will be wrapped in an array and + # interpreted as an attribute hash for a single post. + # + # Passing attributes for an associated collection in the form of a hash + # of hashes can be used with hashes generated from HTTP/HTML parameters, + # where there maybe no natural way to submit an array of hashes. + # # === Saving # # All changes to models, including the destruction of those marked for diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 055d27d85c..4e39654e5b 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -30,6 +30,7 @@ module ActiveRecord ) rake_tasks do + require "active_record/base" load "active_record/railties/databases.rake" end @@ -38,9 +39,14 @@ module ActiveRecord # first time. Also, make it output to STDERR. console do |app| require "active_record/railties/console_sandbox" if app.sandbox? + require "active_record/base" ActiveRecord::Base.logger = Logger.new(STDERR) end + runner do |app| + require "active_record/base" + end + initializer "active_record.initialize_timezone" do ActiveSupport.on_load(:active_record) do self.time_zone_aware_attributes = true @@ -83,13 +89,6 @@ module ActiveRecord end end - initializer "active_record.validate_explain_support" do |app| - if app.config.active_record[:auto_explain_threshold_in_seconds] && - !ActiveRecord::Base.connection.supports_explain? - warn "auto_explain_threshold_in_seconds is set but will be ignored because your adapter does not support this feature. Please unset the configuration to avoid this warning." - end - end - # Expose database runtime to controller for logging. initializer "active_record.log_runtime" do |app| require "active_record/railties/controller_runtime" diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 1f9dbdc3d4..afab793a0c 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -179,14 +179,13 @@ module ActiveRecord def pluck(column_name) if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s) column_name = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}" - else - column_name = column_name.to_s end - relation = clone - relation.select_values = [column_name] - klass.connection.select_all(relation.arel).map! do |attributes| - klass.type_cast_attribute(attributes.keys.first, klass.initialize_attributes(attributes)) + result = klass.connection.exec_query(select(column_name).to_sql) + last_column = result.columns.last + + result.map do |attributes| + klass.type_cast_attribute(last_column, klass.initialize_attributes(attributes)) end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 08cfe4f70d..cdf18f8080 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -254,6 +254,7 @@ module ActiveRecord values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders) relation = relation.dup.select(values) + relation.uniq_value = nil id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values) ids_array = id_rows.map {|row| row[primary_key]} diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index c25570d758..90f2ac3cde 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -17,17 +17,17 @@ module ActiveRecord if method == :includes merged_relation = merged_relation.includes(value) else - merged_relation.send(:"#{method}_values=", value) + merge_relation_method(merged_relation, method, value) end end end (Relation::MULTI_VALUE_METHODS - [:joins, :where, :order]).each do |method| value = r.send(:"#{method}_values") - merged_relation.send(:"#{method}_values=", merged_relation.send(:"#{method}_values") + value) if value.present? + merge_relation_method(merged_relation, method, value) if value.present? end - merged_relation.joins_values += r.joins_values + merge_joins(merged_relation, r) merged_wheres = @where_values + r.where_values @@ -144,5 +144,36 @@ module ActiveRecord relation end + private + + def merge_joins(relation, other) + values = other.joins_values + return if values.blank? + + if other.klass == relation.klass + relation.joins_values += values + else + joins_dependency, rest = values.partition do |join| + case join + when Hash, Symbol, Array + true + else + false + end + end + + join_dependency = ActiveRecord::Associations::JoinDependency.new( + other.klass, + joins_dependency, + [] + ) + + relation.joins_values += join_dependency.join_associations + rest + end + end + + def merge_relation_method(relation, method, value) + relation.send(:"#{method}_values=", relation.send(:"#{method}_values") + value) + end end end diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index 49d01de365..bacb78e2e1 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -37,8 +37,8 @@ module ActiveRecord Array(keys).flatten.each do |key| define_method("#{key}=") do |value| send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash) - send(store_attribute)[key] = value send("#{store_attribute}_will_change!") + send(store_attribute)[key] = value end define_method(key) do diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb new file mode 100644 index 0000000000..5ed2d8aba2 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -0,0 +1,46 @@ +# encoding: utf-8 + +require "cases/helper" +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlByteaTest < ActiveRecord::TestCase + class ByteaDataType < ActiveRecord::Base + self.table_name = 'bytea_data_type' + end + + def setup + @connection = ActiveRecord::Base.connection + begin + @connection.transaction do + @connection.create_table('bytea_data_type') do |t| + t.binary 'payload' + t.binary 'serialized' + end + end + end + @column = ByteaDataType.columns.find { |c| c.name == 'payload' } + assert(@column.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn)) + end + + def teardown + @connection.execute 'drop table if exists bytea_data_type' + end + + class Serializer + def load(str); str; end + def dump(str); str; end + end + + def test_serialize + serializer = Serializer.new + klass = Class.new(ByteaDataType) { + serialize :serialized, Serializer.new + } + obj = klass.new + obj.serialized = "hello world" + obj.save! + obj.reload + assert_equal "hello world", obj.serialized + end +end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index c9b26895ae..58c788e42d 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -387,6 +387,26 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal 15, topic.replies.size end + def test_counter_cache_double_destroy + topic = Topic.create :title => "Zoom-zoom-zoom" + + 5.times do + topic.replies.create(:title => "re: zoom", :content => "speedy quick!") + end + + assert_equal 5, topic.reload[:replies_count] + assert_equal 5, topic.replies.size + + reply = topic.replies.first + + reply.destroy + assert_equal 4, topic.reload[:replies_count] + + reply.destroy + assert_equal 4, topic.reload[:replies_count] + assert_equal 4, topic.replies.size + end + def test_custom_counter_cache reply = Reply.create(:title => "re: zoom", :content => "speedy quick!") assert_equal 0, reply[:replies_count] diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 944f135153..42061d3d73 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1118,4 +1118,11 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_no_queries { assert_equal 2, author.comments_with_order_and_conditions.size } assert_no_queries { assert_equal 5, author.posts.size, "should not cache a subset of the association" } end + + test "preloading a through association twice does not reset it" do + members = Member.includes(:current_membership => :club).includes(:club).to_a + assert_no_queries { + assert_equal 3, members.map(&:current_membership).map(&:club).size + } + end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 869ec1e4b8..d94f5d3207 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1311,6 +1311,33 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert !company.clients.loaded? end + def test_get_ids_for_association_on_new_record_does_not_try_to_find_records + Company.columns # Load schema information so we don't query below + Contract.columns # if running just this test. + + company = Company.new + assert_queries(0) do + company.contract_ids + end + + assert_equal [], company.contract_ids + end + + def test_set_ids_for_association_on_new_record_applies_association_correctly + contract_a = Contract.create! + contract_b = Contract.create! + Contract.create! # another contract + company = Company.new(:name => "Some Company") + + company.contract_ids = [contract_a.id, contract_b.id] + assert_equal [contract_a.id, contract_b.id], company.contract_ids + assert_equal [contract_a, contract_b], company.contracts + + company.save! + assert_equal company, contract_a.reload.company + assert_equal company, contract_b.reload.company + end + def test_get_ids_ignores_include_option assert_equal [readers(:michael_welcome).id], posts(:welcome).readers_with_person_ids end @@ -1492,6 +1519,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal david.essays, Essay.find_all_by_writer_id("David") end + def test_has_many_assignment_with_custom_primary_key + david = people(:david) + + assert_equal ["A Modest Proposal"], david.essays.map(&:name) + david.essays = [Essay.create!(:name => "Remote Work" )] + assert_equal ["Remote Work"], david.essays.map(&:name) + end + def test_blank_custom_primary_key_on_new_record_should_not_run_queries author = Author.new assert !author.essays.loaded? diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index bba1b2b66d..656798300d 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -596,7 +596,7 @@ class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase end class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase - self.use_transactional_fixtures = false unless supports_savepoints? + self.use_transactional_fixtures = false def setup @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @@ -793,6 +793,20 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase assert_equal 2, @pirate.birds.reload.length end + def test_should_save_new_record_that_has_same_value_as_existing_record_marked_for_destruction_on_field_that_has_unique_index + Bird.connection.add_index :birds, :name, :unique => true + + 3.times { |i| @pirate.birds.create(:name => "unique_birds_#{i}") } + + @pirate.birds[0].mark_for_destruction + @pirate.birds.build(:name => @pirate.birds[0].name) + @pirate.save! + + assert_equal 3, @pirate.birds.reload.length + ensure + Bird.connection.remove_index :birds, :column => :name + end + # Add and remove callbacks tests for association collections. %w{ method proc }.each do |callback_type| define_method("test_should_run_add_callback_#{callback_type}s_for_has_many") do @@ -875,8 +889,10 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase @pirate.parrots.each { |parrot| parrot.mark_for_destruction } assert @pirate.save - assert_queries(0) do - assert @pirate.save + Pirate.transaction do + assert_queries(0) do + assert @pirate.save + end end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 97d6c0cf88..67b5d174aa 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1540,6 +1540,16 @@ class BasicsTest < ActiveRecord::TestCase end end + def test_dont_clear_inheritnce_column_when_setting_explicitly + Joke.inheritance_column = "my_type" + before_inherit = Joke.inheritance_column + + Joke.reset_column_information + after_inherit = Joke.inheritance_column + + assert_equal before_inherit, after_inherit unless before_inherit.blank? && after_inherit.blank? + end + def test_set_table_name_symbol_converted_to_string Joke.table_name = :cold_jokes assert_equal 'cold_jokes', Joke.table_name diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index a1dc1de38d..8755e1f580 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -493,10 +493,10 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal [1,2,3,4], Topic.order(:id).pluck("topics.id") end - def test_pluck_replaces_select_clause - taks_relation = Topic.select([:approved, :id]).order(:id) - assert_equal [1,2,3,4], taks_relation.pluck(:id) - assert_equal [false, true, true, true], taks_relation.pluck(:approved) + def test_pluck_does_not_replace_select_clause + taks_relation = Topic.select("approved, id, id AS foo_id").order('foo_id DESC') + assert_equal [4,3,2,1], taks_relation.pluck(:id) + assert_equal [true, true, true, false], taks_relation.pluck(:approved) end def test_pluck_auto_table_name_prefix diff --git a/activerecord/test/cases/disconnected_test.rb b/activerecord/test/cases/disconnected_test.rb new file mode 100644 index 0000000000..cc2c1f6489 --- /dev/null +++ b/activerecord/test/cases/disconnected_test.rb @@ -0,0 +1,26 @@ +require "cases/helper" + +class TestRecord < ActiveRecord::Base +end + +class TestDisconnectedAdapter < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + def setup + @connection = ActiveRecord::Base.connection + end + + def teardown + spec = ActiveRecord::Base.connection_config + ActiveRecord::Base.establish_connection(spec) + @connection = nil + end + + test "can't execute statements while disconnected" do + @connection.execute "SELECT count(*) from products" + @connection.disconnect! + assert_raises(ActiveRecord::StatementInvalid) do + @connection.execute "SELECT count(*) from products" + end + end +end diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb index 7b852a625d..0546e793fe 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_the_statement_is_only_partially_matched + with_queries([]) do |queries| + SUBSCRIBER.call(:name => 'SQL', :sql => 'select_db yo_mama') + assert queries.empty? + end + end + def test_collects_nothing_if_unexplained_sqls with_queries([]) do |queries| SUBSCRIBER.call(:name => 'SQL', :sql => 'SHOW max_identifier_length') diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 7d63d76c34..2efafe5c24 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -31,6 +31,13 @@ class FinderTest < ActiveRecord::TestCase assert_equal(topics(:first).title, Topic.find(1).title) end + def test_symbols_table_ref + Post.first # warm up + x = Symbol.all_symbols.count + Post.where("title" => {"xxxqqqq" => "bar"}) + assert_equal x, Symbol.all_symbols.count + end + # find should handle strings that come from URLs # (example: Category.find(params[:id])) def test_find_with_string @@ -86,6 +93,18 @@ class FinderTest < ActiveRecord::TestCase assert !Topic.includes(:replies).limit(1).where('0 = 1').exists? end + def test_exists_with_distinct_association_includes_and_limit + author = Author.first + assert !author.unique_categorized_posts.includes(:special_comments).limit(0).exists? + assert author.unique_categorized_posts.includes(:special_comments).limit(1).exists? + end + + def test_exists_with_distinct_association_includes_limit_and_order + author = Author.first + assert !author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(0).exists? + assert author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(1).exists? + end + def test_exists_with_empty_table_and_no_args_given Topic.delete_all assert !Topic.exists? diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index 5153945546..13f90e8eef 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -300,6 +300,16 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase assert_admin_attributes(p, true) end + def test_attr_protected_with_newline + p = LoosePerson.new + assert_raises(ActiveRecord::UnknownAttributeError) do + p.attributes = {"comments=\n"=>"hax"} + end + assert_nil p.comments, "Comments is meant to be attr_protected but I assigned it with attributes=" + p.attributes= {"comments(1)\n" => "hax"} + assert_nil p.comments, "Comments is meant to be attr_protected but I assigned it with attributes=" + end + end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index ada4294401..f14eee2eb8 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -4,6 +4,7 @@ require 'models/tagging' require 'models/post' require 'models/topic' require 'models/comment' +require 'models/rating' require 'models/reply' require 'models/author' require 'models/comment' @@ -19,7 +20,7 @@ require 'models/minivan' class RelationTest < ActiveRecord::TestCase fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments, - :tags, :taggings, :cars, :minivans + :ratings, :tags, :taggings, :cars, :minivans def test_do_not_double_quote_string_id van = Minivan.last @@ -332,6 +333,13 @@ class RelationTest < ActiveRecord::TestCase end end + def test_preload_applies_to_all_chained_preloaded_scopes + assert_queries(3) do + post = Post.with_tags.with_comments.first + assert post + end + end + def test_find_with_included_associations assert_queries(2) do posts = Post.includes(:comments).order('posts.id') @@ -689,6 +697,12 @@ class RelationTest < ActiveRecord::TestCase assert_equal 1, comments.count end + def test_relation_merging_with_merged_joins + special_comments_with_ratings = SpecialComment.joins(:ratings) + posts_with_special_comments_with_ratings = Post.group('posts.id').joins(:special_comments).merge(special_comments_with_ratings) + assert_equal 1, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length + end + def test_count posts = Post.scoped diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 0a9643b7e0..629e5e0fff 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -206,6 +206,11 @@ class SchemaDumperTest < ActiveRecord::TestCase end if current_adapter?(:PostgreSQLAdapter) + def test_schema_dump_includes_bigint_default + output = standard_dump + assert_match %r{t.integer\s+"bigint_default",\s+:limit => 8,\s+:default => 0}, output + end + def test_schema_dump_includes_xml_shorthand_definition output = standard_dump if %r{create_table "postgresql_xml_data_type"} =~ output diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index 277fc9d676..bbcbeac959 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -40,4 +40,11 @@ class StoreTest < ActiveRecord::TestCase @john.remember_login = false assert_equal false, @john.remember_login end + + test "updating the store will track changes correctly" do + @john.color = "blue" + assert_equal [{:color => "black"}, {:color => "blue"}], @john.settings_change + @john.homepage = "37signals.com" + assert_equal [{:color => "black"}, {:color => "blue", :homepage => "37signals.com"}], @john.settings_change + end end diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 07c529d685..d316a0b992 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -27,6 +27,7 @@ class Person < ActiveRecord::Base has_many :agents_posts, :through => :agents, :source => :posts has_many :agents_posts_authors, :through => :agents_posts, :source => :author + has_many :essays, :primary_key => "first_name", :foreign_key => "writer_id" scope :males, :conditions => { :gender => 'M' } scope :females, :conditions => { :gender => 'F' } diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 9aa02fa18f..3cfd1a0f76 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -76,6 +76,9 @@ class Post < ActiveRecord::Base end end + scope :with_comments, preload(:comments) + scope :with_tags, preload(:taggings) + has_many :interpolated_taggings, :class_name => 'Tagging', :as => :taggable, :conditions => proc { "1 = #{1}" } has_many :interpolated_tags, :through => :taggings has_many :interpolated_tags_2, :through => :interpolated_taggings, :source => :tag diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index b2c655ddcd..4f546df2eb 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -30,6 +30,7 @@ ActiveRecord::Schema.define do char3 text default 'a text field', positive_integer integer default 1, negative_integer integer default -1, + bigint_default bigint default 0::bigint, decimal_number decimal(3,2) default 2.78, multiline_default text DEFAULT '--- [] diff --git a/activeresource/CHANGELOG.md b/activeresource/CHANGELOG.md index 77419bb904..1d084ec5d9 100644 --- a/activeresource/CHANGELOG.md +++ b/activeresource/CHANGELOG.md @@ -1,7 +1,9 @@ ## unreleased ## -* No changes. +* Fixes an issue that ActiveResource models ignores ActiveResource::Base.include_root_in_json. + Backported from the now separate repo rails/activeresouce. + *Xinjiang Lu* ## Rails 3.2.13 (Mar 18, 2013) ## diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 032a245c3c..f9cc7d03c8 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -1336,7 +1336,7 @@ module ActiveResource end def to_json(options={}) - super({ :root => self.class.element_name }.merge(options)) + super(include_root_in_json ? { :root => self.class.element_name }.merge(options) : options) end def to_xml(options={}) diff --git a/activeresource/test/cases/base_test.rb b/activeresource/test/cases/base_test.rb index 983f0541a8..9176b5db93 100644 --- a/activeresource/test/cases/base_test.rb +++ b/activeresource/test/cases/base_test.rb @@ -1020,7 +1020,6 @@ class BaseTest < Test::Unit::TestCase end def test_to_json - Person.include_root_in_json = true joe = Person.find(6) encode = joe.encode json = joe.to_json @@ -1032,6 +1031,21 @@ class BaseTest < Test::Unit::TestCase assert_match %r{\}\}$}, json end + def test_to_json_without_root + ActiveResource::Base.include_root_in_json = false + joe = Person.find(6) + encode = joe.encode + json = joe.to_json + + assert_equal encode, json + assert_no_match %r{^\{"person":\}}, json + assert_match %r{"id":6}, json + assert_match %r{"name":"Joe"}, json + assert_match %r{\}$}, json + ensure + ActiveResource::Base.include_root_in_json = true + end + def test_to_json_with_element_name old_elem_name = Person.element_name Person.include_root_in_json = true diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 649766cc9b..d612c644af 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,16 @@ ## unreleased ## +* Override `Time.at` to support the passing of Time-like values when called with a single argument. + + *Andrew White* + +* Revert the changes on unicode character encoding from `ActiveSupport::JSON.encode`. + This was causing a regression where the resulting string is always returning UTF-8. + Also it changes the behavior of this method on a stable release. + Fixes #9498. + + *Rafael Mendonça França* + * Fix `ActiveSupport::TimeZone.parse` when time is at a local DST jump. Fixes #9678. diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 9146d82bd8..7524063efb 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -45,6 +45,18 @@ class Time def current ::Time.zone ? ::Time.zone.now : ::Time.now end + + # Layers additional behavior on Time.at so that ActiveSupport::TimeWithZone and DateTime + # instances can be used when called with a single argument + def at_with_coercion(*args) + if args.size == 1 && args.first.acts_like?(:time) + at_without_coercion(args.first.to_i) + else + at_without_coercion(*args) + end + end + alias_method :at_without_coercion, :at + alias_method :at, :at_with_coercion end # Tells whether the Time object's time lies in the past diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index a50e6524c6..bd2f909ca9 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -122,7 +122,13 @@ module ActiveSupport if string.respond_to?(:force_encoding) string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY) end - json = string.gsub(escape_regex) { |s| ESCAPED_CHARS[s] } + json = string. + gsub(escape_regex) { |s| ESCAPED_CHARS[s] }. + gsub(/([\xC0-\xDF][\x80-\xBF]| + [\xE0-\xEF][\x80-\xBF]{2}| + [\xF0-\xF7][\x80-\xBF]{3})+/nx) { |s| + s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/n, '\\\\u\&') + } json = %("#{json}") json.force_encoding(::Encoding::UTF_8) if json.respond_to?(:force_encoding) json diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 0d680832ef..a22e161279 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -774,6 +774,28 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] )) end + def test_at_with_datetime + assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(DateTime.civil(2000, 1, 1, 0, 0, 0)) + + # Only test this if the underlying Time.at raises a TypeError + begin + Time.at_without_coercion(Time.now, 0) + rescue TypeError + assert_raise(TypeError) { assert_equal(Time.utc(2000, 1, 1, 0, 0, 0), Time.at(DateTime.civil(2000, 1, 1, 0, 0, 0), 0)) } + end + end + + def test_at_with_time_with_zone + assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'])) + + # Only test this if the underlying Time.at raises a TypeError + begin + Time.at_without_coercion(Time.now, 0) + rescue TypeError + assert_raise(TypeError) { assert_equal(Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC']), 0)) } + end + end + def test_eql? assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) ) assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) ) diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index e4e13c3f25..72366f9ab4 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -100,11 +100,11 @@ class TestJSONEncoding < Test::Unit::TestCase def test_utf8_string_encoded_properly_when_kcode_is_utf8 with_kcode 'UTF8' do result = ActiveSupport::JSON.encode('€2.99') - assert_equal '"€2.99"', result + assert_equal '"\\u20ac2.99"', result assert_equal(Encoding::UTF_8, result.encoding) if result.respond_to?(:encoding) result = ActiveSupport::JSON.encode('✎☺') - assert_equal '"✎☺"', result + assert_equal '"\\u270e\\u263a"', result assert_equal(Encoding::UTF_8, result.encoding) if result.respond_to?(:encoding) end end @@ -113,22 +113,23 @@ class TestJSONEncoding < Test::Unit::TestCase def test_non_utf8_string_transcodes s = '二'.encode('Shift_JIS') result = ActiveSupport::JSON.encode(s) - assert_equal '"二"', result + assert_equal '"\\u4e8c"', result assert_equal Encoding::UTF_8, result.encoding end - end - def test_wide_utf8_chars - w = '𠜎' - result = ActiveSupport::JSON.encode(w) - assert_equal '"𠜎"', result - end + def test_utf8_hash_key_does_not_change_the_encoding + w = { '𠜎' => 'a' } + result = ActiveSupport::JSON.encode(w) + assert_equal '{"\\u070e":"a"}', result - def test_wide_utf8_roundtrip - hash = { :string => "𐒑" } - json = ActiveSupport::JSON.encode(hash) - decoded_hash = ActiveSupport::JSON.decode(json) - assert_equal "𐒑", decoded_hash['string'] + if RUBY_VERSION >= '2.0' + expected_encoding = Encoding::UTF_8 + else + expected_encoding = Encoding::US_ASCII + end + + assert_equal expected_encoding, result.encoding + end end def test_exception_raised_when_encoding_circular_reference_in_array diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index c0ab4b8317..4bb2ec4618 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,6 +1,15 @@ ## unreleased ## -* No changes. +* Add support for runner hook. + + Backport #7695. + + *Ben Holley* + +* Fixes bug with scaffold generator with `--assets=false --resource-route=false`. + Fixes #9525. + + *Arun Agrawal* ## Rails 3.2.13 (Mar 18, 2013) ## diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index c2ca5a335d..444215f0fa 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -301,8 +301,6 @@ config.cache_store = :memory_store, :size => 64.megabytes If you're running multiple Ruby on Rails server processes (which is the case if you're using mongrel_cluster or Phusion Passenger), then your Rails server process instances won't be able to share cache data with each other. This cache store is not appropriate for large application deployments, but can work well for small, low traffic sites with only a couple of server processes or for development and test environments. -This is the default cache store implementation. - h4. ActiveSupport::Cache::FileStore This cache store uses the file system to store entries. The path to the directory where the store files will be stored must be specified when initializing the cache. @@ -315,6 +313,8 @@ With this cache store, multiple server processes on the same host can share a ca Note that the cache will grow until the disk is full unless you periodically clear out old entries. +This is the default cache store if config.cache_store is not defined and tmp/cache is writable. + h4. ActiveSupport::Cache::MemCacheStore This cache store uses Danga's +memcached+ server to provide a centralized cache for your application. Rails uses the bundled +memcache-client+ gem by default. This is currently the most popular cache store for production websites. It can be used to provide a single, shared cache cluster with very a high performance and redundancy. @@ -434,4 +434,4 @@ end h3. Further reading -* "Scaling Rails Screencasts":http://railslab.newrelic.com/scaling-rails +* "Scaling Rails Screencasts":https://www.youtube.com/playlist?list=PLuVcDOUVjW2ePvFapFSHBZ71ya2fLHZS5 diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index c32a23c50b..99409edbd0 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -1153,6 +1153,7 @@ First, take a look at +comment.rb+: <ruby> class Comment < ActiveRecord::Base + attr_accesssible :body, :commenter, :post belongs_to :post end </ruby> @@ -1215,6 +1216,7 @@ makes each comment belong to a Post: <ruby> class Comment < ActiveRecord::Base + attr_accessible :body, :commenter, :post belongs_to :post end </ruby> diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile index 5ae9cf0f2b..7d768fca19 100644 --- a/railties/guides/source/initialization.textile +++ b/railties/guides/source/initialization.textile @@ -383,7 +383,7 @@ ensure end </ruby> -This is where the first output of the Rails initialization happens. This method creates a trap for +INT+ signals, so if you +CTRL+C+ the server, it will exit the process. As we can see from the code here, it will create the +tmp/cache+, +tmp/pids+, +tmp/sessions+ and +tmp/sockets+ directories if they don't already exist prior to calling +super+. The +super+ method will call +Rack::Server.start+ which begins its definition like this: +This is where the first output of the Rails initialization happens. This method creates a trap for +INT+ signals, so if you <tt>CTRL+C</tt> the server, it will exit the process. As we can see from the code here, it will create the +tmp/cache+, +tmp/pids+, +tmp/sessions+ and +tmp/sockets+ directories if they don't already exist prior to calling +super+. The +super+ method will call +Rack::Server.start+ which begins its definition like this: <ruby> def start diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb index 35b6fc7014..4f4cedbd32 100644 --- a/railties/guides/source/layout.html.erb +++ b/railties/guides/source/layout.html.erb @@ -81,11 +81,11 @@ </p> <p> If you see any typos or factual errors you are confident to - patch, please clone <%= link_to 'docrails', 'https://github.com/lifo/docrails' %> - and push the change yourself. That branch of Rails has public write access. - Commits are still reviewed, but that happens after you've submitted your - contribution. <%= link_to 'docrails', 'https://github.com/lifo/docrails' %> is - cross-merged with master periodically. + patch, please clone the <%= link_to 'rails', 'https://github.com/rails/rails' %> + repository and open a new pull request. You can also ask for commit rights on + <%= link_to 'docrails', 'https://github.com/rails/docrails' %> if you plan to submit + several patches. Commits are reviewed, but that happens after you've submitted your + contribution. This repository is cross-merged with master periodically. </p> <p> You may also find incomplete content, or stuff that is not up to date. diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 4f695159ea..2281b9686c 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -154,6 +154,14 @@ module Rails self end + # Load the application runner and invoke the registered hooks. + # Check <tt>Rails::Railtie.runner</tt> for more info. + def load_runner(app=self) + initialize_runner + super + self + end + # Rails.application.env_config stores some of the Rails initial environment parameters. # Currently stores: # @@ -305,6 +313,9 @@ module Rails require "rails/console/helpers" end + def initialize_runner #:nodoc: + end + def build_original_fullpath(env) path_info = env["PATH_INFO"] query_string = env["QUERY_STRING"] diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index e8cc5d9e3b..a694218695 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -42,6 +42,7 @@ ENV["RAILS_ENV"] = options[:environment] require APP_PATH Rails.application.require_environment! + Rails.application.load_runner if code_or_file.nil? $stderr.puts "Run '#{$0} -h' for help." diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 993dfe43ee..77f335e45f 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -430,6 +430,11 @@ module Rails super end + def load_runner(app=self) + railties.all { |r| r.load_runner(app) } + super + end + def eager_load! railties.all(&:eager_load!) diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile index f4efd3af74..8f8ebbbc1d 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile @@ -1,4 +1,4 @@ -source "http://rubygems.org" +source "https://rubygems.org" # Declare your gem's dependencies in <%= name %>.gemspec. # Bundler will treat runtime dependencies like base dependencies, and diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb index 03a61a035e..353ebe93ed 100644 --- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb @@ -8,6 +8,8 @@ module Rails class_option :stylesheets, :type => :boolean, :desc => "Generate Stylesheets" class_option :stylesheet_engine, :desc => "Engine for Stylesheets" + class_option :assets, :type => :boolean + class_option :resource_route, :type => :boolean hook_for :scaffold_controller, :required => true diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 07a122e7d0..9eb7e7c65d 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -145,6 +145,12 @@ module Rails @load_console end + def runner(&blk) + @load_runner ||= [] + @load_runner << blk if blk + @load_runner + end + def generators(&blk) @generators ||= [] @generators << blk if blk @@ -179,6 +185,10 @@ module Rails self.class.console.each { |block| block.call(app) } end + def load_runner(app=self) + self.class.runner.each { |block| block.call(app) } + end + def load_tasks(app=self) extend Rake::DSL if defined? Rake::DSL self.class.rake_tasks.each { |block| self.instance_exec(app, &block) } diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 107b54c0be..767a60f097 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -174,5 +174,27 @@ module ApplicationTests end end end + + def test_load_activerecord_base_when_we_use_observers + Dir.chdir(app_path) do + `bundle exec rails g model user; + bundle exec rake db:migrate; + bundle exec rails g observer user;` + + add_to_config "config.active_record.observers = :user_observer" + + assert_equal "0", `bundle exec rails r "puts User.count"`.strip + + app_file "lib/tasks/count_user.rake", <<-RUBY + namespace :user do + task :count => :environment do + puts User.count + end + end + RUBY + + assert_equal "0", `bundle exec rake user:count`.strip + end + end end end diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index e50d744e26..4cdc6549db 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -229,6 +229,30 @@ module ApplicationTests end end + def test_root_path + app('development') + + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render :text => "foo" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do + get 'foo', :to => 'foo#index' + root :to => 'foo#index' + end + RUBY + + remove_file 'public/index.html' + + get '/' + assert_equal 'foo', last_response.body + end + test 'routes are added and removed when reloading' do app('development') diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb index 4468fa295e..d086f13c82 100644 --- a/railties/test/application/runner_test.rb +++ b/railties/test/application/runner_test.rb @@ -57,5 +57,15 @@ module ApplicationTests assert_match "script/program_name.rb", Dir.chdir(app_path) { `bundle exec rails runner "script/program_name.rb"` } end + + def test_with_hook + add_to_config <<-RUBY + runner do |app| + app.config.ran = true + end + RUBY + + assert_match "true", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.application.config.ran"` } + end end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 5891af505a..86f0962ac2 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -269,13 +269,27 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "config/routes.rb", /\.routes\.draw do\s*\|map\|\s*$/ end - def test_scaffold_generator_no_assets + def test_scaffold_generator_no_assets_with_switch_no_assets run_generator [ "posts", "--no-assets" ] assert_file "app/assets/stylesheets/scaffold.css" assert_no_file "app/assets/javascripts/posts.js" assert_no_file "app/assets/stylesheets/posts.css" end + def test_scaffold_generator_no_assets_with_switch_assets_false + run_generator [ "posts", "--assets=false" ] + assert_file "app/assets/stylesheets/scaffold.css" + assert_no_file "app/assets/javascripts/posts.js" + assert_no_file "app/assets/stylesheets/posts.css" + end + + def test_scaffold_generator_no_assets_with_switch_resource_route_false + run_generator [ "posts", "--resource-route=false" ] + assert_file "config/routes.rb" do |route| + assert_no_match(/resources :posts$/, route) + end + end + def test_scaffold_generator_no_stylesheets run_generator [ "posts", "--no-stylesheets" ] assert_no_file "app/assets/stylesheets/scaffold.css" diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb index 55f85c7202..335a74a609 100644 --- a/railties/test/railties/railtie_test.rb +++ b/railties/test/railties/railtie_test.rb @@ -163,6 +163,22 @@ module RailtiesTest assert $ran_block end + test "runner block is executed when MyApp.load_runner is called" do + $ran_block = false + + class MyTie < Rails::Railtie + runner do + $ran_block = true + end + end + + require "#{app_path}/config/environment" + + assert !$ran_block + AppTemplate::Application.load_runner + assert $ran_block + end + test "railtie can add initializers" do $ran_block = false |