diff options
72 files changed, 883 insertions, 585 deletions
@@ -2,8 +2,6 @@ source 'https://rubygems.org' gemspec -gem 'rack', :git => 'git://github.com/rack/rack.git' - if ENV['AREL'] gem 'arel', :path => ENV['AREL'] else diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 7d8852f961..1800ff5839 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -1,7 +1,6 @@ require 'mail' require 'action_mailer/collector' require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/proc' require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/hash/except' require 'action_mailer/log_subscriber' @@ -409,7 +408,7 @@ module ActionMailer #:nodoc: # and passing a Mail::Message will do nothing except tell the logger you sent the email. def deliver_mail(mail) #:nodoc: ActiveSupport::Notifications.instrument("deliver.action_mailer") do |payload| - self.set_payload_for_mail(payload, mail) + set_payload_for_mail(payload, mail) yield # Let Mail do the delivery actions end end @@ -602,9 +601,6 @@ module ActionMailer #:nodoc: # end # def mail(headers={}, &block) - # Guard flag to prevent both the old and the new API from firing - # Should be removed when old API is removed - @mail_was_called = true m = @_message # At the beginning, do not consider class default for parts order neither content_type @@ -612,8 +608,9 @@ module ActionMailer #:nodoc: parts_order = headers[:parts_order] # Call all the procs (if any) - default_values = self.class.default.merge(self.class.default) do |k,v| - v.respond_to?(:call) ? v.bind(self).call : v + class_default = self.class.default + default_values = class_default.merge(class_default) do |k,v| + v.respond_to?(:to_proc) ? instance_eval(&v) : v end # Handle defaults diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb index 5beab87ad2..7204822395 100644 --- a/actionmailer/lib/action_mailer/test_helper.rb +++ b/actionmailer/lib/action_mailer/test_helper.rb @@ -1,7 +1,5 @@ module ActionMailer module TestHelper - extend ActiveSupport::Concern - # Asserts that the number of emails sent matches the given number. # # def test_emails diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 4ce0624207..f13b1d5a60 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |s| s.add_dependency('activemodel', version) s.add_dependency('rack-cache', '~> 1.1') s.add_dependency('builder', '~> 3.0.0') - s.add_dependency('rack', '~> 1.4.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('sprockets', '~> 2.2.0') diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 39ff58a447..25f1db8228 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -191,6 +191,15 @@ module ActionDispatch value end + # Whether the given cookie is to be deleted by this CookieJar. + # Like <tt>[]=</tt>, you can pass in an options hash to test if a + # deletion applies to a specific <tt>:path</tt>, <tt>:domain</tt> etc. + def deleted?(key, options = {}) + options.symbolize_keys! + handle_options(options) + @delete_cookies[key.to_s] == options + end + # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie def clear(options = {}) @cookies.each_key{ |k| delete(k, options) } diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 5b7f1c989c..e2d7a29079 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -2,6 +2,7 @@ require 'active_support/core_ext/hash/except' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/inclusion' require 'active_support/inflector' +require 'active_support/deprecation' require 'action_dispatch/routing/redirection' module ActionDispatch @@ -54,6 +55,7 @@ module ActionDispatch def initialize(set, scope, path, options) @set, @scope = set, scope + @segment_keys = nil @options = (@scope[:options] || {}).merge(options) @path = normalize_path(path) normalize_options! @@ -213,7 +215,9 @@ module ActionDispatch end def segment_keys - @segment_keys ||= Journey::Path::Pattern.new( + return @segment_keys if @segment_keys + + @segment_keys = Journey::Path::Pattern.new( Journey::Router::Strexp.compile(@path, requirements, SEPARATORS) ).names end @@ -499,6 +503,16 @@ module ActionDispatch private def map_method(method, args, &block) + if args.length > 2 + ActiveSupport::Deprecation.warn <<-eowarn +The method signature of #{method}() is changing to: + + #{method}(path, options = {}, &block) + +Calling with multiple paths is deprecated. + eowarn + end + options = args.extract_options! options[:via] = method match(*args, options, &block) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 2c21887220..ac4dd7d927 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -360,7 +360,26 @@ module ActionDispatch SEPARATORS, anchor) - Journey::Path::Pattern.new(strexp) + pattern = Journey::Path::Pattern.new(strexp) + + builder = Journey::GTG::Builder.new pattern.spec + + # Get all the symbol nodes followed by literals that are not the + # dummy node. + symbols = pattern.spec.grep(Journey::Nodes::Symbol).find_all { |n| + builder.followpos(n).first.literal? + } + + # Get all the symbol nodes preceded by literals. + symbols.concat pattern.spec.find_all(&:literal?).map { |n| + builder.followpos(n).first + }.find_all(&:symbol?) + + symbols.each { |x| + x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/ + } + + pattern end private :build_path @@ -579,7 +598,8 @@ module ActionDispatch params[key] = URI.parser.unescape(value) end end - + old_params = env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] + env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] = (old_params || {}).merge(params) dispatcher = route.app while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do dispatcher = dispatcher.app diff --git a/actionpack/lib/action_view/flows.rb b/actionpack/lib/action_view/flows.rb index a8f740713f..c0e458cd41 100644 --- a/actionpack/lib/action_view/flows.rb +++ b/actionpack/lib/action_view/flows.rb @@ -22,11 +22,8 @@ module ActionView def append(key, value) @content[key] << value end + alias_method :append!, :append - # Called by provide - def append!(key, value) - @content[key] << value - end end class StreamingFlow < OutputFlow #:nodoc: diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb index 973135e2ea..1187956081 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionpack/lib/action_view/helpers/active_model_helper.rb @@ -16,7 +16,9 @@ module ActionView end end - module_eval "def content_tag(*) error_wrapping(super) end", __FILE__, __LINE__ + def content_tag(*) + error_wrapping(super) + end def tag(type, options, *) tag_generate_errors?(options) ? error_wrapping(super) : super diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 5dbba3c4a7..134eaab8bc 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -422,7 +422,7 @@ module ActionView if sources.is_a?(Array) content_tag("video", options) do - sources.map { |source| tag("source", :src => source) }.join.html_safe + sources.map { |source| tag("source", :src => path_to_video(source)) }.join.html_safe end else options[:src] = path_to_video(sources) @@ -441,10 +441,17 @@ 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(source, options = {}) + def audio_tag(sources, options = {}) options.symbolize_keys! - options[:src] = path_to_audio(source) - tag("audio", options) + + 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 end private diff --git a/actionpack/lib/action_view/helpers/csrf_helper.rb b/actionpack/lib/action_view/helpers/csrf_helper.rb index 1f2bc28cac..eeb0ed94b9 100644 --- a/actionpack/lib/action_view/helpers/csrf_helper.rb +++ b/actionpack/lib/action_view/helpers/csrf_helper.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/string/strip' - module ActionView # = Action View CSRF Helper module Helpers diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index c8301965d9..ea71889519 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -749,7 +749,7 @@ module ActionView def select_day if @options[:use_hidden] || @options[:discard_day] - build_hidden(:day, day) + build_hidden(:day, day || 1) else build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false, :use_two_digit_numbers => @options[:use_two_digit_numbers]) end @@ -757,7 +757,7 @@ module ActionView def select_month if @options[:use_hidden] || @options[:discard_month] - build_hidden(:month, month) + build_hidden(:month, month || 1) else month_options = [] 1.upto(12) do |month_number| @@ -771,7 +771,7 @@ module ActionView def select_year if !@datetime || @datetime == 0 - val = '' + val = '1' middle_year = Date.today.year else val = middle_year = year @@ -854,7 +854,15 @@ module ActionView end def translated_date_order - I18n.translate(:'date.order', :locale => @options[:locale]) || [] + date_order = I18n.translate(:'date.order', :locale => @options[:locale]) || [] + + forbidden_elements = date_order - [:year, :month, :day] + if forbidden_elements.any? + raise StandardError, + "#{@options[:locale]}.date.order only accepts :year, :month and :day" + end + + date_order end # Build full select tag from date type and options. diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index fc1cbfcb14..b0860f87c4 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -57,15 +57,11 @@ module ActionView # # => +1.123.555.1234 x 1343 def number_to_phone(number, options = {}) return unless number + options = options.symbolize_keys - begin - Float(number) - rescue ArgumentError, TypeError - raise InvalidNumberError, number - end if options[:raise] + parse_float(number, true) if options[:raise] number = number.to_s.strip - options = options.symbolize_keys area_code = options[:area_code] delimiter = options[:delimiter] || "-" extension = options[:extension] @@ -75,7 +71,7 @@ module ActionView number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3") else number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3") - number.slice!(0, 1) if number.starts_with?(delimiter) && !delimiter.blank? + number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank? end str = [] @@ -122,8 +118,7 @@ module ActionView # # => 1234567890,50 £ def number_to_currency(number, options = {}) return unless number - - options.symbolize_keys! + options = options.symbolize_keys currency = translations_for('currency', options[:locale]) currency[:negative_format] ||= "-" + currency[:format] if currency[:format] @@ -185,12 +180,10 @@ module ActionView # number_to_percentage("98a", :raise => true) # => InvalidNumberError def number_to_percentage(number, options = {}) return unless number + options = options.symbolize_keys - options.symbolize_keys! - - defaults = defaults_translations(options[:locale]).merge(translations_for('percentage', options[:locale])) - - options = options.reverse_merge(defaults) + defaults = format_translations('percentage', options[:locale]) + options = defaults.merge!(options) format = options[:format] || "%n%" @@ -201,7 +194,8 @@ module ActionView if options[:raise] raise else - e.number.to_s.html_safe? ? format.gsub(/%n/, e.number).html_safe : format.gsub(/%n/, e.number) + formatted_number = format.gsub(/%n/, e.number) + e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number end end end @@ -231,13 +225,11 @@ module ActionView # # number_with_delimiter("112a", :raise => true) # => raise InvalidNumberError def number_with_delimiter(number, options = {}) - options.symbolize_keys! + options = options.symbolize_keys - parse_float_number(number, options[:raise]) do - return number - end + parse_float(number, options[:raise]) or return number - options = options.reverse_merge(defaults_translations(options[:locale])) + options = defaults_translations(options[:locale]).merge(options) parts = number.to_s.to_str.split('.') parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}") @@ -277,15 +269,13 @@ module ActionView # number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.') # # => 1.111,23 def number_with_precision(number, options = {}) - options.symbolize_keys! + options = options.symbolize_keys - number = parse_float_number(number, options[:raise]) do - return number - end + number = (parse_float(number, options[:raise]) or return number) - defaults = defaults_translations(options[:locale]).merge(translations_for('precision', options[:locale])) + defaults = format_translations('precision', options[:locale]) + options = defaults.merge!(options) - options = options.reverse_merge(defaults) # Allow the user to unset default values: Eg.: :significant => false precision = options.delete :precision significant = options.delete :significant strip_insignificant_zeros = options.delete :strip_insignificant_zeros @@ -346,15 +336,13 @@ module ActionView # number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB" # number_to_human_size(524288000, :precision => 5) # => "500 MB" def number_to_human_size(number, options = {}) - options.symbolize_keys! + options = options.symbolize_keys - number = parse_float_number(number, options[:raise]) do - return number - end + number = (parse_float(number, options[:raise]) or return number) - defaults = defaults_translations(options[:locale]).merge(translations_for('human', options[:locale])) + defaults = format_translations('human', options[:locale]) + options = defaults.merge!(options) - options = options.reverse_merge(defaults) #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros) @@ -458,15 +446,13 @@ module ActionView # number_to_human(0.34, :units => :distance) # => "34 centimeters" # def number_to_human(number, options = {}) - options.symbolize_keys! + options = options.symbolize_keys - number = parse_float_number(number, options[:raise]) do - return number - end + number = (parse_float(number, options[:raise]) or return number) - defaults = defaults_translations(options[:locale]).merge(translations_for('human', options[:locale])) + defaults = format_translations('human', options[:locale]) + options = defaults.merge!(options) - options = options.reverse_merge(defaults) #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros) @@ -504,6 +490,10 @@ module ActionView private + def format_translations(namespace, locale) + defaults_translations(locale).merge(translations_for(namespace, locale)) + end + def defaults_translations(locale) I18n.translate(:'number.format', :locale => locale, :default => {}) end @@ -512,14 +502,10 @@ module ActionView I18n.translate(:"number.#{namespace}.format", :locale => locale, :default => {}) end - def parse_float_number(number, raise_error) + def parse_float(number, raise_error) Float(number) rescue ArgumentError, TypeError - if raise_error - raise InvalidNumberError, number - else - yield - end + raise InvalidNumberError, number if raise_error end end end diff --git a/actionpack/lib/action_view/helpers/tags.rb b/actionpack/lib/action_view/helpers/tags.rb index 89b3efda5f..e874d4ca42 100644 --- a/actionpack/lib/action_view/helpers/tags.rb +++ b/actionpack/lib/action_view/helpers/tags.rb @@ -1,28 +1,30 @@ module ActionView module Helpers - module Tags - autoload :Base, 'action_view/helpers/tags/base' - autoload :Label, 'action_view/helpers/tags/label' - autoload :TextField, 'action_view/helpers/tags/text_field' - autoload :PasswordField, 'action_view/helpers/tags/password_field' - autoload :HiddenField, 'action_view/helpers/tags/hidden_field' - autoload :FileField, 'action_view/helpers/tags/file_field' - autoload :SearchField, 'action_view/helpers/tags/search_field' - autoload :TelField, 'action_view/helpers/tags/tel_field' - autoload :UrlField, 'action_view/helpers/tags/url_field' - autoload :EmailField, 'action_view/helpers/tags/email_field' - autoload :NumberField, 'action_view/helpers/tags/number_field' - autoload :RangeField, 'action_view/helpers/tags/range_field' - autoload :TextArea, 'action_view/helpers/tags/text_area' - autoload :CheckBox, 'action_view/helpers/tags/check_box' - autoload :RadioButton, 'action_view/helpers/tags/radio_button' - autoload :Select, 'action_view/helpers/tags/select' - autoload :CollectionSelect, 'action_view/helpers/tags/collection_select' - autoload :GroupedCollectionSelect, 'action_view/helpers/tags/grouped_collection_select' - autoload :TimeZoneSelect, 'action_view/helpers/tags/time_zone_select' - autoload :DateSelect, 'action_view/helpers/tags/date_select' - autoload :TimeSelect, 'action_view/helpers/tags/time_select' - autoload :DatetimeSelect, 'action_view/helpers/tags/datetime_select' + module Tags #:nodoc: + extend ActiveSupport::Autoload + + autoload :Base + autoload :Label + autoload :TextField + autoload :PasswordField + autoload :HiddenField + autoload :FileField + autoload :SearchField + autoload :TelField + autoload :UrlField + autoload :EmailField + autoload :NumberField + autoload :RangeField + autoload :TextArea + autoload :CheckBox + autoload :RadioButton + autoload :Select + autoload :CollectionSelect + autoload :GroupedCollectionSelect + autoload :TimeZoneSelect + autoload :DateSelect + autoload :TimeSelect + autoload :DatetimeSelect end end end diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb index 24956beb9c..449f94d347 100644 --- a/actionpack/lib/action_view/helpers/tags/base.rb +++ b/actionpack/lib/action_view/helpers/tags/base.rb @@ -19,8 +19,9 @@ module ActionView @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match end - def render(&block) - raise "Abstract Method called" + # This is what child classes implement. + def render + raise NotImplementedError, "Subclasses must implement a render method" end private diff --git a/actionpack/lib/action_view/helpers/tags/date_select.rb b/actionpack/lib/action_view/helpers/tags/date_select.rb index 5912598ca1..5d706087b0 100644 --- a/actionpack/lib/action_view/helpers/tags/date_select.rb +++ b/actionpack/lib/action_view/helpers/tags/date_select.rb @@ -12,10 +12,16 @@ module ActionView error_wrapping(datetime_selector(@options, @html_options).send("select_#{select_type}").html_safe) end + class << self + def select_type + @select_type ||= self.name.split("::").last.sub("Select", "").downcase + end + end + private def select_type - self.class.name.split("::").last.sub("Select", "").downcase + self.class.select_type end def datetime_selector(options, html_options) diff --git a/actionpack/lib/action_view/helpers/tags/select.rb b/actionpack/lib/action_view/helpers/tags/select.rb index 71fd4d04b7..02b790db4e 100644 --- a/actionpack/lib/action_view/helpers/tags/select.rb +++ b/actionpack/lib/action_view/helpers/tags/select.rb @@ -4,6 +4,7 @@ module ActionView class Select < Base #:nodoc: def initialize(object_name, method_name, template_object, choices, options, html_options) @choices = choices + @choices = @choices.to_a if @choices.is_a?(Range) @html_options = html_options super(object_name, method_name, template_object, options) diff --git a/actionpack/lib/action_view/helpers/tags/text_field.rb b/actionpack/lib/action_view/helpers/tags/text_field.rb index 0f81726eb4..ce5182d20f 100644 --- a/actionpack/lib/action_view/helpers/tags/text_field.rb +++ b/actionpack/lib/action_view/helpers/tags/text_field.rb @@ -13,10 +13,16 @@ module ActionView tag("input", options) end + class << self + def field_type + @field_type ||= self.name.split("::").last.sub("Field", "").downcase + end + end + private def field_type - @field_type ||= self.class.name.split("::").last.sub("Field", "").downcase + self.class.field_type end end end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 0a25d7ba47..b1a5356ddd 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -344,6 +344,21 @@ module ActionDispatch end end +module ActionDispatch + module RoutingVerbs + def get(uri_or_host, path = nil, port = nil) + host = uri_or_host.host unless path + path ||= uri_or_host.path + + params = {'PATH_INFO' => path, + 'REQUEST_METHOD' => 'GET', + 'HTTP_HOST' => host} + + routes.call(params)[2].join + end + end +end + module RoutingTestHelpers def url_for(set, options, recall = nil) set.send(:url_for, options.merge(:only_path => true, :_path_segments => recall)) diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 4a291582d8..e6d3fa74f2 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -1,6 +1,5 @@ require 'abstract_unit' require 'digest/sha1' -require 'active_support/core_ext/string/strip' require "active_support/log_subscriber/test_helper" # common controller actions @@ -72,9 +71,7 @@ class CustomAuthenticityParamController < RequestForgeryProtectionController end end - # common test methods - module RequestForgeryProtectionTests def setup @token = "cf50faa3fe97702ca1ae" @@ -246,10 +243,6 @@ class FreeCookieControllerTest < ActionController::TestCase end end - - - - class CustomAuthenticityParamControllerTest < ActionController::TestCase def setup ActionController::Base.request_forgery_protection_token = :custom_token_name diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index bec9eb16f3..44a40e0665 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -73,23 +73,76 @@ end class LegacyRouteSetTests < ActiveSupport::TestCase include RoutingTestHelpers + include ActionDispatch::RoutingVerbs attr_reader :rs + alias :routes :rs def setup @rs = ::ActionDispatch::Routing::RouteSet.new @response = nil end - def get(uri_or_host, path = nil, port = nil) - host = uri_or_host.host unless path - path ||= uri_or_host.path + def test_symbols_with_dashes + rs.draw do + match '/:artist/:song-omg', :to => lambda { |env| + resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] + [200, {}, [resp]] + } + end + + hash = JSON.load get(URI('http://example.org/journey/faithfully-omg')) + assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash) + end + + def test_id_with_dash + rs.draw do + match '/journey/:id', :to => lambda { |env| + resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] + [200, {}, [resp]] + } + end + + hash = JSON.load get(URI('http://example.org/journey/faithfully-omg')) + assert_equal({"id"=>"faithfully-omg"}, hash) + end - params = {'PATH_INFO' => path, - 'REQUEST_METHOD' => 'GET', - 'HTTP_HOST' => host} + def test_dash_with_custom_regexp + rs.draw do + match '/:artist/:song-omg', :constraints => { :song => /\d+/ }, :to => lambda { |env| + resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] + [200, {}, [resp]] + } + end - @rs.call(params)[2].join + hash = JSON.load get(URI('http://example.org/journey/123-omg')) + assert_equal({"artist"=>"journey", "song"=>"123"}, hash) + assert_equal 'Not Found', get(URI('http://example.org/journey/faithfully-omg')) + end + + def test_pre_dash + rs.draw do + match '/:artist/omg-:song', :to => lambda { |env| + resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] + [200, {}, [resp]] + } + end + + hash = JSON.load get(URI('http://example.org/journey/omg-faithfully')) + assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash) + end + + def test_pre_dash_with_custom_regexp + rs.draw do + match '/:artist/omg-:song', :constraints => { :song => /\d+/ }, :to => lambda { |env| + resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] + [200, {}, [resp]] + } + end + + hash = JSON.load get(URI('http://example.org/journey/omg-123')) + assert_equal({"artist"=>"journey", "song"=>"123"}, hash) + assert_equal 'Not Found', get(URI('http://example.org/journey/omg-faithfully')) end def test_regexp_precidence @@ -1327,7 +1380,20 @@ class RouteSetTest < ActiveSupport::TestCase end end end - + + def test_route_with_subdomain_and_constraints_must_receive_params + name_param = nil + set.draw do + match 'page/:name' => 'pages#show', :constraints => lambda {|request| + name_param = request.params[:name] + return true + } + end + assert_equal({:controller => 'pages', :action => 'show', :name => 'mypage'}, + set.recognize_path('http://subdomain.example.org/page/mypage')) + assert_equal(name_param, 'mypage') + end + def test_route_requirement_recognize_with_ignore_case set.draw do match 'page/:name' => 'pages#show', diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 6ebd02e85c..3e48d97e67 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -245,6 +245,17 @@ class CookiesTest < ActionController::TestCase assert_cookie_header "user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT" end + def test_deleted_cookie_predicate + cookies.delete("user_name") + assert cookies.deleted?("user_name") + assert_equal false, cookies.deleted?("another") + end + + def test_deleted_cookie_predicate_with_mismatching_options + cookies.delete("user_name", :path => "/path") + assert_equal false, cookies.deleted?("user_name", :path => "/different") + end + def test_cookies_persist_throughout_request response = get :authenticate assert response.headers["Set-Cookie"] =~ /user_name=david/ diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index c6e6ed4d6b..3689b17877 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1,3 +1,4 @@ +# encoding: UTF-8 require 'erb' require 'abstract_unit' require 'controller/fake_controllers' @@ -156,7 +157,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end resources :posts do - get :archive, :toggle_view, :on => :collection + get :archive, :on => :collection + get :toggle_view, :on => :collection post :preview, :on => :member resource :subscription @@ -2543,3 +2545,22 @@ class TestUriPathEscaping < ActionDispatch::IntegrationTest assert_equal 'a b/c+d', @response.body end end + +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 + end + + include Routes.url_helpers + def app; Routes end + + test 'recognizes unicode path' do + get "/#{Rack::Utils.escape("ほげ")}" + assert_equal "200", @response.code + end +end diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index aa7304b3ed..45a8492d58 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -201,8 +201,8 @@ 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="multiple.ogg" /><source src="multiple.avi" /></video>), - %(video_tag(["multiple.ogg", "multiple.avi"], :size => "160x120", :controls => true)) => %(<video controls="controls" height="120" width="160"><source src="multiple.ogg" /><source src="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>) } AudioPathToTag = { @@ -224,6 +224,8 @@ 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"], :autobuffer => true, :controls => true)) => %(<audio autobuffer="autobuffer" controls="controls"><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>) } def test_auto_discovery_link_tag diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb index e3d3d5ff77..ef3d7d97ee 100644 --- a/actionpack/test/template/date_helper_i18n_test.rb +++ b/actionpack/test/template/date_helper_i18n_test.rb @@ -118,4 +118,12 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase I18n.expects(:translate).with(:'date.order', :locale => 'en').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] + + assert_raise StandardError do + datetime_select('post', 'updated_at', :locale => 'en') + end + end end diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index fadfb59572..9e2f4ec347 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -1587,7 +1587,7 @@ class DateHelperTest < ActionView::TestCase start_year = Time.now.year-5 end_year = Time.now.year+5 - expected = '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i"/>' + "\n" + expected = '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i" value="1"/>' + "\n" expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} expected << "<option value=\"\"></option>\n" start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } @@ -1601,6 +1601,40 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, date_select("post", "written_on", :order=>[:year, :month], :include_blank=>true) end + def test_date_select_with_nil_and_blank_and_discard_month + @post = Post.new + + start_year = Time.now.year-5 + end_year = Time.now.year+5 + + expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} + expected << "<option value=\"\"></option>\n" + start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } + expected << "</select>\n" + expected << '<input name="post[written_on(2i)]" type="hidden" id="post_written_on_2i" value="1"/>' + "\n" + expected << '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i" value="1"/>' + "\n" + + assert_dom_equal expected, date_select("post", "written_on", :discard_month => true, :include_blank=>true) + end + + def test_date_select_with_nil_and_blank_and_discard_year + @post = Post.new + + expected = '<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="1" />' + "\n" + + expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} + expected << "<option value=\"\"></option>\n" + 1.upto(12) { |i| expected << %(<option value="#{i}">#{Date::MONTHNAMES[i]}</option>\n) } + expected << "</select>\n" + + expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} + expected << "<option value=\"\"></option>\n" + 1.upto(31) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, date_select("post", "written_on", :discard_year => true, :include_blank=>true) + end + def test_date_select_cant_override_discard_hour @post = Post.new @post.written_on = Date.new(2004, 6, 15) diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 5be6a2e4e5..39d4768861 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -115,6 +115,14 @@ class FormHelperTest < ActionView::TestCase super end + class FooTag < ActionView::Helpers::Tags::Base + def initialize; end + end + + def test_tags_base_child_without_render_method + assert_raise(NotImplementedError) { FooTag.new.render } + end + def test_label assert_dom_equal('<label for="post_title">Title</label>', label("post", "title")) assert_dom_equal('<label for="post_title">The title goes here</label>', label("post", "title", "The title goes here")) diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index 4a889beadd..a903e13bad 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -144,6 +144,13 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_range_options_for_select + assert_dom_equal( + "<option value=\"1\">1</option>\n<option value=\"2\">2</option>\n<option value=\"3\">3</option>", + options_for_select(1..3) + ) + end + def test_array_options_for_string_include_in_other_string_bug_fix assert_dom_equal( "<option value=\"ruby\">ruby</option>\n<option value=\"rubyonrails\" selected=\"selected\">rubyonrails</option>", @@ -671,6 +678,15 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_select_with_range + @post = Post.new + @post.category = 0 + assert_dom_equal( + "<select id=\"post_category\" name=\"post[category]\"><option value=\"1\">1</option>\n<option value=\"2\">2</option>\n<option value=\"3\">3</option></select>", + select("post", "category", 1..3) + ) + end + def test_collection_select @post = Post.new @post.author_name = "Babe" diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index ca897a48cc..482d953907 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -268,6 +268,31 @@ class NumberHelperTest < ActionView::TestCase assert_nil number_to_human(nil) end + def test_number_helpers_do_not_mutate_options_hash + options = { 'raise' => true } + + number_to_phone(1, options) + assert_equal({ 'raise' => true }, options) + + number_to_currency(1, options) + assert_equal({ 'raise' => true }, options) + + number_to_percentage(1, options) + assert_equal({ 'raise' => true }, options) + + number_with_delimiter(1, options) + assert_equal({ 'raise' => true }, options) + + number_with_precision(1, options) + assert_equal({ 'raise' => true }, options) + + number_to_human_size(1, options) + assert_equal({ 'raise' => true }, options) + + number_to_human(1, options) + assert_equal({ 'raise' => true }, options) + end + def test_number_helpers_should_return_non_numeric_param_unchanged assert_equal("+1-x x 123", number_to_phone("x", :country_code => 1, :extension => 123)) assert_equal("x", number_to_phone("x")) diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index a8309bf682..32f2aa46bd 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -63,7 +63,7 @@ module ActiveModel # raises an +ArgumentError+ exception. def add_observer(observer) unless observer.respond_to? :update - raise ArgumentError, "observer needs to respond to `update'" + raise ArgumentError, "observer needs to respond to 'update'" end observer_instances << observer end diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb index 1cf09758f9..e4f602bd80 100644 --- a/activemodel/test/cases/validations/callbacks_test.rb +++ b/activemodel/test/cases/validations/callbacks_test.rb @@ -5,11 +5,10 @@ class Dog include ActiveModel::Validations include ActiveModel::Validations::Callbacks - attr_accessor :name - attr_writer :history + attr_accessor :name, :history - def history - @history ||= [] + def initialize + @history = [] end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 964c4123ef..3549cbb090 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -58,66 +58,68 @@ module ActiveRecord end protected - # We want to generate the methods via module_eval rather than define_method, - # because define_method is slower on dispatch and uses more memory (because it - # creates a closure). - # - # But sometimes the database might return columns with characters that are not - # allowed in normal method names (like 'my_column(omg)'. So to work around this - # we first define with the __temp__ identifier, and then use alias method to - # rename it to what we want. - def define_method_attribute(attr_name) - cast_code = attribute_cast_code(attr_name) - - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def __temp__ - #{internal_attribute_access_code(attr_name, cast_code)} - end - alias_method '#{attr_name}', :__temp__ - undef_method :__temp__ - STR - - generated_external_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def __temp__(v, attributes, attributes_cache, attr_name) - #{external_attribute_access_code(attr_name, cast_code)} - end - alias_method '#{attr_name}', :__temp__ - undef_method :__temp__ - STR - end + # We want to generate the methods via module_eval rather than define_method, + # because define_method is slower on dispatch and uses more memory (because it + # creates a closure). + # + # But sometimes the database might return columns with characters that are not + # allowed in normal method names (like 'my_column(omg)'. So to work around this + # we first define with the __temp__ identifier, and then use alias method to + # rename it to what we want. + def define_method_attribute(attr_name) + cast_code = attribute_cast_code(attr_name) + + generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + def __temp__ + #{internal_attribute_access_code(attr_name, cast_code)} + end + alias_method '#{attr_name}', :__temp__ + undef_method :__temp__ + STR - private - def cacheable_column?(column) - attribute_types_cached_by_default.include?(column.type) - end + generated_external_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + def __temp__(v, attributes, attributes_cache, attr_name) + #{external_attribute_access_code(attr_name, cast_code)} + end + alias_method '#{attr_name}', :__temp__ + undef_method :__temp__ + STR + end - def internal_attribute_access_code(attr_name, cast_code) - access_code = "(v=@attributes[attr_name]) && #{cast_code}" + private + def cacheable_column?(column) + attribute_types_cached_by_default.include?(column.type) + end - unless attr_name == primary_key - access_code.insert(0, "missing_attribute(attr_name, caller) unless @attributes.has_key?(attr_name); ") - 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 - if cache_attribute?(attr_name) - access_code = "@attributes_cache[attr_name] ||= (#{access_code})" - end + access_code << "v && #{cast_code};" - "attr_name = '#{attr_name}'; #{access_code}" + if cache_attribute?(attr_name) + access_code = "@attributes_cache[attr_name] ||= (#{access_code})" end - def external_attribute_access_code(attr_name, cast_code) - access_code = "v && #{cast_code}" + "attr_name = '#{attr_name}'; #{access_code}" + end - if cache_attribute?(attr_name) - access_code = "attributes_cache[attr_name] ||= (#{access_code})" - end + def external_attribute_access_code(attr_name, cast_code) + access_code = "v && #{cast_code}" - access_code + if cache_attribute?(attr_name) + access_code = "attributes_cache[attr_name] ||= (#{access_code})" end - def attribute_cast_code(attr_name) - columns_hash[attr_name].type_cast_code('v') - end + access_code + end + + def attribute_cast_code(attr_name) + columns_hash[attr_name].type_cast_code('v') + end end # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index f93c7cd74a..44ac37c498 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -71,7 +71,8 @@ module ActiveRecord when Date, Time then quoted_date(value) when Symbol then value.to_s else - YAML.dump(value) + to_type = column ? " to #{column.type}" : "" + raise TypeError, "can't cast #{value.class}#{to_type}" end end 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 560773ca86..201c05d8f5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -541,7 +541,7 @@ module ActiveRecord if options.is_a?(Hash) && length = options[:length] case length when Hash - column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name)} + column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?} when Fixnum column_names.each {|name| option_strings[name] += "(#{length})"} end diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index c9c46b8d4f..224f5276eb 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -19,12 +19,6 @@ module ActiveRecord counters.each do |association| has_many_association = reflect_on_association(association.to_sym) - expected_name = if has_many_association.options[:as] - has_many_association.options[:as].to_s.classify - else - self.name - end - foreign_key = has_many_association.foreign_key.to_s child_class = has_many_association.klass belongs_to = child_class.reflect_on_all_associations(:belongs_to) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 01019db2cc..ac70aeba67 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -496,6 +496,10 @@ module ActiveRecord to_a.inspect end + def pretty_print(q) + q.pp(self.to_a) + end + def with_default_scope #:nodoc: if default_scoped? && default_scope = klass.send(:build_default_scope) default_scope = default_scope.merge(self) diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb index 64ecef2077..86687afdda 100644 --- a/activerecord/lib/active_record/test_case.rb +++ b/activerecord/lib/active_record/test_case.rb @@ -1,3 +1,7 @@ +require 'active_support/deprecation' +require 'active_support/test_case' + +ActiveSupport::Deprecation.warn('ActiveRecord::TestCase is deprecated, please use ActiveSupport::TestCase') module ActiveRecord # = Active Record Test Case # @@ -26,36 +30,5 @@ module ActiveRecord assert_equal expected.to_s, actual.to_s, message end end - - def assert_sql(*patterns_to_match) - ActiveRecord::SQLCounter.log = [] - yield - ActiveRecord::SQLCounter.log - ensure - failed_patterns = [] - patterns_to_match.each do |pattern| - failed_patterns << pattern unless ActiveRecord::SQLCounter.log.any?{ |sql| pattern === sql } - end - assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}" - end - - def assert_queries(num = 1) - ActiveRecord::SQLCounter.log = [] - yield - ensure - assert_equal num, ActiveRecord::SQLCounter.log.size, "#{ActiveRecord::SQLCounter.log.size} instead of #{num} queries were executed.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}" - end - - def assert_no_queries(&block) - prev_ignored_sql = ActiveRecord::SQLCounter.ignored_sql - ActiveRecord::SQLCounter.ignored_sql = [] - assert_queries(0, &block) - ensure - ActiveRecord::SQLCounter.ignored_sql = prev_ignored_sql - end - - def sqlite3? connection - connection.class.name.split('::').last == "SQLite3Adapter" - end end end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index e0152e7ccf..46da1b0a2b 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -70,9 +70,9 @@ module ActiveRecord assert_equal bd.to_f, @conn.type_cast(bd, nil) end - def test_type_cast_unknown + def test_type_cast_unknown_should_raise_error obj = Class.new.new - assert_equal YAML.dump(obj), @conn.type_cast(obj, nil) + assert_raise(TypeError) { @conn.type_cast(obj, nil) } end def test_quoted_id diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 87a3a0deb9..0ea1b824e1 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -992,10 +992,9 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "b", duped_topic.title # test if the attribute values have been duped - topic.title = {"a" => "b"} duped_topic = topic.dup - duped_topic.title["a"] = "c" - assert_equal "b", topic.title["a"] + duped_topic.title.replace "c" + assert_equal "a", topic.title # test if attributes set as part of after_initialize are duped correctly assert_equal topic.author_email_address, duped_topic.author_email_address @@ -1006,8 +1005,7 @@ class BasicsTest < ActiveRecord::TestCase assert_not_equal duped_topic.id, topic.id duped_topic.reload - # FIXME: I think this is poor behavior, and will fix it with #5686 - assert_equal({'a' => 'c'}.to_yaml, duped_topic.title) + assert_equal("c", duped_topic.title) end def test_dup_with_aggregate_of_same_name_as_attribute @@ -1893,7 +1891,7 @@ class BasicsTest < ActiveRecord::TestCase assert_equal [], NonExistentTable.attribute_names end - def test_attribtue_names_on_abstract_class + def test_attribute_names_on_abstract_class assert_equal [], AbstractCompany.attribute_names end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index d16cccdaea..b4598ab32a 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -6,8 +6,8 @@ require 'minitest/autorun' require 'stringio' require 'mocha' +require 'cases/test_case' require 'active_record' -require 'active_record/test_case' require 'active_support/dependencies' require 'active_support/logger' diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb index 26d7aeb148..89cf0f5e93 100644 --- a/activerecord/test/cases/migration/index_test.rb +++ b/activerecord/test/cases/migration/index_test.rb @@ -55,7 +55,7 @@ module ActiveRecord assert_raise(ArgumentError) { connection.remove_index(table_name, "no_such_index") } end - def test_add_index_length_limit + def test_add_index_name_length_limit good_index_name = 'x' * connection.index_name_length too_long_index_name = good_index_name + 'x' @@ -103,6 +103,12 @@ module ActiveRecord assert connection.index_exists?(:testings, :foo, :name => "custom_index_name") end + def test_add_index_attribute_length_limit + connection.add_index :testings, [:foo, :bar], :length => {:foo => 10, :bar => nil} + + assert connection.index_exists?(:testings, [:foo, :bar]) + end + def test_add_index connection.add_index("testings", "last_name") connection.remove_index("testings", "last_name") diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb new file mode 100644 index 0000000000..f0fefe6c48 --- /dev/null +++ b/activerecord/test/cases/test_case.rb @@ -0,0 +1,63 @@ +require 'active_support/test_case' + +module ActiveRecord + # = Active Record Test Case + # + # Defines some test assertions to test against SQL queries. + class TestCase < ActiveSupport::TestCase #:nodoc: + setup :cleanup_identity_map + + def setup + cleanup_identity_map + end + + def teardown + ActiveRecord::SQLCounter.log.clear + end + + def cleanup_identity_map + ActiveRecord::IdentityMap.clear + end + + def assert_date_from_db(expected, actual, message = nil) + # SybaseAdapter doesn't have a separate column type just for dates, + # so the time is in the string and incorrectly formatted + if current_adapter?(:SybaseAdapter) + assert_equal expected.to_s, actual.to_date.to_s, message + else + assert_equal expected.to_s, actual.to_s, message + end + end + + def assert_sql(*patterns_to_match) + ActiveRecord::SQLCounter.log = [] + yield + ActiveRecord::SQLCounter.log + ensure + failed_patterns = [] + patterns_to_match.each do |pattern| + failed_patterns << pattern unless ActiveRecord::SQLCounter.log.any?{ |sql| pattern === sql } + end + assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}" + end + + def assert_queries(num = 1) + ActiveRecord::SQLCounter.log = [] + yield + ensure + assert_equal num, ActiveRecord::SQLCounter.log.size, "#{ActiveRecord::SQLCounter.log.size} instead of #{num} queries were executed.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}" + end + + def assert_no_queries(&block) + prev_ignored_sql = ActiveRecord::SQLCounter.ignored_sql + ActiveRecord::SQLCounter.ignored_sql = [] + assert_queries(0, &block) + ensure + ActiveRecord::SQLCounter.ignored_sql = prev_ignored_sql + end + + def sqlite3? connection + connection.class.name.split('::').last == "SQLite3Adapter" + end + end +end diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index ed872c0883..c0d51797ee 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -25,29 +25,29 @@ module ActiveResource # # == Automated mapping # - # Active Resource objects represent your RESTful resources as manipulatable Ruby objects. To map resources + # Active Resource objects represent your RESTful resources as manipulatable Ruby objects. To map resources # to Ruby objects, Active Resource only needs a class name that corresponds to the resource name (e.g., the class # Person maps to the resources people, very similarly to Active Record) and a +site+ value, which holds the # URI of the resources. # # class Person < ActiveResource::Base - # self.site = "http://api.people.com:3000/" + # self.site = "https://api.people.com" # end # - # Now the Person class is mapped to RESTful resources located at <tt>http://api.people.com:3000/people/</tt>, and + # Now the Person class is mapped to RESTful resources located at <tt>https://api.people.com/people/</tt>, and # you can now use Active Resource's life cycle methods to manipulate resources. In the case where you already have # an existing model with the same name as the desired RESTful resource you can set the +element_name+ value. # # class PersonResource < ActiveResource::Base - # self.site = "http://api.people.com:3000/" + # self.site = "https://api.people.com" # self.element_name = "person" # end # # If your Active Resource object is required to use an HTTP proxy you can set the +proxy+ value which holds a URI. # # class PersonResource < ActiveResource::Base - # self.site = "http://api.people.com:3000/" - # self.proxy = "http://user:password@proxy.people.com:8080" + # self.site = "https://api.people.com" + # self.proxy = "https://user:password@proxy.people.com:8080" # end # # @@ -103,7 +103,7 @@ module ActiveResource # You can validate resources client side by overriding validation methods in the base class. # # class Person < ActiveResource::Base - # self.site = "http://api.people.com:3000/" + # self.site = "https://api.people.com" # protected # def validate # errors.add("last", "has invalid characters") unless last =~ /[a-zA-Z]*/ @@ -114,47 +114,64 @@ module ActiveResource # # == Authentication # - # Many REST APIs will require authentication, usually in the form of basic - # HTTP authentication. Authentication can be specified by: + # Many REST APIs require authentication. The HTTP spec describes two ways to + # make requests with a username and password (see RFC 2617). # - # === HTTP Basic Authentication - # * putting the credentials in the URL for the +site+ variable. + # Basic authentication simply sends a username and password along with HTTP + # requests. These sensitive credentials are sent unencrypted, visible to + # any onlooker, so this scheme should only be used with SSL. + # + # Digest authentication sends a crytographic hash of the username, password, + # HTTP method, URI, and a single-use secret key provided by the server. + # Sensitive credentials aren't visible to onlookers, so digest authentication + # doesn't require SSL. However, this doesn't mean the connection is secure! + # Just the username and password. + # + # (You really, really want to use SSL. There's little reason not to.) + # + # === Picking an authentication scheme + # + # Basic authentication is the default. To switch to digest authentication, + # set +auth_type+ to +:digest+: # # class Person < ActiveResource::Base - # self.site = "http://ryan:password@api.people.com:3000/" + # self.auth_type = :digest # end # - # * defining +user+ and/or +password+ variables + # === Setting the username and password + # + # Set +user+ and +password+ on the class, or include them in the +site+ URL. # # class Person < ActiveResource::Base - # self.site = "http://api.people.com:3000/" + # # Set user and password directly: # self.user = "ryan" # self.password = "password" - # end - # - # For obvious security reasons, it is probably best if such services are available - # over HTTPS. # - # Note: Some values cannot be provided in the URL passed to site. e.g. email addresses - # as usernames. In those situations you should use the separate user and password option. + # # Or include them in the site: + # self.site = "https://ryan:password@api.people.com" + # end # # === Certificate Authentication # - # * End point uses an X509 certificate for authentication. <tt>See ssl_options=</tt> for all options. + # You can also authenticate using an X509 certificate. <tt>See ssl_options=</tt> for all options. # # class Person < ActiveResource::Base # self.site = "https://secure.api.people.com/" - # self.ssl_options = {:cert => OpenSSL::X509::Certificate.new(File.open(pem_file)) - # :key => OpenSSL::PKey::RSA.new(File.open(pem_file)), - # :ca_path => "/path/to/OpenSSL/formatted/CA_Certs", - # :verify_mode => OpenSSL::SSL::VERIFY_PEER} + # + # File.open(pem_file_path, 'rb') do |pem_file| + # self.ssl_options = { + # cert: OpenSSL::X509::Certificate.new(pem_file), + # key: OpenSSL::PKey::RSA.new(pem_file), + # ca_path: "/path/to/OpenSSL/formatted/CA_Certs", + # verify_mode: OpenSSL::SSL::VERIFY_PEER } + # end # end # # # == Errors & Validation # # Error handling and validation is handled in much the same manner as you're used to seeing in - # Active Record. Both the response code in the HTTP response and the body of the response are used to + # Active Record. Both the response code in the HTTP response and the body of the response are used to # indicate that an error occurred. # # === Resource errors @@ -163,7 +180,7 @@ module ActiveResource # response code will be returned from the server which will raise an ActiveResource::ResourceNotFound # exception. # - # # GET http://api.people.com:3000/people/999.json + # # GET https://api.people.com/people/999.json # ryan = Person.find(999) # 404, raises ActiveResource::ResourceNotFound # # @@ -185,7 +202,7 @@ module ActiveResource # * Other - ActiveResource::ConnectionError # # These custom exceptions allow you to deal with resource errors more naturally and with more precision - # rather than returning a general HTTP error. For example: + # rather than returning a general HTTP error. For example: # # begin # ryan = Person.find(my_id) @@ -199,7 +216,7 @@ module ActiveResource # an ActiveResource::MissingPrefixParam will be raised. # # class Comment < ActiveResource::Base - # self.site = "http://someip.com/posts/:post_id/" + # self.site = "https://someip.com/posts/:post_id" # end # # Comment.find(1) @@ -208,8 +225,8 @@ module ActiveResource # === Validation errors # # Active Resource supports validations on resources and will return errors if any of these validations fail - # (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by - # a response code of <tt>422</tt> and an XML or JSON representation of the validation errors. The save operation will + # (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by + # a response code of <tt>422</tt> and an XML or JSON representation of the validation errors. The save operation will # then fail (with a <tt>false</tt> return value) and the validation errors can be accessed on the resource in question. # # ryan = Person.find(1) @@ -217,9 +234,9 @@ module ActiveResource # ryan.save # => false # # # When - # # PUT http://api.people.com:3000/people/1.json + # # PUT https://api.people.com/people/1.json # # or - # # PUT http://api.people.com:3000/people/1.json + # # PUT https://api.people.com/people/1.json # # is requested with invalid values, the response is: # # # # Response (422): @@ -240,7 +257,7 @@ module ActiveResource # amount of time before Active Resource times out with the +timeout+ variable. # # class Person < ActiveResource::Base - # self.site = "http://api.people.com:3000/" + # self.site = "https://api.people.com" # self.timeout = 5 # end # @@ -383,22 +400,22 @@ module ActiveResource @known_attributes ||= [] end - # Gets the URI of the REST resources to map for this class. The site variable is required for + # Gets the URI of the REST resources to map for this class. The site variable is required for # Active Resource's mapping to work. def site # Not using superclass_delegating_reader because don't want subclasses to modify superclass instance # # With superclass_delegating_reader # - # Parent.site = 'http://anonymous@test.com' - # Subclass.site # => 'http://anonymous@test.com' + # Parent.site = 'https://anonymous@test.com' + # Subclass.site # => 'https://anonymous@test.com' # Subclass.site.user = 'david' - # Parent.site # => 'http://david@test.com' + # Parent.site # => 'https://david@test.com' # # Without superclass_delegating_reader (expected behavior) # - # Parent.site = 'http://anonymous@test.com' - # Subclass.site # => 'http://anonymous@test.com' + # Parent.site = 'https://anonymous@test.com' + # Subclass.site # => 'https://anonymous@test.com' # Subclass.site.user = 'david' # => TypeError: can't modify frozen object # if defined?(@site) @@ -592,7 +609,7 @@ module ActiveResource prefix(options) end - # An attribute reader for the source string for the resource path \prefix. This + # An attribute reader for the source string for the resource path \prefix. This # method is regenerated at runtime based on what the \prefix is set to. def prefix_source prefix # generate #prefix and #prefix_source methods first @@ -625,7 +642,7 @@ module ActiveResource alias_method :set_element_name, :element_name= #:nodoc: alias_method :set_collection_name, :collection_name= #:nodoc: - # Gets the element path for the given ID in +id+. If the +query_options+ parameter is omitted, Rails + # Gets the element path for the given ID in +id+. If the +query_options+ parameter is omitted, Rails # will split from the \prefix options. # # ==== Options @@ -638,7 +655,7 @@ module ActiveResource # # => /posts/1.json # # class Comment < ActiveResource::Base - # self.site = "http://37s.sunrise.i/posts/:post_id/" + # self.site = "https://37s.sunrise.com/posts/:post_id" # end # # Comment.element_path(1, :post_id => 5) @@ -668,7 +685,7 @@ module ActiveResource # # => /posts/new.json # # class Comment < ActiveResource::Base - # self.site = "http://37s.sunrise.i/posts/:post_id/" + # self.site = "https://37s.sunrise.com/posts/:post_id" # end # # Comment.collection_path(:post_id => 5) @@ -677,7 +694,7 @@ module ActiveResource "#{prefix(prefix_options)}#{collection_name}/new.#{format.extension}" end - # Gets the collection path for the REST resources. If the +query_options+ parameter is omitted, Rails + # Gets the collection path for the REST resources. If the +query_options+ parameter is omitted, Rails # will split from the +prefix_options+. # # ==== Options @@ -725,8 +742,8 @@ module ActiveResource # ryan = Person.new(:first => 'ryan') # ryan.save # - # Returns the newly created resource. If a failure has occurred an - # exception will be raised (see <tt>save</tt>). If the resource is invalid and + # Returns the newly created resource. If a failure has occurred an + # exception will be raised (see <tt>save</tt>). If the resource is invalid and # has not been saved then <tt>valid?</tt> will return <tt>false</tt>, # while <tt>new?</tt> will still return <tt>true</tt>. # @@ -747,11 +764,11 @@ module ActiveResource self.new(attributes).tap { |resource| resource.save } end - # Core method for finding resources. Used similarly to Active Record's +find+ method. + # Core method for finding resources. Used similarly to Active Record's +find+ method. # # ==== Arguments - # The first argument is considered to be the scope of the query. That is, how many - # resources are returned from the request. It can be one of the following. + # The first argument is considered to be the scope of the query. That is, how many + # resources are returned from the request. It can be one of the following. # # * <tt>:one</tt> - Returns a single resource. # * <tt>:first</tt> - Returns the first resource found. @@ -834,7 +851,7 @@ module ActiveResource find(:last, *args) end - # This is an alias for find(:all). You can pass in all the same + # This is an alias for find(:all). You can pass in all the same # arguments to this method as you can to <tt>find(:all)</tt> def all(*args) find(:all, *args) @@ -1015,7 +1032,7 @@ module ActiveResource # not_ryan.new? # => true # # Any active resource member attributes will NOT be cloned, though all other - # attributes are. This is to prevent the conflict between any +prefix_options+ + # attributes are. This is to prevent the conflict between any +prefix_options+ # that refer to the original parent resource and the newly cloned parent # resource that does not exist. # @@ -1031,7 +1048,7 @@ module ActiveResource # Clone all attributes except the pk and any nested ARes cloned = Hash[attributes.reject {|k,v| k == self.class.primary_key || v.is_a?(ActiveResource::Base)}.map { |k, v| [k, v.clone] }] # Form the new resource - bypass initialize of resource with 'new' as that will call 'load' which - # attempts to convert hashes into member objects and arrays into collections of objects. We want + # attempts to convert hashes into member objects and arrays into collections of objects. We want # the raw objects to be cloned so we bypass load by directly setting the attributes hash. resource = self.class.new({}) resource.prefix_options = self.prefix_options @@ -1083,7 +1100,7 @@ module ActiveResource attributes[self.class.primary_key] = id end - # Test for equality. Resource are equal if and only if +other+ is the same object or + # Test for equality. Resource are equal if and only if +other+ is the same object or # is an instance of the same class, is not <tt>new?</tt>, and has the same +id+. # # ==== Examples @@ -1139,7 +1156,7 @@ module ActiveResource end end - # Saves (+POST+) or \updates (+PUT+) a resource. Delegates to +create+ if the object is \new, + # Saves (+POST+) or \updates (+PUT+) a resource. Delegates to +create+ if the object is \new, # +update+ if it exists. If the response to the \save includes a body, it will be assumed that this body # is Json for the final object as it looked after the \save (which would include attributes like +created_at+ # that weren't part of the original submit). @@ -1190,7 +1207,7 @@ module ActiveResource end # Evaluates to <tt>true</tt> if this resource is not <tt>new?</tt> and is - # found on the remote service. Using this method, you can check for + # found on the remote service. Using this method, you can check for # resources that may have been deleted between the object's instantiation # and actions on it. # @@ -1232,7 +1249,7 @@ module ActiveResource end # A method to manually load attributes from a \hash. Recursively loads collections of - # resources. This method is called in +initialize+ and +create+ when a \hash of attributes + # resources. This method is called in +initialize+ and +create+ when a \hash of attributes # is provided. # # ==== Examples @@ -1289,12 +1306,12 @@ module ActiveResource # # Note: Unlike ActiveRecord::Base.update_attribute, this method <b>is</b> # subject to normal validation routines as an update sends the whole body - # of the resource in the request. (See Validations). + # of the resource in the request. (See Validations). # # As such, this method is equivalent to calling update_attributes with a single attribute/value pair. # # If the saving fails because of a connection or remote service error, an - # exception will be raised. If saving fails because the resource is + # exception will be raised. If saving fails because the resource is # invalid then <tt>false</tt> will be returned. def update_attribute(name, value) self.send("#{name}=".to_sym, value) @@ -1305,7 +1322,7 @@ module ActiveResource # and requests that the record be saved. # # If the saving fails because of a connection or remote service error, an - # exception will be raised. If saving fails because the resource is + # exception will be raised. If saving fails because the resource is # invalid then <tt>false</tt> will be returned. # # Note: Though this request can be made with a partial set of the diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index 2a034f9269..46060b6f74 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -166,38 +166,28 @@ module ActiveResource end def configure_http(http) - http = apply_ssl_options(http) - - # Net::HTTP timeouts default to 60 seconds. - if @timeout - http.open_timeout = @timeout - http.read_timeout = @timeout + apply_ssl_options(http).tap do |https| + # Net::HTTP timeouts default to 60 seconds. + if defined? @timeout + https.open_timeout = @timeout + https.read_timeout = @timeout + end end - - http end def apply_ssl_options(http) - return http unless @site.is_a?(URI::HTTPS) - - http.use_ssl = true - http.verify_mode = OpenSSL::SSL::VERIFY_NONE - return http unless defined?(@ssl_options) - - http.ca_path = @ssl_options[:ca_path] if @ssl_options[:ca_path] - http.ca_file = @ssl_options[:ca_file] if @ssl_options[:ca_file] + http.tap do |https| + # Skip config if site is already a https:// URI. + if defined? @ssl_options + http.use_ssl = true - http.cert = @ssl_options[:cert] if @ssl_options[:cert] - http.key = @ssl_options[:key] if @ssl_options[:key] + # Default to no cert verification (WTF? FIXME) + http.verify_mode = OpenSSL::SSL::VERIFY_NONE - http.cert_store = @ssl_options[:cert_store] if @ssl_options[:cert_store] - http.ssl_timeout = @ssl_options[:ssl_timeout] if @ssl_options[:ssl_timeout] - - http.verify_mode = @ssl_options[:verify_mode] if @ssl_options[:verify_mode] - http.verify_callback = @ssl_options[:verify_callback] if @ssl_options[:verify_callback] - http.verify_depth = @ssl_options[:verify_depth] if @ssl_options[:verify_depth] - - http + # All the SSL options have corresponding http settings. + @ssl_options.each { |key, value| http.send "#{key}=", value } + end + end end def default_header diff --git a/activeresource/lib/active_resource/custom_methods.rb b/activeresource/lib/active_resource/custom_methods.rb index 2a651dd48e..a0eb28ed13 100644 --- a/activeresource/lib/active_resource/custom_methods.rb +++ b/activeresource/lib/active_resource/custom_methods.rb @@ -2,7 +2,7 @@ require 'active_support/core_ext/object/blank' module ActiveResource # A module to support custom REST methods and sub-resources, allowing you to break out - # of the "default" REST methods with your own custom resource requests. For example, + # of the "default" REST methods with your own custom resource requests. For example, # say you use Rails to expose a REST service and configure your routes with: # # map.resources :people, :new => { :register => :post }, @@ -20,7 +20,7 @@ module ActiveResource # standard methods. # # class Person < ActiveResource::Base - # self.site = "http://37s.sunrise.i:3000" + # self.site = "https://37s.sunrise.com" # end # # Person.new(:name => 'Ryan').post(:register) # POST /people/new/register.json diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb index 36f52d61d3..666b961f87 100644 --- a/activeresource/lib/active_resource/http_mock.rb +++ b/activeresource/lib/active_resource/http_mock.rb @@ -4,7 +4,7 @@ require 'active_support/core_ext/object/inclusion' module ActiveResource class InvalidRequestError < StandardError; end #:nodoc: - # One thing that has always been a pain with remote web services is testing. The HttpMock + # One thing that has always been a pain with remote web services is testing. The HttpMock # class makes it easy to test your Active Resource models by creating a set of mock responses to specific # requests. # @@ -15,17 +15,17 @@ module ActiveResource # # mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {}) # - # * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +put+, +delete+ or + # * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +put+, +delete+ or # +head+. # * <tt>path</tt> - A string, starting with a "/", defining the URI that is expected to be # called. - # * <tt>request_headers</tt> - Headers that are expected along with the request. This argument uses a - # hash format, such as <tt>{ "Content-Type" => "application/json" }</tt>. This mock will only trigger + # * <tt>request_headers</tt> - Headers that are expected along with the request. This argument uses a + # hash format, such as <tt>{ "Content-Type" => "application/json" }</tt>. This mock will only trigger # if your tests sends a request with identical headers. - # * <tt>body</tt> - The data to be returned. This should be a string of Active Resource parseable content, + # * <tt>body</tt> - The data to be returned. This should be a string of Active Resource parseable content, # such as Json. # * <tt>status</tt> - The HTTP response code, as an integer, to return with the response. - # * <tt>response_headers</tt> - Headers to be returned with the response. Uses the same hash format as + # * <tt>response_headers</tt> - Headers to be returned with the response. Uses the same hash format as # <tt>request_headers</tt> listed above. # # In order for a mock to deliver its content, the incoming request must match by the <tt>http_method</tt>, @@ -291,12 +291,9 @@ module ActiveResource if resp_cls && !resp_cls.body_permitted? @body = nil end - - if @body.nil? - self['Content-Length'] = "0" - else - self['Content-Length'] = body.size.to_s - end + + self['Content-Length'] = @body.nil? ? "0" : body.size.to_s + end # Returns true if code is 2xx, diff --git a/activeresource/test/cases/authorization_test.rb b/activeresource/test/cases/authorization_test.rb index 0185e5432d..fbfe086599 100644 --- a/activeresource/test/cases/authorization_test.rb +++ b/activeresource/test/cases/authorization_test.rb @@ -9,8 +9,18 @@ class AuthorizationTest < ActiveSupport::TestCase @david = { :person => { :id => 2, :name => 'David' } }.to_json @authenticated_conn = ActiveResource::Connection.new("http://david:test123@localhost") @basic_authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' } + end - @nonce = "MTI0OTUxMzc4NzpjYWI3NDM3NDNmY2JmODU4ZjQ2ZjcwNGZkMTJiMjE0NA==" + private + def decode(response) + @authenticated_conn.format.decode(response.body) + end +end + +class BasicAuthorizationTest < AuthorizationTest + def setup + super + @authenticated_conn.auth_type = :basic ActiveResource::HttpMock.respond_to do |mock| mock.get "/people/2.json", @basic_authorization_request_header, @david @@ -19,34 +29,48 @@ class AuthorizationTest < ActiveSupport::TestCase mock.delete "/people/2.json", @basic_authorization_request_header, nil, 200 mock.post "/people/2/addresses.json", @basic_authorization_request_header, nil, 201, 'Location' => '/people/1/addresses/5' mock.head "/people/2.json", @basic_authorization_request_header, nil, 200 + end + end - mock.get "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "fad396f6a34aeba28e28b9b96ddbb671") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } - mock.get "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "c064d5ba8891a25290c76c8c7d31fb7b") }, @david, 200 - mock.get "/people/1.json", { 'Authorization' => request_digest_auth_header("/people/1.json", "f9c0b594257bb8422af4abd429c5bb70") }, @matz, 200 + def test_get + david = decode(@authenticated_conn.get("/people/2.json")) + assert_equal "David", david["name"] + end - mock.put "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "50a685d814f94665b9d160fbbaa3958a") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } - mock.put "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "5a75cde841122d8e0f20f8fd1f98a743") }, nil, 204 + def test_post + response = @authenticated_conn.post("/people/2/addresses.json") + assert_equal "/people/1/addresses/5", response["Location"] + end - mock.delete "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "846f799107eab5ca4285b909ee299a33") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } - mock.delete "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "9f5b155224edbbb69fd99d8ce094681e") }, nil, 200 + def test_put + response = @authenticated_conn.put("/people/2.json") + assert_equal 204, response.code + end - mock.post "/people/2/addresses.json", { 'Authorization' => blank_digest_auth_header("/people/2/addresses.json", "6984d405ff3d9ed07bbf747dcf16afb0") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } - mock.post "/people/2/addresses.json", { 'Authorization' => request_digest_auth_header("/people/2/addresses.json", "4bda6a28dbf930b5af9244073623bd04") }, nil, 201, 'Location' => '/people/1/addresses/5' + def test_delete + response = @authenticated_conn.delete("/people/2.json") + assert_equal 200, response.code + end - mock.head "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "15e5ed84ba5c4cfcd5c98a36c2e4f421") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } - mock.head "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "d4c6d2bcc8717abb2e2ccb8c49ee6a91") }, nil, 200 - end + def test_head + response = @authenticated_conn.head("/people/2.json") + assert_equal 200, response.code + end - # Make client nonce deterministic - class << @authenticated_conn - private + def test_retry_on_401_doesnt_happen_with_basic_auth + assert_raise(ActiveResource::UnauthorizedAccess) { @authenticated_conn.get("/people/1.json") } + assert_equal "", @authenticated_conn.send(:response_auth_header) + end - def client_nonce - 'i-am-a-client-nonce' - end - end + def test_raises_invalid_request_on_unauthorized_requests + assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.json") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.json") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.json") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.json") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.json") } end + def test_authorization_header authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json')) assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization'] @@ -116,7 +140,6 @@ class AuthorizationTest < ActiveSupport::TestCase end def test_authorization_header_if_credentials_supplied_and_auth_type_is_basic - @authenticated_conn.auth_type = :basic authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json')) assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization'] authorization = authorization_header["Authorization"].to_s.split @@ -125,76 +148,77 @@ class AuthorizationTest < ActiveSupport::TestCase assert_equal ["david", "test123"], ::Base64.decode64(authorization[1]).split(":")[0..1] end - def test_authorization_header_if_credentials_supplied_and_auth_type_is_digest - @authenticated_conn.auth_type = :digest - authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json')) - assert_equal blank_digest_auth_header("/people/2.json", "fad396f6a34aeba28e28b9b96ddbb671"), authorization_header['Authorization'] + def test_client_nonce_is_not_nil + assert_not_nil ActiveResource::Connection.new("http://david:test123@localhost").send(:client_nonce) end +end - def test_authorization_header_with_query_string_if_auth_type_is_digest +class DigestAuthorizationTest < AuthorizationTest + def setup + super @authenticated_conn.auth_type = :digest - authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json?only=name')) - assert_equal blank_digest_auth_header("/people/2.json?only=name", "f8457b0b5d21b6b80737a386217afb24"), authorization_header['Authorization'] - end - def test_get - david = decode(@authenticated_conn.get("/people/2.json")) - assert_equal "David", david["name"] - end + # Make client nonce deterministic + def @authenticated_conn.client_nonce; 'i-am-a-client-nonce' end - def test_post - response = @authenticated_conn.post("/people/2/addresses.json") - assert_equal "/people/1/addresses/5", response["Location"] - end + @nonce = "MTI0OTUxMzc4NzpjYWI3NDM3NDNmY2JmODU4ZjQ2ZjcwNGZkMTJiMjE0NA==" - def test_put - response = @authenticated_conn.put("/people/2.json") - assert_equal 204, response.code + ActiveResource::HttpMock.respond_to do |mock| + mock.get "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "fad396f6a34aeba28e28b9b96ddbb671") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.get "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "c064d5ba8891a25290c76c8c7d31fb7b") }, @david, 200 + mock.get "/people/1.json", { 'Authorization' => request_digest_auth_header("/people/1.json", "f9c0b594257bb8422af4abd429c5bb70") }, @matz, 200 + + mock.put "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "50a685d814f94665b9d160fbbaa3958a") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.put "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "5a75cde841122d8e0f20f8fd1f98a743") }, nil, 204 + + mock.delete "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "846f799107eab5ca4285b909ee299a33") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.delete "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "9f5b155224edbbb69fd99d8ce094681e") }, nil, 200 + + mock.post "/people/2/addresses.json", { 'Authorization' => blank_digest_auth_header("/people/2/addresses.json", "6984d405ff3d9ed07bbf747dcf16afb0") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.post "/people/2/addresses.json", { 'Authorization' => request_digest_auth_header("/people/2/addresses.json", "4bda6a28dbf930b5af9244073623bd04") }, nil, 201, 'Location' => '/people/1/addresses/5' + + mock.head "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "15e5ed84ba5c4cfcd5c98a36c2e4f421") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.head "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "d4c6d2bcc8717abb2e2ccb8c49ee6a91") }, nil, 200 + end end - def test_delete - response = @authenticated_conn.delete("/people/2.json") - assert_equal 200, response.code + def test_authorization_header_if_credentials_supplied_and_auth_type_is_digest + authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json')) + assert_equal blank_digest_auth_header("/people/2.json", "fad396f6a34aeba28e28b9b96ddbb671"), authorization_header['Authorization'] end - def test_head - response = @authenticated_conn.head("/people/2.json") - assert_equal 200, response.code + def test_authorization_header_with_query_string_if_auth_type_is_digest + authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json?only=name')) + assert_equal blank_digest_auth_header("/people/2.json?only=name", "f8457b0b5d21b6b80737a386217afb24"), authorization_header['Authorization'] end def test_get_with_digest_auth_handles_initial_401_response_and_retries - @authenticated_conn.auth_type = :digest response = @authenticated_conn.get("/people/2.json") assert_equal "David", decode(response)["name"] end def test_post_with_digest_auth_handles_initial_401_response_and_retries - @authenticated_conn.auth_type = :digest response = @authenticated_conn.post("/people/2/addresses.json") assert_equal "/people/1/addresses/5", response["Location"] assert_equal 201, response.code end def test_put_with_digest_auth_handles_initial_401_response_and_retries - @authenticated_conn.auth_type = :digest - response = @authenticated_conn.put("/people/2.json") - assert_equal 204, response.code + response = @authenticated_conn.put("/people/2.json") + assert_equal 204, response.code end def test_delete_with_digest_auth_handles_initial_401_response_and_retries - @authenticated_conn.auth_type = :digest response = @authenticated_conn.delete("/people/2.json") assert_equal 200, response.code end def test_head_with_digest_auth_handles_initial_401_response_and_retries - @authenticated_conn.auth_type = :digest response = @authenticated_conn.head("/people/2.json") assert_equal 200, response.code end def test_get_with_digest_auth_caches_nonce - @authenticated_conn.auth_type = :digest response = @authenticated_conn.get("/people/2.json") assert_equal "David", decode(response)["name"] @@ -203,19 +227,6 @@ class AuthorizationTest < ActiveSupport::TestCase assert_equal "Matz", decode(response)["name"] end - def test_retry_on_401_only_happens_with_digest_auth - assert_raise(ActiveResource::UnauthorizedAccess) { @authenticated_conn.get("/people/1.json") } - assert_equal "", @authenticated_conn.send(:response_auth_header) - end - - def test_raises_invalid_request_on_unauthorized_requests - assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.json") } - assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.json") } - assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.json") } - assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.json") } - assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.json") } - end - def test_raises_invalid_request_on_unauthorized_requests_with_digest_auth @conn.auth_type = :digest assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.json") } @@ -225,17 +236,7 @@ class AuthorizationTest < ActiveSupport::TestCase assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.json") } end - def test_client_nonce_is_not_nil - assert_not_nil ActiveResource::Connection.new("http://david:test123@localhost").send(:client_nonce) - end - - protected - def assert_response_raises(klass, code) - assert_raise(klass, "Expected response code #{code} to raise #{klass}") do - @conn.__send__(:handle_response, Response.new(code)) - end - end - + private def blank_digest_auth_header(uri, response) %Q(Digest username="david", realm="", qop="", uri="#{uri}", nonce="", nc="0", cnonce="i-am-a-client-nonce", opaque="", response="#{response}") end @@ -247,8 +248,4 @@ class AuthorizationTest < ActiveSupport::TestCase def response_digest_auth_header %Q(Digest realm="RailsTestApp", qop="auth", algorithm=MD5, nonce="#{@nonce}", opaque="ef6dfb078ba22298d366f99567814ffb") end - - def decode(response) - @authenticated_conn.format.decode(response.body) - end end diff --git a/activeresource/test/cases/connection_test.rb b/activeresource/test/cases/connection_test.rb index 653912f000..0a07ead15e 100644 --- a/activeresource/test/cases/connection_test.rb +++ b/activeresource/test/cases/connection_test.rb @@ -224,7 +224,6 @@ class ConnectionTest < ActiveSupport::TestCase http = Net::HTTP.new('') @conn.site="https://secure" @conn.ssl_options={:verify_mode => OpenSSL::SSL::VERIFY_PEER} - @conn.timeout = 10 # prevent warning about uninitialized. @conn.send(:configure_http, http) assert http.use_ssl? diff --git a/activeresource/test/fixtures/street_address.rb b/activeresource/test/fixtures/street_address.rb index 94a86702b0..6a8adb98b5 100644 --- a/activeresource/test/fixtures/street_address.rb +++ b/activeresource/test/fixtures/street_address.rb @@ -1,4 +1,4 @@ class StreetAddress < ActiveResource::Base - self.site = "http://37s.sunrise.i:3000/people/:person_id/" + self.site = "http://37s.sunrise.i:3000/people/:person_id" self.element_name = 'address' end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index c339e93808..02a989db22 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,7 @@ ## Rails 4.0.0 (unreleased) ## +* Remove ActiveSupport::TestCase#pending method, use `skip` instead. *Carlos Antonio da Silva* + * Deprecates the compatibility method Module#local_constant_names, use Module#local_constants instead (which returns symbols). *fxn* diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index d4a9b93ff2..5eaeac2cb3 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -357,9 +357,9 @@ module ActiveSupport # calculating which callbacks can be omitted because of per_key conditions. # def __run_callbacks(key, kind, object, &blk) #:nodoc: - name = __callback_runner_name(key, kind) + name = __callback_runner_name(kind) unless object.respond_to?(name) - str = send("_#{kind}_callbacks").compile(key, object) + str = object.send("_#{kind}_callbacks").compile(key, object) class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{name}() #{str} end protected :#{name} @@ -369,11 +369,11 @@ module ActiveSupport end def __reset_runner(symbol) - name = __callback_runner_name(nil, symbol) + name = __callback_runner_name(symbol) undef_method(name) if method_defined?(name) end - def __callback_runner_name(key, kind) + def __callback_runner_name(kind) "_run__#{self.name.hash.abs}__#{kind}__callbacks" 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 6dd321ecf9..e42453389e 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -2,7 +2,7 @@ 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 def local_offset - ::Time.local(2007).utc_offset.to_r / 86400 + ::Time.local(2012).utc_offset.to_r / 86400 end # Returns <tt>Time.zone.now.to_datetime</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise returns <tt>Time.now.to_datetime</tt>. 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 d7b3ad7d8d..e5f895478a 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -58,32 +58,17 @@ class DateTime alias_method :default_inspect, :inspect alias_method :inspect, :readable_inspect - # Converts self to a Ruby Date object; time portion is discarded. - def to_date - ::Date.new(year, month, day) - end unless instance_methods(false).include?(:to_date) - # Attempts to convert self to a Ruby Time object; returns self if out of range of Ruby Time class. # If self has an offset other than 0, self will just be returned unaltered, since there's no clean way to map it to a Time. def to_time self.offset == 0 ? ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * 1000000) : self end - # To be able to keep Times, Dates and DateTimes interchangeable on conversions. - def to_datetime - self - end unless instance_methods(false).include?(:to_datetime) - 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 civil(year, month, day, hour, min, sec, offset) end - # Converts datetime to an appropriate format for use in XML. - def xmlschema - strftime("%Y-%m-%dT%H:%M:%S%Z") - end unless instance_methods(false).include?(:xmlschema) - # Converts self to a floating-point number of seconds since the Unix epoch. def to_f seconds_since_unix_epoch.to_f diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index 4169557286..9a52c916ec 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -3,7 +3,6 @@ require 'active_support/testing/setup_and_teardown' require 'active_support/testing/assertions' require 'active_support/testing/deprecation' require 'active_support/testing/declarative' -require 'active_support/testing/pending' require 'active_support/testing/isolation' require 'active_support/testing/mochaing' require 'active_support/core_ext/kernel/reporting' @@ -40,7 +39,6 @@ module ActiveSupport include ActiveSupport::Testing::SetupAndTeardown include ActiveSupport::Testing::Assertions include ActiveSupport::Testing::Deprecation - include ActiveSupport::Testing::Pending extend ActiveSupport::Testing::Declarative # test/unit backwards compatibility methods diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index 5d5de4798d..1a0681e850 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -37,10 +37,6 @@ module ActiveSupport !ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/)) end - def self.included(base) - base.send :include, MiniTest - end - def _run_class_setup # class setup method should only happen in parent unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST'] self.class.setup if self.class.respond_to?(:setup) @@ -48,18 +44,16 @@ module ActiveSupport end end - module MiniTest - def run(runner) - _run_class_setup + def run(runner) + _run_class_setup - serialized = run_in_isolation do |isolated_runner| - super(isolated_runner) - end - - retval, proxy = Marshal.load(serialized) - proxy.__replay__(runner) - retval + serialized = run_in_isolation do |isolated_runner| + super(isolated_runner) end + + retval, proxy = Marshal.load(serialized) + proxy.__replay__(runner) + retval end module Forking diff --git a/activesupport/lib/active_support/testing/pending.rb b/activesupport/lib/active_support/testing/pending.rb deleted file mode 100644 index 510f80f32c..0000000000 --- a/activesupport/lib/active_support/testing/pending.rb +++ /dev/null @@ -1,20 +0,0 @@ -# Some code from jeremymcanally's "pending" -# https://github.com/jeremymcanally/pending/tree/master - -module ActiveSupport - module Testing - module Pending - - unless defined?(Spec) - - @@pending_cases = [] - @@at_exit = false - - def pending(description = "", &block) - skip(description.blank? ? nil : description) - end - end - - end - end -end diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb index 209bfac19f..244ee1a224 100644 --- a/activesupport/lib/active_support/testing/performance.rb +++ b/activesupport/lib/active_support/testing/performance.rb @@ -13,7 +13,6 @@ module ActiveSupport included do superclass_delegating_accessor :profile_options self.profile_options = {} - include ForMiniTest end # each implementation should define metrics and freeze the defaults @@ -36,40 +35,38 @@ module ActiveSupport "#{self.class.name}##{method_name}" end - module ForMiniTest - def run(runner) - @runner = runner + def run(runner) + @runner = runner - run_warmup - if full_profile_options && metrics = full_profile_options[:metrics] - metrics.each do |metric_name| - if klass = Metrics[metric_name.to_sym] - run_profile(klass.new) - end + run_warmup + if full_profile_options && metrics = full_profile_options[:metrics] + metrics.each do |metric_name| + if klass = Metrics[metric_name.to_sym] + run_profile(klass.new) end end - - return end - def run_test(metric, mode) - result = '.' + return + end + + def run_test(metric, mode) + result = '.' + begin + run_callbacks :setup + setup + metric.send(mode) { __send__ method_name } + rescue Exception => e + result = @runner.puke(self.class, method_name, e) + ensure begin - run_callbacks :setup - setup - metric.send(mode) { __send__ method_name } + teardown + run_callbacks :teardown, :enumerator => :reverse_each rescue Exception => e result = @runner.puke(self.class, method_name, e) - ensure - begin - teardown - run_callbacks :teardown, :enumerator => :reverse_each - rescue Exception => e - result = @runner.puke(self.class, method_name, e) - end end - result end + result end protected diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 2c2a420619..032787f0f4 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -344,6 +344,54 @@ module CallbacksTest end end + module ExtendModule + def self.extended(base) + base.class_eval do + set_callback :save, :before, :record3 + end + end + def record3 + @recorder << 3 + end + end + + module IncludeModule + def self.included(base) + base.class_eval do + set_callback :save, :before, :record2 + end + end + def record2 + @recorder << 2 + end + end + + class ExtendCallbacks + + include ActiveSupport::Callbacks + + define_callbacks :save + set_callback :save, :before, :record1 + + include IncludeModule + + def save + run_callbacks :save + end + + attr_reader :recorder + + def initialize + @recorder = [] + end + + private + + def record1 + @recorder << 1 + end + end + class AroundCallbacksTest < ActiveSupport::TestCase def test_save_around around = AroundPerson.new @@ -645,4 +693,12 @@ module CallbacksTest end end + class ExtendCallbacksTest < ActiveSupport::TestCase + def test_save + model = ExtendCallbacks.new.extend ExtendModule + model.save + assert_equal [1, 2, 3], model.recorder + end + end + end diff --git a/rails.gemspec b/rails.gemspec index 1d4c6c36c8..cd7c5d1ee9 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -8,7 +8,7 @@ Gem::Specification.new do |s| s.description = 'Ruby on Rails is a full-stack web framework optimized for programmer happiness and sustainable productivity. It encourages beautiful code by favoring convention over configuration.' s.required_ruby_version = '>= 1.9.3' - s.required_rubygems_version = ">= 1.3.6" + s.required_rubygems_version = ">= 1.8.11" s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' diff --git a/railties/guides/code/getting_started/config/application.rb b/railties/guides/code/getting_started/config/application.rb index dad91d7943..d2cd5c028b 100644 --- a/railties/guides/code/getting_started/config/application.rb +++ b/railties/guides/code/getting_started/config/application.rb @@ -37,7 +37,7 @@ module Blog # Use SQL instead of Active Record's schema dumper when creating the database. # This is necessary if your schema can't be completely dumped by the schema dumper, - # like if you have constraints or database-specific column types + # like if you have constraints or database-specific column types. # config.active_record.schema_format = :sql # Enforce whitelist mode for mass assignment. @@ -46,10 +46,10 @@ module Blog # parameters by using an attr_accessible or attr_protected declaration. # config.active_record.whitelist_attributes = true - # Enable the asset pipeline + # Enable the asset pipeline. config.assets.enabled = true - # Version of your assets, change this if you want to expire all your assets + # Version of your assets, change this if you want to expire all your assets. config.assets.version = '1.0' end end diff --git a/railties/guides/code/getting_started/config/environments/development.rb b/railties/guides/code/getting_started/config/environments/development.rb index aefd25c6b6..cec2b20c0b 100644 --- a/railties/guides/code/getting_started/config/environments/development.rb +++ b/railties/guides/code/getting_started/config/environments/development.rb @@ -1,34 +1,34 @@ Blog::Application.configure do - # Settings specified here will take precedence over those in config/application.rb + # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false - # Show full error reports and disable caching + # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false - # Don't care if the mailer can't send + # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false - # Print deprecation notices to the Rails logger + # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log - # Only use best-standards-support built into browsers + # Only use best-standards-support built into browsers. config.action_dispatch.best_standards_support = :builtin - # Raise exception on mass assignment protection for ActiveRecord models + # Raise exception on mass assignment protection for ActiveRecord models. config.active_record.mass_assignment_sanitizer = :strict # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL) + # with SQLite, MySQL, and PostgreSQL). config.active_record.auto_explain_threshold_in_seconds = 0.5 - # Do not compress assets + # Do not compress assets. config.assets.compress = false - # Expands the lines which load the assets + # Expands the lines which load the assets. config.assets.debug = true end diff --git a/railties/guides/code/getting_started/config/environments/production.rb b/railties/guides/code/getting_started/config/environments/production.rb index c9b2f41c39..cfb8c960d6 100644 --- a/railties/guides/code/getting_started/config/environments/production.rb +++ b/railties/guides/code/getting_started/config/environments/production.rb @@ -1,67 +1,67 @@ Blog::Application.configure do - # Settings specified here will take precedence over those in config/application.rb + # Settings specified here will take precedence over those in config/application.rb. - # Code is not reloaded between requests + # Code is not reloaded between requests. config.cache_classes = true - # Full error reports are disabled and caching is turned on + # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true - # Disable Rails's static asset server (Apache or nginx will already do this) + # Disable Rails's static asset server (Apache or nginx will already do this). config.serve_static_assets = false - # Compress JavaScripts and CSS + # Compress JavaScripts and CSS. config.assets.compress = true - # Don't fallback to assets pipeline if a precompiled asset is missed + # Don't fallback to assets pipeline if a precompiled asset is missed. config.assets.compile = false - # Generate digests for assets URLs + # Generate digests for assets URLs. config.assets.digest = true - # Defaults to Rails.root.join("public/assets") + # Defaults to Rails.root.join("public/assets"). # config.assets.manifest = YOUR_PATH - # Specifies the header that your server uses for sending files + # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true - # See everything in the log (default is :info) + # See everything in the log (default is :info). # config.log_level = :debug - # Prepend all log lines with the following tags + # Prepend all log lines with the following tags. # config.log_tags = [ :subdomain, :uuid ] - # Use a different logger for distributed setups + # Use a different logger for distributed setups. # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) - # Use a different cache store in production + # Use a different cache store in production. # config.cache_store = :mem_cache_store - # Enable serving of images, stylesheets, and JavaScripts from an asset server + # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.action_controller.asset_host = "http://assets.example.com" - # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) + # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added). # config.assets.precompile += %w( search.js ) - # Disable delivery errors, bad email addresses will be ignored + # Disable delivery errors, bad email addresses will be ignored. # config.action_mailer.raise_delivery_errors = false - # Enable threaded mode + # Enable threaded mode. # config.threadsafe! # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation can not be found) + # the I18n.default_locale when a translation can not be found). config.i18n.fallbacks = true - # Send deprecation notices to registered listeners + # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL) + # with SQLite, MySQL, and PostgreSQL). # config.active_record.auto_explain_threshold_in_seconds = 0.5 end diff --git a/railties/guides/code/getting_started/config/environments/test.rb b/railties/guides/code/getting_started/config/environments/test.rb index e97f82c6bd..f2bc932fb3 100644 --- a/railties/guides/code/getting_started/config/environments/test.rb +++ b/railties/guides/code/getting_started/config/environments/test.rb @@ -1,5 +1,5 @@ Blog::Application.configure do - # Settings specified here will take precedence over those in config/application.rb + # Settings specified here will take precedence over those in config/application.rb. # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that @@ -7,28 +7,28 @@ Blog::Application.configure do # and recreated between test runs. Don't rely on the data there! config.cache_classes = true - # Configure static asset server for tests with Cache-Control for performance + # Configure static asset server for tests with Cache-Control for performance. config.serve_static_assets = true config.static_cache_control = "public, max-age=3600" - # Show full error reports and disable caching + # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false - # Raise exceptions instead of rendering exception templates + # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false - # Disable request forgery protection in test environment - config.action_controller.allow_forgery_protection = false + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test - # Raise exception on mass assignment protection for Active Record models + # Raise exception on mass assignment protection for Active Record models. config.active_record.mass_assignment_sanitizer = :strict - # Print deprecation notices to the stderr + # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr end diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 5970a45839..84687e2e4f 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -965,7 +965,7 @@ If you're working with dates or times within scopes, due to how they are evaluat <ruby> class Post < ActiveRecord::Base - scope :last_week, lambda { where("created_at < ?", Time.zone.now ) } + scope :created_before_now, lambda { where("created_at < ?", Time.zone.now ) } end </ruby> @@ -977,21 +977,21 @@ When a +lambda+ is used for a +scope+, it can take arguments: <ruby> class Post < ActiveRecord::Base - scope :1_week_before, lambda { |time| where("created_at < ?", time) } + scope :created_before, lambda { |time| where("created_at < ?", time) } end </ruby> This may then be called using this: <ruby> -Post.1_week_before(Time.zone.now) +Post.created_before(Time.zone.now) </ruby> However, this is just duplicating the functionality that would be provided to you by a class method. <ruby> class Post < ActiveRecord::Base - def self.1_week_before(time) + def self.created_before(time) where("created_at < ?", time) end end @@ -1000,7 +1000,7 @@ end Using a class method is the preferred way to accept arguments for scopes. These methods will still be accessible on the association objects: <ruby> -category.posts.1_week_before(time) +category.posts.created_before(time) </ruby> h4. Working with scopes diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index b9944bed26..7da495211d 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -2,7 +2,6 @@ module Rails class Application module Finisher include Initializable - $rails_rake_task = nil initializer :add_generator_templates do config.generators.templates.unshift(*paths["lib/templates"].existent) @@ -49,7 +48,7 @@ module Rails end initializer :eager_load! do - if config.cache_classes && !$rails_rake_task + if config.cache_classes && !(defined?($rails_rake_task) && $rails_rake_task) ActiveSupport.run_load_hooks(:before_eager_load, self) eager_load! end diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 96997021ee..29a2ad3111 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 => $2.to_i + when /decimal\{(\d+)(,|\.|\-)(\d+)\}/ + return :decimal, :precision => $1.to_i, :scale => $3.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 712068a942..5e9c385ab8 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -2,8 +2,6 @@ source 'https://rubygems.org' <%= rails_gemfile_entry -%> -gem 'rack', :git => 'https://github.com/rack/rack.git' - <%= database_gemfile_entry -%> <%= "gem 'jruby-openssl'\n" if defined?(JRUBY_VERSION) -%> 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 bcd3a2ad24..3517956e4a 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -13,9 +13,9 @@ require "active_resource/railtie" <% end -%> if defined?(Bundler) - # If you precompile assets before deploying to production, use this line + # If you precompile assets before deploying to production, use this line. Bundler.require(*Rails.groups(:assets => %w(development test))) - # If you want your assets lazily compiled in production, use this line + # If you want your assets lazily compiled in production, use this line. # Bundler.require(:default, :assets, Rails.env) end @@ -47,7 +47,7 @@ module <%= app_const_base %> # Use SQL instead of Active Record's schema dumper when creating the database. # This is necessary if your schema can't be completely dumped by the schema dumper, - # like if you have constraints or database-specific column types + # like if you have constraints or database-specific column types. # config.active_record.schema_format = :sql # Enforce whitelist mode for mass assignment. @@ -57,10 +57,10 @@ module <%= app_const_base %> # config.active_record.whitelist_attributes = true <% unless options.skip_sprockets? -%> - # Enable the asset pipeline + # Enable the asset pipeline. config.assets.enabled = true - # Version of your assets, change this if you want to expire all your assets + # Version of your assets, change this if you want to expire all your assets. config.assets.version = '1.0' <% end -%> end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index 5f7cc5af61..eb4dfa7c89 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -1,38 +1,38 @@ <%= app_const %>.configure do - # Settings specified here will take precedence over those in config/application.rb + # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false - # Show full error reports and disable caching + # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false - # Don't care if the mailer can't send + # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false - # Print deprecation notices to the Rails logger + # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log - # Only use best-standards-support built into browsers + # Only use best-standards-support built into browsers. config.action_dispatch.best_standards_support = :builtin <%- unless options.skip_active_record? -%> - # Raise exception on mass assignment protection for Active Record models + # Raise exception on mass assignment protection for Active Record models. config.active_record.mass_assignment_sanitizer = :strict # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL) + # with SQLite, MySQL, and PostgreSQL). config.active_record.auto_explain_threshold_in_seconds = 0.5 <%- end -%> <%- unless options.skip_sprockets? -%> - # Do not compress assets + # Do not compress assets. config.assets.compress = false - # Expands the lines which load the assets + # Expands the lines which load the assets. config.assets.debug = true <%- end -%> end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 0f571f7c1a..e9a86d175e 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -1,73 +1,73 @@ <%= app_const %>.configure do - # Settings specified here will take precedence over those in config/application.rb + # Settings specified here will take precedence over those in config/application.rb. - # Code is not reloaded between requests + # Code is not reloaded between requests. config.cache_classes = true - # Full error reports are disabled and caching is turned on + # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true - # Disable Rails's static asset server (Apache or nginx will already do this) + # Disable Rails's static asset server (Apache or nginx will already do this). config.serve_static_assets = false <%- unless options.skip_sprockets? -%> - # Compress JavaScripts and CSS + # Compress JavaScripts and CSS. config.assets.compress = true - # Don't fallback to assets pipeline if a precompiled asset is missed + # Don't fallback to assets pipeline if a precompiled asset is missed. config.assets.compile = false - # Generate digests for assets URLs + # Generate digests for assets URLs. config.assets.digest = true - # Defaults to Rails.root.join("public/assets") + # Defaults to Rails.root.join("public/assets"). # config.assets.manifest = YOUR_PATH <%- end -%> - # Specifies the header that your server uses for sending files + # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true - # See everything in the log (default is :info) + # See everything in the log (default is :info). # config.log_level = :debug - # Prepend all log lines with the following tags + # Prepend all log lines with the following tags. # config.log_tags = [ :subdomain, :uuid ] - # Use a different logger for distributed setups + # Use a different logger for distributed setups. # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) - # Use a different cache store in production + # Use a different cache store in production. # config.cache_store = :mem_cache_store - # Enable serving of images, stylesheets, and JavaScripts from an asset server + # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.action_controller.asset_host = "http://assets.example.com" <%- unless options.skip_sprockets? -%> - # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) + # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added). # config.assets.precompile += %w( search.js ) <%- end -%> - # Disable delivery errors, bad email addresses will be ignored + # Disable delivery errors, bad email addresses will be ignored. # config.action_mailer.raise_delivery_errors = false - # Enable threaded mode + # Enable threaded mode. # config.threadsafe! # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation can not be found) + # the I18n.default_locale when a translation can not be found). config.i18n.fallbacks = true - # Send deprecation notices to registered listeners + # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify <%- unless options.skip_active_record? -%> # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL) + # with SQLite, MySQL, and PostgreSQL). # config.active_record.auto_explain_threshold_in_seconds = 0.5 <%- end -%> end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index 50656ac637..b725dd19f6 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -1,5 +1,5 @@ <%= app_const %>.configure do - # Settings specified here will take precedence over those in config/application.rb + # Settings specified here will take precedence over those in config/application.rb. # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that @@ -7,19 +7,19 @@ # and recreated between test runs. Don't rely on the data there! config.cache_classes = true - # Configure static asset server for tests with Cache-Control for performance + # Configure static asset server for tests with Cache-Control for performance. config.serve_static_assets = true config.static_cache_control = "public, max-age=3600" - # Show full error reports and disable caching + # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false - # Raise exceptions instead of rendering exception templates + # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false - # Disable request forgery protection in test environment - config.action_controller.allow_forgery_protection = false + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the @@ -27,10 +27,10 @@ config.action_mailer.delivery_method = :test <%- unless options.skip_active_record? -%> - # Raise exception on mass assignment protection for Active Record models + # Raise exception on mass assignment protection for Active Record models. config.active_record.mass_assignment_sanitizer = :strict <%- end -%> - # Print deprecation notices to the stderr + # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr end diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index 68fbd58061..4e08e5dae1 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -105,14 +105,14 @@ class MigrationGeneratorTest < Rails::Generators::TestCase def test_add_migration_with_attributes_index_declaration_and_attribute_options migration = "add_title_and_content_to_books" - run_generator [migration, "title:string{40}:index", "content:string{255}", "price:decimal{5,2}:index", "discount:decimal{3,2}:uniq"] + run_generator [migration, "title:string{40}:index", "content:string{255}", "price:decimal{1,2}:index", "discount:decimal{3.4}:uniq"] assert_migration "db/migrate/#{migration}.rb" do |content| assert_method :change, content do |up| assert_match(/add_column :books, :title, :string, limit: 40/, up) assert_match(/add_column :books, :content, :string, limit: 255/, up) - assert_match(/add_column :books, :price, :decimal, precision: 5, scale: 2/, up) - assert_match(/add_column :books, :discount, :decimal, precision: 3, scale: 2/, up) + assert_match(/add_column :books, :price, :decimal, precision: 1, scale: 2/, up) + assert_match(/add_column :books, :discount, :decimal, precision: 3, scale: 4/, up) end assert_match(/add_index :books, :title/, content) assert_match(/add_index :books, :price/, content) |