diff options
117 files changed, 996 insertions, 401 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 5e78aba0cc..0dafef90ae 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,7 @@ ## Rails 4.0.0 (unreleased) ## +* `label` form helper accepts :for => nil to not generate the attribute. *Carlos Antonio da Silva* + * Add `:format` option to number_to_percentage *Rodrigo Flores* * Add `config.action_view.logger` to configure logger for ActionView. *Rafael França* @@ -24,7 +26,9 @@ This is a behavior change, previously the hidden tag had a value of the disabled checkbox. *Tadas Tamosauskas* -## Rails 3.2.0 (unreleased) ## +* `favicon_link_tag` helper will now use the favicon in app/assets by default. *Lucas Caton* + +## Rails 3.2.0 (January 20, 2012) ## * Add `config.action_dispatch.default_charset` to configure default charset for ActionDispatch::Response. *Carlos Antonio da Silva* diff --git a/actionpack/Rakefile b/actionpack/Rakefile index effb6badfc..bb1e704767 100755 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -55,15 +55,15 @@ end task :lines do lines, codelines, total_lines, total_codelines = 0, 0, 0, 0 - for file_name in FileList["lib/**/*.rb"] + FileList["lib/**/*.rb"].each do |file_name| next if file_name =~ /vendor/ - f = File.open(file_name) - - while line = f.gets - lines += 1 - next if line =~ /^\s*$/ - next if line =~ /^\s*#/ - codelines += 1 + File.open(file_name, 'r') do |f| + while line = f.gets + lines += 1 + next if line =~ /^\s*$/ + next if line =~ /^\s*#/ + codelines += 1 + end end puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}" diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index f13b1d5a60..b205acdb79 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| s.add_dependency('builder', '~> 3.0.0') s.add_dependency('rack', '~> 1.4.1') s.add_dependency('rack-test', '~> 0.6.1') - s.add_dependency('journey', '~> 1.0.0') + s.add_dependency('journey', '~> 1.0.1') s.add_dependency('sprockets', '~> 2.2.0') s.add_dependency('erubis', '~> 2.7.0') diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index f4eaa2fd1b..a0c54dbd84 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -40,7 +40,6 @@ module ActionController autoload :Integration, 'action_controller/deprecated/integration_test' autoload :IntegrationTest, 'action_controller/deprecated/integration_test' autoload :PerformanceTest, 'action_controller/deprecated/performance_test' - autoload :UrlWriter, 'action_controller/deprecated' autoload :Routing, 'action_controller/deprecated' autoload :TestCase, 'action_controller/test_case' autoload :TemplateAssertions, 'action_controller/test_case' diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb index 3da4f91051..a6b3aee5e7 100644 --- a/actionpack/lib/action_dispatch/http/mime_types.rb +++ b/actionpack/lib/action_dispatch/http/mime_types.rb @@ -9,7 +9,7 @@ Mime::Type.register "text/calendar", :ics Mime::Type.register "text/csv", :csv Mime::Type.register "image/png", :png, [], %w(png) -Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe) +Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg) Mime::Type.register "image/gif", :gif, [], %w(gif) Mime::Type.register "image/bmp", :bmp, [], %w(bmp) Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index e2d7a29079..a17a39bed3 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -491,7 +491,7 @@ module ActionDispatch map_method(:put, args, &block) end - # Define a route that only recognizes HTTP PUT. + # Define a route that only recognizes HTTP DELETE. # For supported arguments, see <tt>Base#match</tt>. # # Example: diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index 4d963803e6..33796008bd 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -417,8 +417,8 @@ module ActionDispatch deliveries = ActionMailer::Base.deliveries assert !deliveries.empty?, "No e-mail in delivery list" - for delivery in deliveries - for part in (delivery.parts.empty? ? [delivery] : delivery.parts) + deliveries.each do |delivery| + (delivery.parts.empty? ? [delivery] : delivery.parts).each do |part| if part["Content-Type"].to_s =~ /^text\/html\W/ root = HTML::Document.new(part.body.to_s).root assert_select root, ":root", &block diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 134eaab8bc..40a950c644 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/array/extract_options' +require 'active_support/core_ext/hash/keys' require 'action_view/helpers/asset_tag_helpers/javascript_tag_helpers' require 'action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers' require 'action_view/helpers/asset_tag_helpers/asset_paths' @@ -232,7 +234,7 @@ module ActionView # # generates # - # <link href="/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" /> + # <link href="/assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" /> # # You may specify a different file in the first argument: # @@ -250,7 +252,7 @@ module ActionView # # <%= favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png' %> # - def favicon_link_tag(source='/favicon.ico', options={}) + def favicon_link_tag(source='favicon.ico', options={}) tag('link', { :rel => 'shortcut icon', :type => 'image/vnd.microsoft.icon', @@ -353,8 +355,8 @@ module ActionView # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" /> # image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # => # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" /> - def image_tag(source, options = {}) - options.symbolize_keys! + def image_tag(source, options={}) + options = options.dup.symbolize_keys! src = options[:src] = path_to_image(source) @@ -407,26 +409,19 @@ module ActionView # <video src="/trailers/hd.avi" width="16" height="16" /> # video_tag("/trailers/hd.avi", :height => '32', :width => '32') # => # <video height="32" src="/trailers/hd.avi" width="32" /> + # video_tag("trailer.ogg", "trailer.flv") # => + # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> # video_tag(["trailer.ogg", "trailer.flv"]) # => - # <video><source src="trailer.ogg" /><source src="trailer.ogg" /><source src="trailer.flv" /></video> + # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> # video_tag(["trailer.ogg", "trailer.flv"] :size => "160x120") # => - # <video height="120" width="160"><source src="trailer.ogg" /><source src="trailer.flv" /></video> - def video_tag(sources, options = {}) - options.symbolize_keys! + # <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> + def video_tag(*sources) + multiple_sources_tag('video', sources) do |options| + options[:poster] = path_to_image(options[:poster]) if options[:poster] - options[:poster] = path_to_image(options[:poster]) if options[:poster] - - if size = options.delete(:size) - options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$} - end - - if sources.is_a?(Array) - content_tag("video", options) do - sources.map { |source| tag("source", :src => path_to_video(source)) }.join.html_safe + if size = options.delete(:size) + options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$} end - else - options[:src] = path_to_video(sources) - tag("video", options) end end @@ -441,17 +436,10 @@ module ActionView # <audio src="/audios/sound.wav" /> # audio_tag("sound.wav", :autoplay => true, :controls => true) # => # <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" /> - def audio_tag(sources, options = {}) - options.symbolize_keys! - - if sources.is_a?(Array) - content_tag("audio", options) do - sources.collect { |source| tag("source", :src => path_to_audio(source)) }.join.html_safe - end - else - options[:src] = path_to_audio(sources) - tag("audio", options) - end + # audio_tag("sound.wav", "sound.mid") # => + # <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio> + def audio_tag(*sources) + multiple_sources_tag('audio', sources) end private @@ -459,6 +447,22 @@ module ActionView def asset_paths @asset_paths ||= AssetTagHelper::AssetPaths.new(config, controller) end + + def multiple_sources_tag(type, sources) + options = sources.extract_options!.dup.symbolize_keys! + sources.flatten! + + yield options if block_given? + + if sources.size > 1 + content_tag(type, options) do + safe_join sources.map { |source| tag("source", :src => send("path_to_#{type}", source)) } + end + else + options[:src] = send("path_to_#{type}", sources.first) + tag(type, options) + end + end end end end diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index ea71889519..2d37923825 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -508,7 +508,7 @@ module ActionView # Returns a select tag with options for each of the days 1 through 31 with the current day selected. # The <tt>date</tt> can also be substituted for a day number. - # If you want to display days with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true. + # If you want to display days with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true. # Override the field name using the <tt>:field_name</tt> option, 'day' by default. # # ==== Examples @@ -854,7 +854,7 @@ module ActionView end def translated_date_order - date_order = I18n.translate(:'date.order', :locale => @options[:locale]) || [] + date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => []) forbidden_elements = date_order - [:year, :month, :day] if forbidden_elements.any? @@ -990,18 +990,12 @@ module ActionView # Returns the separator for a given datetime component. def separator(type) case type - when :year - @options[:discard_year] ? "" : @options[:date_separator] - when :month - @options[:discard_month] ? "" : @options[:date_separator] - when :day - @options[:discard_day] ? "" : @options[:date_separator] + when :year, :month, :day + @options[:"discard_#{type}"] ? "" : @options[:date_separator] when :hour (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator] - when :minute - @options[:discard_minute] ? "" : @options[:time_separator] - when :second - @options[:include_seconds] ? @options[:time_separator] : "" + when :minute, :second + @options[:"discard_#{type}"] ? "" : @options[:time_separator] end end end diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index c8811e3b10..c4cdfef4a2 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -334,7 +334,7 @@ module ActionView end.join("\n").html_safe end - # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning + # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning # the result of a call to the +value_method+ as the option value and the +text_method+ as the option text. # Example: # options_from_collection_for_select(@people, 'id', 'name') @@ -418,9 +418,9 @@ module ActionView # wrap the output in an appropriate <tt><select></tt> tag. def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil) collection.map do |group| - group_label_string = eval("group.#{group_label_method}") + group_label_string = group.send(group_label_method) "<optgroup label=\"#{ERB::Util.html_escape(group_label_string)}\">" + - options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key) + + options_from_collection_for_select(group.send(group_method), option_key_method, option_value_method, selected_key) + '</optgroup>' end.join.html_safe end diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index d7a2651bad..ecd26891d6 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -118,7 +118,7 @@ module ActionView # escape_once("<< Accept & Checkout") # # => "<< Accept & Checkout" def escape_once(html) - html.to_s.gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] } + ERB::Util.html_escape_once(html) end private diff --git a/actionpack/lib/action_view/helpers/tags/collection_select.rb b/actionpack/lib/action_view/helpers/tags/collection_select.rb index f84140d8d0..ec78e6e5f9 100644 --- a/actionpack/lib/action_view/helpers/tags/collection_select.rb +++ b/actionpack/lib/action_view/helpers/tags/collection_select.rb @@ -12,9 +12,14 @@ module ActionView end def render - selected_value = @options.has_key?(:selected) ? @options[:selected] : value(@object) + option_tags_options = { + :selected => @options.fetch(:selected) { value(@object) }, + :disabled => @options[:disabled] + } + select_content_tag( - options_from_collection_for_select(@collection, @value_method, @text_method, :selected => selected_value, :disabled => @options[:disabled]), @options, @html_options + options_from_collection_for_select(@collection, @value_method, @text_method, option_tags_options), + @options, @html_options ) end end diff --git a/actionpack/lib/action_view/helpers/tags/label.rb b/actionpack/lib/action_view/helpers/tags/label.rb index 74ac92ee18..1bd71c2778 100644 --- a/actionpack/lib/action_view/helpers/tags/label.rb +++ b/actionpack/lib/action_view/helpers/tags/label.rb @@ -30,7 +30,7 @@ module ActionView add_default_name_and_id_for_value(tag_value, name_and_id) options.delete("index") options.delete("namespace") - options["for"] ||= name_and_id["id"] + options["for"] = name_and_id["id"] unless options.key?("for") if block_given? @template_object.label_tag(name_and_id["id"], options, &block) diff --git a/actionpack/lib/action_view/helpers/tags/select.rb b/actionpack/lib/action_view/helpers/tags/select.rb index 02b790db4e..53a108b7e6 100644 --- a/actionpack/lib/action_view/helpers/tags/select.rb +++ b/actionpack/lib/action_view/helpers/tags/select.rb @@ -11,21 +11,30 @@ module ActionView end def render - selected_value = @options.has_key?(:selected) ? @options[:selected] : value(@object) + option_tags_options = { + :selected => @options.fetch(:selected) { value(@object) }, + :disabled => @options[:disabled] + } - # Grouped choices look like this: - # - # [nil, []] - # { nil => [] } - # - if !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last - option_tags = grouped_options_for_select(@choices, :selected => selected_value, :disabled => @options[:disabled]) + option_tags = if grouped_choices? + grouped_options_for_select(@choices, option_tags_options) else - option_tags = options_for_select(@choices, :selected => selected_value, :disabled => @options[:disabled]) + options_for_select(@choices, option_tags_options) end select_content_tag(option_tags, @options, @html_options) end + + private + + # Grouped choices look like this: + # + # [nil, []] + # { nil => [] } + # + def grouped_choices? + !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last + end end end end diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index d27d49821b..b5fc882e31 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -323,30 +323,24 @@ module ActionView # # def button_to(name, options = {}, html_options = {}) html_options = html_options.stringify_keys - convert_boolean_attributes!(html_options, %w( disabled )) + convert_boolean_attributes!(html_options, %w(disabled)) - method_tag = '' - if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s) - method_tag = method_tag(method) - end + url = options.is_a?(String) ? options : url_for(options) + remote = html_options.delete('remote') - form_method = method.to_s == 'get' ? 'get' : 'post' + method = html_options.delete('method').to_s + method_tag = %w{put delete}.include?(method) ? method_tag(method) : "" + + form_method = method == 'get' ? 'get' : 'post' form_options = html_options.delete('form') || {} form_options[:class] ||= html_options.delete('form_class') || 'button_to' - - remote = html_options.delete('remote') + form_options.merge!(:method => form_method, :action => url) + form_options.merge!("data-remote" => "true") if remote request_token_tag = form_method == 'post' ? token_tag : '' - url = options.is_a?(String) ? options : self.url_for(options) - name ||= url - html_options = convert_options_to_data_attributes(options, html_options) - - html_options.merge!("type" => "submit", "value" => name) - - form_options.merge!(:method => form_method, :action => url) - form_options.merge!("data-remote" => "true") if remote + html_options.merge!("type" => "submit", "value" => name || url) "#{tag(:form, form_options, true)}<div>#{method_tag}#{tag("input", html_options)}#{request_token_tag}</div></form>".html_safe end @@ -596,11 +590,7 @@ module ActionView # We ignore any extra parameters in the request_uri if the # submitted url doesn't have any either. This lets the function # work with things like ?order=asc - if url_string.index("?") - request_uri = request.fullpath - else - request_uri = request.path - end + request_uri = url_string.index("?") ? request.fullpath : request.path if url_string =~ /^\w+:\/\// url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}" @@ -630,12 +620,12 @@ module ActionView end def link_to_remote_options?(options) - options.is_a?(Hash) && options.key?('remote') && options.delete('remote') + options.is_a?(Hash) && options.delete('remote') end def add_method_to_attributes!(html_options, method) if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/ - html_options["rel"] = "#{html_options["rel"]} nofollow".strip + html_options["rel"] = "#{html_options["rel"]} nofollow".lstrip end html_options["data-method"] = method end diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index e231aade01..3033294883 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -85,8 +85,7 @@ module ActionView # == Rendering objects that respond to `to_partial_path` # # Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work - # and pick the proper path by checking `to_proper_path` method. If the object passed to render is a collection, - # all objects must return the same path. + # and pick the proper path by checking `to_partial_path` method. # # # @account.to_partial_path returns 'accounts/account', so it can be used to replace: # # <%= render :partial => "accounts/account", :locals => { :account => @account} %> diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index b00f69e636..fece499c94 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -59,8 +59,10 @@ module ActionView end def determine_default_helper_class(name) - mod = name.sub(/Test$/, '').safe_constantize + mod = name.sub(/Test$/, '').constantize mod.is_a?(Class) ? nil : mod + rescue NameError + nil end def helper_method(*methods) diff --git a/actionpack/lib/sprockets/assets.rake b/actionpack/lib/sprockets/assets.rake index f3547359cd..2e92fe416b 100644 --- a/actionpack/lib/sprockets/assets.rake +++ b/actionpack/lib/sprockets/assets.rake @@ -6,7 +6,11 @@ namespace :assets do groups = ENV['RAILS_GROUPS'] || 'assets' args = [$0, task,"RAILS_ENV=#{env}","RAILS_GROUPS=#{groups}"] args << "--trace" if Rake.application.options.trace - fork ? ruby(*args) : Kernel.exec(FileUtils::RUBY, *args) + if $0 =~ /rake\.bat\Z/i + Kernel.exec $0, *args + else + fork ? ruby(*args) : Kernel.exec(FileUtils::RUBY, *args) + end end # We are currently running with no explicit bundler group diff --git a/actionpack/test/controller/addresses_render_test.rb b/actionpack/test/controller/addresses_render_test.rb index 0b5f2d7679..07f27fd362 100644 --- a/actionpack/test/controller/addresses_render_test.rb +++ b/actionpack/test/controller/addresses_render_test.rb @@ -3,16 +3,18 @@ require 'active_support/logger' require 'controller/fake_controllers' class Address - def Address.count(conditions = nil, join = nil) - nil - end + class << self + def count(conditions = nil, join = nil) + nil + end - def Address.find_all(arg1, arg2, arg3, arg4) - [] - end + def find_all(arg1, arg2, arg3, arg4) + [] + end - def self.find(*args) - [] + def find(*args) + [] + end end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 3689b17877..93f5419487 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2550,7 +2550,6 @@ class TestUnicodePaths < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do match "/#{Rack::Utils.escape("ほげ")}" => lambda { |env| - path_params = env['action_dispatch.request.path_parameters'] [200, { 'Content-Type' => 'text/plain' }, []] }, :as => :unicode_path end diff --git a/actionpack/test/fixtures/developers.yml b/actionpack/test/fixtures/developers.yml index 308bf75de2..3656564f63 100644 --- a/actionpack/test/fixtures/developers.yml +++ b/actionpack/test/fixtures/developers.yml @@ -8,7 +8,7 @@ jamis: name: Jamis salary: 150000 -<% for digit in 3..10 %> +<% (3..10).each do |digit| %> dev_<%= digit %>: id: <%= digit %> name: fixture_<%= digit %> diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 45a8492d58..90c2ca7a3d 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -168,7 +168,7 @@ class AssetTagHelperTest < ActionView::TestCase } FaviconLinkToTag = { - %(favicon_link_tag) => %(<link href="/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />), + %(favicon_link_tag) => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />), %(favicon_link_tag 'favicon.ico') => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />), %(favicon_link_tag 'favicon.ico', :rel => 'foo') => %(<link href="/images/favicon.ico" rel="foo" type="image/vnd.microsoft.icon" />), %(favicon_link_tag 'favicon.ico', :rel => 'foo', :type => 'bar') => %(<link href="/images/favicon.ico" rel="foo" type="bar" />), @@ -201,6 +201,7 @@ class AssetTagHelperTest < ActionView::TestCase %(video_tag("error.avi", "size" => "x")) => %(<video src="/videos/error.avi" />), %(video_tag("http://media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="http://media.rubyonrails.org/video/rails_blog_2.mov" />), %(video_tag("//media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="//media.rubyonrails.org/video/rails_blog_2.mov" />), + %(video_tag("multiple.ogg", "multiple.avi")) => %(<video><source src="/videos/multiple.ogg" /><source src="/videos/multiple.avi" /></video>), %(video_tag(["multiple.ogg", "multiple.avi"])) => %(<video><source src="/videos/multiple.ogg" /><source src="/videos/multiple.avi" /></video>), %(video_tag(["multiple.ogg", "multiple.avi"], :size => "160x120", :controls => true)) => %(<video controls="controls" height="120" width="160"><source src="/videos/multiple.ogg" /><source src="/videos/multiple.avi" /></video>) } @@ -224,6 +225,7 @@ class AssetTagHelperTest < ActionView::TestCase %(audio_tag("rss.wav", :autoplay => true, :controls => true)) => %(<audio autoplay="autoplay" controls="controls" src="/audios/rss.wav" />), %(audio_tag("http://media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="http://media.rubyonrails.org/audio/rails_blog_2.mov" />), %(audio_tag("//media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="//media.rubyonrails.org/audio/rails_blog_2.mov" />), + %(audio_tag("audio.mp3", "audio.ogg")) => %(<audio><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>), %(audio_tag(["audio.mp3", "audio.ogg"])) => %(<audio><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>), %(audio_tag(["audio.mp3", "audio.ogg"], :autobuffer => true, :controls => true)) => %(<audio autobuffer="autobuffer" controls="controls"><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>) } @@ -443,6 +445,12 @@ class AssetTagHelperTest < ActionView::TestCase ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end + def test_image_tag_does_not_modify_options + options = {:size => '16x10'} + image_tag('icon', options) + assert_equal({:size => '16x10'}, options) + end + def test_favicon_link_tag FaviconLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end @@ -486,6 +494,14 @@ class AssetTagHelperTest < ActionView::TestCase AudioLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end + def test_video_audio_tag_does_not_modify_options + options = {:autoplay => true} + video_tag('video', options) + assert_equal({:autoplay => true}, options) + audio_tag('audio', options) + assert_equal({:autoplay => true}, options) + end + def test_timebased_asset_id expected_time = File.mtime(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).to_i.to_s assert_equal %(<img alt="Rails" src="/images/rails.png?#{expected_time}" />), image_tag("rails.png") diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb index ef3d7d97ee..82b62e64f3 100644 --- a/actionpack/test/template/date_helper_i18n_test.rb +++ b/actionpack/test/template/date_helper_i18n_test.rb @@ -103,7 +103,7 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase I18n.expects(:translate).with(('datetime.prompts.' + key.to_s).to_sym, :locale => 'en').returns prompt end - I18n.expects(:translate).with(:'date.order', :locale => 'en').returns [:year, :month, :day] + I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:year, :month, :day] datetime_select('post', 'updated_at', :locale => 'en', :include_seconds => true, :prompt => true) end @@ -115,12 +115,12 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase end def test_date_or_time_select_given_no_order_options_translates_order - I18n.expects(:translate).with(:'date.order', :locale => 'en').returns [:year, :month, :day] + I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:year, :month, :day] datetime_select('post', 'updated_at', :locale => 'en') end def test_date_or_time_select_given_invalid_order - I18n.expects(:translate).with(:'date.order', :locale => 'en').returns [:invalid, :month, :day] + I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:invalid, :month, :day] assert_raise StandardError do datetime_select('post', 'updated_at', :locale => 'en') diff --git a/actionpack/test/template/erb_util_test.rb b/actionpack/test/template/erb_util_test.rb index eba2ef64e0..ca2710e9b3 100644 --- a/actionpack/test/template/erb_util_test.rb +++ b/actionpack/test/template/erb_util_test.rb @@ -44,4 +44,18 @@ class ErbUtilTest < ActiveSupport::TestCase assert_equal chr, html_escape(chr) end end + + def test_html_escape_once + assert_equal '1 < 2 & 3', html_escape_once('1 < 2 & 3') + end + + def test_html_escape_once_returns_unsafe_strings_when_passed_unsafe_strings + value = html_escape_once('1 < 2 & 3') + assert !value.html_safe? + end + + def test_html_escape_once_returns_safe_strings_when_passed_safe_strings + value = html_escape_once('1 < 2 & 3'.html_safe) + assert value.html_safe? + end end diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 39d4768861..8680631a48 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -215,6 +215,10 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, "for" => "my_for")) end + def test_label_does_not_generate_for_attribute_when_given_nil + assert_dom_equal('<label>Title</label>', label(:post, :title, :for => nil)) + end + def test_label_with_id_attribute_as_symbol assert_dom_equal('<label for="post_title" id="my_id">Title</label>', label(:post, :title, nil, :id => "my_id")) end diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index cf4dafbac4..37ec0e323d 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -31,13 +31,13 @@ class UrlHelperTest < ActiveSupport::TestCase setup :_prepare_context - def hash_for(opts = []) - ActiveSupport::OrderedHash[*([:controller, "foo", :action, "bar"].concat(opts))] + def hash_for(options = {}) + { :controller => "foo", :action => "bar" }.merge!(options) end alias url_hash hash_for def test_url_for_does_not_escape_urls - assert_equal "/?a=b&c=d", url_for(hash_for([:a, :b, :c, :d])) + assert_equal "/?a=b&c=d", url_for(hash_for(:a => :b, :c => :d)) end def test_url_for_with_back @@ -168,7 +168,7 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_link_tag_with_host_option - hash = hash_for([:host, "www.example.com"]) + hash = hash_for(:host => "www.example.com") expected = %q{<a href="http://www.example.com/">Test Link</a>} assert_dom_equal(expected, link_to('Test Link', hash)) end @@ -343,7 +343,7 @@ class UrlHelperTest < ActiveSupport::TestCase def test_current_page_with_params_that_match @request = request_for_url("/?order=desc&page=1") - assert current_page?(hash_for([:order, "desc", :page, "1"])) + assert current_page?(hash_for(:order => "desc", :page => "1")) assert current_page?("http://www.example.com/?order=desc&page=1") end @@ -371,20 +371,20 @@ class UrlHelperTest < ActiveSupport::TestCase @request = request_for_url("/?order=desc&page=1") assert_equal "Showing", - link_to_unless_current("Showing", hash_for([:order, 'desc', :page, '1'])) + link_to_unless_current("Showing", hash_for(:order => 'desc', :page => '1')) assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=1") @request = request_for_url("/?order=desc") assert_equal %{<a href="/?order=asc">Showing</a>}, - link_to_unless_current("Showing", hash_for([:order, :asc])) + link_to_unless_current("Showing", hash_for(:order => :asc)) assert_equal %{<a href="http://www.example.com/?order=asc">Showing</a>}, link_to_unless_current("Showing", "http://www.example.com/?order=asc") @request = request_for_url("/?order=desc") assert_equal %{<a href="/?order=desc&page=2\">Showing</a>}, - link_to_unless_current("Showing", hash_for([:order, "desc", :page, 2])) + link_to_unless_current("Showing", hash_for(:order => "desc", :page => 2)) assert_equal %{<a href="http://www.example.com/?order=desc&page=2">Showing</a>}, link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=2") diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index bd9ed996fe..a7a40ee03d 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,4 +1,4 @@ -## Rails 3.2.0 (unreleased) ## +## Rails 3.2.0 (January 20, 2012) ## * Deprecated `define_attr_method` in `ActiveModel::AttributeMethods`, because this only existed to support methods like `set_table_name` in Active Record, which are themselves being deprecated. diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 023c872055..75feba1fe7 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -224,7 +224,7 @@ module ActiveModel def add(attribute, message = nil, options = {}) message = normalize_message(attribute, message, options) if options[:strict] - raise ActiveModel::StrictValidationFailed, message + raise ActiveModel::StrictValidationFailed, full_message(attribute, message) end self[attribute] << message diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index db78864c67..e7a57cf691 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -29,7 +29,7 @@ module ActiveModel # user.save # => true # user.authenticate("notright") # => false # user.authenticate("mUc3m00RsqyRe") # => user - # User.find_by_name("david").try(:authenticate, "notright") # => nil + # User.find_by_name("david").try(:authenticate, "notright") # => false # User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user def has_secure_password # Load bcrypt-ruby only when has_secure_password is used. diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb index 3713fc828e..9bb72d6631 100644 --- a/activemodel/lib/active_model/validations/validates.rb +++ b/activemodel/lib/active_model/validations/validates.rb @@ -1,7 +1,6 @@ require 'active_support/core_ext/hash/slice' module ActiveModel - # == Active Model validates method module Validations module ClassMethods @@ -101,11 +100,11 @@ module ActiveModel end end - # This method is used to define validation that can not be corrected by end user - # and is considered exceptional. - # So each validator defined with bang or <tt>:strict</tt> option set to <tt>true</tt> - # will always raise <tt>ActiveModel::InternalValidationFailed</tt> instead of adding error - # when validation fails + # This method is used to define validation that cannot be corrected by end + # user and is considered exceptional. So each validator defined with bang + # or <tt>:strict</tt> option set to <tt>true</tt> will always raise + # <tt>ActiveModel::StrictValidationFailed</tt> instead of adding error + # when validation fails. # See <tt>validates</tt> for more information about validation itself. def validates!(*attributes) options = attributes.extract_options! @@ -118,7 +117,7 @@ module ActiveModel # When creating custom validators, it might be useful to be able to specify # additional default keys. This can be done by overwriting this method. def _validates_default_keys - [ :if, :unless, :on, :allow_blank, :allow_nil , :strict] + [:if, :unless, :on, :allow_blank, :allow_nil , :strict] end def _parse_validates_options(options) #:nodoc: diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index ed4d8fcdca..0b1de62a48 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -58,8 +58,7 @@ class ValidationsTest < ActiveModel::TestCase r = Reply.new r.valid? - errors = [] - r.errors.each {|attr, messages| errors << [attr.to_s, messages] } + errors = r.errors.collect {|attr, messages| [attr.to_s, messages]} assert errors.include?(["title", "is Empty"]) assert errors.include?(["content", "is Empty"]) @@ -311,7 +310,7 @@ class ValidationsTest < ActiveModel::TestCase end def test_strict_validation_particular_validator - Topic.validates :title, :presence => {:strict => true} + Topic.validates :title, :presence => { :strict => true } assert_raises ActiveModel::StrictValidationFailed do Topic.new.valid? end @@ -331,9 +330,18 @@ class ValidationsTest < ActiveModel::TestCase end end + def test_strict_validation_error_message + Topic.validates :title, :strict => true, :presence => true + + exception = assert_raises(ActiveModel::StrictValidationFailed) do + Topic.new.valid? + end + assert_equal "Title can't be blank", exception.message + end + def test_does_not_modify_options_argument - options = {:presence => true} + options = { :presence => true } Topic.validates :title, options - assert_equal({:presence => true}, options) + assert_equal({ :presence => true }, options) end end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index a458f9e6e4..69cf1193b6 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,51 @@ ## Rails 4.0.0 (unreleased) ## +* Implemented ActiveRecord::Relation#none method + + The `none` method returns a chainable relation with zero records + (an instance of the NullRelation class). + + Any subsequent condition chained to the returned relation will continue + generating an empty relation and will not fire any query to the database. + + *Juanjo Bazán* + +* Added the `ActiveRecord::NullRelation` class implementing the null + object pattern for the Relation class. *Juanjo Bazán* + +* Added deprecation for the `:dependent => :restrict` association option. + + Please note: + + * Up until now `has_many` and `has_one`, `:dependent => :restrict` + option raised a `DeleteRestrictionError` at the time of destroying + the object. Instead, it will add an error on the model. + + * To fix this warning, make sure your code isn't relying on a + `DeleteRestrictionError` and then add + `config.active_record.dependent_restrict_raises = false` to your + application config. + + * New rails application would be generated with the + `config.active_record.dependent_restrict_raises = false` in the + application config. + + *Manoj Kumar* + +* Added `create_join_table` migration helper to create HABTM join tables + + create_join_table :products, :categories + # => + # create_table :categories_products, :id => false do |td| + # td.integer :product_id, :null => false + # td.integer :category_id, :null => false + # end + + *Rafael Mendonça França* + +* The primary key is always initialized in the @attributes hash to nil (unless + another value has been specified). + * In previous releases, the following would generate a single query with an `OUTER JOIN comments`, rather than two separate queries: @@ -70,7 +116,16 @@ * PostgreSQL hstore types are automatically deserialized from the database. -## Rails 3.2.0 (unreleased) ## + +## Rails 3.2.1 (unreleased) ## + +* The threshold for auto EXPLAIN is ignored if there's no logger. *fxn* + +* Fix possible race condition when two threads try to define attribute + methods for the same class. + + +## Rails 3.2.0 (January 20, 2012) ## * Added a `with_lock` method to ActiveRecord objects, which starts a transaction, locks the object (pessimistically) and yields to the block. @@ -196,7 +251,7 @@ Client.select(:name).uniq - This also allows you to revert the unqueness in a relation: + This also allows you to revert the uniqueness in a relation: Client.select(:name).uniq.uniq(false) diff --git a/activerecord/Rakefile b/activerecord/Rakefile index d769a73dba..98020ad3ab 100755 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -187,15 +187,15 @@ end task :lines do lines, codelines, total_lines, total_codelines = 0, 0, 0, 0 - for file_name in FileList["lib/active_record/**/*.rb"] + FileList["lib/active_record/**/*.rb"].each do |file_name| next if file_name =~ /vendor/ - f = File.open(file_name) - - while line = f.gets - lines += 1 - next if line =~ /^\s*$/ - next if line =~ /^\s*#/ - codelines += 1 + File.open(file_name, 'r') do |f| + while line = f.gets + lines += 1 + next if line =~ /^\s*$/ + next if line =~ /^\s*#/ + codelines += 1 + end end puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}" diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 78e958442f..73c8a06ab7 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -43,6 +43,7 @@ module ActiveRecord autoload :AutosaveAssociation autoload :Relation + autoload :NullRelation autoload_under 'relation' do autoload :QueryMethods diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 58725246c8..958821add6 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1097,8 +1097,8 @@ module ActiveRecord # alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated # objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated # objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. If set to - # <tt>:restrict</tt> this object raises an <tt>ActiveRecord::DeleteRestrictionError</tt> exception and - # cannot be deleted if it has any associated objects. + # <tt>:restrict</tt> an error will be added to the object, preventing its deletion, if any associated + # objects are present. # # If using with the <tt>:through</tt> option, the association on the join model must be # a +belongs_to+, and the records which get deleted are the join records, rather than @@ -1251,8 +1251,8 @@ module ActiveRecord # If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. # If set to <tt>:nullify</tt>, the associated object's foreign key is set to +NULL+. - # Also, association is assigned. If set to <tt>:restrict</tt> this object raises an - # <tt>ActiveRecord::DeleteRestrictionError</tt> exception and cannot be deleted if it has any associated object. + # If set to <tt>:restrict</tt>, an error will be added to the object, preventing its deletion, if an + # associated object is present. # [:foreign_key] # Specify the foreign key used for the association. By default this is guessed to be the name # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index 6e2e5f9de0..c3fa4a05fd 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -51,5 +51,34 @@ module ActiveRecord::Associations::Builder association(name).writer(value) end end + + def dependent_restrict_raises? + ActiveRecord::Base.dependent_restrict_raises == true + end + + def dependent_restrict_deprecation_warning + if dependent_restrict_raises? + msg = "In the next release, `:dependent => :restrict` will not raise a `DeleteRestrictionError`. "\ + "Instead, it will add an error on the model. To fix this warning, make sure your code " \ + "isn't relying on a `DeleteRestrictionError` and then add " \ + "`config.active_record.dependent_restrict_raises = false` to your application config." + ActiveSupport::Deprecation.warn msg + end + end + + def define_restrict_dependency_method + name = self.name + mixin.redefine_method(dependency_method_name) do + # has_many or has_one associations + if send(name).respond_to?(:exists?) ? send(name).exists? : !send(name).nil? + if dependent_restrict_raises? + raise ActiveRecord::DeleteRestrictionError.new(name) + else + errors.add(:base, :restrict_dependent_destroy, :model => name.to_s.singularize) + return false + end + end + end + end end end diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb index fc6799fb15..9ddfd433e4 100644 --- a/activerecord/lib/active_record/associations/builder/has_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_many.rb @@ -21,6 +21,7 @@ module ActiveRecord::Associations::Builder ":nullify or :restrict (#{options[:dependent].inspect})" end + dependent_restrict_deprecation_warning if options[:dependent] == :restrict send("define_#{options[:dependent]}_dependency_method") model.before_destroy dependency_method_name end @@ -52,13 +53,6 @@ module ActiveRecord::Associations::Builder end end - def define_restrict_dependency_method - name = self.name - mixin.redefine_method(dependency_method_name) do - raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).empty? - end - end - def dependency_method_name "has_many_dependent_for_#{name}" end diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb index 7a6cd3890f..bc8a212bee 100644 --- a/activerecord/lib/active_record/associations/builder/has_one.rb +++ b/activerecord/lib/active_record/associations/builder/has_one.rb @@ -34,15 +34,12 @@ module ActiveRecord::Associations::Builder ":nullify or :restrict (#{options[:dependent].inspect})" end + dependent_restrict_deprecation_warning if options[:dependent] == :restrict send("define_#{options[:dependent]}_dependency_method") model.before_destroy dependency_method_name end end - def dependency_method_name - "has_one_dependent_#{options[:dependent]}_for_#{name}" - end - def define_destroy_dependency_method name = self.name mixin.redefine_method(dependency_method_name) do @@ -52,11 +49,8 @@ module ActiveRecord::Associations::Builder alias :define_delete_dependency_method :define_destroy_dependency_method alias :define_nullify_dependency_method :define_destroy_dependency_method - def define_restrict_dependency_method - name = self.name - mixin.redefine_method(dependency_method_name) do - raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).nil? - end + def dependency_method_name + "has_one_dependent_#{options[:dependent]}_for_#{name}" end end end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 7aed64d48c..b2136605e1 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -58,7 +58,7 @@ module ActiveRecord end end - relation.uniq.pluck(column) + relation.pluck(column) end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 3549cbb090..c129dc8c52 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -92,11 +92,7 @@ module ActiveRecord end def internal_attribute_access_code(attr_name, cast_code) - if attr_name == primary_key - access_code = "v = @attributes[attr_name];" - else - access_code = "v = @attributes.fetch(attr_name) { missing_attribute(attr_name, caller) };" - end + access_code = "v = @attributes.fetch(attr_name) { missing_attribute(attr_name, caller) };" access_code << "v && #{cast_code};" diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 27ff13ad89..6ba64bb88f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -65,18 +65,24 @@ module ActiveRecord end private - def cache_sql(sql, binds) - result = - if @query_cache[sql].key?(binds) - ActiveSupport::Notifications.instrument("sql.active_record", - :sql => sql, :name => "CACHE", :connection_id => object_id) - @query_cache[sql][binds] - else - @query_cache[sql][binds] = yield - end + def cache_sql(sql, binds) + result = + if @query_cache[sql].key?(binds) + ActiveSupport::Notifications.instrument("sql.active_record", + :sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id) + @query_cache[sql][binds] + else + @query_cache[sql][binds] = yield + end + # FIXME: we should guarantee that all cached items are Result + # objects. Then we can avoid this conditional + if ActiveRecord::Result === result + result.dup + else result.collect { |row| row.dup } end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 0cac6d1391..84c340770a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1,9 +1,12 @@ require 'active_support/deprecation/reporting' require 'active_record/schema_migration' +require 'active_record/migration/join_table' module ActiveRecord module ConnectionAdapters # :nodoc: module SchemaStatements + include ActiveRecord::Migration::JoinTable + # Returns a Hash of mappings from the abstract data types to the native # database types. See TableDefinition#column for details on the recognized # abstract data types. @@ -170,6 +173,45 @@ module ActiveRecord execute create_sql end + # Creates a new join table with the name created using the lexical order of the first two + # arguments. These arguments can be be a String or a Symbol. + # + # # Creates a table called 'assemblies_parts' with no id. + # create_join_table(:assemblies, :parts) + # + # You can pass a +options+ hash can include the following keys: + # [<tt>:table_name</tt>] + # Sets the table name overriding the default + # [<tt>:column_options</tt>] + # Any extra options you want appended to the columns definition. + # [<tt>:options</tt>] + # Any extra options you want appended to the table definition. + # [<tt>:temporary</tt>] + # Make a temporary table. + # [<tt>:force</tt>] + # Set to true to drop the table before creating it. + # Defaults to false. + # + # ===== Examples + # ====== Add a backend specific option to the generated SQL (MySQL) + # create_join_table(:assemblies, :parts, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8') + # generates: + # CREATE TABLE assemblies_parts ( + # assembly_id int NOT NULL, + # part_id int NOT NULL, + # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 + def create_join_table(table_1, table_2, options = {}) + join_table_name = find_join_table_name(table_1, table_2, options) + + column_options = options.delete(:column_options) || {} + column_options.reverse_merge!({:null => false}) + + create_table(join_table_name, options.merge!(:id => false)) do |td| + td.integer :"#{table_1.to_s.singularize}_id", column_options + td.integer :"#{table_2.to_s.singularize}_id", column_options + end + end + # A block for changing columns in +table+. # # === Example diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 201c05d8f5..9d9dbcc355 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -505,7 +505,7 @@ module ActiveRecord execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result| create_table = each_hash(result).first[:"Create Table"] if create_table.to_s =~ /PRIMARY KEY\s+\((.+)\)/ - keys = $1.split(",").map { |key| key.gsub(/`/, "") } + keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") } keys.length == 1 ? [keys.first, nil] : nil else nil diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 6086c32dbe..c1332fde1a 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -225,7 +225,7 @@ module ActiveRecord # column values as values. def select(sql, name = nil, binds = []) binds = binds.dup - exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a + exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name) end def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index e432c5af32..5905242747 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -408,7 +408,7 @@ module ActiveRecord def select(sql, name = nil, binds = []) @connection.query_with_result = true - rows = exec_query(sql, name, binds).to_a + rows = exec_query(sql, name, binds) @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped rows end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index dbfb375ba8..1d8e5d813a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1249,7 +1249,7 @@ module ActiveRecord # Executes a SELECT query and returns the results, performing any data type # conversions that are required to be performed here instead of in PostgreSQLColumn. def select(sql, name = nil, binds = []) - exec_query(sql, name, binds).to_a + exec_query(sql, name, binds) end def select_raw(sql, name = nil) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 69750a911d..0520fc8b62 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -454,7 +454,7 @@ module ActiveRecord protected def select(sql, name = nil, binds = []) #:nodoc: - exec_query(sql, name, binds).to_a + exec_query(sql, name, binds) end def table_structure(table_name) diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index adba3f710c..a2ce620354 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -72,6 +72,16 @@ module ActiveRecord # The connection handler config_attribute :connection_handler self.connection_handler = ConnectionAdapters::ConnectionHandler.new + + ## + # :singleton-method: + # Specifies wether or not has_many or has_one association option + # :dependent => :restrict raises an exception. If set to true, the + # ActiveRecord::DeleteRestrictionError exception will be raised + # along with a DEPRECATION WARNING. If set to false, an error would + # be added to the model instead. + config_attribute :dependent_restrict_raises, :global => true + self.dependent_restrict_raises = true end module ClassMethods @@ -202,6 +212,7 @@ module ActiveRecord cloned_attributes.delete(self.class.primary_key) @attributes = cloned_attributes + @attributes[self.class.primary_key] = nil run_callbacks(:initialize) if _initialize_callbacks.any? @@ -326,6 +337,10 @@ module ActiveRecord end def init_internals + pk = self.class.primary_key + + @attributes[pk] = nil unless @attributes.key?(pk) + @relation = nil @aggregation_cache = {} @association_cache = {} diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb index b309df9b1b..38dbbef5fc 100644 --- a/activerecord/lib/active_record/dynamic_finder_match.rb +++ b/activerecord/lib/active_record/dynamic_finder_match.rb @@ -6,33 +6,23 @@ module ActiveRecord # class DynamicFinderMatch def self.match(method) - finder = :first - bang = false - instantiator = nil - - case method.to_s - when /^find_(all_|last_)?by_([_a-zA-Z]\w*)$/ - finder = :last if $1 == 'last_' - finder = :all if $1 == 'all_' - names = $2 - when /^find_by_([_a-zA-Z]\w*)\!$/ - bang = true - names = $1 - when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/ - instantiator = $1 == 'initialize' ? :new : :create - names = $2 - else - return nil + method = method.to_s + klass = [FindBy, FindByBang, FindOrInitializeCreateBy].find do |_klass| + _klass.matches?(method) end + klass.new(method) if klass + end - new(finder, instantiator, bang, names.split('_and_')) + def self.matches?(method) + method =~ self::METHOD_PATTERN end - def initialize(finder, instantiator, bang, attribute_names) - @finder = finder - @instantiator = instantiator - @bang = bang - @attribute_names = attribute_names + def initialize(method) + @finder = :first + @instantiator = nil + match_data = method.match(self.class::METHOD_PATTERN) + @attribute_names = match_data[-1].split("_and_") + initialize_from_match_data(match_data) end attr_reader :finder, :attribute_names, :instantiator @@ -41,16 +31,54 @@ module ActiveRecord @finder && !@instantiator end + def creator? + @finder == :first && @instantiator == :create + end + def instantiator? - @finder == :first && @instantiator + @instantiator end - def creator? - @finder == :first && @instantiator == :create + def bang? + false + end + + def valid_arguments?(arguments) + arguments.size >= @attribute_names.size end + private + + def initialize_from_match_data(match_data) + end + end + + class FindBy < DynamicFinderMatch + METHOD_PATTERN = /^find_(all_|last_)?by_([_a-zA-Z]\w*)$/ + + def initialize_from_match_data(match_data) + @finder = :last if match_data[1] == 'last_' + @finder = :all if match_data[1] == 'all_' + end + end + + class FindByBang < DynamicFinderMatch + METHOD_PATTERN = /^find_by_([_a-zA-Z]\w*)\!$/ + def bang? - @bang + true + end + end + + class FindOrInitializeCreateBy < DynamicFinderMatch + METHOD_PATTERN = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/ + + def initialize_from_match_data(match_data) + @instantiator = match_data[1] == 'initialize' ? :new : :create + end + + def valid_arguments?(arguments) + arguments.size == 1 && arguments.first.is_a?(Hash) || super end end end diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb index e9068089f0..60ce3dd4f1 100644 --- a/activerecord/lib/active_record/dynamic_matchers.rb +++ b/activerecord/lib/active_record/dynamic_matchers.rb @@ -25,7 +25,7 @@ module ActiveRecord if match = (DynamicFinderMatch.match(method_id) || DynamicScopeMatch.match(method_id)) attribute_names = match.attribute_names super unless all_attributes_exists?(attribute_names) - if arguments.size < attribute_names.size + unless match.valid_arguments?(arguments) method_trace = "#{__FILE__}:#{__LINE__}:in `#{method_id}'" backtrace = [method_trace] + caller raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{attribute_names.size})", backtrace diff --git a/activerecord/lib/active_record/dynamic_scope_match.rb b/activerecord/lib/active_record/dynamic_scope_match.rb index c832e927d6..a502155aac 100644 --- a/activerecord/lib/active_record/dynamic_scope_match.rb +++ b/activerecord/lib/active_record/dynamic_scope_match.rb @@ -19,5 +19,9 @@ module ActiveRecord attr_reader :scope, :attribute_names alias :scope? :scope + + def valid_arguments?(arguments) + arguments.size >= @attribute_names.size + end end end diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index e502d7e52b..01cacf6153 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -19,6 +19,8 @@ module ActiveRecord # currently collected. A false value indicates collecting is turned # off. Otherwise it is an array of queries. def logging_query_plan # :nodoc: + return yield unless logger + threshold = auto_explain_threshold_in_seconds current = Thread.current if threshold && current[:available_queries_for_explain].nil? diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml index 44328f63b6..8892f7ef2f 100644 --- a/activerecord/lib/active_record/locale/en.yml +++ b/activerecord/lib/active_record/locale/en.yml @@ -10,6 +10,7 @@ en: messages: taken: "has already been taken" record_invalid: "Validation failed: %{errors}" + restrict_dependent_destroy: "Cannot delete record because dependent %{model} exists" # Append your own errors here or at the model/attributes scope. # You can define own errors for models or model attributes. diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index 4e27293cb4..96b62fdd61 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -8,11 +8,14 @@ module ActiveRecord # * add_index # * add_timestamps # * create_table + # * create_join_table # * remove_timestamps # * rename_column # * rename_index # * rename_table class CommandRecorder + include JoinTable + attr_accessor :commands, :delegate def initialize(delegate = nil) @@ -48,7 +51,7 @@ module ActiveRecord super || delegate.respond_to?(*args) end - [:create_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method| + [:create_table, :create_join_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method| class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{method}(*args) # def create_table(*args) record(:"#{method}", args) # record(:create_table, args) @@ -62,6 +65,12 @@ module ActiveRecord [:drop_table, [args.first]] end + def invert_create_join_table(args) + table_name = find_join_table_name(*args) + + [:drop_table, [table_name]] + end + def invert_rename_table(args) [:rename_table, args.reverse] end @@ -99,7 +108,6 @@ module ActiveRecord rescue NoMethodError => e raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@delegate}") end - end end end diff --git a/activerecord/lib/active_record/migration/join_table.rb b/activerecord/lib/active_record/migration/join_table.rb new file mode 100644 index 0000000000..01a580781b --- /dev/null +++ b/activerecord/lib/active_record/migration/join_table.rb @@ -0,0 +1,17 @@ +module ActiveRecord + class Migration + module JoinTable #:nodoc: + private + + def find_join_table_name(table_1, table_2, options = {}) + options.delete(:table_name) { join_table_name(table_1, table_2) } + end + + def join_table_name(table_1, table_2) + tables_names = [table_1, table_2].map(&:to_s).sort + + tables_names.join("_").to_sym + end + end + end +end diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 261f6fa974..61f82af0c3 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -115,7 +115,7 @@ module ActiveRecord # the documentation for ActiveRecord::Base#table_name. def table_name=(value) @original_table_name = @table_name if defined?(@table_name) - @table_name = value + @table_name = value && value.to_s @quoted_table_name = nil @arel_table = nil @relation = Relation.new(self, arel_table) diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb new file mode 100644 index 0000000000..60c37ac2b7 --- /dev/null +++ b/activerecord/lib/active_record/null_relation.rb @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- + +module ActiveRecord + # = Active Record Null Relation + class NullRelation < Relation + def exec_queries + @records = [] + end + end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 94e34e1bd4..5945b05190 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -8,7 +8,7 @@ module ActiveRecord delegate :find_each, :find_in_batches, :to => :scoped delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, - :having, :create_with, :uniq, :references, :to => :scoped + :having, :create_with, :uniq, :references, :none, :to => :scoped delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :to => :scoped # Executes a custom SQL query against your database and returns all the results. The results will @@ -35,7 +35,8 @@ module ActiveRecord # > [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...] def find_by_sql(sql, binds = []) logging_query_plan do - connection.select_all(sanitize_sql(sql), "#{name} Load", binds).collect! { |record| instantiate(record) } + result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds) + result_set.map { |record| instantiate(record) } end end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index bf9b4bf1c9..6bf3050af9 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -333,7 +333,7 @@ module ActiveRecord def select_for_count if @select_values.present? select = @select_values.join(", ") - select if select !~ /(,|\*)/ + select if select !~ /[,*]/ end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index a8ae7208fc..6a28d7b155 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -196,6 +196,39 @@ module ActiveRecord relation end + # Returns a chainable relation with zero records, specifically an + # instance of the NullRelation class. + # + # The returned NullRelation inherits from Relation and implements the + # Null Object pattern so it is an object with defined null behavior: + # it always returns an empty array of records and avoids any database query. + # + # Any subsequent condition chained to the returned relation will continue + # generating an empty relation and will not fire any query to the database. + # + # Used in cases where is needed a method or a scope that could return zero + # results but the response has to be chainable. + # + # For example: + # + # @posts = current_user.visible_posts.where(:name => params[:name]) + # # => the visible_post method response has to be a chainable Relation + # + # def visible_posts + # case role + # if 'Country Manager' + # Post.where(:country => country) + # if 'Reviewer' + # Post.published + # if 'Bad User' + # Post.none # => returning [] instead breaks the previous code + # end + # end + # + def none + NullRelation.new(@klass, @table) + end + def readonly(value = true) relation = clone relation.readonly_value = value diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index 9ceab2eabc..60a2e90e23 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -24,6 +24,31 @@ module ActiveRecord hash_rows end + alias :map! :map + alias :collect! :map + + def empty? + rows.empty? + end + + def to_ary + hash_rows + end + + def [](idx) + hash_rows[idx] + end + + def last + hash_rows.last + end + + def initialize_copy(other) + @columns = columns.dup + @rows = rows.dup + @hash_rows = nil + end + private def hash_rows @hash_rows ||= @rows.map { |row| diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 3a741ba600..7fd5d76ba5 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -57,8 +57,7 @@ module ActiveRecord def build_relation(klass, table, attribute, value) #:nodoc: reflection = klass.reflect_on_association(attribute) - column = nil - if(reflection) + if reflection column = klass.columns_hash[reflection.foreign_key] attribute = reflection.foreign_key value = value.attributes[reflection.primary_key_column.name] diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index f1a341437f..ab1e821aab 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1140,16 +1140,42 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_nil companies(:leetsoft).reload.client_of assert_nil companies(:jadedpixel).reload.client_of - assert_equal num_accounts, Account.count end def test_restrict - firm = RestrictedFirm.new(:name => 'restrict') - firm.save! + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = true + + firm = RestrictedFirm.create!(:name => 'restrict') firm.companies.create(:name => 'child') + assert !firm.companies.empty? assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy } + assert RestrictedFirm.exists?(:name => 'restrict') + assert firm.companies.exists?(:name => 'child') + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + end + + def test_restrict_when_dependent_restrict_raises_config_set_to_false + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = false + + firm = RestrictedFirm.create!(:name => 'restrict') + firm.companies.create(:name => 'child') + + assert !firm.companies.empty? + + firm.destroy + + assert !firm.errors.empty? + + assert_equal "Cannot delete record because dependent company exists", firm.errors[:base].first + assert RestrictedFirm.exists?(:name => 'restrict') + assert firm.companies.exists?(:name => 'child') + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before end def test_included_in_collection @@ -1253,6 +1279,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert company.clients_using_sql.loaded? end + def test_get_ids_for_ordered_association + assert_equal [companies(:second_client).id, companies(:first_client).id], companies(:first_firm).clients_ordered_by_name_ids + end + def test_assign_ids_ignoring_blanks firm = Firm.create!(:name => 'Apple') firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, ''] @@ -1401,29 +1431,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm.clients.last end end - + def test_custom_primary_key_on_new_record_should_fetch_with_query author = Author.new(:name => "David") assert !author.essays.loaded? - - assert_queries 1 do + + assert_queries 1 do assert_equal 1, author.essays.size end - + assert_equal author.essays, Essay.find_all_by_writer_id("David") - + end - + def test_has_many_custom_primary_key david = authors(:david) assert_equal david.essays, Essay.find_all_by_writer_id("David") end - + def test_blank_custom_primary_key_on_new_record_should_not_run_queries author = Author.new assert !author.essays.loaded? - - assert_queries 0 do + + assert_queries 0 do assert_equal 0, author.essays.size end end @@ -1649,4 +1679,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [bulb2], car.bulbs assert_equal [bulb2], car.reload.bulbs end + + def test_building_has_many_association_with_restrict_dependency + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = true + + klass = Class.new(ActiveRecord::Base) + + assert_deprecated { klass.has_many :companies, :dependent => :restrict } + assert_not_deprecated { klass.has_many :companies } + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + end 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 4612bc2618..9cc09194dc 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -738,7 +738,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_select_chosen_fields_only author = authors(:david) - assert_equal ['body'], author.comments.select('comments.body').first.attributes.keys + assert_equal ['body', 'id'].sort, author.comments.select('comments.body').first.attributes.keys.sort end def test_get_has_many_through_belongs_to_ids_with_conditions diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 26931e3e85..37be6a279d 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -157,11 +157,38 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_dependence_with_restrict - firm = RestrictedFirm.new(:name => 'restrict') - firm.save! + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = true + + firm = RestrictedFirm.create!(:name => 'restrict') firm.create_account(:credit_limit => 10) + assert_not_nil firm.account + assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy } + assert RestrictedFirm.exists?(:name => 'restrict') + assert firm.account.present? + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + end + + def test_dependence_with_restrict_with_dependent_restrict_raises_config_set_to_false + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = false + + firm = RestrictedFirm.create!(:name => 'restrict') + firm.create_account(:credit_limit => 10) + + assert_not_nil firm.account + + firm.destroy + + assert !firm.errors.empty? + assert_equal "Cannot delete record because dependent account exists", firm.errors[:base].first + assert RestrictedFirm.exists?(:name => 'restrict') + assert firm.account.present? + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before end def test_successful_build_association @@ -456,4 +483,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal car.id, bulb.attributes_after_initialize['car_id'] end + + def test_building_has_one_association_with_dependent_restrict + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = true + + klass = Class.new(ActiveRecord::Base) + + assert_deprecated { klass.has_one :account, :dependent => :restrict } + assert_not_deprecated { klass.has_one :account } + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 0ea1b824e1..6c3e4fc6d0 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1104,7 +1104,10 @@ class BasicsTest < ActiveRecord::TestCase # TODO: extend defaults tests to other databases! if current_adapter?(:PostgreSQLAdapter) def test_default + tz = Default.default_timezone + Default.default_timezone = :local default = Default.new + Default.default_timezone = tz # fixed dates / times assert_equal Date.new(2004, 1, 1), default.fixed_date @@ -1446,6 +1449,11 @@ class BasicsTest < ActiveRecord::TestCase end end + def test_set_table_name_symbol_converted_to_string + Joke.table_name = :cold_jokes + assert_equal 'cold_jokes', Joke.table_name + end + def test_quoted_table_name_after_set_table_name klass = Class.new(ActiveRecord::Base) @@ -1574,7 +1582,7 @@ class BasicsTest < ActiveRecord::TestCase Developer.find(:all) end assert developers.size >= 2 - for i in 1...developers.size + (1...developers.size).each do |i| assert developers[i-1].salary >= developers[i].salary end end diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb index 6ae6f83446..83c9b6e107 100644 --- a/activerecord/test/cases/explain_test.rb +++ b/activerecord/test/cases/explain_test.rb @@ -14,7 +14,7 @@ if ActiveRecord::Base.connection.supports_explain? base.connection end - def test_logging_query_plan + def test_logging_query_plan_with_logger base.logger.expects(:warn).with do |message| message.starts_with?('EXPLAIN for:') end @@ -24,6 +24,20 @@ if ActiveRecord::Base.connection.supports_explain? end end + def test_logging_query_plan_without_logger + original = base.logger + base.logger = nil + + base.logger.expects(:warn).never + + with_threshold(0) do + car = Car.where(:name => 'honda').first + assert_equal 'honda', car.name + end + ensure + base.logger = original + end + def test_collect_queries_for_explain base.auto_explain_threshold_in_seconds = nil queries = Thread.current[:available_queries_for_explain] = [] diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 7d80a56858..76c041397a 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -881,6 +881,17 @@ class FinderTest < ActiveRecord::TestCase assert_equal 23, sig38.client_of end + def test_find_or_create_from_two_attributes_and_hash + number_of_companies = Company.count + sig38 = Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23}) + assert_equal number_of_companies + 1, Company.count + assert_equal sig38, Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23}) + assert sig38.persisted? + assert_equal "38signals", sig38.name + assert_equal 17, sig38.firm_id + assert_equal 23, sig38.client_of + end + def test_find_or_create_from_one_aggregate_attribute number_of_customers = Customer.count created_customer = Customer.find_or_create_by_balance(Money.new(123)) diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 5c67cbfcce..562b370c97 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -782,7 +782,7 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase end def test_table_name_is_defined_in_the_model - assert_equal :randomly_named_table, ActiveRecord::Fixtures::all_loaded_fixtures["admin/randomly_named_a9"].table_name - assert_equal :randomly_named_table, Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name + assert_equal 'randomly_named_table', ActiveRecord::Fixtures::all_loaded_fixtures["admin/randomly_named_a9"].table_name + assert_equal 'randomly_named_table', Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name end end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index b4598ab32a..359cabd016 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -22,6 +22,9 @@ ActiveSupport::Deprecation.debug = true # Enable Identity Map only when ENV['IM'] is set to "true" ActiveRecord::IdentityMap.enabled = (ENV['IM'] == "true") +# Avoid deprecation warning setting dependent_restric_raises to false. The default is true +ActiveRecord::Base.dependent_restrict_raises = false + # Connect to the database ARTest.connect diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index a1ade59e52..f0b1f74bd3 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -173,7 +173,6 @@ module ActiveRecord skip "not supported on #{connection.class}" end - connection.create_table :testings do |t| t.column :foo, :string end diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 8f136bce2b..7d026961be 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -73,6 +73,18 @@ module ActiveRecord assert_equal [:drop_table, [:people_reminders]], drop_table end + def test_invert_create_join_table + @recorder.record :create_join_table, [:musics, :artists] + drop_table = @recorder.inverse.first + assert_equal [:drop_table, [:artists_musics]], drop_table + end + + def test_invert_create_join_table_with_table_name + @recorder.record :create_join_table, [:musics, :artists, {:table_name => :catalog}] + drop_table = @recorder.inverse.first + assert_equal [:drop_table, [:catalog]], drop_table + end + def test_invert_rename_table @recorder.record :rename_table, [:old, :new] rename = @recorder.inverse.first diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb new file mode 100644 index 0000000000..0428d9ba76 --- /dev/null +++ b/activerecord/test/cases/migration/create_join_table_test.rb @@ -0,0 +1,70 @@ +require 'cases/helper' + +module ActiveRecord + class Migration + class CreateJoinTableTest < ActiveRecord::TestCase + attr_reader :connection + + def setup + super + @connection = ActiveRecord::Base.connection + end + + def test_create_join_table + connection.create_join_table :artists, :musics + + assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort + ensure + connection.drop_table :artists_musics + end + + def test_create_join_table_set_not_null_by_default + connection.create_join_table :artists, :musics + + assert_equal [false, false], connection.columns(:artists_musics).map(&:null) + ensure + connection.drop_table :artists_musics + end + + def test_create_join_table_with_strings + connection.create_join_table 'artists', 'musics' + + assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort + ensure + connection.drop_table :artists_musics + end + + def test_create_join_table_with_the_proper_order + connection.create_join_table :videos, :musics + + assert_equal %w(music_id video_id), connection.columns(:musics_videos).map(&:name).sort + ensure + connection.drop_table :musics_videos + end + + def test_create_join_table_with_the_table_name + connection.create_join_table :artists, :musics, :table_name => :catalog + + assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort + ensure + connection.drop_table :catalog + end + + def test_create_join_table_with_the_table_name_as_string + connection.create_join_table :artists, :musics, :table_name => 'catalog' + + assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort + ensure + connection.drop_table :catalog + end + + def test_create_join_table_with_column_options + connection.create_join_table :artists, :musics, :column_options => {:null => true} + + assert_equal [true, true], connection.columns(:artists_musics).map(&:null) + ensure + connection.drop_table :artists_musics + end + end + end +end diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index b4d1a631fa..e6e50a4cd4 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -200,3 +200,19 @@ class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase assert_equal 'foo', model.primary_key end end + +if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + def test_primaery_key_method_with_ansi_quotes + con = ActiveRecord::Base.connection + con.execute("SET SESSION sql_mode='ANSI_QUOTES'") + assert_equal "id", con.primary_key("topics") + ensure + con.reconnect! + end + + end +end + diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 297fb56570..16f05f2198 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -189,8 +189,8 @@ class ReflectionTest < ActiveRecord::TestCase def test_reflection_of_all_associations # FIXME these assertions bust a lot - assert_equal 38, Firm.reflect_on_all_associations.size - assert_equal 28, Firm.reflect_on_all_associations(:has_many).size + assert_equal 39, Firm.reflect_on_all_associations.size + assert_equal 29, Firm.reflect_on_all_associations(:has_many).size assert_equal 10, Firm.reflect_on_all_associations(:has_one).size assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 5e19465253..0471d03f3b 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -215,6 +215,19 @@ class RelationTest < ActiveRecord::TestCase assert_equal [2, 4, 6, 8, 10], even_ids.sort end + def test_none + assert_no_queries do + assert_equal [], Developer.none + assert_equal [], Developer.scoped.none + end + end + + def test_none_chainable + assert_no_queries do + assert_equal [], Developer.none.where(:name => 'David') + end + end + def test_joins_with_nil_argument assert_nothing_raised { DependentFirm.joins(nil).first } end diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index 61b04b3e37..a4c065e667 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -13,7 +13,8 @@ class SerializationTest < ActiveRecord::TestCase :created_at => Time.utc(2006, 8, 1), :awesome => false, :preferences => { :gem => '<strong>ruby</strong>' }, - :alternative_id => nil + :alternative_id => nil, + :id => nil } end @@ -24,7 +25,7 @@ class SerializationTest < ActiveRecord::TestCase end def test_serialize_should_be_reversible - for format in FORMATS + FORMATS.each do |format| @serialized = Contact.new.send("to_#{format}") contact = Contact.new.send("from_#{format}", @serialized) @@ -33,7 +34,7 @@ class SerializationTest < ActiveRecord::TestCase end def test_serialize_should_allow_attribute_only_filtering - for format in FORMATS + FORMATS.each do |format| @serialized = Contact.new(@contact_attributes).send("to_#{format}", :only => [ :age, :name ]) contact = Contact.new.send("from_#{format}", @serialized) assert_equal @contact_attributes[:name], contact.name, "For #{format}" @@ -42,7 +43,7 @@ class SerializationTest < ActiveRecord::TestCase end def test_serialize_should_allow_attribute_except_filtering - for format in FORMATS + FORMATS.each do |format| @serialized = Contact.new(@contact_attributes).send("to_#{format}", :except => [ :age, :name ]) contact = Contact.new.send("from_#{format}", @serialized) assert_nil contact.name, "For #{format}" diff --git a/activerecord/test/fixtures/developers.yml b/activerecord/test/fixtures/developers.yml index 308bf75de2..3656564f63 100644 --- a/activerecord/test/fixtures/developers.yml +++ b/activerecord/test/fixtures/developers.yml @@ -8,7 +8,7 @@ jamis: name: Jamis salary: 150000 -<% for digit in 3..10 %> +<% (3..10).each do |digit| %> dev_<%= digit %>: id: <%= digit %> name: fixture_<%= digit %> diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index d1a8a82786..fbdfaa2c29 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -45,6 +45,7 @@ class Firm < Company has_many :unsorted_clients_with_symbol, :class_name => :Client has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC" has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id" + has_many :clients_ordered_by_name, :order => "name", :class_name => "Client" has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false has_many :dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :destroy has_many :exclusively_dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index e2cd7ce9e4..f4226d4720 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -724,8 +724,6 @@ ActiveRecord::Schema.define do create_table :countries_treaties, :force => true, :id => false do |t| t.string :country_id, :null => false t.string :treaty_id, :null => false - t.datetime :created_at - t.datetime :updated_at end create_table :liquid, :force => true do |t| diff --git a/activeresource/CHANGELOG.md b/activeresource/CHANGELOG.md index 09d1eeb1c8..a69bfd0d73 100644 --- a/activeresource/CHANGELOG.md +++ b/activeresource/CHANGELOG.md @@ -1,4 +1,4 @@ -## Rails 3.2.0 (unreleased) ## +## Rails 3.2.0 (January 20, 2012) ## * Redirect responses: 303 See Other and 307 Temporary Redirect now behave like 301 Moved Permanently and 302 Found. GH #3302. diff --git a/activeresource/Rakefile b/activeresource/Rakefile index b1c18ff189..042d9fb0c7 100755 --- a/activeresource/Rakefile +++ b/activeresource/Rakefile @@ -33,7 +33,7 @@ end task :lines do lines, codelines, total_lines, total_codelines = 0, 0, 0, 0 - for file_name in FileList["lib/active_resource/**/*.rb"] + FileList["lib/active_resource/**/*.rb"].each do |file_name| next if file_name =~ /vendor/ f = File.open(file_name) diff --git a/activeresource/test/cases/format_test.rb b/activeresource/test/cases/format_test.rb index 21fdc24832..30342ecc74 100644 --- a/activeresource/test/cases/format_test.rb +++ b/activeresource/test/cases/format_test.rb @@ -19,7 +19,7 @@ class FormatTest < ActiveSupport::TestCase end def test_formats_on_single_element - for format in [ :json, :xml ] + [ :json, :xml ].each do |format| using_format(Person, format) do ActiveResource::HttpMock.respond_to.get "/people/1.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@david) assert_equal @david[:name], Person.find(1).name @@ -28,7 +28,7 @@ class FormatTest < ActiveSupport::TestCase end def test_formats_on_collection - for format in [ :json, :xml ] + [ :json, :xml ].each do |format| using_format(Person, format) do ActiveResource::HttpMock.respond_to.get "/people.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@programmers) remote_programmers = Person.find(:all) @@ -39,7 +39,7 @@ class FormatTest < ActiveSupport::TestCase end def test_formats_on_custom_collection_method - for format in [ :json, :xml ] + [ :json, :xml ].each do |format| using_format(Person, format) do ActiveResource::HttpMock.respond_to.get "/people/retrieve.#{format}?name=David", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode([@david]) remote_programmers = Person.get(:retrieve, :name => 'David') diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 02a989db22..ad9a12fc9b 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,7 @@ ## Rails 4.0.0 (unreleased) ## +* Add html_escape_once to ERB::Util, and delegate escape_once tag helper to it. *Carlos Antonio da Silva* + * Remove ActiveSupport::TestCase#pending method, use `skip` instead. *Carlos Antonio da Silva* * Deprecates the compatibility method Module#local_constant_names, @@ -14,7 +16,7 @@ * BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger from Ruby stdlib. -## Rails 3.2.0 (unreleased) ## +## Rails 3.2.0 (January 20, 2012) ## * Add ActiveSupport::Cache::NullStore for use in development and testing. *Brian Durand* diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 7d032ca984..26e737e917 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -77,8 +77,7 @@ module ActiveSupport def expand_cache_key(key, namespace = nil) expanded_cache_key = namespace ? "#{namespace}/" : "" - prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"] - if prefix + if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"] expanded_cache_key << "#{prefix}/" end @@ -91,7 +90,7 @@ module ActiveSupport def retrieve_cache_key(key) case when key.respond_to?(:cache_key) then key.cache_key - when key.is_a?(Array) then ['Array', *key.map { |element| retrieve_cache_key(element) }].to_param + when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param else key.to_param end.to_s end diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index e42453389e..6f730e4b6f 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -1,7 +1,11 @@ +require 'active_support/deprecation' + class DateTime class << self - # DateTimes aren't aware of DST rules, so use a consistent non-DST offset when creating a DateTime with an offset in the local zone + # *DEPRECATED*: Use +DateTime.civil_from_format+ directly. def local_offset + ActiveSupport::Deprecation.warn 'DateTime.local_offset is deprecated. ' \ + 'Use DateTime.civil_from_format directly.', caller ::Time.local(2012).utc_offset.to_r / 86400 end diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb index e5f895478a..dc55e9c33c 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -64,8 +64,18 @@ class DateTime self.offset == 0 ? ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * 1000000) : self end + # Returns DateTime with local offset for given year if format is local else offset is zero + # + # DateTime.civil_from_format :local, 2012 + # # => Sun, 01 Jan 2012 00:00:00 +0300 + # DateTime.civil_from_format :local, 2012, 12, 17 + # # => Mon, 17 Dec 2012 00:00:00 +0000 def self.civil_from_format(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0) - offset = utc_or_local.to_sym == :local ? local_offset : 0 + if utc_or_local.to_sym == :local + offset = ::Time.local(year, month, day).utc_offset.to_r / 86400 + else + offset = 0 + end civil(year, month, day, hour, min, sec, offset) end diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 73aa7dd89a..104ee251de 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -5,6 +5,8 @@ class ERB module Util HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' } JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' } + HTML_ESCAPE_ONCE_REGEXP = /[\"><]|&(?!([a-zA-Z]+|(#\d+));)/ + JSON_ESCAPE_REGEXP = /[&"><]/ # A utility method for escaping HTML tag characters. # This method is also aliased as <tt>h</tt>. @@ -33,6 +35,21 @@ class ERB singleton_class.send(:remove_method, :html_escape) module_function :html_escape + # Returns an escaped version of +html+ without affecting existing escaped entities. + # + # ==== Examples + # html_escape_once("1 < 2 & 3") + # # => "1 < 2 & 3" + # + # html_escape_once("<< Accept & Checkout") + # # => "<< Accept & Checkout" + def html_escape_once(s) + result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP) { |special| HTML_ESCAPE[special] } + s.html_safe? ? result.html_safe : result + end + + module_function :html_escape_once + # A utility method for escaping HTML entities in JSON strings # using \uXXXX JavaScript escape sequences for string literals: # @@ -51,7 +68,7 @@ class ERB # <%=j @person.to_json %> # def json_escape(s) - result = s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] } + result = s.to_s.gsub(JSON_ESCAPE_REGEXP) { |special| JSON_ESCAPE[special] } s.html_safe? ? result.html_safe : result end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index c245b5b53c..4e6eb27756 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -94,7 +94,10 @@ module ActiveSupport result = lower_case_and_underscored_word.to_s.dup inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } result.gsub!(/_id$/, "") - result.gsub(/(_)?([a-z\d]*)/i) { "#{$1 && ' '}#{inflections.acronyms[$2] || $2.downcase}" }.gsub(/^\w/) { $&.upcase } + result.gsub!(/_/, ' ') + result.gsub(/([a-z\d]*)/i) { |match| + "#{inflections.acronyms[match] || match.downcase}" + }.gsub(/^\w/) { $&.upcase } end # Capitalizes all the words and replaces some characters in the string to create @@ -240,7 +243,7 @@ module ActiveSupport begin constantize(camel_cased_word) rescue NameError => e - raise unless e.message =~ /uninitialized constant #{const_regexp(camel_cased_word)}$/ || + raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ || e.name.to_s == camel_cased_word.to_s rescue ArgumentError => e raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/ diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index ac61870871..9a748dfa60 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -1,4 +1,5 @@ # encoding: utf-8 +require 'active_support/json' require 'active_support/core_ext/string/access' require 'active_support/core_ext/string/behavior' require 'active_support/core_ext/module/delegation' @@ -188,6 +189,10 @@ module ActiveSupport #:nodoc: chars(Unicode.tidy_bytes(@wrapped_string, force)) end + def as_json(options = nil) #:nodoc: + to_s.as_json(options) + end + %w(capitalize downcase reverse tidy_bytes upcase).each do |method| define_method("#{method}!") do |*args| @wrapped_string = send(method, *args).to_s diff --git a/activesupport/lib/active_support/testing/performance/ruby.rb b/activesupport/lib/active_support/testing/performance/ruby.rb index 26731c6bd7..b7a34ea279 100644 --- a/activesupport/lib/active_support/testing/performance/ruby.rb +++ b/activesupport/lib/active_support/testing/performance/ruby.rb @@ -86,9 +86,12 @@ module ActiveSupport end protected - # overridden by each implementation def with_gc_stats + GC::Profiler.enable + GC.start yield + ensure + GC::Profiler.disable end end @@ -124,27 +127,42 @@ module ActiveSupport class Memory < DigitalInformationUnit Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY) + + # Ruby 1.9 + GCdata patch + if GC.respond_to?(:malloc_allocated_size) + def measure + GC.malloc_allocated_size + end + end end class Objects < Amount Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS) + + # Ruby 1.9 + GCdata patch + if GC.respond_to?(:malloc_allocations) + def measure + GC.malloc_allocations + end + end end class GcRuns < Amount Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS) + + def measure + GC.count + end end class GcTime < Time Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME) + + def measure + GC::Profiler.total_time + end end end end end end - -if RUBY_VERSION.between?('1.9.2', '2.0') - require 'active_support/testing/performance/ruby/yarv' -else - $stderr.puts 'Update your ruby interpreter to be able to run benchmarks.' - exit -end diff --git a/activesupport/lib/active_support/testing/performance/ruby/yarv.rb b/activesupport/lib/active_support/testing/performance/ruby/yarv.rb deleted file mode 100644 index c34d31bf10..0000000000 --- a/activesupport/lib/active_support/testing/performance/ruby/yarv.rb +++ /dev/null @@ -1,48 +0,0 @@ -module ActiveSupport - module Testing - module Performance - module Metrics - class Base - protected - def with_gc_stats - GC::Profiler.enable - GC.start - yield - ensure - GC::Profiler.disable - end - end - - class Memory < DigitalInformationUnit - # Ruby 1.9 + GCdata patch - if GC.respond_to?(:malloc_allocated_size) - def measure - GC.malloc_allocated_size - end - end - end - - class Objects < Amount - # Ruby 1.9 + GCdata patch - if GC.respond_to?(:malloc_allocations) - def measure - GC.malloc_allocations - end - end - end - - class GcRuns < Amount - def measure - GC.count - end - end - - class GcTime < Time - def measure - GC::Profiler.total_time - end - end - end - end - end -end diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb index 6c222b83ba..dbb6c71907 100644 --- a/activesupport/lib/active_support/xml_mini/jdom.rb +++ b/activesupport/lib/active_support/xml_mini/jdom.rb @@ -71,7 +71,7 @@ module ActiveSupport child_nodes = element.child_nodes if child_nodes.length > 0 - for i in 0...child_nodes.length + (0...child_nodes.length).each do |i| child = child_nodes.item(i) merge_element!(hash, child) unless child.node_type == Node.TEXT_NODE end @@ -133,7 +133,7 @@ module ActiveSupport def get_attributes(element) attribute_hash = {} attributes = element.attributes - for i in 0...attributes.length + (0...attributes.length).each do |i| attribute_hash[CONTENT_KEY] ||= '' attribute_hash[attributes.item(i).name] = attributes.item(i).value end @@ -147,7 +147,7 @@ module ActiveSupport def texts(element) texts = [] child_nodes = element.child_nodes - for i in 0...child_nodes.length + (0...child_nodes.length).each do |i| item = child_nodes.item(i) if item.node_type == Node.TEXT_NODE texts << item.get_data @@ -163,7 +163,7 @@ module ActiveSupport def empty_content?(element) text = '' child_nodes = element.child_nodes - for i in 0...child_nodes.length + (0...child_nodes.length).each do |i| item = child_nodes.item(i) if item.node_type == Node.TEXT_NODE text << item.get_data.strip diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 3454c378d3..b03865da93 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -4,19 +4,19 @@ require 'active_support/cache' class CacheKeyTest < ActiveSupport::TestCase def test_expand_cache_key - assert_equal 'Array/1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true]) - assert_equal 'name/Array/1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true], :name) + assert_equal '1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true]) + assert_equal 'name/1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true], :name) end def test_expand_cache_key_with_rails_cache_id begin ENV['RAILS_CACHE_ID'] = 'c99' assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key(:foo) - assert_equal 'c99/Array/foo', ActiveSupport::Cache.expand_cache_key([:foo]) - assert_equal 'c99/Array/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar]) + assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key([:foo]) + assert_equal 'c99/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar]) assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key(:foo, :nm) - assert_equal 'nm/c99/Array/foo', ActiveSupport::Cache.expand_cache_key([:foo], :nm) - assert_equal 'nm/c99/Array/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm) + assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key([:foo], :nm) + assert_equal 'nm/c99/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm) ensure ENV['RAILS_CACHE_ID'] = nil end @@ -55,7 +55,7 @@ class CacheKeyTest < ActiveSupport::TestCase def key.cache_key :foo_key end - assert_equal 'Array/foo_key', ActiveSupport::Cache.expand_cache_key([key]) + assert_equal 'foo_key', ActiveSupport::Cache.expand_cache_key([key]) end def test_expand_cache_key_of_nil @@ -69,14 +69,6 @@ class CacheKeyTest < ActiveSupport::TestCase def test_expand_cache_key_of_true assert_equal 'true', ActiveSupport::Cache.expand_cache_key(true) end - - def test_expand_cache_key_of_one_element_array_different_than_key_of_element - element = 'foo' - array = [element] - element_cache_key = ActiveSupport::Cache.expand_cache_key(element) - array_cache_key = ActiveSupport::Cache.expand_cache_key(array) - assert_not_equal element_cache_key, array_cache_key - end end class CacheStoreSettingTest < ActiveSupport::TestCase diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb index 81d200a0c8..135f894056 100644 --- a/activesupport/test/constantize_test_cases.rb +++ b/activesupport/test/constantize_test_cases.rb @@ -19,7 +19,7 @@ module ConstantizeTestCases assert_raise(NameError) { yield("Ace::ConstantizeTestCases") } assert_raise(NameError) { yield("Ace::Base::ConstantizeTestCases") } end - + def run_safe_constantize_tests_on assert_nothing_raised { assert_equal Ace::Base::Case, yield("Ace::Base::Case") } assert_nothing_raised { assert_equal Ace::Base::Case, yield("::Ace::Base::Case") } @@ -33,5 +33,6 @@ module ConstantizeTestCases assert_nothing_raised { assert_equal nil, yield("blargle") } assert_nothing_raised { assert_equal nil, yield("Ace::ConstantizeTestCases") } assert_nothing_raised { assert_equal nil, yield("Ace::Base::ConstantizeTestCases") } + assert_nothing_raised { assert_equal nil, yield("#<Class:0x7b8b718b>::Nested_1") } end -end
\ No newline at end of file +end diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 433dafde83..57b5b95a66 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -43,8 +43,8 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_civil_from_format - assert_equal DateTime.civil(2010, 5, 4, 0, 0, 0, DateTime.local_offset), DateTime.civil_from_format(:local, 2010, 5, 4) - assert_equal DateTime.civil(2010, 5, 4, 0, 0, 0, 0), DateTime.civil_from_format(:utc, 2010, 5, 4) + assert_equal Time.local(2010, 5, 4, 0, 0, 0), DateTime.civil_from_format(:local, 2010, 5, 4) + assert_equal Time.utc(2010, 5, 4, 0, 0, 0), DateTime.civil_from_format(:utc, 2010, 5, 4) end def test_seconds_since_midnight @@ -331,15 +331,6 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert DateTime.new.acts_like_time? end - def test_local_offset - with_env_tz 'US/Eastern' do - assert_equal Rational(-5, 24), DateTime.local_offset - end - with_env_tz 'US/Central' do - assert_equal Rational(-6, 24), DateTime.local_offset - end - end - def test_utc? assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12).utc? assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc? diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index cfd5a27f08..d45ab70f53 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -618,14 +618,14 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30), Time.utc(2005, 2, 21, 17, 44, 30) assert_equal Time.time_with_datetime_fallback(:local, 2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30) assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0) - assert_equal Time.time_with_datetime_fallback(:local, 2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, DateTime.local_offset) + assert_equal Time.time_with_datetime_fallback(:local, 2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30) assert_equal Time.time_with_datetime_fallback(:utc, 1900, 2, 21, 17, 44, 30), DateTime.civil(1900, 2, 21, 17, 44, 30, 0) assert_equal Time.time_with_datetime_fallback(:utc, 2005), Time.utc(2005) assert_equal Time.time_with_datetime_fallback(:utc, 2039), DateTime.civil(2039, 1, 1, 0, 0, 0, 0) assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30, 1), Time.utc(2005, 2, 21, 17, 44, 30, 1) #with usec # This won't overflow on 64bit linux unless time_is_64bits? - assert_equal Time.time_with_datetime_fallback(:local, 1900, 2, 21, 17, 44, 30), DateTime.civil(1900, 2, 21, 17, 44, 30, DateTime.local_offset, 0) + assert_equal Time.time_with_datetime_fallback(:local, 1900, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1900, 2, 21, 17, 44, 30) assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1), DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0) assert_equal ::Date::ITALY, Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1).start # use Ruby's default start value @@ -647,10 +647,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase def test_local_time assert_equal Time.local_time(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30) - assert_equal Time.local_time(2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, DateTime.local_offset) + assert_equal Time.local_time(2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30) unless time_is_64bits? - assert_equal Time.local_time(1901, 2, 21, 17, 44, 30), DateTime.civil(1901, 2, 21, 17, 44, 30, DateTime.local_offset) + assert_equal Time.local_time(1901, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1901, 2, 21, 17, 44, 30) end end diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 63e7a35c01..90aa13b3e6 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -88,6 +88,9 @@ class MultibyteCharsTest < ActiveSupport::TestCase assert(('a'.mb_chars << 'b'.mb_chars).kind_of?(@proxy_class)) end + def test_should_return_string_as_json + assert_equal UNICODE_STRING, @chars.as_json + end end class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index 2fde07995b..bdde5141a9 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -7,6 +7,10 @@ class SafeBufferTest < ActiveSupport::TestCase @buffer = ActiveSupport::SafeBuffer.new end + def test_titleize + assert_equal 'Foo', "foo".html_safe.titleize + end + test "Should look like a string" do assert @buffer.is_a?(String) assert_equal "", @buffer diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index eae74b4dd5..05e94fdfb5 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,8 +1,10 @@ ## Rails 4.0.0 (unreleased) ## +* Scaffold now uses `content_tag_for` in index.html.erb *José Valim* + * Rails::Plugin has gone. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies. *Santiago Pastorino* -## Rails 3.2.0 (unreleased) ## +## Rails 3.2.0 (January 20, 2012) ## * Turn gem has been removed from default Gemfile. We still looking for a best presentation for tests output. *Guillermo Iguaran* @@ -67,7 +69,7 @@ config/initializers/* will not be executed. Plugins developers need to special case their initializers that are - meant to be run in the assets group by adding :group => :assets. + meant to be run in the assets group by adding :group => :assets. ## Rails 3.1.0 (August 30, 2011) ## diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 6ac5204ae5..3b4f2befda 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -1400,6 +1400,9 @@ A threshold of +nil+ disables automatic EXPLAINs. The default threshold in development mode is 0.5 seconds, and +nil+ in test and production modes. +INFO. Automatic EXPLAIN gets disabled if Active Record has no logger, regardless +of the value of the threshold. + h5. Disabling Automatic EXPLAIN Automatic EXPLAIN can be selectively silenced with +ActiveRecord::Base.silence_auto_explain+: diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index e082fd2941..aac5e13978 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -309,7 +309,7 @@ Rails follows a simple set of coding style conventions. * Two spaces, no tabs. * No trailing whitespace. Blank lines should not have any space. -* Indent after private/protected. +* Outdent private/protected from method definitions. Same indentation as the class/module. * Prefer +&&+/+||+ over +and+/+or+. * Prefer class << self block over self.method for class methods. * +MyClass.my_method(my_arg)+ not +my_method( my_arg )+ or +my_method my_arg+. diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile index 66160f8b26..c11f8e221b 100644 --- a/railties/guides/source/migrations.textile +++ b/railties/guides/source/migrations.textile @@ -114,6 +114,7 @@ database independent way (you'll read about them in detail later): * +change_column+ * +change_table+ * +create_table+ +* +create_join_table+ * +drop_table+ * +remove_column+ * +remove_index+ @@ -384,6 +385,35 @@ end will append +ENGINE=BLACKHOLE+ to the SQL statement used to create the table (when using MySQL, the default is +ENGINE=InnoDB+). +h4. Creating a Join Table + +Migration method +create_join_table+ creates a HABTM join table. A typical use +would be + +<ruby> +create_join_table :products, :categories +</ruby> + +which creates a +categories_products+ table with two columns called +category_id+ and +product_id+. +These columns have the option +:null+ set to +false+ by default. + +You can pass the option +:table_name+ with you want to customize the table name. For example, + +<ruby> +create_join_table :products, :categories, :table_name => :categorization +</ruby> + +will create a +categorization+ table. + +By default, +create_join_table+ will create two columns with no options, but you can specify these +options using the +:column_options+ option. For example, + +<ruby> +create_join_table :products, :categories, :column_options => {:null => true} +</ruby> + +will create the +product_id+ and +category_id+ with the +:null+ option as +true+. + h4. Changing Tables A close cousin of +create_table+ is +change_table+, used for changing existing diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 2778dce331..c163081bfc 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -290,7 +290,15 @@ module Rails end def build_original_fullpath(env) - ["#{env["SCRIPT_NAME"]}#{env["PATH_INFO"]}", env["QUERY_STRING"]].reject(&:blank?).join("?") + path_info = env["PATH_INFO"] + query_string = env["QUERY_STRING"] + script_name = env["SCRIPT_NAME"] + + if query_string.present? + "#{script_name}#{path_info}?#{query_string}" + else + "#{script_name}#{path_info}" + end end end end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 9e9d26a783..1ad08220ee 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -112,11 +112,10 @@ module Rails end def colorize_logging - @colorize_logging + ActiveSupport::LogSubscriber.colorize_logging end def colorize_logging=(val) - @colorize_logging = val ActiveSupport::LogSubscriber.colorize_logging = val self.generators.colorize_logging = val end diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb index 435ea83ad8..03538b2422 100644 --- a/railties/lib/rails/code_statistics.rb +++ b/railties/lib/rails/code_statistics.rb @@ -37,24 +37,26 @@ class CodeStatistics #:nodoc: next unless file_name =~ pattern - f = File.open(directory + "/" + file_name) comment_started = false - while line = f.gets - stats["lines"] += 1 - if(comment_started) - if line =~ /^=end/ - comment_started = false - end - next - else - if line =~ /^=begin/ - comment_started = true + + File.open(directory + "/" + file_name) do |f| + while line = f.gets + stats["lines"] += 1 + if(comment_started) + if line =~ /^=end/ + comment_started = false + end next + else + if line =~ /^=begin/ + comment_started = true + next + end end + stats["classes"] += 1 if line =~ /^\s*class\s+[_A-Z]/ + stats["methods"] += 1 if line =~ /^\s*def\s+[_a-z]/ + stats["codelines"] += 1 unless line =~ /^\s*$/ || line =~ /^\s*#/ end - stats["classes"] += 1 if line =~ /^\s*class\s+[_A-Z]/ - stats["methods"] += 1 if line =~ /^\s*def\s+[_a-z]/ - stats["codelines"] += 1 unless line =~ /^\s*$/ || line =~ /^\s*#/ end end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 45f55a2a0a..32793b1a70 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -246,7 +246,7 @@ module Rails sentinel = /\.routes\.draw do\s*$/ in_root do - inject_into_file 'config/routes.rb', "\n #{routing_code}\n", { :after => sentinel, :verbose => false } + inject_into_file 'config/routes.rb', "\n #{routing_code}", { :after => sentinel, :verbose => false } end end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index d3420a6a3c..7c449657b5 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -181,10 +181,6 @@ module Rails end end - def ruby_debugger_gemfile_entry - "gem 'ruby-debug19', :require => 'ruby-debug'" - end - def assets_gemfile_entry return if options[:skip_sprockets] @@ -248,7 +244,7 @@ module Rails end def run_bundle - bundle_command('install') unless options[:skip_gemfile] || options[:skip_bundle] + bundle_command('install') unless options[:skip_gemfile] || options[:skip_bundle] || options[:pretend] end def empty_directory_with_gitkeep(destination, config = {}) diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index a98244c525..8f779316c1 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -254,17 +254,13 @@ module Rails nesting = class_name.split('::') last_name = nesting.pop - # Hack to limit const_defined? to non-inherited on 1.9 - extra = [] - extra << false unless Object.method(:const_defined?).arity == 1 - # Extract the last Module in the nesting last = nesting.inject(Object) do |last_module, nest| - break unless last_module.const_defined?(nest, *extra) + break unless last_module.const_defined?(nest, false) last_module.const_get(nest) end - if last && last.const_defined?(last_name.camelize, *extra) + if last && last.const_defined?(last_name.camelize, false) raise Error, "The name '#{class_name}' is either already used in your application " << "or reserved by Ruby on Rails. Please choose an alternative and run " << "this generator again." diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb index 7b1a2a1841..85296ca37b 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb @@ -10,16 +10,14 @@ <th></th> </tr> -<%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %> - <tr> + <%%= content_tag_for(:tr, @<%= plural_table_name %>) do |<%= singular_table_name %>| %> <% attributes.each do |attribute| -%> <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td> <% end -%> <td><%%= link_to 'Show', <%= singular_table_name %> %></td> <td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td> <td><%%= link_to 'Destroy', <%= singular_table_name %>, <%= key_value :confirm, "'Are you sure?'" %>, <%= key_value :method, ":delete" %> %></td> - </tr> -<%% end %> + <%% end %> </table> <br /> diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 29a2ad3111..7dfc1aa599 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -32,8 +32,8 @@ module Rails case type when /(string|text|binary|integer)\{(\d+)\}/ return $1, :limit => $2.to_i - when /decimal\{(\d+)(,|\.|\-)(\d+)\}/ - return :decimal, :precision => $1.to_i, :scale => $3.to_i + when /decimal\{(\d+)[,.-](\d+)\}/ + return :decimal, :precision => $1.to_i, :scale => $2.to_i else return type, {} end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 5e9c385ab8..43bfe5bad6 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -1,4 +1,4 @@ -source 'https://rubygems.org' +source :rubygems <%= rails_gemfile_entry -%> @@ -22,4 +22,4 @@ source 'https://rubygems.org' # gem 'capistrano', :group => :development # To use debugger -# <%= ruby_debugger_gemfile_entry %> +# gem 'ruby-debug19', :require => 'ruby-debug' diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 3517956e4a..acf47a03e5 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -56,6 +56,11 @@ module <%= app_const_base %> # parameters by using an attr_accessible or attr_protected declaration. # config.active_record.whitelist_attributes = true + # Specifies wether or not has_many or has_one association option :dependent => :restrict raises + # an exception. If set to true, then an ActiveRecord::DeleteRestrictionError exception would be + # raised. If set to false, then an error will be added on the model instead. + <%= comment_if :skip_active_record %>config.active_record.dependent_restrict_raises = false + <% unless options.skip_sprockets? -%> # Enable the asset pipeline. config.assets.enabled = true diff --git a/railties/lib/rails/rubyprof_ext.rb b/railties/lib/rails/rubyprof_ext.rb index f6e90357ce..017eba3a76 100644 --- a/railties/lib/rails/rubyprof_ext.rb +++ b/railties/lib/rails/rubyprof_ext.rb @@ -12,7 +12,7 @@ module Prof #:nodoc: io.puts " time seconds seconds calls ms/call ms/call name" sum = 0.0 - for r in results + results.each do |r| sum += r.self_time name = if r.method_class.nil? diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 01156e1b83..1469c9af4d 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -64,7 +64,7 @@ module ApplicationTests files << Dir["#{app_path}/public/assets/foo/application.js"].first files.each do |file| assert_not_nil file, "Expected application.js asset to be generated, but none found" - assert_equal "alert()", File.read(file) + assert_equal "alert();", File.read(file) end end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index cadf60d46f..3dffd1c74c 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -534,5 +534,10 @@ module ApplicationTests assert_equal app.env_config['action_dispatch.logger'], Rails.logger assert_equal app.env_config['action_dispatch.backtrace_cleaner'], Rails.backtrace_cleaner end + + test "config.colorize_logging default is true" do + make_basic_app + assert app.config.colorize_logging + end end end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index f3071843e2..a3c24c392b 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -202,6 +202,7 @@ class AppGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "--skip-active-record"] assert_no_file "config/database.yml" assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ + assert_file "config/application.rb", /#\s+config\.active_record\.dependent_restrict_raises = false/ assert_file "test/test_helper.rb" do |helper_content| assert_no_match(/fixtures :all/, helper_content) end @@ -349,6 +350,16 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_active_record_dependent_restrict_raises_is_present_application_config + run_generator + assert_file "config/application.rb", /config\.active_record\.dependent_restrict_raises = false/ + end + + def test_pretend_option + output = run_generator [File.join(destination_root, "myapp"), "--pretend"] + assert_no_match(/run bundle install/, output) + end + protected def action(*args, &block) |