Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge commit 'mainstream/master'

Conflicts:

	railties/doc/guides/html/activerecord_validations_callbacks.html
	railties/doc/guides/source/activerecord_validations_callbacks.txt
  • Loading branch information...
commit 9b98362cda013f85406ae8b26922264d382794dd 2 parents ea12819 + 69387ce
@lifo lifo authored
Showing with 600 additions and 482 deletions.
  1. +3 −3 actionmailer/test/mail_service_test.rb
  2. +1 −2  actionpack/lib/action_controller.rb
  3. +8 −8 actionpack/lib/action_controller/benchmarking.rb
  4. +2 −2 actionpack/lib/action_controller/failsafe.rb
  5. +97 −58 actionpack/lib/action_controller/integration.rb
  6. +1 −8 actionpack/lib/action_controller/rack_process.rb
  7. +6 −6 actionpack/lib/action_controller/request_profiler.rb
  8. +3 −3 actionpack/lib/action_view/helpers/benchmark_helper.rb
  9. +1 −1  actionpack/lib/action_view/helpers/tag_helper.rb
  10. +1 −0  actionpack/test/controller/caching_test.rb
  11. +13 −46 actionpack/test/controller/integration_test.rb
  12. +4 −4 actionpack/test/controller/rack_test.rb
  13. +2 −0  activerecord/CHANGELOG
  14. +2 −0  activerecord/lib/active_record.rb
  15. +2 −2 activerecord/lib/active_record/base.rb
  16. +5 −5 activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
  17. +2 −2 activerecord/lib/active_record/dirty.rb
  18. +16 −7 activerecord/lib/active_record/serializers/xml_serializer.rb
  19. +2 −2 activerecord/lib/active_record/transactions.rb
  20. +1 −1  activerecord/lib/active_record/validations.rb
  21. +3 −2 activerecord/test/cases/calculations_test.rb
  22. +12 −0 activerecord/test/cases/dirty_test.rb
  23. +4 −4 activerecord/test/cases/validations_i18n_test.rb
  24. +7 −0 activerecord/test/cases/xml_serialization_test.rb
  25. +2 −2 activeresource/lib/active_resource/connection.rb
  26. +8 −0 activesupport/CHANGELOG
  27. +4 −1 activesupport/lib/active_support/buffered_logger.rb
  28. +2 −2 activesupport/lib/active_support/cache.rb
  29. +10 −9 activesupport/lib/active_support/core_ext/array/conversions.rb
  30. +13 −6 activesupport/lib/active_support/core_ext/benchmark.rb
  31. +13 −7 activesupport/lib/active_support/core_ext/hash/conversions.rb
  32. +3 −3 activesupport/lib/active_support/dependencies.rb
  33. +1 −1  activesupport/lib/active_support/deprecation.rb
  34. +3 −2 activesupport/lib/active_support/locale/en.yml
  35. +8 −0 activesupport/lib/active_support/multibyte/chars.rb
  36. +23 −36 activesupport/lib/active_support/ordered_hash.rb
  37. +2 −2 activesupport/lib/active_support/vendor.rb
  38. +240 −221 activesupport/lib/active_support/vendor/{memcache-client-1.5.1 → memcache-client-1.5.0.5}/memcache.rb
  39. +6 −0 activesupport/test/buffered_logger_test.rb
  40. +10 −9 activesupport/test/core_ext/array_ext_test.rb
  41. +7 −0 activesupport/test/core_ext/hash_ext_test.rb
  42. +15 −6 activesupport/test/i18n_test.rb
  43. +4 −0 activesupport/test/multibyte_chars_test.rb
  44. +10 −0 activesupport/test/ordered_hash_test.rb
  45. +2 −0  railties/CHANGELOG
  46. +1 −1  railties/lib/commands/about.rb
  47. +3 −1 railties/lib/commands/runner.rb
  48. +1 −1  railties/lib/rails_generator/generators/applications/app/app_generator.rb
  49. +5 −5 railties/lib/rails_generator/generators/applications/app/template_runner.rb
  50. +6 −0 railties/lib/tasks/framework.rake
  51. +0 −1  railties/test/gem_dependency_test.rb
View
6 actionmailer/test/mail_service_test.rb
@@ -967,13 +967,13 @@ def test_starttls_is_disabled_if_not_supported
class InheritableTemplateRootTest < Test::Unit::TestCase
def test_attr
expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
- assert_equal expected, FunkyPathMailer.template_root
+ assert_equal expected, FunkyPathMailer.template_root.to_s
sub = Class.new(FunkyPathMailer)
sub.template_root = 'test/path'
- assert_equal 'test/path', sub.template_root
- assert_equal expected, FunkyPathMailer.template_root
+ assert_equal 'test/path', sub.template_root.to_s
+ assert_equal expected, FunkyPathMailer.template_root.to_s
end
end
View
3  actionpack/lib/action_controller.rb
@@ -38,7 +38,7 @@ module ActionController
# TODO: Review explicit to see if they will automatically be handled by
# the initilizer if they are really needed.
def self.load_all!
- [Base, CgiRequest, CgiResponse, RackRequest, RackRequest, Http::Headers, UrlRewriter, UrlWriter]
+ [Base, CGIHandler, CgiRequest, RackRequest, RackRequest, Http::Headers, UrlRewriter, UrlWriter]
end
autoload :AbstractRequest, 'action_controller/request'
@@ -91,7 +91,6 @@ module Http
# DEPRECATE: Remove CGI support
autoload :CgiRequest, 'action_controller/cgi_process'
- autoload :CgiResponse, 'action_controller/cgi_process'
autoload :CGIHandler, 'action_controller/cgi_process'
end
View
16 actionpack/lib/action_controller/benchmarking.rb
@@ -23,8 +23,8 @@ module ClassMethods
def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
if logger && logger.level == log_level
result = nil
- seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
- logger.add(log_level, "#{title} (#{('%.1f' % (seconds * 1000))}ms)")
+ ms = Benchmark.ms { result = use_silence ? silence { yield } : yield }
+ logger.add(log_level, "#{title} (#{('%.1f' % ms)}ms)")
result
else
yield
@@ -48,7 +48,7 @@ def render_with_benchmark(options = nil, extra_options = {}, &block)
end
render_output = nil
- @view_runtime = Benchmark::realtime { render_output = render_without_benchmark(options, extra_options, &block) }
+ @view_runtime = Benchmark.ms { render_output = render_without_benchmark(options, extra_options, &block) }
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
@db_rt_before_render = db_runtime
@@ -65,11 +65,11 @@ def render_with_benchmark(options = nil, extra_options = {}, &block)
private
def perform_action_with_benchmark
if logger
- seconds = [ Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001 ].max
+ ms = [Benchmark.ms { perform_action_without_benchmark }, 0.01].max
logging_view = defined?(@view_runtime)
logging_active_record = Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
- log_message = "Completed in #{sprintf("%.0f", seconds * 1000)}ms"
+ log_message = 'Completed in %.0fms' % ms
if logging_view || logging_active_record
log_message << " ("
@@ -87,21 +87,21 @@ def perform_action_with_benchmark
log_message << " [#{complete_request_uri rescue "unknown"}]"
logger.info(log_message)
- response.headers["X-Runtime"] = "#{sprintf("%.0f", seconds * 1000)}ms"
+ response.headers["X-Runtime"] = "%.0f" % ms
else
perform_action_without_benchmark
end
end
def view_runtime
- "View: %.0f" % (@view_runtime * 1000)
+ "View: %.0f" % @view_runtime
end
def active_record_runtime
db_runtime = ActiveRecord::Base.connection.reset_runtime
db_runtime += @db_rt_before_render if @db_rt_before_render
db_runtime += @db_rt_after_render if @db_rt_after_render
- "DB: %.0f" % (db_runtime * 1000)
+ "DB: %.0f" % db_runtime
end
end
end
View
4 actionpack/lib/action_controller/failsafe.rb
@@ -42,8 +42,8 @@ def log_failsafe_exception(exception)
end
def failsafe_logger
- if defined?(::RAILS_DEFAULT_LOGGER) && !::RAILS_DEFAULT_LOGGER.nil?
- ::RAILS_DEFAULT_LOGGER
+ if defined? Rails && Rails.logger
+ Rails.logger
else
Logger.new($stderr)
end
View
155 actionpack/lib/action_controller/integration.rb
@@ -9,13 +9,17 @@ module Integration #:nodoc:
# multiple sessions and run them side-by-side, you can also mimic (to some
# limited extent) multiple simultaneous users interacting with your system.
#
- # Typically, you will instantiate a new session using IntegrationTest#open_session,
- # rather than instantiating Integration::Session directly.
+ # Typically, you will instantiate a new session using
+ # IntegrationTest#open_session, rather than instantiating
+ # Integration::Session directly.
class Session
include Test::Unit::Assertions
include ActionController::TestCase::Assertions
include ActionController::TestProcess
+ # Rack application to use
+ attr_accessor :application
+
# The integer HTTP status code of the last request.
attr_reader :status
@@ -57,7 +61,8 @@ class MultiPartNeededException < Exception
end
# Create and initialize a new Session instance.
- def initialize
+ def initialize(app)
+ @application = app
reset!
end
@@ -76,11 +81,13 @@ def reset!
self.host = "www.example.com"
self.remote_addr = "127.0.0.1"
- self.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
+ self.accept = "text/xml,application/xml,application/xhtml+xml," +
+ "text/html;q=0.9,text/plain;q=0.8,image/png," +
+ "*/*;q=0.5"
unless defined? @named_routes_configured
# install the named routes in this session instance.
- klass = class<<self; self; end
+ klass = class << self; self; end
Routing::Routes.install_helpers(klass)
# the helpers are made protected by default--we make them public for
@@ -94,7 +101,7 @@ def reset!
#
# session.https!
# session.https!(false)
- def https!(flag=true)
+ def https!(flag = true)
@https = flag
end
@@ -119,7 +126,7 @@ def host!(name)
# performed on the location header.
def follow_redirect!
raise "not a redirect! #{@status} #{@status_message}" unless redirect?
- get(interpret_uri(headers['location'].first))
+ get(interpret_uri(headers['location']))
status
end
@@ -164,17 +171,21 @@ def redirect?
# Performs a GET request with the given parameters.
#
- # - +path+: The URI (as a String) on which you want to perform a GET request.
- # - +parameters+: The HTTP parameters that you want to pass. This may be +nil+,
+ # - +path+: The URI (as a String) on which you want to perform a GET
+ # request.
+ # - +parameters+: The HTTP parameters that you want to pass. This may
+ # be +nil+,
# a Hash, or a String that is appropriately encoded
- # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
+ # (<tt>application/x-www-form-urlencoded</tt> or
+ # <tt>multipart/form-data</tt>).
# - +headers+: Additional HTTP headers to pass, as a Hash. The keys will
# automatically be upcased, with the prefix 'HTTP_' added if needed.
#
- # This method returns an AbstractResponse object, which one can use to inspect
- # the details of the response. Furthermore, if this method was called from an
- # ActionController::IntegrationTest object, then that object's <tt>@response</tt>
- # instance variable will point to the same response object.
+ # This method returns an AbstractResponse object, which one can use to
+ # inspect the details of the response. Furthermore, if this method was
+ # called from an ActionController::IntegrationTest object, then that
+ # object's <tt>@response</tt> instance variable will point to the same
+ # response object.
#
# You can also perform POST, PUT, DELETE, and HEAD requests with +post+,
# +put+, +delete+, and +head+.
@@ -182,22 +193,26 @@ def get(path, parameters = nil, headers = nil)
process :get, path, parameters, headers
end
- # Performs a POST request with the given parameters. See get() for more details.
+ # Performs a POST request with the given parameters. See get() for more
+ # details.
def post(path, parameters = nil, headers = nil)
process :post, path, parameters, headers
end
- # Performs a PUT request with the given parameters. See get() for more details.
+ # Performs a PUT request with the given parameters. See get() for more
+ # details.
def put(path, parameters = nil, headers = nil)
process :put, path, parameters, headers
end
- # Performs a DELETE request with the given parameters. See get() for more details.
+ # Performs a DELETE request with the given parameters. See get() for
+ # more details.
def delete(path, parameters = nil, headers = nil)
process :delete, path, parameters, headers
end
- # Performs a HEAD request with the given parameters. See get() for more details.
+ # Performs a HEAD request with the given parameters. See get() for more
+ # details.
def head(path, parameters = nil, headers = nil)
process :head, path, parameters, headers
end
@@ -212,7 +227,8 @@ def head(path, parameters = nil, headers = nil)
def xml_http_request(request_method, path, parameters = nil, headers = nil)
headers ||= {}
headers['X-Requested-With'] = 'XMLHttpRequest'
- headers['Accept'] ||= 'text/javascript, text/html, application/xml, text/xml, */*'
+ headers['Accept'] ||= 'text/javascript, text/html, application/xml, ' +
+ 'text/xml, */*'
process(request_method, path, parameters, headers)
end
@@ -221,7 +237,9 @@ def xml_http_request(request_method, path, parameters = nil, headers = nil)
# Returns the URL for the given options, according to the rules specified
# in the application's routes.
def url_for(options)
- controller ? controller.url_for(options) : generic_url_rewriter.rewrite(options)
+ controller ?
+ controller.url_for(options) :
+ generic_url_rewriter.rewrite(options)
end
private
@@ -247,17 +265,33 @@ def process(method, path, parameters = nil, headers = nil)
data = nil
end
+ env["QUERY_STRING"] ||= ""
+
+ data = data.is_a?(IO) ? data : StringIO.new(data || '')
+
env.update(
- "REQUEST_METHOD" => method.to_s.upcase,
+ "REQUEST_METHOD" => method.to_s.upcase,
+ "SERVER_NAME" => host,
+ "SERVER_PORT" => (https? ? "443" : "80"),
+ "HTTPS" => https? ? "on" : "off",
+ "rack.url_scheme" => https? ? "https" : "http",
+ "SCRIPT_NAME" => "",
+
"REQUEST_URI" => path,
"HTTP_HOST" => host,
"REMOTE_ADDR" => remote_addr,
- "SERVER_PORT" => (https? ? "443" : "80"),
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
"CONTENT_LENGTH" => data ? data.length.to_s : nil,
"HTTP_COOKIE" => encode_cookies,
- "HTTPS" => https? ? "on" : "off",
"HTTP_ACCEPT" => accept,
+
+ "rack.version" => [0,1],
+ "rack.input" => data,
+ "rack.errors" => StringIO.new,
+ "rack.multithread" => true,
+ "rack.multiprocess" => true,
+ "rack.run_once" => false,
+
"action_controller.test" => true
)
@@ -273,48 +307,43 @@ def process(method, path, parameters = nil, headers = nil)
ActionController::Base.clear_last_instantiation!
- env['rack.input'] = data.is_a?(IO) ? data : StringIO.new(data || '')
- @status, @headers, result_body = ActionController::Dispatcher.new.call(env)
+ app = Rack::Lint.new(@application)
+
+ status, headers, body = app.call(env)
@request_count += 1
- @controller = ActionController::Base.last_instantiation
- @request = @controller.request
- @response = @controller.response
+ if @controller = ActionController::Base.last_instantiation
+ @request = @controller.request
+ @response = @controller.response
- # Decorate the response with the standard behavior of the TestResponse
- # so that things like assert_response can be used in integration
- # tests.
- @response.extend(TestResponseBehavior)
+ # Decorate the response with the standard behavior of the
+ # TestResponse so that things like assert_response can be
+ # used in integration tests.
+ @response.extend(TestResponseBehavior)
+ end
@html_document = nil
- # Inject status back in for backwords compatibility with CGI
- @headers['Status'] = @status
-
- @status, @status_message = @status.split(/ /)
- @status = @status.to_i
+ @status = status.to_i
+ @status_message = StatusCodes::STATUS_CODES[@status]
- cgi_headers = Hash.new { |h,k| h[k] = [] }
- @headers.each do |key, value|
- cgi_headers[key.downcase] << value
- end
- cgi_headers['set-cookie'] = cgi_headers['set-cookie'].first
- @headers = cgi_headers
+ @headers = Rack::Utils::HeaderHash.new(headers)
- @response.headers['cookie'] ||= []
- (@headers['set-cookie'] || []).each do |cookie|
+ (@headers['Set-Cookie'] || []).each do |cookie|
name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
@cookies[name] = value
-
- # Fake CGI cookie header
- # DEPRECATE: Use response.headers["Set-Cookie"] instead
- @response.headers['cookie'] << CGI::Cookie::new("name" => name, "value" => value)
end
- return status
+ @body = ""
+ body.each { |part| @body << part }
+
+ return @status
rescue MultiPartNeededException
boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
- status = process(method, path, multipart_body(parameters, boundary), (headers || {}).merge({"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"}))
+ status = process(method, path,
+ multipart_body(parameters, boundary),
+ (headers || {}).merge(
+ {"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"}))
return status
end
@@ -336,7 +365,7 @@ def generic_url_rewriter
"SERVER_PORT" => https? ? "443" : "80",
"HTTPS" => https? ? "on" : "off"
}
- ActionController::UrlRewriter.new(ActionController::RackRequest.new(env), {})
+ UrlRewriter.new(RackRequest.new(env), {})
end
def name_with_prefix(prefix, name)
@@ -350,9 +379,13 @@ def requestify(parameters, prefix=nil)
raise MultiPartNeededException
elsif Hash === parameters
return nil if parameters.empty?
- parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
+ parameters.map { |k,v|
+ requestify(v, name_with_prefix(prefix, k))
+ }.join("&")
elsif Array === parameters
- parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&")
+ parameters.map { |v|
+ requestify(v, name_with_prefix(prefix, ""))
+ }.join("&")
elsif prefix.nil?
parameters
else
@@ -459,7 +492,8 @@ def reset!
# can use this method to open multiple sessions that ought to be tested
# simultaneously.
def open_session
- session = Integration::Session.new
+ application = ActionController::Dispatcher.new
+ session = Integration::Session.new(application)
# delegate the fixture accessors back to the test instance
extras = Module.new { attr_accessor :delegate, :test_result }
@@ -467,12 +501,16 @@ def open_session
self.class.fixture_table_names.each do |table_name|
name = table_name.tr(".", "_")
next unless respond_to?(name)
- extras.__send__(:define_method, name) { |*args| delegate.send(name, *args) }
+ extras.__send__(:define_method, name) { |*args|
+ delegate.send(name, *args)
+ }
end
end
# delegate add_assertion to the test case
- extras.__send__(:define_method, :add_assertion) { test_result.add_assertion }
+ extras.__send__(:define_method, :add_assertion) {
+ test_result.add_assertion
+ }
session.extend(extras)
session.delegate = self
session.test_result = @_result
@@ -600,7 +638,8 @@ def run(*args) #:nodoc:
# would potentially have to set their values for both Test::Unit::TestCase
# ActionController::IntegrationTest, since by the time the value is set on
# TestCase, IntegrationTest has already been defined and cannot inherit
- # changes to those variables. So, we make those two attributes copy-on-write.
+ # changes to those variables. So, we make those two attributes
+ # copy-on-write.
class << self
def use_transactional_fixtures=(flag) #:nodoc:
View
9 actionpack/lib/action_controller/rack_process.rb
@@ -55,14 +55,7 @@ def key?(key)
end
def cookies
- return {} unless @env["HTTP_COOKIE"]
-
- unless @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
- @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
- @env["rack.request.cookie_hash"] = CGI::Cookie::parse(@env["rack.request.cookie_string"])
- end
-
- @env["rack.request.cookie_hash"]
+ Rack::Request.new(@env).cookies
end
def server_port
View
12 actionpack/lib/action_controller/request_profiler.rb
@@ -20,7 +20,7 @@ def benchmark(n, profiling = false)
@quiet = true
print ' '
- result = Benchmark.realtime do
+ ms = Benchmark.ms do
n.times do |i|
run(profiling)
print_progress(i)
@@ -28,7 +28,7 @@ def benchmark(n, profiling = false)
end
puts
- result
+ ms
ensure
@quiet = false
end
@@ -88,7 +88,7 @@ def run
puts 'Warming up once'
elapsed = warmup(sandbox)
- puts '%.2f sec, %d requests, %d req/sec' % [elapsed, sandbox.request_count, sandbox.request_count / elapsed]
+ puts '%.0f ms, %d requests, %d req/sec' % [elapsed, sandbox.request_count, 1000 * sandbox.request_count / elapsed]
puts "\n#{options[:benchmark] ? 'Benchmarking' : 'Profiling'} #{options[:n]}x"
options[:benchmark] ? benchmark(sandbox) : profile(sandbox)
@@ -106,13 +106,13 @@ def profile(sandbox)
def benchmark(sandbox, profiling = false)
sandbox.request_count = 0
- elapsed = sandbox.benchmark(options[:n], profiling).to_f
+ elapsed = sandbox.benchmark(options[:n], profiling)
count = sandbox.request_count.to_i
- puts '%.2f sec, %d requests, %d req/sec' % [elapsed, count, count / elapsed]
+ puts '%.0f ms, %d requests, %d req/sec' % [elapsed, count, 1000 * count / elapsed]
end
def warmup(sandbox)
- Benchmark.realtime { sandbox.run(false) }
+ Benchmark.ms { sandbox.run(false) }
end
def default_options
View
6 actionpack/lib/action_view/helpers/benchmark_helper.rb
@@ -22,12 +22,12 @@ module BenchmarkHelper
# (:debug, :info, :warn, :error); the default value is :info.
def benchmark(message = "Benchmarking", level = :info)
if controller.logger
- seconds = Benchmark.realtime { yield }
- controller.logger.send(level, "#{message} (#{'%.1f' % (seconds * 1000)}ms)")
+ ms = Benchmark.ms { yield }
+ controller.logger.send(level, '%s (%.1fms)' % [message, ms])
else
yield
end
end
end
end
-end
+end
View
2  actionpack/lib/action_view/helpers/tag_helper.rb
@@ -1,4 +1,4 @@
-require 'erb'
+require 'action_view/erb/util'
require 'set'
module ActionView
View
1  actionpack/test/controller/caching_test.rb
@@ -48,6 +48,7 @@ def setup
ActionController::Routing::Routes.draw do |map|
map.main '', :controller => 'posts'
+ map.formatted_posts 'posts.:format', :controller => 'posts'
map.resources :posts
map.connect ':controller/:action/:id'
end
View
59 actionpack/test/controller/integration_test.rb
@@ -2,19 +2,13 @@
uses_mocha 'integration' do
-module IntegrationSessionStubbing
- def stub_integration_session(session)
- session.stubs(:process)
- session.stubs(:generic_url_rewriter)
- end
-end
-
class SessionTest < Test::Unit::TestCase
- include IntegrationSessionStubbing
+ StubApp = lambda { |env|
+ [200, {"Content-Type" => "text/html"}, "Hello, World!"]
+ }
def setup
- @session = ActionController::Integration::Session.new
- stub_integration_session(@session)
+ @session = ActionController::Integration::Session.new(StubApp)
end
def test_https_bang_works_and_sets_truth_by_default
@@ -36,14 +30,6 @@ def test_follow_redirect_raises_when_no_redirect
assert_raise(RuntimeError) { @session.follow_redirect! }
end
- def test_follow_redirect_calls_get_and_returns_status
- @session.stubs(:redirect?).returns(true)
- @session.stubs(:headers).returns({"location" => ["www.google.com"]})
- @session.stubs(:status).returns(200)
- @session.expects(:get)
- assert_equal 200, @session.follow_redirect!
- end
-
def test_request_via_redirect_uses_given_method
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"}
@session.expects(:put).with(path, args, headers)
@@ -207,13 +193,10 @@ def test_xml_http_request_override_accept
end
class IntegrationTestTest < Test::Unit::TestCase
- include IntegrationSessionStubbing
-
def setup
@test = ::ActionController::IntegrationTest.new(:default_test)
@test.class.stubs(:fixture_table_names).returns([])
@session = @test.open_session
- stub_integration_session(@session)
end
def test_opens_new_session
@@ -231,15 +214,15 @@ def test_opens_new_session
# Tests that integration tests don't call Controller test methods for processing.
# Integration tests have their own setup and teardown.
class IntegrationTestUsesCorrectClass < ActionController::IntegrationTest
- include IntegrationSessionStubbing
-
def self.fixture_table_names
[]
end
def test_integration_methods_called
reset!
- stub_integration_session(@integration_session)
+ @integration_session.stubs(:generic_url_rewriter)
+ @integration_session.stubs(:process)
+
%w( get post head put delete ).each do |verb|
assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') }
end
@@ -281,13 +264,9 @@ def test_get
get '/get'
assert_equal 200, status
assert_equal "OK", status_message
- assert_equal "200 OK", response.headers["Status"]
- assert_equal ["200 OK"], headers["status"]
assert_response 200
assert_response :success
assert_response :ok
- assert_equal [], response.headers["cookie"]
- assert_equal [], headers["cookie"]
assert_equal({}, cookies)
assert_equal "OK", response.body
assert_kind_of HTML::Document, html_document
@@ -300,13 +279,9 @@ def test_post
post '/post'
assert_equal 201, status
assert_equal "Created", status_message
- assert_equal "201 Created", response.headers["Status"]
- assert_equal ["201 Created"], headers["status"]
assert_response 201
assert_response :success
assert_response :created
- assert_equal [], response.headers["cookie"]
- assert_equal [], headers["cookie"]
assert_equal({}, cookies)
assert_equal "Created", response.body
assert_kind_of HTML::Document, html_document
@@ -321,17 +296,9 @@ def test_cookie_monster
get '/cookie_monster'
assert_equal 410, status
assert_equal "Gone", status_message
- assert_equal "410 Gone", response.headers["Status"]
- assert_equal ["410 Gone"], headers["status"]
assert_response 410
assert_response :gone
- assert_equal ["cookie_1=; path=/", "cookie_3=chocolate; path=/"], response.headers["Set-Cookie"]
- assert_equal ["cookie_1=; path=/", "cookie_3=chocolate; path=/"], headers['set-cookie']
- assert_equal [
- CGI::Cookie::new("name" => "cookie_1", "value" => ""),
- CGI::Cookie::new("name" => "cookie_3", "value" => "chocolate")
- ], response.headers["cookie"]
- assert_equal [], headers["cookie"]
+ assert_equal ["cookie_1=; path=/", "cookie_3=chocolate; path=/"], headers["Set-Cookie"]
assert_equal({"cookie_1"=>"", "cookie_2"=>"oatmeal", "cookie_3"=>"chocolate"}, cookies)
assert_equal "Gone", response.body
end
@@ -342,14 +309,16 @@ def test_redirect
get '/redirect'
assert_equal 302, status
assert_equal "Found", status_message
- assert_equal "302 Found", response.headers["Status"]
- assert_equal ["302 Found"], headers["status"]
assert_response 302
assert_response :redirect
assert_response :found
assert_equal "<html><body>You are being <a href=\"http://www.example.com/get\">redirected</a>.</body></html>", response.body
assert_kind_of HTML::Document, html_document
assert_equal 1, request_count
+
+ follow_redirect!
+ assert_response :success
+ assert_equal "/get", path
end
end
@@ -358,8 +327,6 @@ def test_xml_http_request_get
xhr :get, '/get'
assert_equal 200, status
assert_equal "OK", status_message
- assert_equal "200 OK", response.headers["Status"]
- assert_equal ["200 OK"], headers["status"]
assert_response 200
assert_response :success
assert_response :ok
@@ -372,7 +339,7 @@ def test_get_with_query_string
get '/get_with_params?foo=bar'
assert_equal '/get_with_params?foo=bar', request.env["REQUEST_URI"]
assert_equal '/get_with_params?foo=bar', request.request_uri
- assert_equal nil, request.env["QUERY_STRING"]
+ assert_equal "", request.env["QUERY_STRING"]
assert_equal 'foo=bar', request.query_string
assert_equal 'bar', request.parameters['foo']
View
8 actionpack/test/controller/rack_test.rb
@@ -153,12 +153,12 @@ def test_cgi_environment_variables
def test_cookie_syntax_resilience
cookies = @request.cookies
- assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"], cookies.inspect
- assert_equal ["yes"], cookies["is_admin"], cookies.inspect
+ assert_equal "c84ace84796670c052c6ceb2451fb0f2", cookies["_session_id"], cookies.inspect
+ assert_equal "yes", cookies["is_admin"], cookies.inspect
alt_cookies = @alt_cookie_fmt_request.cookies
- assert_equal ["c84ace847,96670c052c6ceb2451fb0f2"], alt_cookies["_session_id"], alt_cookies.inspect
- assert_equal ["yes"], alt_cookies["is_admin"], alt_cookies.inspect
+ #assert_equal "c84ace847,96670c052c6ceb2451fb0f2", alt_cookies["_session_id"], alt_cookies.inspect
+ assert_equal "yes", alt_cookies["is_admin"], alt_cookies.inspect
end
end
View
2  activerecord/CHANGELOG
@@ -1,5 +1,7 @@
*2.3.0/3.0*
+* I18n the word separator for error messages. Introduces the activerecord.errors.format.separator translation key. #1294 [Akira Matsuda]
+
* Add :having as a key to find and the relevant associations. [miloops]
* Added default_scope to Base #1381 [Paweł Kondzior]. Example:
View
2  activerecord/lib/active_record.rb
@@ -37,6 +37,8 @@ def self.load_all!
[Base, DynamicFinderMatch, ConnectionAdapters::AbstractAdapter]
end
+ autoload :VERSION, 'active_record/version'
+
autoload :ActiveRecordError, 'active_record/base'
autoload :ConnectionNotEstablished, 'active_record/base'
View
4 activerecord/lib/active_record/base.rb
@@ -1416,8 +1416,8 @@ def sanitize(object) #:nodoc:
def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
if logger && logger.level <= log_level
result = nil
- seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
- logger.add(log_level, "#{title} (#{'%.1f' % (seconds * 1000)}ms)")
+ ms = Benchmark.ms { result = use_silence ? silence { yield } : yield }
+ logger.add(log_level, '%s (%.1fms)' % [title, ms])
result
else
yield
View
10 activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -160,9 +160,9 @@ def decrement_open_transactions
@open_transactions -= 1
end
- def log_info(sql, name, seconds)
+ def log_info(sql, name, ms)
if @logger && @logger.debug?
- name = "#{name.nil? ? "SQL" : name} (#{sprintf("%.1f", seconds * 1000)}ms)"
+ name = '%s (%.1fms)' % [name || 'SQL', ms]
@logger.debug(format_log_entry(name, sql.squeeze(' ')))
end
end
@@ -171,9 +171,9 @@ def log_info(sql, name, seconds)
def log(sql, name)
if block_given?
result = nil
- seconds = Benchmark.realtime { result = yield }
- @runtime += seconds
- log_info(sql, name, seconds)
+ ms = Benchmark.ms { result = yield }
+ @runtime += ms
+ log_info(sql, name, ms)
result
else
log_info(sql, name, 0)
View
4 activerecord/lib/active_record/dirty.rb
@@ -151,12 +151,12 @@ def update_with_dirty
def field_changed?(attr, old, value)
if column = column_for_attribute(attr)
- if column.type == :integer && column.null && (old.nil? || old == 0)
+ if column.type == :integer && column.null && (old.nil? || old == 0) && value.blank?
# For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
# Hence we don't record it as a change if the value changes from nil to ''.
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
# be typecast back to 0 (''.to_i => 0)
- value = nil if value.blank?
+ value = nil
else
value = column.type_cast(value)
end
View
23 activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -23,11 +23,12 @@ module Serialization
# </topic>
#
# This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>,
- # <tt>:skip_instruct</tt>, <tt>:skip_types</tt> and <tt>:dasherize</tt>.
+ # <tt>:skip_instruct</tt>, <tt>:skip_types</tt>, <tt>:dasherize</tt> and <tt>:camelize</tt> .
# The <tt>:only</tt> and <tt>:except</tt> options are the same as for the
# +attributes+ method. The default is to dasherize all column names, but you
- # can disable this setting <tt>:dasherize</tt> to +false+. To not have the
- # column type included in the XML output set <tt>:skip_types</tt> to +true+.
+ # can disable this setting <tt>:dasherize</tt> to +false+. Setting <tt>:camelize</tt>
+ # to +true+ will camelize all column names - this also overrides <tt>:dasherize</tt>.
+ # To not have the column type included in the XML output set <tt>:skip_types</tt> to +true+.
#
# For instance:
#
@@ -178,13 +179,22 @@ def builder
def root
root = (options[:root] || @record.class.to_s.underscore).to_s
- dasherize? ? root.dasherize : root
+ reformat_name(root)
end
def dasherize?
!options.has_key?(:dasherize) || options[:dasherize]
end
+ def camelize?
+ options.has_key?(:camelize) && options[:camelize]
+ end
+
+ def reformat_name(name)
+ name = name.camelize if camelize?
+ dasherize? ? name.dasherize : name
+ end
+
def serializable_attributes
serializable_attribute_names.collect { |name| Attribute.new(name, @record) }
end
@@ -212,7 +222,7 @@ def add_procs
def add_tag(attribute)
builder.tag!(
- dasherize? ? attribute.name.dasherize : attribute.name,
+ reformat_name(attribute.name),
attribute.value.to_s,
attribute.decorations(!options[:skip_types])
)
@@ -220,8 +230,7 @@ def add_tag(attribute)
def add_associations(association, records, opts)
if records.is_a?(Enumerable)
- tag = association.to_s
- tag = tag.dasherize if dasherize?
+ tag = reformat_name(association.to_s)
if records.empty?
builder.tag!(tag, :type => :array)
else
View
4 activerecord/lib/active_record/transactions.rb
@@ -147,7 +147,7 @@ def save_with_transactions(perform_validation = true) #:nodoc:
end
def save_with_transactions! #:nodoc:
- rollback_active_record_state! { transaction { save_without_transactions! } }
+ rollback_active_record_state! { self.class.transaction { save_without_transactions! } }
end
# Reset id and @new_record if the transaction rolls back.
@@ -175,7 +175,7 @@ def rollback_active_record_state!
# instance.
def with_transaction_returning_status(method, *args)
status = nil
- transaction do
+ self.class.transaction do
status = send(method, *args)
raise ActiveRecord::Rollback unless status
end
View
2  activerecord/lib/active_record/validations.rb
@@ -205,7 +205,7 @@ def full_messages(options = {})
else
#key = :"activerecord.att.#{@base.class.name.underscore.to_sym}.#{attr}"
attr_name = @base.class.human_attribute_name(attr)
- full_messages << attr_name + ' ' + message
+ full_messages << attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') + message
end
end
end
View
5 activerecord/test/cases/calculations_test.rb
@@ -171,8 +171,9 @@ def test_should_group_by_association_with_non_numeric_foreign_key
Account.expects(:columns).at_least_once.returns([column])
c = Account.count(:all, :group => :firm)
- assert_equal Firm, c.first.first.class
- assert_equal 1, c.first.last
+ first_key = c.keys.first
+ assert_equal Firm, first_key.class
+ assert_equal 1, c[first_key]
end
end
View
12 activerecord/test/cases/dirty_test.rb
@@ -68,6 +68,18 @@ def test_nullable_integer_not_marked_as_changed_if_new_value_is_blank
end
end
+ def test_nullable_integer_zero_to_string_zero_not_marked_as_changed
+ pirate = Pirate.new
+ pirate.parrot_id = 0
+ pirate.catchphrase = 'arrr'
+ assert pirate.save!
+
+ assert !pirate.changed?
+
+ pirate.parrot_id = '0'
+ assert !pirate.changed?
+ end
+
def test_zero_to_blank_marked_as_changed
pirate = Pirate.new
pirate.catchphrase = "Yarrrr, me hearties"
View
8 activerecord/test/cases/validations_i18n_test.rb
@@ -506,7 +506,7 @@ def test_validates_length_of_within_finds_global_default_translation
# validates_length_of :is w/o mocha
- def test_validates_length_of_within_finds_custom_model_key_translation
+ def test_validates_length_of_is_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
@@ -515,7 +515,7 @@ def test_validates_length_of_within_finds_custom_model_key_translation
assert_equal 'custom message', @topic.errors.on(:title)
end
- def test_validates_length_of_within_finds_global_default_translation
+ def test_validates_length_of_is_finds_global_default_translation
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
Topic.validates_length_of :title, :is => 5
@@ -525,7 +525,7 @@ def test_validates_length_of_within_finds_global_default_translation
# validates_uniqueness_of w/o mocha
- def test_validates_length_of_within_finds_custom_model_key_translation
+ def test_validates_length_of_is_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
@@ -534,7 +534,7 @@ def test_validates_length_of_within_finds_custom_model_key_translation
assert_equal 'custom message', @topic.errors.on(:title)
end
- def test_validates_length_of_within_finds_global_default_translation
+ def test_validates_length_of_is_finds_global_default_translation
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
Topic.validates_length_of :title, :is => 5
View
7 activerecord/test/cases/xml_serialization_test.rb
@@ -31,6 +31,13 @@ def test_should_allow_undasherized_tags
assert_match %r{<created_at}, @xml
end
+ def test_should_allow_camelized_tags
+ @xml = Contact.new.to_xml :root => 'xml_contact', :camelize => true
+ assert_match %r{^<XmlContact>}, @xml
+ assert_match %r{</XmlContact>$}, @xml
+ assert_match %r{<CreatedAt}, @xml
+ end
+
def test_should_include_yielded_additions
@xml = Contact.new.to_xml do |xml|
xml.creator "David"
View
4 activeresource/lib/active_resource/connection.rb
@@ -146,8 +146,8 @@ def head(path, headers = {})
def request(method, path, *arguments)
logger.info "#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}" if logger
result = nil
- time = Benchmark.realtime { result = http.send(method, path, *arguments) }
- logger.info "--> %d %s (%d %.2fs)" % [result.code, result.message, result.body ? result.body.length : 0, time] if logger
+ ms = Benchmark.ms { result = http.send(method, path, *arguments) }
+ logger.info "--> %d %s (%d %.0fms)" % [result.code, result.message, result.body ? result.body.length : 0, ms] if logger
handle_response(result)
rescue Timeout::Error => e
raise TimeoutError.new(e.message)
View
8 activesupport/CHANGELOG
@@ -1,5 +1,13 @@
*2.3.0 [Edge]*
+* Add Benchmark.ms convenience method to benchmark realtime in milliseconds. [Jeremy Kemper]
+
+* Updated included memcache-client to the 1.5.0.5 version which includes fixes from fiveruns and 37signals to deal with failover and timeouts #1535 [Joshua Sierles]
+
+* Multibyte: add multibyte-safe Chars#ord rather than falling back to String#ord. #1483 [Jason Cheow]
+
+* I18n support for Array#to_sentence. Introduces support.array.words_connector, .two_words_connector, and .last_word_connector translation keys. #1397 [Akira Matsuda]
+
* Added ActiveSupport::OrderedHash#each_key and ActiveSupport::OrderedHash#each_value #1410 [Christoffer Sawicki]
* Added ActiveSupport::MessageVerifier and MessageEncryptor to aid users who need to store signed and/or encrypted messages. [Koz]
View
5 activesupport/lib/active_support/buffered_logger.rb
@@ -96,9 +96,12 @@ def flush
@guard.synchronize do
unless buffer.empty?
old_buffer = buffer
- clear_buffer
@log.write(old_buffer.join)
end
+
+ # Important to do this even if buffer was empty or else @buffer will
+ # accumulate empty arrays for each request where nothing was logged.
+ clear_buffer
end
end
View
4 activesupport/lib/active_support/cache.rb
@@ -143,13 +143,13 @@ def fetch(key, options = {})
log("miss", key, options)
value = nil
- seconds = Benchmark.realtime { value = yield }
+ ms = Benchmark.ms { value = yield }
@logger_off = true
write(key, value, options)
@logger_off = false
- log("write (will save #{'%.2f' % (seconds * 1000)}ms)", key, nil)
+ log('write (will save %.2fms)' % ms, key, nil)
value
end
View
19 activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -3,15 +3,16 @@ module CoreExtensions #:nodoc:
module Array #:nodoc:
module Conversions
# Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options:
- # * <tt>:connector</tt> - The word used to join the last element in arrays with two or more elements (default: "and")
- # * <tt>:skip_last_comma</tt> - Set to true to return "a, b and c" instead of "a, b, and c".
+ # * <tt>:words_connector</tt> - The sign or word used to join the elements in arrays with two or more elements (default: ", ")
+ # * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ")
+ # * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ")
def to_sentence(options = {})
- options.assert_valid_keys(:connector, :skip_last_comma, :locale)
+ options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
- default = I18n.translate(:'support.array.sentence_connector', :locale => options[:locale])
- default_skip_last_comma = I18n.translate(:'support.array.skip_last_comma', :locale => options[:locale])
- options.reverse_merge! :connector => default, :skip_last_comma => default_skip_last_comma
- options[:connector] = "#{options[:connector]} " unless options[:connector].nil? || options[:connector].strip == ''
+ default_words_connector = I18n.translate(:'support.array.words_connector', :locale => options[:locale])
+ default_two_words_connector = I18n.translate(:'support.array.two_words_connector', :locale => options[:locale])
+ default_last_word_connector = I18n.translate(:'support.array.last_word_connector', :locale => options[:locale])
+ options.reverse_merge! :words_connector => default_words_connector, :two_words_connector => default_two_words_connector, :last_word_connector => default_last_word_connector
case length
when 0
@@ -19,9 +20,9 @@ def to_sentence(options = {})
when 1
self[0].to_s
when 2
- "#{self[0]} #{options[:connector]}#{self[1]}"
+ "#{self[0]}#{options[:two_words_connector]}#{self[1]}"
else
- "#{self[0...-1].join(', ')}#{options[:skip_last_comma] ? '' : ','} #{options[:connector]}#{self[-1]}"
+ "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
end
end
View
19 activesupport/lib/active_support/core_ext/benchmark.rb
@@ -1,12 +1,19 @@
require 'benchmark'
class << Benchmark
- remove_method :realtime
+ # Earlier Ruby had a slower implementation.
+ if RUBY_VERSION < '1.8.7'
+ remove_method :realtime
- def realtime
- r0 = Time.now
- yield
- r1 = Time.now
- r1.to_f - r0.to_f
+ def realtime
+ r0 = Time.now
+ yield
+ r1 = Time.now
+ r1.to_f - r0.to_f
+ end
+ end
+
+ def ms
+ 1000 * realtime { yield }
end
end
View
20 activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -94,8 +94,7 @@ def to_xml(options = {})
options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]),
:root => "hash" })
options[:builder].instruct! unless options.delete(:skip_instruct)
- dasherize = !options.has_key?(:dasherize) || options[:dasherize]
- root = dasherize ? options[:root].to_s.dasherize : options[:root].to_s
+ root = rename_key(options[:root].to_s, options)
options[:builder].__send__(:method_missing, root) do
each do |key, value|
@@ -122,7 +121,7 @@ def to_xml(options = {})
else
type_name = XML_TYPE_NAMES[value.class.name]
- key = dasherize ? key.to_s.dasherize : key.to_s
+ key = rename_key(key.to_s, options)
attributes = options[:skip_types] || value.nil? || type_name.nil? ? { } : { :type => type_name }
if value.nil?
@@ -142,9 +141,16 @@ def to_xml(options = {})
end
+ def rename_key(key, options = {})
+ camelize = options.has_key?(:camelize) && options[:camelize]
+ dasherize = !options.has_key?(:dasherize) || options[:dasherize]
+ key = key.camelize if camelize
+ dasherize ? key.dasherize : key
+ end
+
module ClassMethods
def from_xml(xml)
- typecast_xml_value(undasherize_keys(XmlMini.parse(xml)))
+ typecast_xml_value(unrename_keys(XmlMini.parse(xml)))
end
private
@@ -210,15 +216,15 @@ def typecast_xml_value(value)
end
end
- def undasherize_keys(params)
+ def unrename_keys(params)
case params.class.to_s
when "Hash"
params.inject({}) do |h,(k,v)|
- h[k.to_s.tr("-", "_")] = undasherize_keys(v)
+ h[k.to_s.underscore.tr("-", "_")] = unrename_keys(v)
h
end
when "Array"
- params.map { |v| undasherize_keys(v) }
+ params.map { |v| unrename_keys(v) }
else
params
end
View
6 activesupport/lib/active_support/dependencies.rb
@@ -559,9 +559,9 @@ class LoadingModule #:nodoc:
# Old style environment.rb referenced this method directly. Please note, it doesn't
# actually *do* anything any more.
def self.root(*args)
- if defined?(RAILS_DEFAULT_LOGGER)
- RAILS_DEFAULT_LOGGER.warn "Your environment.rb uses the old syntax, it may not continue to work in future releases."
- RAILS_DEFAULT_LOGGER.warn "For upgrade instructions please see: http://manuals.rubyonrails.com/read/book/19"
+ if defined? Rails && Rails.logger
+ Rails.logger.warn "Your environment.rb uses the old syntax, it may not continue to work in future releases."
+ Rails.logger.warn "For upgrade instructions please see: http://manuals.rubyonrails.com/read/book/19"
end
end
end
View
2  activesupport/lib/active_support/deprecation.rb
@@ -13,7 +13,7 @@ module Deprecation #:nodoc:
$stderr.puts callstack.join("\n ") if debug
},
'development' => Proc.new { |message, callstack|
- logger = defined?(::RAILS_DEFAULT_LOGGER) ? ::RAILS_DEFAULT_LOGGER : Logger.new($stderr)
+ logger = defined? Rails ? Rails.logger : Logger.new($stderr)
logger.warn message
logger.debug callstack.join("\n ") if debug
}
View
5 activesupport/lib/active_support/locale/en.yml
@@ -28,5 +28,6 @@ en:
# Used in array.to_sentence.
support:
array:
- sentence_connector: "and"
- skip_last_comma: false
+ words_connector: ", "
+ two_words_connector: " and "
+ last_word_connector: ", and "
View
8 activesupport/lib/active_support/multibyte/chars.rb
@@ -344,6 +344,14 @@ def slice(*args)
end
alias_method :[], :slice
+ # Converts first character in the string to Unicode value
+ #
+ # Example:
+ # 'こんにちは'.mb_chars.ord #=> 12371
+ def ord
+ self.class.u_unpack(@wrapped_string)[0]
+ end
+
# Convert characters in the string to uppercase.
#
# Example:
View
59 activesupport/lib/active_support/ordered_hash.rb
@@ -4,62 +4,49 @@ module ActiveSupport
if RUBY_VERSION >= '1.9'
OrderedHash = ::Hash
else
- class OrderedHash < Array #:nodoc:
- def []=(key, value)
- if pair = assoc(key)
- pair.pop
- pair << value
- else
- self << [key, value]
- end
- value
+ class OrderedHash < Hash #:nodoc:
+ def initialize(*args, &block)
+ super
+ @keys = []
end
- def [](key)
- pair = assoc(key)
- pair ? pair.last : nil
+ def []=(key, value)
+ if !has_key?(key)
+ @keys << key
+ end
+ super
end
def delete(key)
- pair = assoc(key)
- pair ? array_index = index(pair) : nil
- array_index ? delete_at(array_index).last : nil
+ array_index = has_key?(key) && index(key)
+ if array_index
+ @keys.delete_at(array_index)
+ end
+ super
end
def keys
- collect { |key, value| key }
+ @keys
end
def values
- collect { |key, value| value }
+ @keys.collect { |key| self[key] }
end
def to_hash
- returning({}) do |hash|
- each { |array| hash[array[0]] = array[1] }
- end
- end
-
- def has_key?(k)
- !assoc(k).nil?
- end
-
- alias_method :key?, :has_key?
- alias_method :include?, :has_key?
- alias_method :member?, :has_key?
-
- def has_value?(v)
- any? { |key, value| value == v }
+ Hash.new(self)
end
- alias_method :value?, :has_value?
-
def each_key
- each { |key, value| yield key }
+ @keys.each { |key| yield key }
end
def each_value
- each { |key, value| yield value }
+ @keys.each { |key| yield self[key]}
+ end
+
+ def each
+ keys.each {|key| yield [key, self[key]]}
end
end
end
View
4 activesupport/lib/active_support/vendor.rb
@@ -9,9 +9,9 @@
require 'builder'
begin
- gem 'memcache-client', '~> 1.5.1'
+ gem 'memcache-client', '~> 1.5.0.5'
rescue Gem::LoadError
- $:.unshift "#{File.dirname(__FILE__)}/vendor/memcache-client-1.5.1"
+ $:.unshift "#{File.dirname(__FILE__)}/vendor/memcache-client-1.5.0.5"
end
begin
View
461 .../vendor/memcache-client-1.5.1/memcache.rb → ...endor/memcache-client-1.5.0.5/memcache.rb
@@ -26,36 +26,13 @@
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+$TESTING = defined?($TESTING) && $TESTING
require 'socket'
require 'thread'
require 'timeout'
require 'rubygems'
-
-class String
-
- ##
- # Uses the ITU-T polynomial in the CRC32 algorithm.
-
- def crc32_ITU_T
- n = length
- r = 0xFFFFFFFF
-
- n.times do |i|
- r ^= self[i]
- 8.times do
- if (r & 1) != 0 then
- r = (r>>1) ^ 0xEDB88320
- else
- r >>= 1
- end
- end
- end
-
- r ^ 0xFFFFFFFF
- end
-
-end
+require 'zlib'
##
# A Ruby client library for memcached.
@@ -69,7 +46,7 @@ class MemCache
##
# The version of MemCache you are using.
- VERSION = '1.5.0'
+ VERSION = '1.5.0.5'
##
# Default options for the cache object.
@@ -78,6 +55,7 @@ class MemCache
:namespace => nil,
:readonly => false,
:multithread => false,
+ :failover => true
}
##
@@ -113,6 +91,10 @@ class MemCache
attr_reader :servers
##
+ # Whether this client should failover reads and writes to another server
+
+ attr_accessor :failover
+ ##
# Accepts a list of +servers+ and a list of +opts+. +servers+ may be
# omitted. See +servers=+ for acceptable server list arguments.
#
@@ -148,6 +130,7 @@ def initialize(*args)
@namespace = opts[:namespace]
@readonly = opts[:readonly]
@multithread = opts[:multithread]
+ @failover = opts[:failover]
@mutex = Mutex.new if @multithread
@buckets = []
self.servers = servers
@@ -182,7 +165,7 @@ def readonly?
def servers=(servers)
# Create the server objects.
- @servers = servers.collect do |server|
+ @servers = Array(servers).collect do |server|
case server
when String
host, port, weight = server.split ':', 3
@@ -212,15 +195,12 @@ def servers=(servers)
# 0. +key+ can not be decremented below 0.
def decr(key, amount = 1)
- server, cache_key = request_setup key
-
- if @multithread then
- threadsafe_cache_decr server, cache_key, amount
- else
+ raise MemCacheError, "Update of readonly cache" if @readonly
+ with_server(key) do |server, cache_key|
cache_decr server, cache_key, amount
end
- rescue TypeError, SocketError, SystemCallError, IOError => err
- handle_error server, err
+ rescue TypeError => err
+ handle_error nil, err
end
##
@@ -228,21 +208,14 @@ def decr(key, amount = 1)
# unmarshalled.
def get(key, raw = false)
- server, cache_key = request_setup key
-
- value = if @multithread then
- threadsafe_cache_get server, cache_key
- else
- cache_get server, cache_key
- end
-
- return nil if value.nil?
-
- value = Marshal.load value unless raw
-
- return value
- rescue TypeError, SocketError, SystemCallError, IOError => err
- handle_error server, err
+ with_server(key) do |server, cache_key|
+ value = cache_get server, cache_key
+ return nil if value.nil?
+ value = Marshal.load value unless raw
+ return value
+ end
+ rescue TypeError => err
+ handle_error nil, err
end
##
@@ -280,36 +253,29 @@ def get_multi(*keys)
server_keys.each do |server, keys_for_server|
keys_for_server = keys_for_server.join ' '
- values = if @multithread then
- threadsafe_cache_get_multi server, keys_for_server
- else
- cache_get_multi server, keys_for_server
- end
+ values = cache_get_multi server, keys_for_server
values.each do |key, value|
results[cache_keys[key]] = Marshal.load value
end
end
return results
- rescue TypeError, SocketError, SystemCallError, IOError => err
- handle_error server, err
+ rescue TypeError, IndexError => err
+ handle_error nil, err
end
##
- # Increments the value for +key+ by +amount+ and retruns the new value.
+ # Increments the value for +key+ by +amount+ and returns the new value.
# +key+ must already exist. If +key+ is not an integer, it is assumed to be
# 0.
def incr(key, amount = 1)
- server, cache_key = request_setup key
-
- if @multithread then
- threadsafe_cache_incr server, cache_key, amount
- else
+ raise MemCacheError, "Update of readonly cache" if @readonly
+ with_server(key) do |server, cache_key|
cache_incr server, cache_key, amount
end
- rescue TypeError, SocketError, SystemCallError, IOError => err
- handle_error server, err
+ rescue TypeError => err
+ handle_error nil, err
end
##
@@ -321,23 +287,23 @@ def incr(key, amount = 1)
def set(key, value, expiry = 0, raw = false)
raise MemCacheError, "Update of readonly cache" if @readonly
- server, cache_key = request_setup key
- socket = server.socket
+ with_server(key) do |server, cache_key|
- value = Marshal.dump value unless raw
- command = "set #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
+ value = Marshal.dump value unless raw
+ command = "set #{cache_key} 0 #{expiry} #{value.to_s.size}\r\n#{value}\r\n"
- begin
- @mutex.lock if @multithread
- socket.write command
- result = socket.gets
- raise_on_error_response! result
- result
- rescue SocketError, SystemCallError, IOError => err
- server.close
- raise MemCacheError, err.message
- ensure
- @mutex.unlock if @multithread
+ with_socket_management(server) do |socket|
+ socket.write command
+ result = socket.gets
+ raise_on_error_response! result
+
+ if result.nil?
+ server.close
+ raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
+ end
+
+ result
+ end
end
end
@@ -351,23 +317,16 @@ def set(key, value, expiry = 0, raw = false)
def add(key, value, expiry = 0, raw = false)
raise MemCacheError, "Update of readonly cache" if @readonly
- server, cache_key = request_setup key
- socket = server.socket
-
- value = Marshal.dump value unless raw
- command = "add #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
-
- begin
- @mutex.lock if @multithread
- socket.write command
- result = socket.gets
- raise_on_error_response! result
- result
- rescue SocketError, SystemCallError, IOError => err
- server.close
- raise MemCacheError, err.message
- ensure
- @mutex.unlock if @multithread
+ with_server(key) do |server, cache_key|
+ value = Marshal.dump value unless raw
+ command = "add #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
+
+ with_socket_management(server) do |socket|
+ socket.write command
+ result = socket.gets
+ raise_on_error_response! result
+ result
+ end
end
end
@@ -375,26 +334,15 @@ def add(key, value, expiry = 0, raw = false)
# Removes +key+ from the cache in +expiry+ seconds.
def delete(key, expiry = 0)
- @mutex.lock if @multithread
-
- raise MemCacheError, "No active servers" unless active?
- cache_key = make_cache_key key
- server = get_server_for_key cache_key
-
- sock = server.socket
- raise MemCacheError, "No connection to server" if sock.nil?
-
- begin
- sock.write "delete #{cache_key} #{expiry}\r\n"
- result = sock.gets
- raise_on_error_response! result
- result
- rescue SocketError, SystemCallError, IOError => err
- server.close
- raise MemCacheError, err.message
+ raise MemCacheError, "Update of readonly cache" if @readonly
+ with_server(key) do |server, cache_key|
+ with_socket_management(server) do |socket|
+ socket.write "delete #{cache_key} #{expiry}\r\n"
+ result = socket.gets
+ raise_on_error_response! result
+ result
+ end
end
- ensure
- @mutex.unlock if @multithread
end
##
@@ -403,21 +351,19 @@ def delete(key, expiry = 0)
def flush_all
raise MemCacheError, 'No active servers' unless active?
raise MemCacheError, "Update of readonly cache" if @readonly
+
begin
@mutex.lock if @multithread
@servers.each do |server|
- begin
- sock = server.socket
- raise MemCacheError, "No connection to server" if sock.nil?
- sock.write "flush_all\r\n"
- result = sock.gets
+ with_socket_management(server) do |socket|
+ socket.write "flush_all\r\n"
+ result = socket.gets
raise_on_error_response! result
result
- rescue SocketError, SystemCallError, IOError => err
- server.close
- raise MemCacheError, err.message
end
end
+ rescue IndexError => err
+ handle_error nil, err
ensure
@mutex.unlock if @multithread
end
@@ -469,14 +415,13 @@ def stats
server_stats = {}
@servers.each do |server|
- sock = server.socket
- raise MemCacheError, "No connection to server" if sock.nil?
+ next unless server.alive?
- value = nil
- begin
- sock.write "stats\r\n"
+ with_socket_management(server) do |socket|
+ value = nil
+ socket.write "stats\r\n"
stats = {}
- while line = sock.gets do
+ while line = socket.gets do
raise_on_error_response! line
break if line == "END\r\n"
if line =~ /\ASTAT ([\w]+) ([\w\.\:]+)/ then
@@ -498,12 +443,10 @@ def stats
end
end
server_stats["#{server.host}:#{server.port}"] = stats
- rescue SocketError, SystemCallError, IOError => err
- server.close
- raise MemCacheError, err.message
end
end
+ raise MemCacheError, "No active servers" if server_stats.empty?
server_stats
end
@@ -520,7 +463,7 @@ def []=(key, value)
set key, value
end
- protected
+ protected unless $TESTING
##
# Create a key for the cache, incorporating the namespace qualifier if
@@ -537,7 +480,7 @@ def make_cache_key(key)
##
# Pick a server to handle the request based on a hash of the key.
- def get_server_for_key(key)
+ def get_server_for_key(key, options = {})
raise ArgumentError, "illegal character in key #{key.inspect}" if
key =~ /\s/
raise ArgumentError, "key too long #{key.inspect}" if key.length > 250
@@ -545,13 +488,17 @@ def get_server_for_key(key)
return @servers.first if @servers.length == 1
hkey = hash_for key
-
- 20.times do |try|
- server = @buckets[hkey % @buckets.nitems]
- return server if server.alive?
- hkey += hash_for "#{try}#{key}"
+
+ if @failover
+ 20.times do |try|
+ server = @buckets[hkey % @buckets.compact.size]
+ return server if server.alive?
+ hkey += hash_for "#{try}#{key}"
+ end
+ else
+ return @buckets[hkey % @buckets.compact.size]
end
-
+
raise MemCacheError, "No servers available"
end
@@ -560,7 +507,7 @@ def get_server_for_key(key)
# sketchy for down servers).
def hash_for(key)
- (key.crc32_ITU_T >> 16) & 0x7fff
+ (Zlib.crc32(key) >> 16) & 0x7fff
end
##
@@ -568,12 +515,13 @@ def hash_for(key)
# found.
def cache_decr(server, cache_key, amount)
- socket = server.socket
- socket.write "decr #{cache_key} #{amount}\r\n"
- text = socket.gets
- raise_on_error_response! text
- return nil if text == "NOT_FOUND\r\n"
- return text.to_i
+ with_socket_management(server) do |socket|
+ socket.write "decr #{cache_key} #{amount}\r\n"
+ text = socket.gets
+ raise_on_error_response! text
+ return nil if text == "NOT_FOUND\r\n"
+ return text.to_i
+ end
end
##
@@ -581,52 +529,54 @@ def cache_decr(server, cache_key, amount)
# miss.
def cache_get(server, cache_key)
- socket = server.socket
- socket.write "get #{cache_key}\r\n"
- keyline = socket.gets # "VALUE <key> <flags> <bytes>\r\n"
+ with_socket_management(server) do |socket|
+ socket.write "get #{cache_key}\r\n"
+ keyline = socket.gets # "VALUE <key> <flags> <bytes>\r\n"
- if keyline.nil? then
- server.close
- raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
- end
+ if keyline.nil? then
+ server.close
+ raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
+ end
- raise_on_error_response! keyline
- return nil if keyline == "END\r\n"
+ raise_on_error_response! keyline
+ return nil if keyline == "END\r\n"
- unless keyline =~ /(\d+)\r/ then
- server.close
- raise MemCacheError, "unexpected response #{keyline.inspect}"
+ unless keyline =~ /(\d+)\r/ then
+ server.close
+ raise MemCacheError, "unexpected response #{keyline.inspect}"
+ end
+ value = socket.read $1.to_i
+ socket.read 2 # "\r\n"
+ socket.gets # "END\r\n"
+ return value
end
- value = socket.read $1.to_i
- socket.read 2 # "\r\n"
- socket.gets # "END\r\n"
- return value
end
##
# Fetches +cache_keys+ from +server+ using a multi-get.
def cache_get_multi(server, cache_keys)
- values = {}
- socket = server.socket
- socket.write "get #{cache_keys}\r\n"
+ with_socket_management(server) do |socket|
+ values = {}
+ socket.write "get #{cache_keys}\r\n"
- while keyline = socket.gets do
- return values if keyline == "END\r\n"
- raise_on_error_response! keyline
+ while keyline = socket.gets do
+ return values if keyline == "END\r\n"
+ raise_on_error_response! keyline
- unless keyline =~ /\AVALUE (.+) (.+) (.+)/ then
- server.close
- raise MemCacheError, "unexpected response #{keyline.inspect}"
+ unless keyline =~ /\AVALUE (.+) (.+) (.+)/ then
+ server.close
+ raise MemCacheError, "unexpected response #{keyline.inspect}"
+ end
+
+ key, data_length = $1, $3
+ values[$1] = socket.read data_length.to_i
+ socket.read(2) # "\r\n"
end
- key, data_length = $1, $3
- values[$1] = socket.read data_length.to_i
- socket.read(2) # "\r\n"
+ server.close
+ raise MemCacheError, "lost connection to #{server.host}:#{server.port}" # TODO: retry here too
end
-
- server.close
- raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
end
##
@@ -634,18 +584,76 @@ def cache_get_multi(server, cache_keys)
# found.
def cache_incr(server, cache_key, amount)
- socket = server.socket
- socket.write "incr #{cache_key} #{amount}\r\n"
- text = socket.gets
- raise_on_error_response! text
- return nil if text == "NOT_FOUND\r\n"
- return text.to_i
+ with_socket_management(server) do |socket|
+ socket.write "incr #{cache_key} #{amount}\r\n"
+ text = socket.gets
+ raise_on_error_response! text
+ return nil if text == "NOT_FOUND\r\n"
+ return text.to_i
+ end
+ end
+
+ ##
+ # Gets or creates a socket connected to the given server, and yields it
+ # to the block, wrapped in a mutex synchronization if @multithread is true.
+ #
+ # If a socket error (SocketError, SystemCallError, IOError) or protocol error
+ # (MemCacheError) is raised by the block, closes the socket, attempts to
+ # connect again, and retries the block (once). If an error is again raised,
+ # reraises it as MemCacheError.
+ #
+ # If unable to connect to the server (or if in the reconnect wait period),
+ # raises MemCacheError. Note that the socket connect code marks a server
+ # dead for a timeout period, so retrying does not apply to connection attempt
+ # failures (but does still apply to unexpectedly lost connections etc.).
+
+ def with_socket_management(server, &block)
+ @mutex.lock if @multithread
+ retried = false
+
+ begin
+ socket = server.socket
+
+ # Raise an IndexError to show this server is out of whack. If were inside
+ # a with_server block, we'll catch it and attempt to restart the operation.
+
+ raise IndexError, "No connection to server (#{server.status})" if socket.nil?
+
+ block.call(socket)
+
+ rescue SocketError => err
+ server.mark_dead(err.message)
+ handle_error(server, err)
+
+ rescue MemCacheError, SocketError, SystemCallError, IOError => err
</