aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionmailer/test/abstract_unit.rb1
-rw-r--r--actionpack/CHANGELOG4
-rw-r--r--actionpack/Rakefile2
-rw-r--r--actionpack/lib/action_controller.rb2
-rw-r--r--actionpack/lib/action_controller/base.rb30
-rw-r--r--actionpack/lib/action_controller/dispatcher.rb14
-rw-r--r--actionpack/lib/action_controller/http_authentication.rb191
-rw-r--r--actionpack/lib/action_controller/integration.rb94
-rw-r--r--actionpack/lib/action_controller/middleware_stack.rb21
-rwxr-xr-xactionpack/lib/action_controller/request.rb65
-rw-r--r--actionpack/lib/action_controller/request_parser.rb7
-rw-r--r--actionpack/lib/action_controller/rescue.rb8
-rw-r--r--actionpack/lib/action_controller/response.rb9
-rw-r--r--actionpack/lib/action_controller/routing/route_set.rb10
-rw-r--r--actionpack/lib/action_controller/test_case.rb22
-rw-r--r--actionpack/lib/action_controller/test_process.rb54
-rw-r--r--actionpack/lib/action_controller/url_encoded_pair_parser.rb9
-rw-r--r--actionpack/lib/action_view/base.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb474
-rw-r--r--actionpack/lib/action_view/helpers/benchmark_helper.rb29
-rw-r--r--actionpack/lib/action_view/inline_template.rb2
-rw-r--r--actionpack/lib/action_view/paths.rb107
-rw-r--r--actionpack/lib/action_view/renderable.rb9
-rw-r--r--actionpack/lib/action_view/renderable_partial.rb7
-rw-r--r--actionpack/lib/action_view/template.rb85
-rw-r--r--actionpack/lib/action_view/test_case.rb3
-rw-r--r--actionpack/test/abstract_unit.rb1
-rw-r--r--actionpack/test/controller/addresses_render_test.rb9
-rw-r--r--actionpack/test/controller/base_test.rb22
-rw-r--r--actionpack/test/controller/benchmark_test.rb6
-rw-r--r--actionpack/test/controller/capture_test.rb9
-rw-r--r--actionpack/test/controller/content_type_test.rb9
-rw-r--r--actionpack/test/controller/cookie_test.rb8
-rw-r--r--actionpack/test/controller/dispatcher_test.rb9
-rw-r--r--actionpack/test/controller/filters_test.rb8
-rw-r--r--actionpack/test/controller/flash_test.rb8
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb73
-rw-r--r--actionpack/test/controller/integration_test.rb94
-rw-r--r--actionpack/test/controller/integration_upload_test.rb46
-rw-r--r--actionpack/test/controller/layout_test.rb3
-rw-r--r--actionpack/test/controller/middleware_stack_test.rb70
-rw-r--r--actionpack/test/controller/query_string_parsing_test.rb120
-rw-r--r--actionpack/test/controller/rack_test.rb7
-rw-r--r--actionpack/test/controller/render_test.rb7
-rw-r--r--actionpack/test/controller/request/json_params_parsing_test.rb45
-rw-r--r--actionpack/test/controller/request/xml_params_parsing_test.rb88
-rw-r--r--actionpack/test/controller/request_test.rb185
-rw-r--r--actionpack/test/controller/rescue_test.rb28
-rw-r--r--actionpack/test/controller/routing_test.rb133
-rw-r--r--actionpack/test/controller/send_file_test.rb3
-rw-r--r--actionpack/test/fixtures/multipart/hello.txt1
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb22
-rw-r--r--actionpack/test/template/benchmark_helper_test.rb74
-rw-r--r--actionpack/test/template/compiled_templates_test.rb9
-rw-r--r--actionpack/test/template/render_test.rb7
-rw-r--r--activerecord/CHANGELOG2
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/association_preload.rb4
-rwxr-xr-xactiverecord/lib/active_record/associations.rb4
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb5
-rwxr-xr-xactiverecord/lib/active_record/base.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb4
-rw-r--r--activerecord/lib/active_record/dynamic_scope_match.rb25
-rw-r--r--activerecord/test/cases/associations/eager_test.rb33
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb6
-rw-r--r--activerecord/test/cases/named_scope_test.rb22
-rw-r--r--activesupport/CHANGELOG7
-rw-r--r--activesupport/lib/active_support/callbacks.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/object/misc.rb15
-rw-r--r--activesupport/lib/active_support/json/decoding.rb2
-rw-r--r--activesupport/lib/active_support/testing/performance.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb2
-rw-r--r--activesupport/lib/active_support/vendor.rb4
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/.gitignore3
-rwxr-xr-xactivesupport/lib/active_support/vendor/i18n-0.1.1/MIT-LICENSE20
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/README.textile20
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/Rakefile5
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/i18n.gemspec27
-rwxr-xr-xactivesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb (renamed from activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb)74
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb (renamed from activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb)58
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/exceptions.rb (renamed from activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/exceptions.rb)6
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/test/all.rb5
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_exceptions_test.rb100
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_test.rb125
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.rb1
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.yml3
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb502
-rw-r--r--activesupport/test/callbacks_test.rb42
-rw-r--r--activesupport/test/core_ext/object_ext_test.rb8
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb9
-rw-r--r--activesupport/test/json/decoding_test.rb3
-rwxr-xr-xci/ci_build.rb15
-rw-r--r--ci/ci_setup_notes.txt11
-rw-r--r--ci/cruise_config.rb3
-rw-r--r--ci/geminstaller.yml8
-rw-r--r--railties/doc/guides/html/activerecord_validations_callbacks.html122
-rw-r--r--railties/doc/guides/html/index.html2
-rw-r--r--railties/doc/guides/html/performance_testing.html557
-rw-r--r--railties/doc/guides/source/activerecord_validations_callbacks.txt79
-rw-r--r--railties/doc/guides/source/index.txt2
-rw-r--r--railties/doc/guides/source/performance_testing.txt433
-rw-r--r--railties/lib/initializer.rb9
-rw-r--r--railties/lib/rails/backtrace_cleaner.rb5
-rw-r--r--railties/lib/rails_generator/commands.rb2
-rw-r--r--railties/lib/test_help.rb1
-rw-r--r--railties/test/console_app_test.rb9
-rw-r--r--railties/test/error_page_test.rb11
-rw-r--r--railties/test/fcgi_dispatcher_test.rb49
-rw-r--r--railties/test/gem_dependency_test.rb24
109 files changed, 3444 insertions, 1453 deletions
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 4900f6fb35..a6126d6f7f 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -17,7 +17,6 @@ $:.unshift "#{File.dirname(__FILE__)}/fixtures/helpers"
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
ActionMailer::Base.template_root = FIXTURE_LOAD_PATH
-ActionMailer::Base.template_root.load
class MockSMTP
def self.deliveries
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index a8abf48441..26e40e76d5 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,9 @@
*2.3.0 [Edge]*
+* Added :silence option to BenchmarkHelper#benchmark and turned log_level into a hash parameter and deprecated the old use [DHH]
+
+* Fixed the AssetTagHelper cache to use the computed asset host as part of the cache key instead of just assuming the its a string #1299 [DHH]
+
* Make ActionController#render(string) work as a shortcut for render :file/:template/:action => string. [#1435] [Pratik Naik] Examples:
# Instead of render(:action => 'other_action')
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 1a1b908122..c389e5a8d6 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -81,7 +81,7 @@ spec = Gem::Specification.new do |s|
s.requirements << 'none'
s.add_dependency('activesupport', '= 2.3.0' + PKG_BUILD)
- s.add_dependency('rack', '= 0.4.0')
+ s.add_dependency('rack', '>= 0.9.0')
s.require_path = 'lib'
s.autorequire = 'action_controller'
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 98fb490d64..8c022e5625 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -31,7 +31,7 @@ rescue LoadError
end
end
-gem 'rack', '~> 0.4.0'
+gem 'rack', '>= 0.9.0'
require 'rack'
module ActionController
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 5b83494eb4..e22114195c 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -382,6 +382,13 @@ module ActionController #:nodoc:
attr_accessor :action_name
class << self
+ def call(env)
+ # HACK: For global rescue to have access to the original request and response
+ request = env["action_controller.rescue.request"] ||= Request.new(env)
+ response = env["action_controller.rescue.response"] ||= Response.new
+ process(request, response)
+ end
+
# Factory for the standard create, process loop where the controller is discarded after processing.
def process(request, response) #:nodoc:
new.process(request, response)
@@ -862,7 +869,7 @@ module ActionController #:nodoc:
validate_render_arguments(options, extra_options, block_given?)
if options.nil?
- options = { :template => default_template.filename, :layout => true }
+ options = { :template => default_template, :layout => true }
elsif options == :update
options = extra_options.merge({ :update => true })
elsif options.is_a?(String) || options.is_a?(Symbol)
@@ -1118,7 +1125,7 @@ module ActionController #:nodoc:
end
# Sets the etag, last_modified, or both on the response and renders a
- # "304 Not Modified" response if the request is already fresh.
+ # "304 Not Modified" response if the request is already fresh.
#
# Example:
#
@@ -1126,8 +1133,8 @@ module ActionController #:nodoc:
# @article = Article.find(params[:id])
# fresh_when(:etag => @article, :last_modified => @article.created_at.utc)
# end
- #
- # This will render the show template if the request isn't sending a matching etag or
+ #
+ # This will render the show template if the request isn't sending a matching etag or
# If-Modified-Since header and just a "304 Not Modified" response if there's a match.
def fresh_when(options)
options.assert_valid_keys(:etag, :last_modified)
@@ -1232,7 +1239,7 @@ module ActionController #:nodoc:
log_processing_for_parameters
end
end
-
+
def log_processing_for_request_id
request_id = "\n\nProcessing #{self.class.name}\##{action_name} "
request_id << "to #{params[:format]} " if params[:format]
@@ -1244,7 +1251,7 @@ module ActionController #:nodoc:
def log_processing_for_parameters
parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
parameters = parameters.except!(:controller, :action, :format, :_method)
-
+
logger.info " Parameters: #{parameters.inspect}" unless parameters.empty?
end
@@ -1343,9 +1350,12 @@ module ActionController #:nodoc:
end
Base.class_eval do
- include Flash, Filters, Layout, Benchmarking, Rescue, MimeResponds, Helpers
- include Cookies, Caching, Verification, Streaming
- include SessionManagement, HttpAuthentication::Basic::ControllerMethods
- include RecordIdentifier, RequestForgeryProtection, Translation
+ [ Filters, Layout, Benchmarking, Rescue, Flash, MimeResponds, Helpers,
+ Cookies, Caching, Verification, Streaming, SessionManagement,
+ HttpAuthentication::Basic::ControllerMethods, RecordIdentifier,
+ RequestForgeryProtection, Translation
+ ].each do |mod|
+ include mod
+ end
end
end
diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb
index 4dc76e1b49..781bc48887 100644
--- a/actionpack/lib/action_controller/dispatcher.rb
+++ b/actionpack/lib/action_controller/dispatcher.rb
@@ -8,6 +8,8 @@ module ActionController
# Development mode callbacks
before_dispatch :reload_application
after_dispatch :cleanup_application
+
+ ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
end
if defined?(ActiveRecord)
@@ -60,11 +62,10 @@ module ActionController
def dispatch
begin
run_callbacks :before_dispatch
- controller = Routing::Routes.recognize(@request)
- controller.process(@request, @response).to_a
+ Routing::Routes.call(@env)
rescue Exception => exception
if controller ||= (::ApplicationController rescue Base)
- controller.process_with_exception(@request, @response, exception).to_a
+ controller.call_with_exception(@env, exception).to_a
else
raise exception
end
@@ -83,8 +84,7 @@ module ActionController
end
def _call(env)
- @request = Request.new(env)
- @response = Response.new
+ @env = env
dispatch
end
@@ -93,7 +93,6 @@ module ActionController
run_callbacks :prepare_dispatch
Routing::Routes.reload
- ActionView::Helpers::AssetTagHelper::AssetTag::Cache.clear
end
# Cleanup the application by clearing out loaded classes so they can
@@ -110,8 +109,7 @@ module ActionController
def checkin_connections
# Don't return connection (and peform implicit rollback) if this request is a part of integration test
- # TODO: This callback should have direct access to env
- return if @request.key?("rack.test")
+ return if @env.key?("rack.test")
ActiveRecord::Base.clear_active_connections!
end
end
diff --git a/actionpack/lib/action_controller/http_authentication.rb b/actionpack/lib/action_controller/http_authentication.rb
index 2ed810db7d..3cb5829eca 100644
--- a/actionpack/lib/action_controller/http_authentication.rb
+++ b/actionpack/lib/action_controller/http_authentication.rb
@@ -55,7 +55,31 @@ module ActionController
# end
# end
#
- #
+ # Simple Digest example. Note the block must return the user's password so the framework
+ # can appropriately hash it to check the user's credentials. Returning nil will cause authentication to fail.
+ #
+ # class PostsController < ApplicationController
+ # Users = {"dhh" => "secret"}
+ #
+ # before_filter :authenticate, :except => [ :index ]
+ #
+ # def index
+ # render :text => "Everyone can see me!"
+ # end
+ #
+ # def edit
+ # render :text => "I'm only accessible if you know the password"
+ # end
+ #
+ # private
+ # def authenticate
+ # authenticate_or_request_with_http_digest(realm) do |user_name|
+ # Users[user_name]
+ # end
+ # end
+ # end
+ #
+ #
# In your integration tests, you can do something like this:
#
# def test_access_granted_from_xml
@@ -108,7 +132,10 @@ module ActionController
end
def decode_credentials(request)
- ActiveSupport::Base64.decode64(authorization(request).split.last || '')
+ # Properly decode credentials spanning a new-line
+ auth = authorization(request)
+ auth.slice!('Basic ')
+ ActiveSupport::Base64.decode64(auth || '')
end
def encode_credentials(user_name, password)
@@ -120,5 +147,165 @@ module ActionController
controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
end
end
+
+ module Digest
+ extend self
+
+ module ControllerMethods
+ def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure)
+ begin
+ authenticate_with_http_digest!(realm, &password_procedure)
+ rescue ActionController::HttpAuthentication::Error => e
+ msg = e.message
+ msg = "#{msg} expected '#{e.expected}' was '#{e.was}'" unless e.expected.nil?
+ raise msg if e.fatal?
+ request_http_digest_authentication(realm, msg)
+ end
+ end
+
+ # Authenticate using HTTP Digest, throwing ActionController::HttpAuthentication::Error on failure.
+ # This allows more detailed analysis of authentication failures
+ # to be relayed to the client.
+ def authenticate_with_http_digest!(realm = "Application", &login_procedure)
+ HttpAuthentication::Digest.authenticate(self, realm, &login_procedure)
+ end
+
+ # Authenticate with HTTP Digest, returns true or false
+ def authenticate_with_http_digest(realm = "Application", &login_procedure)
+ HttpAuthentication::Digest.authenticate(self, realm, &login_procedure) rescue false
+ end
+
+ # Render output including the HTTP Digest authentication header
+ def request_http_digest_authentication(realm = "Application", message = nil)
+ HttpAuthentication::Digest.authentication_request(self, realm, message)
+ end
+
+ # Add HTTP Digest authentication header to result headers
+ def http_digest_authentication_header(realm = "Application")
+ HttpAuthentication::Digest.authentication_header(self, realm)
+ end
+ end
+
+ # Raises error unless authentictaion succeeds, returns true otherwise
+ def authenticate(controller, realm, &password_procedure)
+ raise Error.new(false), "No authorization header found" unless authorization(controller.request)
+ validate_digest_response(controller, realm, &password_procedure)
+ true
+ end
+
+ def authorization(request)
+ request.env['HTTP_AUTHORIZATION'] ||
+ request.env['X-HTTP_AUTHORIZATION'] ||
+ request.env['X_HTTP_AUTHORIZATION'] ||
+ request.env['REDIRECT_X_HTTP_AUTHORIZATION']
+ end
+
+ # Raises error unless the request credentials response value matches the expected value.
+ def validate_digest_response(controller, realm, &password_procedure)
+ credentials = decode_credentials(controller.request)
+
+ # Check the nonce, opaque and realm.
+ # Ignore nc, as we have no way to validate the number of times this nonce has been used
+ validate_nonce(controller.request, credentials[:nonce])
+ raise Error.new(false, realm, credentials[:realm]), "Realm doesn't match" unless realm == credentials[:realm]
+ raise Error.new(true, opaque(controller.request), credentials[:opaque]),"Opaque doesn't match" unless opaque(controller.request) == credentials[:opaque]
+
+ password = password_procedure.call(credentials[:username])
+ raise Error.new(false), "No password" if password.nil?
+ expected = expected_response(controller.request.env['REQUEST_METHOD'], controller.request.url, credentials, password)
+ raise Error.new(false, expected, credentials[:response]), "Invalid response" unless expected == credentials[:response]
+ end
+
+ # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
+ def expected_response(http_method, uri, credentials, password)
+ ha1 = ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
+ ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase,uri].join(':'))
+ ::Digest::MD5.hexdigest([ha1,credentials[:nonce], credentials[:nc], credentials[:cnonce],credentials[:qop],ha2].join(':'))
+ end
+
+ def encode_credentials(http_method, credentials, password)
+ credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password)
+ "Digest " + credentials.sort_by {|x| x[0].to_s }.inject([]) {|a, v| a << "#{v[0]}='#{v[1]}'" }.join(', ')
+ end
+
+ def decode_credentials(request)
+ authorization(request).to_s.gsub(/^Digest\s+/,'').split(',').inject({}) do |hash, pair|
+ key, value = pair.split('=', 2)
+ hash[key.strip.to_sym] = value.to_s.gsub(/^"|"$/,'').gsub(/'/, '')
+ hash
+ end
+ end
+
+ def authentication_header(controller, realm)
+ controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce(controller.request)}", opaque="#{opaque(controller.request)}")
+ end
+
+ def authentication_request(controller, realm, message = "HTTP Digest: Access denied")
+ authentication_header(controller, realm)
+ controller.send! :render, :text => message, :status => :unauthorized
+ end
+
+ # Uses an MD5 digest based on time to generate a value to be used only once.
+ #
+ # A server-specified data string which should be uniquely generated each time a 401 response is made.
+ # It is recommended that this string be base64 or hexadecimal data.
+ # Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
+ #
+ # The contents of the nonce are implementation dependent.
+ # The quality of the implementation depends on a good choice.
+ # A nonce might, for example, be constructed as the base 64 encoding of
+ #
+ # => time-stamp H(time-stamp ":" ETag ":" private-key)
+ #
+ # where time-stamp is a server-generated time or other non-repeating value,
+ # ETag is the value of the HTTP ETag header associated with the requested entity,
+ # and private-key is data known only to the server.
+ # With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and
+ # reject the request if it did not match the nonce from that header or
+ # if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity.
+ # The inclusion of the ETag prevents a replay request for an updated version of the resource.
+ # (Note: including the IP address of the client in the nonce would appear to offer the server the ability
+ # to limit the reuse of the nonce to the same client that originally got it.
+ # However, that would break proxy farms, where requests from a single user often go through different proxies in the farm.
+ # Also, IP address spoofing is not that hard.)
+ #
+ # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
+ # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
+ # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
+ # of this document.
+ #
+ # The nonce is opaque to the client.
+ def nonce(request, time = Time.now)
+ session_id = request.is_a?(String) ? request : request.session.session_id
+ t = time.to_i
+ hashed = [t, session_id]
+ digest = ::Digest::MD5.hexdigest(hashed.join(":"))
+ Base64.encode64("#{t}:#{digest}").gsub("\n", '')
+ end
+
+ def validate_nonce(request, value)
+ t = Base64.decode64(value).split(":").first.to_i
+ raise Error.new(true), "Stale Nonce" if (t - Time.now.to_i).abs > 10 * 60
+ n = nonce(request, t)
+ raise Error.new(true, value, n), "Bad Nonce" unless n == value
+ end
+
+ # Opaque based on digest of session_id
+ def opaque(request)
+ session_id = request.is_a?(String) ? request : request.session.session_id
+ @opaque ||= Base64.encode64(::Digest::MD5::hexdigest(session_id)).gsub("\n", '')
+ end
+ end
+
+ class Error < RuntimeError
+ attr_accessor :expected, :was
+ def initialize(fatal = false, expected = nil, was = nil)
+ @fatal = fatal
+ @expected = expected
+ @was = was
+ end
+
+ def fatal?; @fatal; end
+ end
end
end
diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb
index 71e2524e81..ded72a71fb 100644
--- a/actionpack/lib/action_controller/integration.rb
+++ b/actionpack/lib/action_controller/integration.rb
@@ -68,12 +68,21 @@ module ActionController
# A running counter of the number of requests processed.
attr_accessor :request_count
+ # Nonce value for Digest Authentication, implicitly set on response with WWW-Authentication
+ attr_accessor :nonce
+
+ # Opaque value for Digest Authentication, implicitly set on response with WWW-Authentication
+ attr_accessor :opaque
+
+ # Opaque value for Authentication, implicitly set on response with WWW-Authentication
+ attr_accessor :realm
+
class MultiPartNeededException < Exception
end
# Create and initialize a new Session instance.
- def initialize(app)
- @application = app
+ def initialize(app = nil)
+ @application = app || ActionController::Dispatcher.new
reset!
end
@@ -243,6 +252,53 @@ module ActionController
end
alias xhr :xml_http_request
+ def request_with_noauth(http_method, uri, parameters, headers)
+ process_with_auth http_method, uri, parameters, headers
+ end
+
+ # Performs a request with the given http_method and parameters, including HTTP Basic authorization headers.
+ # See get() for more details on paramters and headers.
+ #
+ # You can perform GET, POST, PUT, DELETE, and HEAD requests with #get_with_basic, #post_with_basic,
+ # #put_with_basic, #delete_with_basic, and #head_with_basic.
+ def request_with_basic(http_method, uri, parameters, headers, user_name, password)
+ process_with_auth http_method, uri, parameters, headers.merge(:authorization => ActionController::HttpAuthentication::Basic.encode_credentials(user_name, password))
+ end
+
+ # Performs a request with the given http_method and parameters, including HTTP Digest authorization headers.
+ # See get() for more details on paramters and headers.
+ #
+ # You can perform GET, POST, PUT, DELETE, and HEAD requests with #get_with_digest, #post_with_digest,
+ # #put_with_digest, #delete_with_digest, and #head_with_digest.
+ def request_with_digest(http_method, uri, parameters, headers, user_name, password)
+ # Realm, Nonce, and Opaque taken from previoius 401 response
+
+ credentials = {
+ :username => user_name,
+ :realm => @realm,
+ :nonce => @nonce,
+ :qop => "auth",
+ :nc => "00000001",
+ :cnonce => "0a4f113b",
+ :opaque => @opaque,
+ :uri => uri
+ }
+
+ raise "Digest request without previous 401 response" if @opaque.nil?
+
+ process_with_auth http_method, uri, parameters, headers.merge(:authorization => ActionController::HttpAuthentication::Digest.encode_credentials(http_method, credentials, password))
+ end
+
+ # def get_with_basic, def post_with_basic, def put_with_basic, def delete_with_basic, def head_with_basic
+ # def get_with_digest, def post_with_digest, def put_with_digest, def delete_with_digest, def head_with_digest
+ [:get, :post, :put, :delete, :head].each do |method|
+ [:noauth, :basic, :digest].each do |auth_type|
+ define_method("#{method}_with_#{auth_type}") do |uri, parameters, headers, *auth|
+ send("request_with_#{auth_type}", method, uri, parameters, headers, *auth)
+ end
+ end
+ end
+
# Returns the URL for the given options, according to the rules specified
# in the application's routes.
def url_for(options)
@@ -311,8 +367,10 @@ module ActionController
env[key] = value
end
- unless ActionController::Base.respond_to?(:clear_last_instantiation!)
- ActionController::Base.module_eval { include ControllerCapture }
+ [ControllerCapture, ActionController::ProcessWithTest].each do |mod|
+ unless ActionController::Base < mod
+ ActionController::Base.class_eval { include mod }
+ end
end
ActionController::Base.clear_last_instantiation!
@@ -340,6 +398,7 @@ module ActionController
if @controller = ActionController::Base.last_instantiation
@request = @controller.request
@response = @controller.response
+ @controller.send(:set_test_assigns)
else
# Decorate responses from Rack Middleware and Rails Metal
# as an Response for the purposes of integration testing
@@ -364,6 +423,32 @@ module ActionController
return status
end
+ # Same as process, but handles authentication returns to perform
+ # Basic or Digest authentication
+ def process_with_auth(method, path, parameters = nil, headers = nil)
+ status = process(method, path, parameters, headers)
+
+ if status == 401
+ # Extract authentication information from response
+ auth_data = @response.headers['WWW-Authenticate']
+ if /^Basic /.match(auth_data)
+ # extract realm, to be used in subsequent request
+ @realm = auth_header.split(' ')[1]
+ elsif /^Digest/.match(auth_data)
+ creds = auth_data.to_s.gsub(/^Digest\s+/,'').split(',').inject({}) do |hash, pair|
+ key, value = pair.split('=', 2)
+ hash[key.strip.to_sym] = value.to_s.gsub(/^"|"$/,'').gsub(/'/, '')
+ hash
+ end
+ @realm = creds[:realm]
+ @nonce = creds[:nonce]
+ @opaque = creds[:opaque]
+ end
+ end
+
+ return status
+ end
+
# Encode the cookies hash in a format suitable for passing to a
# request.
def encode_cookies
@@ -509,7 +594,6 @@ EOF
# can use this method to open multiple sessions that ought to be tested
# simultaneously.
def open_session(application = nil)
- application ||= ActionController::Dispatcher.new
session = Integration::Session.new(application)
# delegate the fixture accessors back to the test instance
diff --git a/actionpack/lib/action_controller/middleware_stack.rb b/actionpack/lib/action_controller/middleware_stack.rb
index 74f28565c0..2bccba2ba1 100644
--- a/actionpack/lib/action_controller/middleware_stack.rb
+++ b/actionpack/lib/action_controller/middleware_stack.rb
@@ -1,6 +1,14 @@
module ActionController
class MiddlewareStack < Array
class Middleware
+ def self.new(klass, *args, &block)
+ if klass.is_a?(self)
+ klass
+ else
+ super
+ end
+ end
+
attr_reader :args, :block
def initialize(klass, *args, &block)
@@ -65,6 +73,19 @@ module ActionController
block.call(self) if block_given?
end
+ def insert(index, *objs)
+ index = self.index(index) unless index.is_a?(Integer)
+ objs = objs.map { |obj| Middleware.new(obj) }
+ super(index, *objs)
+ end
+
+ alias_method :insert_before, :insert
+
+ def insert_after(index, *objs)
+ index = self.index(index) unless index.is_a?(Integer)
+ insert(index + 1, *objs)
+ end
+
def use(*args, &block)
middleware = Middleware.new(*args, &block)
push(middleware)
diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb
index ab028b9eb2..3d13705f8f 100755
--- a/actionpack/lib/action_controller/request.rb
+++ b/actionpack/lib/action_controller/request.rb
@@ -6,24 +6,17 @@ require 'active_support/memoizable'
require 'action_controller/cgi_ext'
module ActionController
- # CgiRequest and TestRequest provide concrete implementations.
- class Request
+ class Request < Rack::Request
extend ActiveSupport::Memoizable
- class SessionFixationAttempt < StandardError #:nodoc:
- end
-
- # The hash of environment variables for this request,
- # such as { 'RAILS_ENV' => 'production' }.
- attr_reader :env
-
def initialize(env)
- @env = env
+ super
+ @parser = ActionController::RequestParser.new(env)
end
- %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO
+ %w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
- REMOTE_IDENT REMOTE_USER SCRIPT_NAME
+ REMOTE_IDENT REMOTE_USER REMOTE_ADDR
SERVER_NAME SERVER_PROTOCOL
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
@@ -45,8 +38,7 @@ module ActionController
# <tt>:get</tt>. If the request \method is not listed in the HTTP_METHODS
# constant above, an UnknownHttpMethod exception is raised.
def request_method
- method = @env['REQUEST_METHOD']
- HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
+ HTTP_METHOD_LOOKUP[super] || raise(UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
end
memoize :request_method
@@ -94,16 +86,15 @@ module ActionController
# Returns the content length of the request as an integer.
def content_length
- @env['CONTENT_LENGTH'].to_i
+ super.to_i
end
- memoize :content_length
# The MIME type of the HTTP request, such as Mime::XML.
#
# For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
def content_type
- Mime::Type.lookup(parser.content_type_without_parameters)
+ Mime::Type.lookup(@parser.content_type_without_parameters)
end
memoize :content_type
@@ -391,16 +382,17 @@ EOM
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
- parser.raw_post
+ @parser.raw_post
end
# Returns both GET and POST \parameters in a single hash.
def parameters
@parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
end
+ alias_method :params, :parameters
def path_parameters=(parameters) #:nodoc:
- @path_parameters = parameters
+ @env["rack.routing_args"] = parameters
@symbolized_path_parameters = @parameters = nil
end
@@ -416,44 +408,35 @@ EOM
#
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
def path_parameters
- @path_parameters ||= {}
+ @env["rack.routing_args"] ||= {}
end
def body
- parser.body
- end
-
- def remote_addr
- @env['REMOTE_ADDR']
+ @parser.body
end
- def referrer
- @env['HTTP_REFERER']
+ # Override Rack's GET method to support nested query strings
+ def GET
+ @parser.query_parameters
end
- alias referer referrer
+ alias_method :query_parameters, :GET
- def query_parameters
- @query_parameters ||= parser.query_parameters
- end
-
- def request_parameters
- @request_parameters ||= parser.request_parameters
+ # Override Rack's POST method to support nested query strings
+ def POST
+ @parser.request_parameters
end
+ alias_method :request_parameters, :POST
def body_stream #:nodoc:
@env['rack.input']
end
- def cookies
- Rack::Request.new(@env).cookies
- end
-
def session
@env['rack.session'] ||= {}
end
def session=(session) #:nodoc:
- @session = session
+ @env['rack.session'] = session
end
def reset_session
@@ -476,9 +459,5 @@ EOM
def named_host?(host)
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
end
-
- def parser
- @parser ||= ActionController::RequestParser.new(@env)
- end
end
end
diff --git a/actionpack/lib/action_controller/request_parser.rb b/actionpack/lib/action_controller/request_parser.rb
index 82ee4c84c4..d1739ef4d0 100644
--- a/actionpack/lib/action_controller/request_parser.rb
+++ b/actionpack/lib/action_controller/request_parser.rb
@@ -2,14 +2,15 @@ module ActionController
class RequestParser
def initialize(env)
@env = env
+ freeze
end
def request_parameters
- @request_parameters ||= parse_formatted_request_parameters
+ @env["action_controller.request_parser.request_parameters"] ||= parse_formatted_request_parameters
end
def query_parameters
- @query_parameters ||= self.class.parse_query_parameters(query_string)
+ @env["action_controller.request_parser.query_parameters"] ||= self.class.parse_query_parameters(query_string)
end
# Returns the query string, accounting for server idiosyncrasies.
@@ -90,7 +91,7 @@ module ActionController
end
def content_length
- @content_length ||= @env['CONTENT_LENGTH'].to_i
+ @env['CONTENT_LENGTH'].to_i
end
# The raw content type string. Use when you need parameters such as
diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb
index 5ef79a36ce..4b7d1e32fd 100644
--- a/actionpack/lib/action_controller/rescue.rb
+++ b/actionpack/lib/action_controller/rescue.rb
@@ -38,8 +38,8 @@ module ActionController #:nodoc:
'ActionView::TemplateError' => 'template_error'
}
- RESCUES_TEMPLATE_PATH = ActionView::PathSet::Path.new(
- File.join(File.dirname(__FILE__), "templates"), true)
+ RESCUES_TEMPLATE_PATH = ActionView::Template::EagerPath.new(
+ File.join(File.dirname(__FILE__), "templates"))
def self.included(base) #:nodoc:
base.cattr_accessor :rescue_responses
@@ -59,7 +59,9 @@ module ActionController #:nodoc:
end
module ClassMethods
- def process_with_exception(request, response, exception) #:nodoc:
+ def call_with_exception(env, exception) #:nodoc:
+ request = env["action_controller.rescue.request"] ||= Request.new(env)
+ response = env["action_controller.rescue.response"] ||= Response.new
new.process(request, response, :rescue_action, exception)
end
end
diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb
index 64319fe102..27860a6207 100644
--- a/actionpack/lib/action_controller/response.rb
+++ b/actionpack/lib/action_controller/response.rb
@@ -231,10 +231,13 @@ module ActionController # :nodoc:
# Don't set the Content-Length for block-based bodies as that would mean
# reading it all into memory. Not nice for, say, a 2GB streaming file.
def set_content_length!
- unless body.respond_to?(:call) || (status && status.to_s[0..2] == '304')
- self.headers["Content-Length"] ||= body.size
+ if status && status.to_s[0..2] == '204'
+ headers.delete('Content-Length')
+ elsif length = headers['Content-Length']
+ headers['Content-Length'] = length.to_s
+ elsif !body.respond_to?(:call) && (!status || status.to_s[0..2] != '304')
+ headers["Content-Length"] = body.size.to_s
end
- headers["Content-Length"] = headers["Content-Length"].to_s if headers["Content-Length"]
end
def convert_language!
diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb
index 5975977365..044ace7de1 100644
--- a/actionpack/lib/action_controller/routing/route_set.rb
+++ b/actionpack/lib/action_controller/routing/route_set.rb
@@ -195,8 +195,8 @@ module ActionController
def formatted_#{selector}(*args) # def formatted_users_url(*args)
ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn(
"formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " +
- "please pass format to the standard" + # "please pass format to the standard" +
- "#{selector}() method instead.", caller) # "users_url() method instead.", caller)
+ "Please pass format to the standard " + # "Please pass format to the standard " +
+ "#{selector} method instead.", caller) # "users_url method instead.", caller)
#{selector}(*args) # users_url(*args)
end # end
protected :#{selector} # protected :users_url
@@ -427,6 +427,12 @@ module ActionController
end
end
+ def call(env)
+ request = Request.new(env)
+ app = Routing::Routes.recognize(request)
+ app.call(env).to_a
+ end
+
def recognize(request)
params = recognize_path(request.path, extract_request_environment(request))
request.path_parameters = params.with_indifferent_access
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 7ed1a3e160..0b0d0c799b 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -1,4 +1,5 @@
require 'active_support/test_case'
+require 'action_controller/test_process'
module ActionController
# Superclass for ActionController functional tests. Functional tests allow you to
@@ -102,6 +103,8 @@ module ActionController
#
# assert_redirected_to page_url(:title => 'foo')
class TestCase < ActiveSupport::TestCase
+ include TestProcess
+
module Assertions
%w(response selector tag dom routing model).each do |kind|
include ActionController::Assertions.const_get("#{kind.camelize}Assertions")
@@ -124,17 +127,18 @@ module ActionController
#
# The exception is stored in the exception accessor for further inspection.
module RaiseActionExceptions
- attr_accessor :exception
+ protected
+ attr_accessor :exception
- def rescue_action_without_handler(e)
- self.exception = e
-
- if request.remote_addr == "0.0.0.0"
- raise(e)
- else
- super(e)
+ def rescue_action_without_handler(e)
+ self.exception = e
+
+ if request.remote_addr == "0.0.0.0"
+ raise(e)
+ else
+ super(e)
+ end
end
- end
end
setup :setup_controller_request_and_response
diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb
index 285a8b09e4..8180d4ee93 100644
--- a/actionpack/lib/action_controller/test_process.rb
+++ b/actionpack/lib/action_controller/test_process.rb
@@ -1,32 +1,4 @@
-require 'action_controller/test_case'
-
module ActionController #:nodoc:
- class Base
- attr_reader :assigns
-
- # Process a test request called with a TestRequest object.
- def self.process_test(request)
- new.process_test(request)
- end
-
- def process_test(request) #:nodoc:
- process(request, TestResponse.new)
- end
-
- def process_with_test(*args)
- returning process_without_test(*args) do
- @assigns = {}
- (instance_variable_names - @@protected_instance_variables).each do |var|
- value = instance_variable_get(var)
- @assigns[var[1..-1]] = value
- response.template.assigns[var[1..-1]] = value if response
- end
- end
- end
-
- alias_method_chain :process, :test
- end
-
class TestRequest < Request #:nodoc:
attr_accessor :cookies, :session_options
attr_accessor :query_parameters, :path, :session
@@ -433,7 +405,9 @@ module ActionController #:nodoc:
@request.session = ActionController::TestSession.new(session) unless session.nil?
@request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
build_request_uri(action, parameters)
- @controller.process(@request, @response)
+
+ Base.class_eval { include ProcessWithTest } unless Base < ProcessWithTest
+ @controller.process_with_test(@request, @response)
end
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@@ -545,12 +519,24 @@ module ActionController #:nodoc:
ActionController::Routing.const_set(:Routes, real_routes) if real_routes
end
end
-end
-module Test
- module Unit
- class TestCase #:nodoc:
- include ActionController::TestProcess
+ module ProcessWithTest #:nodoc:
+ def self.included(base)
+ base.class_eval { attr_reader :assigns }
+ end
+
+ def process_with_test(*args)
+ process(*args).tap { set_test_assigns }
end
+
+ private
+ def set_test_assigns
+ @assigns = {}
+ (instance_variable_names - self.class.protected_instance_variables).each do |var|
+ name, value = var[1..-1], instance_variable_get(var)
+ @assigns[name] = value
+ response.template.assigns[name] = value if response
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/url_encoded_pair_parser.rb b/actionpack/lib/action_controller/url_encoded_pair_parser.rb
index bea96c711d..9883ad0d85 100644
--- a/actionpack/lib/action_controller/url_encoded_pair_parser.rb
+++ b/actionpack/lib/action_controller/url_encoded_pair_parser.rb
@@ -70,11 +70,12 @@ module ActionController
top[-1][key] = value
else
top << {key => value}.with_indifferent_access
- push top.last
- value = top[key]
end
+ push top.last
+ return top[key]
else
top << value
+ return value
end
elsif top.is_a? Hash
key = CGI.unescape(key)
@@ -84,12 +85,10 @@ module ActionController
else
raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
end
-
- return value
end
def type_conflict!(klass, value)
raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 8958e61e9d..70a0ba91a7 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -225,7 +225,7 @@ module ActionView #:nodoc:
end
# Returns the result of a render that's dictated by the options hash. The primary options are:
- #
+ #
# * <tt>:partial</tt> - See ActionView::Partials.
# * <tt>:update</tt> - Calls update_page with the block given.
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index e595c534dd..7e1110afab 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -157,7 +157,7 @@ module ActionView
# javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr.js
# javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js
def javascript_path(source)
- JavaScriptTag.new(self, @controller, source).public_path
+ compute_public_path(source, 'javascripts', 'js')
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
@@ -255,17 +255,15 @@ module ActionView
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name)
- unless File.exists?(joined_javascript_path)
- JavaScriptSources.create(self, @controller, sources, recursive).write_asset_file_contents(joined_javascript_path)
- end
+ write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) unless File.exists?(joined_javascript_path)
javascript_src_tag(joined_javascript_name, options)
else
- JavaScriptSources.create(self, @controller, sources, recursive).expand_sources.collect { |source|
- javascript_src_tag(source, options)
- }.join("\n")
+ expand_javascript_sources(sources, recursive).collect { |source| javascript_src_tag(source, options) }.join("\n")
end
end
+ @@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
+
# Register one or more javascript files to be included when <tt>symbol</tt>
# is passed to <tt>javascript_include_tag</tt>. This method is typically intended
# to be called from plugin initialization to register javascript files
@@ -278,9 +276,11 @@ module ActionView
# <script type="text/javascript" src="/javascripts/body.js"></script>
# <script type="text/javascript" src="/javascripts/tail.js"></script>
def self.register_javascript_expansion(expansions)
- JavaScriptSources.expansions.merge!(expansions)
+ @@javascript_expansions.merge!(expansions)
end
+ @@stylesheet_expansions = {}
+
# Register one or more stylesheet files to be included when <tt>symbol</tt>
# is passed to <tt>stylesheet_link_tag</tt>. This method is typically intended
# to be called from plugin initialization to register stylesheet files
@@ -293,7 +293,7 @@ module ActionView
# <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" />
# <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" />
def self.register_stylesheet_expansion(expansions)
- StylesheetSources.expansions.merge!(expansions)
+ @@stylesheet_expansions.merge!(expansions)
end
# Register one or more additional JavaScript files to be included when
@@ -301,11 +301,11 @@ module ActionView
# typically intended to be called from plugin initialization to register additional
# .js files that the plugin installed in <tt>public/javascripts</tt>.
def self.register_javascript_include_default(*sources)
- JavaScriptSources.expansions[:defaults].concat(sources)
+ @@javascript_expansions[:defaults].concat(sources)
end
def self.reset_javascript_include_default #:nodoc:
- JavaScriptSources.expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup
+ @@javascript_expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup
end
# Computes the path to a stylesheet asset in the public stylesheets directory.
@@ -320,7 +320,7 @@ module ActionView
# stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style.css
# stylesheet_path "http://www.railsapplication.com/css/style.js" # => http://www.railsapplication.com/css/style.css
def stylesheet_path(source)
- StylesheetTag.new(self, @controller, source).public_path
+ compute_public_path(source, 'stylesheets', 'css')
end
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
@@ -365,7 +365,6 @@ module ActionView
# compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
# is set to true (which is the case by default for the Rails production environment, but not for the development
# environment). Examples:
-
#
# ==== Examples
# stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
@@ -396,14 +395,10 @@ module ActionView
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name)
- unless File.exists?(joined_stylesheet_path)
- StylesheetSources.create(self, @controller, sources, recursive).write_asset_file_contents(joined_stylesheet_path)
- end
+ write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) unless File.exists?(joined_stylesheet_path)
stylesheet_tag(joined_stylesheet_name, options)
else
- StylesheetSources.create(self, @controller, sources, recursive).expand_sources.collect { |source|
- stylesheet_tag(source, options)
- }.join("\n")
+ expand_stylesheet_sources(sources, recursive).collect { |source| stylesheet_tag(source, options) }.join("\n")
end
end
@@ -418,7 +413,7 @@ module ActionView
# image_path("/icons/edit.png") # => /icons/edit.png
# image_path("http://www.railsapplication.com/img/edit.png") # => http://www.railsapplication.com/img/edit.png
def image_path(source)
- ImageTag.new(self, @controller, source).public_path
+ compute_public_path(source, 'images')
end
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
@@ -473,353 +468,190 @@ module ActionView
tag("img", options)
end
- private
- def javascript_src_tag(source, options)
- content_tag("script", "", { "type" => Mime::JS, "src" => path_to_javascript(source) }.merge(options))
- end
-
- def stylesheet_tag(source, options)
- tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
- end
-
- module ImageAsset
- DIRECTORY = 'images'.freeze
-
- def directory
- DIRECTORY
- end
-
- def extension
- nil
- end
- end
-
- module JavaScriptAsset
- DIRECTORY = 'javascripts'.freeze
- EXTENSION = 'js'.freeze
-
- def public_directory
- JAVASCRIPTS_DIR
- end
-
- def directory
- DIRECTORY
- end
+ def self.cache_asset_timestamps
+ @@cache_asset_timestamps
+ end
- def extension
- EXTENSION
- end
- end
+ # You can enable or disable the asset tag timestamps cache.
+ # With the cache enabled, the asset tag helper methods will make fewer
+ # expense file system calls. However this prevents you from modifying
+ # any asset files while the server is running.
+ #
+ # ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
+ def self.cache_asset_timestamps=(value)
+ @@cache_asset_timestamps = value
+ end
- module StylesheetAsset
- DIRECTORY = 'stylesheets'.freeze
- EXTENSION = 'css'.freeze
+ @@cache_asset_timestamps = true
- def public_directory
- STYLESHEETS_DIR
+ private
+ # Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
+ # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
+ # roots. Rewrite the asset path for cache-busting asset ids. Include
+ # asset host, if configured, with the correct request protocol.
+ def compute_public_path(source, dir, ext = nil, include_host = true)
+ has_request = @controller.respond_to?(:request)
+
+ if ext && (File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}")))
+ source += ".#{ext}"
end
- def directory
- DIRECTORY
- end
+ unless source =~ %r{^[-a-z]+://}
+ source = "/#{dir}/#{source}" unless source[0] == ?/
- def extension
- EXTENSION
- end
- end
+ source = rewrite_asset_path(source)
- class AssetTag
- extend ActiveSupport::Memoizable
-
- Cache = {}
- CacheGuard = Mutex.new
-
- ProtocolRegexp = %r{^[-a-z]+://}.freeze
-
- def initialize(template, controller, source, include_host = true)
- # NOTE: The template arg is temporarily needed for a legacy plugin
- # hook that is expected to call rewrite_asset_path on the
- # template. This should eventually be removed.
- @template = template
- @controller = controller
- @source = source
- @include_host = include_host
- @cache_key = if controller.respond_to?(:request)
- [self.class.name,controller.request.protocol,
- ActionController::Base.asset_host,
- ActionController::Base.relative_url_root,
- source, include_host]
- else
- [self.class.name,ActionController::Base.asset_host, source, include_host]
+ if has_request && include_host
+ unless source =~ %r{^#{ActionController::Base.relative_url_root}/}
+ source = "#{ActionController::Base.relative_url_root}#{source}"
+ end
end
end
-
- def public_path
- compute_public_path(@source)
- end
- memoize :public_path
- def asset_file_path
- File.join(ASSETS_DIR, public_path.split('?').first)
- end
- memoize :asset_file_path
-
- def contents
- File.read(asset_file_path)
- end
-
- def mtime
- File.mtime(asset_file_path)
- end
-
- private
- def request
- request? && @controller.request
- end
+ if include_host && source !~ %r{^[-a-z]+://}
+ host = compute_asset_host(source)
- def request?
- @controller.respond_to?(:request)
+ if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
+ host = "#{@controller.request.protocol}#{host}"
end
- # Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
- # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
- # roots. Rewrite the asset path for cache-busting asset ids. Include
- # asset host, if configured, with the correct request protocol.
- def compute_public_path(source)
- if source =~ ProtocolRegexp
- source += ".#{extension}" if missing_extension?(source)
- source = prepend_asset_host(source)
- source
- else
- CacheGuard.synchronize do
- Cache[@cache_key + [source]] ||= begin
- source += ".#{extension}" if missing_extension?(source) || file_exists_with_extension?(source)
- source = "/#{directory}/#{source}" unless source[0] == ?/
- source = rewrite_asset_path(source)
- source = prepend_relative_url_root(source)
- source = prepend_asset_host(source)
- source
- end
- end
- end
- end
-
- def missing_extension?(source)
- extension && File.extname(source).blank?
- end
-
- def file_exists_with_extension?(source)
- extension && File.exist?(File.join(ASSETS_DIR, directory, "#{source}.#{extension}"))
- end
-
- def prepend_relative_url_root(source)
- relative_url_root = ActionController::Base.relative_url_root
- if request? && @include_host && source !~ %r{^#{relative_url_root}/}
- "#{relative_url_root}#{source}"
- else
- source
- end
- end
+ "#{host}#{source}"
+ else
+ source
+ end
+ end
- def prepend_asset_host(source)
- if @include_host && source !~ ProtocolRegexp
- host = compute_asset_host(source)
- if request? && !host.blank? && host !~ ProtocolRegexp
- host = "#{request.protocol}#{host}"
- end
- "#{host}#{source}"
+ # Pick an asset host for this source. Returns +nil+ if no host is set,
+ # the host if no wildcard is set, the host interpolated with the
+ # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
+ # or the value returned from invoking the proc if it's a proc or the value from
+ # invoking call if it's an object responding to call.
+ def compute_asset_host(source)
+ if host = ActionController::Base.asset_host
+ if host.is_a?(Proc) || host.respond_to?(:call)
+ case host.is_a?(Proc) ? host.arity : host.method(:call).arity
+ when 2
+ request = @controller.respond_to?(:request) && @controller.request
+ host.call(source, request)
else
- source
- end
- end
-
- # Pick an asset host for this source. Returns +nil+ if no host is set,
- # the host if no wildcard is set, the host interpolated with the
- # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
- # or the value returned from invoking the proc if it's a proc or the value from
- # invoking call if it's an object responding to call.
- def compute_asset_host(source)
- if host = ActionController::Base.asset_host
- if host.is_a?(Proc) || host.respond_to?(:call)
- case host.is_a?(Proc) ? host.arity : host.method(:call).arity
- when 2
- host.call(source, request)
- else
- host.call(source)
- end
- else
- (host =~ /%d/) ? host % (source.hash % 4) : host
- end
+ host.call(source)
end
+ else
+ (host =~ /%d/) ? host % (source.hash % 4) : host
end
+ end
+ end
- # Use the RAILS_ASSET_ID environment variable or the source's
- # modification time as its cache-busting asset id.
- def rails_asset_id(source)
- if asset_id = ENV["RAILS_ASSET_ID"]
- asset_id
- else
- path = File.join(ASSETS_DIR, source)
+ @@asset_timestamps_cache = {}
+ @@asset_timestamps_cache_guard = Mutex.new
+
+ # Use the RAILS_ASSET_ID environment variable or the source's
+ # modification time as its cache-busting asset id.
+ def rails_asset_id(source)
+ if asset_id = ENV["RAILS_ASSET_ID"]
+ asset_id
+ else
+ if @@cache_asset_timestamps && (asset_id = @@asset_timestamps_cache[source])
+ asset_id
+ else
+ path = File.join(ASSETS_DIR, source)
+ asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : ''
- if File.exist?(path)
- File.mtime(path).to_i.to_s
- else
- ''
+ if @@cache_asset_timestamps
+ @@asset_timestamps_cache_guard.synchronize do
+ @@asset_timestamps_cache[source] = asset_id
end
end
- end
- # Break out the asset path rewrite in case plugins wish to put the asset id
- # someplace other than the query string.
- def rewrite_asset_path(source)
- if @template.respond_to?(:rewrite_asset_path)
- # DEPRECATE: This way to override rewrite_asset_path
- @template.send(:rewrite_asset_path, source)
- else
- asset_id = rails_asset_id(source)
- if asset_id.blank?
- source
- else
- "#{source}?#{asset_id}"
- end
- end
+ asset_id
end
+ end
end
- class ImageTag < AssetTag
- include ImageAsset
+ # Break out the asset path rewrite in case plugins wish to put the asset id
+ # someplace other than the query string.
+ def rewrite_asset_path(source)
+ asset_id = rails_asset_id(source)
+ if asset_id.blank?
+ source
+ else
+ source + "?#{asset_id}"
+ end
end
- class JavaScriptTag < AssetTag
- include JavaScriptAsset
+ def javascript_src_tag(source, options)
+ content_tag("script", "", { "type" => Mime::JS, "src" => path_to_javascript(source) }.merge(options))
end
- class StylesheetTag < AssetTag
- include StylesheetAsset
+ def stylesheet_tag(source, options)
+ tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
end
- class AssetCollection
- extend ActiveSupport::Memoizable
-
- Cache = {}
- CacheGuard = Mutex.new
-
- def self.create(template, controller, sources, recursive)
- CacheGuard.synchronize do
- key = [self, sources, recursive]
- Cache[key] ||= new(template, controller, sources, recursive).freeze
- end
- end
+ def compute_javascript_paths(*args)
+ expand_javascript_sources(*args).collect { |source| compute_public_path(source, 'javascripts', 'js', false) }
+ end
- def initialize(template, controller, sources, recursive)
- # NOTE: The template arg is temporarily needed for a legacy plugin
- # hook. See NOTE under AssetTag#initialize for more details
- @template = template
- @controller = controller
- @sources = sources
- @recursive = recursive
- end
+ def compute_stylesheet_paths(*args)
+ expand_stylesheet_sources(*args).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) }
+ end
- def write_asset_file_contents(joined_asset_path)
- FileUtils.mkdir_p(File.dirname(joined_asset_path))
- File.open(joined_asset_path, "w+") { |cache| cache.write(joined_contents) }
- mt = latest_mtime
- File.utime(mt, mt, joined_asset_path)
+ def expand_javascript_sources(sources, recursive = false)
+ if sources.include?(:all)
+ all_javascript_files = collect_asset_files(JAVASCRIPTS_DIR, ('**' if recursive), '*.js')
+ ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq
+ else
+ expanded_sources = sources.collect do |source|
+ determine_source(source, @@javascript_expansions)
+ end.flatten
+ expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
+ expanded_sources
end
-
- private
- def determine_source(source, collection)
- case source
- when Symbol
- collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
- else
- source
- end
- end
-
- def validate_sources!
- @sources.collect { |source| determine_source(source, self.class.expansions) }.flatten
- end
-
- def all_asset_files
- path = [public_directory, ('**' if @recursive), "*.#{extension}"].compact
- Dir[File.join(*path)].collect { |file|
- file[-(file.size - public_directory.size - 1)..-1].sub(/\.\w+$/, '')
- }.sort
- end
-
- def tag_sources
- expand_sources.collect { |source| tag_class.new(@template, @controller, source, false) }
- end
-
- def joined_contents
- tag_sources.collect { |source| source.contents }.join("\n\n")
- end
-
- # Set mtime to the latest of the combined files to allow for
- # consistent ETag without a shared filesystem.
- def latest_mtime
- tag_sources.map { |source| source.mtime }.max
- end
end
- class JavaScriptSources < AssetCollection
- include JavaScriptAsset
-
- EXPANSIONS = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
-
- def self.expansions
- EXPANSIONS
+ def expand_stylesheet_sources(sources, recursive)
+ if sources.first == :all
+ collect_asset_files(STYLESHEETS_DIR, ('**' if recursive), '*.css')
+ else
+ sources.collect do |source|
+ determine_source(source, @@stylesheet_expansions)
+ end.flatten
end
+ end
- APPLICATION_JS = "application".freeze
- APPLICATION_FILE = "application.js".freeze
-
- def expand_sources
- if @sources.include?(:all)
- assets = all_asset_files
- ((defaults.dup & assets) + assets).uniq!
- else
- expanded_sources = validate_sources!
- expanded_sources << APPLICATION_JS if include_application?
- expanded_sources
- end
+ def determine_source(source, collection)
+ case source
+ when Symbol
+ collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
+ else
+ source
end
- memoize :expand_sources
-
- private
- def tag_class
- JavaScriptTag
- end
-
- def defaults
- determine_source(:defaults, self.class.expansions)
- end
+ end
- def include_application?
- @sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, APPLICATION_FILE))
- end
+ def join_asset_file_contents(paths)
+ paths.collect { |path| File.read(asset_file_path(path)) }.join("\n\n")
end
- class StylesheetSources < AssetCollection
- include StylesheetAsset
+ def write_asset_file_contents(joined_asset_path, asset_paths)
+ FileUtils.mkdir_p(File.dirname(joined_asset_path))
+ File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
- EXPANSIONS = {}
+ # Set mtime to the latest of the combined files to allow for
+ # consistent ETag without a shared filesystem.
+ mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max
+ File.utime(mt, mt, joined_asset_path)
+ end
- def self.expansions
- EXPANSIONS
- end
+ def asset_file_path(path)
+ File.join(ASSETS_DIR, path.split('?').first)
+ end
- def expand_sources
- @sources.first == :all ? all_asset_files : validate_sources!
- end
- memoize :expand_sources
+ def collect_asset_files(*path)
+ dir = path.first
- private
- def tag_class
- StylesheetTag
- end
+ Dir[File.join(*path.compact)].collect do |file|
+ file[-(file.size - dir.size - 1)..-1].sub(/\.\w+$/, '')
+ end.sort
end
end
end
-end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/benchmark_helper.rb b/actionpack/lib/action_view/helpers/benchmark_helper.rb
index 372d24a22e..61c2cecb04 100644
--- a/actionpack/lib/action_view/helpers/benchmark_helper.rb
+++ b/actionpack/lib/action_view/helpers/benchmark_helper.rb
@@ -18,12 +18,33 @@ module ActionView
# That would add something like "Process data files (345.2ms)" to the log,
# which you can then use to compare timings when optimizing your code.
#
- # You may give an optional logger level as the second argument
+ # You may give an optional logger level as the :level option.
# (:debug, :info, :warn, :error); the default value is :info.
- def benchmark(message = "Benchmarking", level = :info)
+ #
+ # <% benchmark "Low-level files", :level => :debug do %>
+ # <%= lowlevel_files_operation %>
+ # <% end %>
+ #
+ # Finally, you can pass true as the third argument to silence all log activity
+ # inside the block. This is great for boiling down a noisy block to just a single statement:
+ #
+ # <% benchmark "Process data files", :level => :info, :silence => true do %>
+ # <%= expensive_and_chatty_files_operation %>
+ # <% end %>
+ def benchmark(message = "Benchmarking", options = {})
if controller.logger
- ms = Benchmark.ms { yield }
- controller.logger.send(level, '%s (%.1fms)' % [message, ms])
+ if options.is_a?(Symbol)
+ ActiveSupport::Deprecation.warn("use benchmark('#{message}', :level => :#{options}) instead", caller)
+ options = { :level => options, :silence => false }
+ else
+ options.assert_valid_keys(:level, :silence)
+ options[:level] ||= :info
+ end
+
+ result = nil
+ ms = Benchmark.ms { result = options[:silence] ? controller.logger.silence { yield } : yield }
+ controller.logger.send(options[:level], '%s (%.1fms)' % [ message, ms ])
+ result
else
yield
end
diff --git a/actionpack/lib/action_view/inline_template.rb b/actionpack/lib/action_view/inline_template.rb
index 5e00cef13f..54efa543c8 100644
--- a/actionpack/lib/action_view/inline_template.rb
+++ b/actionpack/lib/action_view/inline_template.rb
@@ -12,7 +12,7 @@ module ActionView #:nodoc:
private
# Always recompile inline templates
- def recompile?(local_assigns)
+ def recompile?
true
end
end
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
index b030156889..19207e7262 100644
--- a/actionpack/lib/action_view/paths.rb
+++ b/actionpack/lib/action_view/paths.rb
@@ -2,7 +2,7 @@ module ActionView #:nodoc:
class PathSet < Array #:nodoc:
def self.type_cast(obj)
if obj.is_a?(String)
- Path.new(obj)
+ Template::EagerPath.new(obj)
else
obj
end
@@ -32,111 +32,6 @@ module ActionView #:nodoc:
super(*objs.map { |obj| self.class.type_cast(obj) })
end
- class Path #:nodoc:
- attr_reader :path, :paths
- delegate :hash, :inspect, :to => :path
-
- def initialize(path, load = false)
- raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
- @path = path.freeze
- reload! if load
- end
-
- def to_s
- if defined?(RAILS_ROOT)
- path.to_s.sub(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '')
- else
- path.to_s
- end
- end
-
- def to_str
- path.to_str
- end
-
- def ==(path)
- to_str == path.to_str
- end
-
- def eql?(path)
- to_str == path.to_str
- end
-
- # Returns a ActionView::Template object for the given path string. The
- # input path should be relative to the view path directory,
- # +hello/index.html.erb+. This method also has a special exception to
- # match partial file names without a handler extension. So
- # +hello/index.html+ will match the first template it finds with a
- # known template extension, +hello/index.html.erb+. Template extensions
- # should not be confused with format extensions +html+, +js+, +xml+,
- # etc. A format must be supplied to match a formated file. +hello/index+
- # will never match +hello/index.html.erb+.
- #
- # This method also has two different implementations, one that is "lazy"
- # and makes file system calls every time and the other is cached,
- # "eager" which looks up the template in an in memory index. The "lazy"
- # version is designed for development where you want to automatically
- # find new templates between requests. The "eager" version is designed
- # for production mode and it is much faster but requires more time
- # upfront to build the file index.
- def [](path)
- if loaded?
- @paths[path]
- else
- Dir.glob("#{@path}/#{path}*").each do |file|
- template = create_template(file)
- if template.accessible_paths.include?(path)
- return template
- end
- end
- nil
- end
- end
-
- def loaded?
- @loaded ? true : false
- end
-
- def load
- reload! unless loaded?
- self
- end
-
- # Rebuild load path directory cache
- def reload!
- @paths = {}
-
- templates_in_path do |template|
- template.load!
- template.accessible_paths.each do |path|
- @paths[path] = template
- end
- end
-
- @paths.freeze
- @loaded = true
- end
-
- private
- def templates_in_path
- (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file|
- yield create_template(file) unless File.directory?(file)
- end
- end
-
- def create_template(file)
- Template.new(file.split("#{self}/").last, self)
- end
- end
-
- def load
- each { |path| path.load }
- end
-
- def reload!
- each { |path| path.reload! }
- end
-
def find_template(original_template_path, format = nil)
return original_template_path if original_template_path.respond_to?(:render)
template_path = original_template_path.sub(/^\//, '')
diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb
index d8e72f1179..153e14f68b 100644
--- a/actionpack/lib/action_view/renderable.rb
+++ b/actionpack/lib/action_view/renderable.rb
@@ -60,7 +60,7 @@ module ActionView
def compile(local_assigns)
render_symbol = method_name(local_assigns)
- if recompile?(render_symbol)
+ if !Base::CompiledTemplates.method_defined?(render_symbol) || recompile?
compile!(render_symbol, local_assigns)
end
end
@@ -89,11 +89,8 @@ module ActionView
end
end
- # Method to check whether template compilation is necessary.
- # The template will be compiled if the file has not been compiled yet, or
- # if local_assigns has a new key, which isn't supported by the compiled code yet.
- def recompile?(symbol)
- !Base::CompiledTemplates.method_defined?(symbol) || !loaded?
+ def recompile?
+ false
end
end
end
diff --git a/actionpack/lib/action_view/renderable_partial.rb b/actionpack/lib/action_view/renderable_partial.rb
index d92ff1b8d3..3ea836fa25 100644
--- a/actionpack/lib/action_view/renderable_partial.rb
+++ b/actionpack/lib/action_view/renderable_partial.rb
@@ -25,12 +25,11 @@ module ActionView
end
def render_partial(view, object = nil, local_assigns = {}, as = nil)
- object ||= local_assigns[:object] ||
- local_assigns[variable_name]
+ object ||= local_assigns[:object] || local_assigns[variable_name]
- if view.respond_to?(:controller)
+ if object.nil? && view.respond_to?(:controller)
ivar = :"@#{variable_name}"
- object ||=
+ object =
if view.controller.instance_variable_defined?(ivar)
ActiveSupport::Deprecation::DeprecatedObjectProxy.new(
view.controller.instance_variable_get(ivar),
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 5b384d0e4d..88ee07d95b 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -1,5 +1,83 @@
module ActionView #:nodoc:
class Template
+ class Path
+ attr_reader :path, :paths
+ delegate :hash, :inspect, :to => :path
+
+ def initialize(path)
+ raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
+ @path = path.freeze
+ end
+
+ def to_s
+ if defined?(RAILS_ROOT)
+ path.to_s.sub(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '')
+ else
+ path.to_s
+ end
+ end
+
+ def to_str
+ path.to_str
+ end
+
+ def ==(path)
+ to_str == path.to_str
+ end
+
+ def eql?(path)
+ to_str == path.to_str
+ end
+
+ # Returns a ActionView::Template object for the given path string. The
+ # input path should be relative to the view path directory,
+ # +hello/index.html.erb+. This method also has a special exception to
+ # match partial file names without a handler extension. So
+ # +hello/index.html+ will match the first template it finds with a
+ # known template extension, +hello/index.html.erb+. Template extensions
+ # should not be confused with format extensions +html+, +js+, +xml+,
+ # etc. A format must be supplied to match a formated file. +hello/index+
+ # will never match +hello/index.html.erb+.
+ def [](path)
+ templates_in_path do |template|
+ if template.accessible_paths.include?(path)
+ return template
+ end
+ end
+ nil
+ end
+
+ private
+ def templates_in_path
+ (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file|
+ yield create_template(file) unless File.directory?(file)
+ end
+ end
+
+ def create_template(file)
+ Template.new(file.split("#{self}/").last, self)
+ end
+ end
+
+ class EagerPath < Path
+ def initialize(path)
+ super
+
+ @paths = {}
+ templates_in_path do |template|
+ template.load!
+ template.accessible_paths.each do |path|
+ @paths[path] = template
+ end
+ end
+ @paths.freeze
+ end
+
+ def [](path)
+ @paths[path]
+ end
+ end
+
extend TemplateHandlers
extend ActiveSupport::Memoizable
include Renderable
@@ -115,13 +193,12 @@ module ActionView #:nodoc:
File.mtime(filename) > mtime
end
- def loaded?
- @loaded
+ def recompile?
+ !@cached
end
def load!
- @loaded = true
- compile({})
+ @cached = true
freeze
end
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 1a9ef983a5..ec337bb05b 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -1,3 +1,5 @@
+require 'active_support/test_case'
+
module ActionView
class Base
alias_method :initialize_without_template_tracking, :initialize
@@ -21,6 +23,7 @@ module ActionView
class TestCase < ActiveSupport::TestCase
include ActionController::TestCase::Assertions
+ include ActionController::TestProcess
class_inheritable_accessor :helper_class
@@helper_class = nil
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 2ba056e60f..30e2d863d0 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -34,7 +34,6 @@ ActionController::Base.session_store = nil
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
ActionController::Base.view_paths = FIXTURE_LOAD_PATH
-ActionController::Base.view_paths.load
def uses_mocha(test_name)
yield
diff --git a/actionpack/test/controller/addresses_render_test.rb b/actionpack/test/controller/addresses_render_test.rb
index b26cae24fb..556b0593ea 100644
--- a/actionpack/test/controller/addresses_render_test.rb
+++ b/actionpack/test/controller/addresses_render_test.rb
@@ -19,17 +19,14 @@ class AddressesTestController < ActionController::Base
def self.controller_path; "addresses"; end
end
-class AddressesTest < Test::Unit::TestCase
- def setup
- @controller = AddressesTestController.new
+class AddressesTest < ActionController::TestCase
+ tests AddressesTestController
+ def setup
# enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
# a more accurate simulation of what happens in "real life".
@controller.logger = Logger.new(nil)
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
-
@request.host = "www.nextangle.com"
end
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 18d185b264..9523189f41 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -129,6 +129,8 @@ class PerformActionTest < ActionController::TestCase
@response = ActionController::TestResponse.new
@request.host = "www.nextangle.com"
+
+ rescue_action_in_public!
end
def test_get_on_priv_should_show_selector
@@ -164,14 +166,12 @@ class PerformActionTest < ActionController::TestCase
end
end
-class DefaultUrlOptionsTest < Test::Unit::TestCase
- def setup
- @controller = DefaultUrlOptionsController.new
-
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
+class DefaultUrlOptionsTest < ActionController::TestCase
+ tests DefaultUrlOptionsController
+ def setup
@request.host = 'www.example.com'
+ rescue_action_in_public!
end
def test_default_url_options_are_used_if_set
@@ -189,14 +189,12 @@ class DefaultUrlOptionsTest < Test::Unit::TestCase
end
end
-class EmptyUrlOptionsTest < Test::Unit::TestCase
- def setup
- @controller = NonEmptyController.new
-
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
+class EmptyUrlOptionsTest < ActionController::TestCase
+ tests NonEmptyController
+ def setup
@request.host = 'www.example.com'
+ rescue_action_in_public!
end
def test_ensure_url_for_works_as_expected_when_called_with_no_options_if_default_url_options_is_not_set
diff --git a/actionpack/test/controller/benchmark_test.rb b/actionpack/test/controller/benchmark_test.rb
index 608ea5f5a9..f9100a2313 100644
--- a/actionpack/test/controller/benchmark_test.rb
+++ b/actionpack/test/controller/benchmark_test.rb
@@ -11,17 +11,17 @@ class BenchmarkedController < ActionController::Base
end
end
-class BenchmarkTest < Test::Unit::TestCase
+class BenchmarkTest < ActionController::TestCase
+ tests BenchmarkedController
+
class MockLogger
def method_missing(*args)
end
end
def setup
- @controller = BenchmarkedController.new
# benchmark doesn't do anything unless a logger is set
@controller.logger = MockLogger.new
- @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
@request.host = "test.actioncontroller.i"
end
diff --git a/actionpack/test/controller/capture_test.rb b/actionpack/test/controller/capture_test.rb
index 5ded6a5d26..6dfa0995eb 100644
--- a/actionpack/test/controller/capture_test.rb
+++ b/actionpack/test/controller/capture_test.rb
@@ -23,17 +23,14 @@ class CaptureController < ActionController::Base
def rescue_action(e) raise end
end
-class CaptureTest < Test::Unit::TestCase
- def setup
- @controller = CaptureController.new
+class CaptureTest < ActionController::TestCase
+ tests CaptureController
+ def setup
# enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
# a more accurate simulation of what happens in "real life".
@controller.logger = Logger.new(nil)
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
-
@request.host = "www.nextangle.com"
end
diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb
index ae71d62e11..32c1757ef9 100644
--- a/actionpack/test/controller/content_type_test.rb
+++ b/actionpack/test/controller/content_type_test.rb
@@ -50,16 +50,13 @@ class ContentTypeController < ActionController::Base
def rescue_action(e) raise end
end
-class ContentTypeTest < Test::Unit::TestCase
- def setup
- @controller = ContentTypeController.new
+class ContentTypeTest < ActionController::TestCase
+ tests ContentTypeController
+ def setup
# enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
# a more accurate simulation of what happens in "real life".
@controller.logger = Logger.new(nil)
-
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
end
def test_render_defaults
diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb
index 3ddc5768a9..9508348ca1 100644
--- a/actionpack/test/controller/cookie_test.rb
+++ b/actionpack/test/controller/cookie_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-class CookieTest < Test::Unit::TestCase
+class CookieTest < ActionController::TestCase
class TestController < ActionController::Base
def authenticate
cookies["user_name"] = "david"
@@ -41,11 +41,9 @@ class CookieTest < Test::Unit::TestCase
end
end
- def setup
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
+ tests TestController
- @controller = TestController.new
+ def setup
@request.host = "www.nextangle.com"
end
diff --git a/actionpack/test/controller/dispatcher_test.rb b/actionpack/test/controller/dispatcher_test.rb
index fd06b4ea99..7cd4e71aa1 100644
--- a/actionpack/test/controller/dispatcher_test.rb
+++ b/actionpack/test/controller/dispatcher_test.rb
@@ -32,11 +32,6 @@ class DispatcherTest < Test::Unit::TestCase
dispatch(false)
end
- def test_clears_asset_tag_cache_before_dispatch_if_in_loading_mode
- ActionView::Helpers::AssetTagHelper::AssetTag::Cache.expects(:clear).once
- dispatch(false)
- end
-
def test_leaves_dependencies_after_dispatch_if_not_in_loading_mode
ActionController::Routing::Routes.expects(:reload).never
ActiveSupport::Dependencies.expects(:clear).never
@@ -96,9 +91,7 @@ class DispatcherTest < Test::Unit::TestCase
private
def dispatch(cache_classes = true)
- controller = mock()
- controller.stubs(:process).returns([200, {}, 'response'])
- ActionController::Routing::Routes.stubs(:recognize).returns(controller)
+ ActionController::Routing::RouteSet.any_instance.stubs(:call).returns([200, {}, 'response'])
Dispatcher.define_dispatcher_callbacks(cache_classes)
@dispatcher.call({})
end
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index dafa344473..e83fde2349 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -634,9 +634,11 @@ class FilterTest < Test::Unit::TestCase
private
def test_process(controller, action = "show")
+ ActionController::Base.class_eval { include ActionController::ProcessWithTest } unless ActionController::Base < ActionController::ProcessWithTest
request = ActionController::TestRequest.new
request.action = action
- controller.process(request, ActionController::TestResponse.new)
+ controller = controller.new if controller.is_a?(Class)
+ controller.process_with_test(request, ActionController::TestResponse.new)
end
end
@@ -874,8 +876,10 @@ class YieldingAroundFiltersTest < Test::Unit::TestCase
protected
def test_process(controller, action = "show")
+ ActionController::Base.class_eval { include ActionController::ProcessWithTest } unless ActionController::Base < ActionController::ProcessWithTest
request = ActionController::TestRequest.new
request.action = action
- controller.process(request, ActionController::TestResponse.new)
+ controller = controller.new if controller.is_a?(Class)
+ controller.process_with_test(request, ActionController::TestResponse.new)
end
end
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index e562531bf3..d8a892811e 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-class FlashTest < Test::Unit::TestCase
+class FlashTest < ActionController::TestCase
class TestController < ActionController::Base
def set_flash
flash["that"] = "hello"
@@ -73,11 +73,7 @@ class FlashTest < Test::Unit::TestCase
end
end
- def setup
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
- @controller = TestController.new
- end
+ tests TestController
def test_flash
get :set_flash
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
new file mode 100644
index 0000000000..d5c8636a9e
--- /dev/null
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -0,0 +1,73 @@
+require 'abstract_unit'
+
+class HttpDigestAuthenticationTest < Test::Unit::TestCase
+ include ActionController::HttpAuthentication::Digest
+
+ class DummyController
+ attr_accessor :headers, :renders, :request, :response
+
+ def initialize
+ @headers, @renders = {}, []
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ request.session.session_id = "test_session"
+ end
+
+ def render(options)
+ self.renderers << options
+ end
+ end
+
+ def setup
+ @controller = DummyController.new
+ @credentials = {
+ :username => "dhh",
+ :realm => "testrealm@host.com",
+ :nonce => ActionController::HttpAuthentication::Digest.nonce(@controller.request),
+ :qop => "auth",
+ :nc => "00000001",
+ :cnonce => "0a4f113b",
+ :opaque => ActionController::HttpAuthentication::Digest.opaque(@controller.request),
+ :uri => "http://test.host/"
+ }
+ @encoded_credentials = ActionController::HttpAuthentication::Digest.encode_credentials("GET", @credentials, "secret")
+ end
+
+ def test_decode_credentials
+ set_headers
+ assert_equal @credentials, decode_credentials(@controller.request)
+ end
+
+ def test_nonce_format
+ assert_nothing_thrown do
+ validate_nonce(@controller.request, nonce(@controller.request))
+ end
+ end
+
+ def test_authenticate_should_raise_for_nil_password
+ set_headers ActionController::HttpAuthentication::Digest.encode_credentials(:get, @credentials, nil)
+ assert_raise ActionController::HttpAuthentication::Error do
+ authenticate(@controller, @credentials[:realm]) { |user| user == "dhh" && "secret" }
+ end
+ end
+
+ def test_authenticate_should_raise_for_incorrect_password
+ set_headers
+ assert_raise ActionController::HttpAuthentication::Error do
+ authenticate(@controller, @credentials[:realm]) { |user| user == "dhh" && "bad password" }
+ end
+ end
+
+ def test_authenticate_should_not_raise_for_correct_password
+ set_headers
+ assert_nothing_thrown do
+ authenticate(@controller, @credentials[:realm]) { |user| user == "dhh" && "secret" }
+ end
+ end
+
+ private
+ def set_headers(value = @encoded_credentials, name = 'HTTP_AUTHORIZATION', method = "GET")
+ @controller.request.env[name] = value
+ @controller.request.env["REQUEST_METHOD"] = method
+ end
+end
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index c28050fe0d..7ac9d97096 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -4,11 +4,29 @@ uses_mocha 'integration' do
class SessionTest < Test::Unit::TestCase
StubApp = lambda { |env|
- [200, {"Content-Type" => "text/html"}, "Hello, World!"]
+ [200, {"Content-Type" => "text/html", "Content-Length" => "13"}, "Hello, World!"]
}
def setup
+ @credentials = {
+ :username => "username",
+ :realm => "MyApp",
+ :nonce => ActionController::HttpAuthentication::Digest.nonce("session_id"),
+ :qop => "auth",
+ :nc => "00000001",
+ :cnonce => "0a4f113b",
+ :opaque => ActionController::HttpAuthentication::Digest.opaque("session_id"),
+ :uri => "/index"
+ }
+
@session = ActionController::Integration::Session.new(StubApp)
+ @session.nonce = @credentials[:nonce]
+ @session.opaque = @credentials[:opaque]
+ @session.realm = @credentials[:realm]
+ end
+
+ def encoded_credentials(method)
+ ActionController::HttpAuthentication::Digest.encode_credentials(method, @credentials, "password")
end
def test_https_bang_works_and_sets_truth_by_default
@@ -132,6 +150,76 @@ class SessionTest < Test::Unit::TestCase
@session.head(path,params,headers)
end
+ def test_get_with_basic
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n")
+ @session.expects(:process).with(:get,path,params,expected_headers)
+ @session.get_with_basic(path,params,headers,'username','password')
+ end
+
+ def test_post_with_basic
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n")
+ @session.expects(:process).with(:post,path,params,expected_headers)
+ @session.post_with_basic(path,params,headers,'username','password')
+ end
+
+ def test_put_with_basic
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n")
+ @session.expects(:process).with(:put,path,params,expected_headers)
+ @session.put_with_basic(path,params,headers,'username','password')
+ end
+
+ def test_delete_with_basic
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n")
+ @session.expects(:process).with(:delete,path,params,expected_headers)
+ @session.delete_with_basic(path,params,headers,'username','password')
+ end
+
+ def test_head_with_basic
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n")
+ @session.expects(:process).with(:head,path,params,expected_headers)
+ @session.head_with_basic(path,params,headers,'username','password')
+ end
+
+ def test_get_with_digest
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => encoded_credentials(:get))
+ @session.expects(:process).with(:get,path,params,expected_headers)
+ @session.get_with_digest(path,params,headers,'username','password')
+ end
+
+ def test_post_with_digest
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => encoded_credentials(:post))
+ @session.expects(:process).with(:post,path,params,expected_headers)
+ @session.post_with_digest(path,params,headers,'username','password')
+ end
+
+ def test_put_with_digest
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => encoded_credentials(:put))
+ @session.expects(:process).with(:put,path,params,expected_headers)
+ @session.put_with_digest(path,params,headers,'username','password')
+ end
+
+ def test_delete_with_digest
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => encoded_credentials(:delete))
+ @session.expects(:process).with(:delete,path,params,expected_headers)
+ @session.delete_with_digest(path,params,headers,'username','password')
+ end
+
+ def test_head_with_digest
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => encoded_credentials(:head))
+ @session.expects(:process).with(:head,path,params,expected_headers)
+ @session.head_with_digest(path,params,headers,'username','password')
+ end
+
def test_xml_http_request_get
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
@@ -377,9 +465,9 @@ class MetalTest < ActionController::IntegrationTest
class Poller
def self.call(env)
if env["PATH_INFO"] =~ /^\/success/
- [200, {"Content-Type" => "text/plain"}, "Hello World!"]
+ [200, {"Content-Type" => "text/plain", "Content-Length" => "12"}, "Hello World!"]
else
- [404, {"Content-Type" => "text/plain"}, '']
+ [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, '']
end
end
end
diff --git a/actionpack/test/controller/integration_upload_test.rb b/actionpack/test/controller/integration_upload_test.rb
index 39d2e164e4..d579980c19 100644
--- a/actionpack/test/controller/integration_upload_test.rb
+++ b/actionpack/test/controller/integration_upload_test.rb
@@ -10,6 +10,10 @@ class UploadTestController < ActionController::Base
SessionUploadTest.last_request_type = ActionController::Base.param_parsers[request.content_type]
render :text => "got here"
end
+
+ def read
+ render :text => "File: #{params[:uploaded_data].read}"
+ end
end
class SessionUploadTest < ActionController::IntegrationTest
@@ -19,21 +23,43 @@ class SessionUploadTest < ActionController::IntegrationTest
attr_accessor :last_request_type
end
- # def setup
- # @session = ActionController::Integration::Session.new
- # end
- def test_post_with_upload
- uses_mocha "test_post_with_upload" do
- ActiveSupport::Dependencies.stubs(:load?).returns(false)
+ def test_upload_and_read_file
+ with_test_routing do
+ post '/read', :uploaded_data => fixture_file_upload(FILES_DIR + "/hello.txt", "text/plain")
+ assert_equal "File: Hello", response.body
+ end
+ end
+
+ # The lint wrapper is used in integration tests
+ # instead of a normal StringIO class
+ InputWrapper = Rack::Lint::InputWrapper
+
+ def test_post_with_upload_with_unrewindable_input
+ InputWrapper.any_instance.expects(:rewind).raises(Errno::ESPIPE)
+
+ with_test_routing do
+ post '/read', :uploaded_data => fixture_file_upload(FILES_DIR + "/hello.txt", "text/plain")
+ assert_equal "File: Hello", response.body
+ end
+ end
+
+ def test_post_with_upload_with_params_parsing
+ with_test_routing do
+ params = { :uploaded_data => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg") }
+ post '/update', params, :location => 'blah'
+ assert_equal(:multipart_form, SessionUploadTest.last_request_type)
+ end
+ end
+
+ private
+ def with_test_routing
with_routing do |set|
set.draw do |map|
map.update 'update', :controller => "upload_test", :action => "update", :method => :post
+ map.read 'read', :controller => "upload_test", :action => "read", :method => :post
end
- params = { :uploaded_data => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg") }
- post '/update', params, :location => 'blah'
- assert_equal(:multipart_form, SessionUploadTest.last_request_type)
+ yield
end
end
- end
end
diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb
index c2efe9d00b..2f5e830fba 100644
--- a/actionpack/test/controller/layout_test.rb
+++ b/actionpack/test/controller/layout_test.rb
@@ -146,8 +146,7 @@ class LayoutExceptionRaised < ActionController::TestCase
def test_exception_raised_when_layout_file_not_found
@controller = SetsNonExistentLayoutFile.new
get :hello
- @response.template.class.module_eval { attr_accessor :exception }
- assert_equal ActionView::MissingTemplate, @response.template.exception.class
+ assert_kind_of ActionView::MissingTemplate, @response.template.instance_eval { @exception }
end
end
diff --git a/actionpack/test/controller/middleware_stack_test.rb b/actionpack/test/controller/middleware_stack_test.rb
new file mode 100644
index 0000000000..5029f5f609
--- /dev/null
+++ b/actionpack/test/controller/middleware_stack_test.rb
@@ -0,0 +1,70 @@
+require 'abstract_unit'
+
+class MiddlewareStackTest < ActiveSupport::TestCase
+ class FooMiddleware; end
+ class BarMiddleware; end
+ class BazMiddleware; end
+
+ def setup
+ @stack = ActionController::MiddlewareStack.new
+ @stack.use FooMiddleware
+ @stack.use BarMiddleware
+ end
+
+ test "use should push middleware as class onto the stack" do
+ assert_difference "@stack.size" do
+ @stack.use BazMiddleware
+ end
+ assert_equal BazMiddleware, @stack.last.klass
+ end
+
+ test "use should push middleware as a string onto the stack" do
+ assert_difference "@stack.size" do
+ @stack.use "MiddlewareStackTest::BazMiddleware"
+ end
+ assert_equal BazMiddleware, @stack.last.klass
+ end
+
+ test "use should push middleware as a symbol onto the stack" do
+ assert_difference "@stack.size" do
+ @stack.use :"MiddlewareStackTest::BazMiddleware"
+ end
+ assert_equal BazMiddleware, @stack.last.klass
+ end
+
+ test "use should push middleware class with arguments onto the stack" do
+ assert_difference "@stack.size" do
+ @stack.use BazMiddleware, true, :foo => "bar"
+ end
+ assert_equal BazMiddleware, @stack.last.klass
+ assert_equal([true, {:foo => "bar"}], @stack.last.args)
+ end
+
+ test "insert inserts middleware at the integer index" do
+ @stack.insert(1, BazMiddleware)
+ assert_equal BazMiddleware, @stack[1].klass
+ end
+
+ test "insert_after inserts middleware after the integer index" do
+ @stack.insert_after(1, BazMiddleware)
+ assert_equal BazMiddleware, @stack[2].klass
+ end
+
+ test "insert_before inserts middleware before another middleware class" do
+ @stack.insert_before(BarMiddleware, BazMiddleware)
+ assert_equal BazMiddleware, @stack[1].klass
+ end
+
+ test "insert_after inserts middleware after another middleware class" do
+ @stack.insert_after(BarMiddleware, BazMiddleware)
+ assert_equal BazMiddleware, @stack[2].klass
+ end
+
+ test "active returns all only enabled middleware" do
+ assert_no_difference "@stack.active.size" do
+ assert_difference "@stack.size" do
+ @stack.use BazMiddleware, :if => lambda { false }
+ end
+ end
+ end
+end
diff --git a/actionpack/test/controller/query_string_parsing_test.rb b/actionpack/test/controller/query_string_parsing_test.rb
new file mode 100644
index 0000000000..a31e326ddf
--- /dev/null
+++ b/actionpack/test/controller/query_string_parsing_test.rb
@@ -0,0 +1,120 @@
+require 'abstract_unit'
+
+class QueryStringParsingTest < ActionController::IntegrationTest
+ class TestController < ActionController::Base
+ class << self
+ attr_accessor :last_query_parameters
+ end
+
+ def parse
+ self.class.last_query_parameters = request.query_parameters
+ head :ok
+ end
+ end
+
+ def teardown
+ TestController.last_query_parameters = nil
+ end
+
+ test "query string" do
+ assert_parses(
+ {"action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"},
+ "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1"
+ )
+ end
+
+ test "deep query string" do
+ assert_parses(
+ {'x' => {'y' => {'z' => '10'}}},
+ "x[y][z]=10"
+ )
+ end
+
+ test "deep query string with array" do
+ assert_parses({'x' => {'y' => {'z' => ['10']}}}, 'x[y][z][]=10')
+ assert_parses({'x' => {'y' => {'z' => ['10', '5']}}}, 'x[y][z][]=10&x[y][z][]=5')
+ end
+
+ test "deep query string with array of hash" do
+ assert_parses({'x' => {'y' => [{'z' => '10'}]}}, 'x[y][][z]=10')
+ assert_parses({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, 'x[y][][z]=10&x[y][][w]=10')
+ assert_parses({'x' => {'y' => [{'z' => '10', 'v' => {'w' => '10'}}]}}, 'x[y][][z]=10&x[y][][v][w]=10')
+ end
+
+ test "deep query string with array of hashes with one pair" do
+ assert_parses({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, 'x[y][][z]=10&x[y][][z]=20')
+ end
+
+ test "deep query string with array of hashes with multiple pairs" do
+ assert_parses(
+ {'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}},
+ 'x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b'
+ )
+ end
+
+ test "query string with nil" do
+ assert_parses(
+ { "action" => "create_customer", "full_name" => ''},
+ "action=create_customer&full_name="
+ )
+ end
+
+ test "query string with array" do
+ assert_parses(
+ { "action" => "create_customer", "selected" => ["1", "2", "3"]},
+ "action=create_customer&selected[]=1&selected[]=2&selected[]=3"
+ )
+ end
+
+ test "query string with amps" do
+ assert_parses(
+ { "action" => "create_customer", "name" => "Don't & Does"},
+ "action=create_customer&name=Don%27t+%26+Does"
+ )
+ end
+
+ test "query string with many equal" do
+ assert_parses(
+ { "action" => "create_customer", "full_name" => "abc=def=ghi"},
+ "action=create_customer&full_name=abc=def=ghi"
+ )
+ end
+
+ test "query string without equal" do
+ assert_parses({ "action" => nil }, "action")
+ end
+
+ test "query string with empty key" do
+ assert_parses(
+ { "action" => "create_customer", "full_name" => "David Heinemeier Hansson" },
+ "action=create_customer&full_name=David%20Heinemeier%20Hansson&=Save"
+ )
+ end
+
+ test "query string with many ampersands" do
+ assert_parses(
+ { "action" => "create_customer", "full_name" => "David Heinemeier Hansson"},
+ "&action=create_customer&&&full_name=David%20Heinemeier%20Hansson"
+ )
+ end
+
+ test "unbalanced query string with array" do
+ assert_parses(
+ {'location' => ["1", "2"], 'age_group' => ["2"]},
+ "location[]=1&location[]=2&age_group[]=2"
+ )
+ end
+
+ private
+ def assert_parses(expected, actual)
+ with_routing do |set|
+ set.draw do |map|
+ map.connect ':action', :controller => "query_string_parsing_test/test"
+ end
+
+ get "/parse", actual
+ assert_response :ok
+ assert_equal(expected, TestController.last_query_parameters)
+ end
+ end
+end
diff --git a/actionpack/test/controller/rack_test.rb b/actionpack/test/controller/rack_test.rb
index 31bff4ae6d..8fd004a9e9 100644
--- a/actionpack/test/controller/rack_test.rb
+++ b/actionpack/test/controller/rack_test.rb
@@ -236,7 +236,12 @@ class RackResponseTest < BaseRackTest
status, headers, body = @response.to_a
assert_equal 200, status
- assert_equal({"Content-Type" => "text/html; charset=utf-8", "Cache-Control" => "no-cache", "Set-Cookie" => []}, headers)
+ assert_equal({
+ "Content-Type" => "text/html; charset=utf-8",
+ "Content-Length" => "",
+ "Cache-Control" => "no-cache",
+ "Set-Cookie" => []
+ }, headers)
parts = []
body.each { |part| parts << part }
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 5fd41d8eec..8809aa7c34 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -1218,6 +1218,11 @@ class RenderTest < ActionController::TestCase
assert_equal "404 Not Found", @response.status
assert_response :not_found
+ get :head_with_symbolic_status, :status => "no_content"
+ assert_equal "204 No Content", @response.status
+ assert !@response.headers.include?('Content-Length')
+ assert_response :no_content
+
ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE.each do |status, code|
get :head_with_symbolic_status, :status => status.to_s
assert_equal code, @response.response_code
@@ -1470,7 +1475,7 @@ class EtagRenderTest < ActionController::TestCase
def test_render_against_etag_request_should_have_no_content_length_when_match
@request.if_none_match = etag_for("hello david")
get :render_hello_world_from_variable
- assert !@response.headers.has_key?("Content-Length")
+ assert !@response.headers.has_key?("Content-Length"), @response.headers['Content-Length']
end
def test_render_against_etag_request_should_200_when_no_match
diff --git a/actionpack/test/controller/request/json_params_parsing_test.rb b/actionpack/test/controller/request/json_params_parsing_test.rb
new file mode 100644
index 0000000000..a3dde72c4e
--- /dev/null
+++ b/actionpack/test/controller/request/json_params_parsing_test.rb
@@ -0,0 +1,45 @@
+require 'abstract_unit'
+
+class JsonParamsParsingTest < ActionController::IntegrationTest
+ class TestController < ActionController::Base
+ class << self
+ attr_accessor :last_request_parameters
+ end
+
+ def parse
+ self.class.last_request_parameters = request.request_parameters
+ head :ok
+ end
+ end
+
+ def teardown
+ TestController.last_request_parameters = nil
+ end
+
+ test "parses json params for application json" do
+ assert_parses(
+ {"person" => {"name" => "David"}},
+ "{\"person\": {\"name\": \"David\"}}", { 'CONTENT_TYPE' => 'application/json' }
+ )
+ end
+
+ test "parses json params for application jsonrequest" do
+ assert_parses(
+ {"person" => {"name" => "David"}},
+ "{\"person\": {\"name\": \"David\"}}", { 'CONTENT_TYPE' => 'application/jsonrequest' }
+ )
+ end
+
+ private
+ def assert_parses(expected, actual, headers = {})
+ with_routing do |set|
+ set.draw do |map|
+ map.connect ':action', :controller => "json_params_parsing_test/test"
+ end
+
+ post "/parse", actual, headers
+ assert_response :ok
+ assert_equal(expected, TestController.last_request_parameters)
+ end
+ end
+end
diff --git a/actionpack/test/controller/request/xml_params_parsing_test.rb b/actionpack/test/controller/request/xml_params_parsing_test.rb
new file mode 100644
index 0000000000..ee764e726e
--- /dev/null
+++ b/actionpack/test/controller/request/xml_params_parsing_test.rb
@@ -0,0 +1,88 @@
+require 'abstract_unit'
+
+class XmlParamsParsingTest < ActionController::IntegrationTest
+ class TestController < ActionController::Base
+ class << self
+ attr_accessor :last_request_parameters
+ end
+
+ def parse
+ self.class.last_request_parameters = request.request_parameters
+ head :ok
+ end
+ end
+
+ def teardown
+ TestController.last_request_parameters = nil
+ end
+
+ test "parses hash params" do
+ with_test_routing do
+ xml = "<person><name>David</name></person>"
+ post "/parse", xml, default_headers
+ assert_response :ok
+ assert_equal({"person" => {"name" => "David"}}, TestController.last_request_parameters)
+ end
+ end
+
+ test "parses single file" do
+ with_test_routing do
+ xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar></person>"
+ post "/parse", xml, default_headers
+ assert_response :ok
+
+ person = TestController.last_request_parameters
+ assert_equal "image/jpg", person['person']['avatar'].content_type
+ assert_equal "me.jpg", person['person']['avatar'].original_filename
+ assert_equal "ABC", person['person']['avatar'].read
+ end
+ end
+
+ test "parses multiple files" do
+ xml = <<-end_body
+ <person>
+ <name>David</name>
+ <avatars>
+ <avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar>
+ <avatar type='file' name='you.gif' content_type='image/gif'>#{ActiveSupport::Base64.encode64('DEF')}</avatar>
+ </avatars>
+ </person>
+ end_body
+
+ with_test_routing do
+ post "/parse", xml, default_headers
+ assert_response :ok
+ end
+
+ person = TestController.last_request_parameters
+
+ assert_equal "image/jpg", person['person']['avatars']['avatar'].first.content_type
+ assert_equal "me.jpg", person['person']['avatars']['avatar'].first.original_filename
+ assert_equal "ABC", person['person']['avatars']['avatar'].first.read
+
+ assert_equal "image/gif", person['person']['avatars']['avatar'].last.content_type
+ assert_equal "you.gif", person['person']['avatars']['avatar'].last.original_filename
+ assert_equal "DEF", person['person']['avatars']['avatar'].last.read
+ end
+
+ private
+ def with_test_routing
+ with_routing do |set|
+ set.draw do |map|
+ map.connect ':action', :controller => "xml_params_parsing_test/test"
+ end
+ yield
+ end
+ end
+
+ def default_headers
+ {'CONTENT_TYPE' => 'application/xml'}
+ end
+end
+
+class LegacyXmlParamsParsingTest < XmlParamsParsingTest
+ private
+ def default_headers
+ {'HTTP_X_POST_DATA_FORMAT' => 'xml'}
+ end
+end
diff --git a/actionpack/test/controller/request_test.rb b/actionpack/test/controller/request_test.rb
index 349cea268f..c53f1bc2d9 100644
--- a/actionpack/test/controller/request_test.rb
+++ b/actionpack/test/controller/request_test.rb
@@ -391,8 +391,8 @@ class RequestTest < ActiveSupport::TestCase
end
def test_parameters
- @request.instance_eval { @request_parameters = { "foo" => 1 } }
- @request.instance_eval { @query_parameters = { "bar" => 2 } }
+ @request.stubs(:request_parameters).returns({ "foo" => 1 })
+ @request.stubs(:query_parameters).returns({ "bar" => 2 })
assert_equal({"foo" => 1, "bar" => 2}, @request.parameters)
assert_equal({"foo" => 1}, @request.request_parameters)
@@ -407,113 +407,10 @@ class RequestTest < ActiveSupport::TestCase
end
class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase
- def setup
- @query_string = "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1"
- @query_string_with_empty = "action=create_customer&full_name="
- @query_string_with_array = "action=create_customer&selected[]=1&selected[]=2&selected[]=3"
- @query_string_with_amps = "action=create_customer&name=Don%27t+%26+Does"
- @query_string_with_multiple_of_same_name =
- "action=update_order&full_name=Lau%20Taarnskov&products=4&products=2&products=3"
- @query_string_with_many_equal = "action=create_customer&full_name=abc=def=ghi"
- @query_string_without_equal = "action"
- @query_string_with_many_ampersands =
- "&action=create_customer&&&full_name=David%20Heinemeier%20Hansson"
- @query_string_with_empty_key = "action=create_customer&full_name=David%20Heinemeier%20Hansson&=Save"
- end
-
- def test_query_string
- assert_equal(
- { "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"},
- ActionController::RequestParser.parse_query_parameters(@query_string)
- )
- end
-
- def test_deep_query_string
- expected = {'x' => {'y' => {'z' => '10'}}}
- assert_equal(expected, ActionController::RequestParser.parse_query_parameters('x[y][z]=10'))
- end
-
- def test_deep_query_string_with_array
- assert_equal({'x' => {'y' => {'z' => ['10']}}}, ActionController::RequestParser.parse_query_parameters('x[y][z][]=10'))
- assert_equal({'x' => {'y' => {'z' => ['10', '5']}}}, ActionController::RequestParser.parse_query_parameters('x[y][z][]=10&x[y][z][]=5'))
- end
-
- def test_deep_query_string_with_array_of_hash
- assert_equal({'x' => {'y' => [{'z' => '10'}]}}, ActionController::RequestParser.parse_query_parameters('x[y][][z]=10'))
- assert_equal({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][w]=10'))
- end
-
- def test_deep_query_string_with_array_of_hashes_with_one_pair
- assert_equal({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][z]=20'))
- assert_equal("10", ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')["x"]["y"].first["z"])
- assert_equal("10", ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][z]=20').with_indifferent_access[:x][:y].first[:z])
- end
-
- def test_deep_query_string_with_array_of_hashes_with_multiple_pairs
- assert_equal(
- {'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}},
- ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b')
- )
- end
-
- def test_query_string_with_nil
- assert_equal(
- { "action" => "create_customer", "full_name" => ''},
- ActionController::RequestParser.parse_query_parameters(@query_string_with_empty)
- )
- end
-
- def test_query_string_with_array
- assert_equal(
- { "action" => "create_customer", "selected" => ["1", "2", "3"]},
- ActionController::RequestParser.parse_query_parameters(@query_string_with_array)
- )
- end
-
- def test_query_string_with_amps
- assert_equal(
- { "action" => "create_customer", "name" => "Don't & Does"},
- ActionController::RequestParser.parse_query_parameters(@query_string_with_amps)
- )
- end
-
- def test_query_string_with_many_equal
- assert_equal(
- { "action" => "create_customer", "full_name" => "abc=def=ghi"},
- ActionController::RequestParser.parse_query_parameters(@query_string_with_many_equal)
- )
- end
-
- def test_query_string_without_equal
- assert_equal(
- { "action" => nil },
- ActionController::RequestParser.parse_query_parameters(@query_string_without_equal)
- )
- end
-
- def test_query_string_with_empty_key
- assert_equal(
- { "action" => "create_customer", "full_name" => "David Heinemeier Hansson" },
- ActionController::RequestParser.parse_query_parameters(@query_string_with_empty_key)
- )
- end
-
- def test_query_string_with_many_ampersands
- assert_equal(
- { "action" => "create_customer", "full_name" => "David Heinemeier Hansson"},
- ActionController::RequestParser.parse_query_parameters(@query_string_with_many_ampersands)
- )
- end
-
def test_unbalanced_query_string_with_array
assert_equal(
{'location' => ["1", "2"], 'age_group' => ["2"]},
- ActionController::RequestParser.parse_query_parameters("location[]=1&location[]=2&age_group[]=2")
- )
- assert_equal(
- {'location' => ["1", "2"], 'age_group' => ["2"]},
- ActionController::RequestParser.parse_request_parameters({'location[]' => ["1", "2"],
- 'age_group[]' => ["2"]})
+ ActionController::RequestParser.parse_request_parameters({'location[]' => ["1", "2"], 'age_group[]' => ["2"]})
)
end
@@ -813,79 +710,3 @@ class MultipartRequestParameterParsingTest < ActiveSupport::TestCase
end
end
end
-
-class XmlParamsParsingTest < ActiveSupport::TestCase
- def test_hash_params
- person = parse_body("<person><name>David</name></person>")[:person]
- assert_kind_of Hash, person
- assert_equal 'David', person['name']
- end
-
- def test_single_file
- person = parse_body("<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar></person>")
-
- assert_equal "image/jpg", person['person']['avatar'].content_type
- assert_equal "me.jpg", person['person']['avatar'].original_filename
- assert_equal "ABC", person['person']['avatar'].read
- end
-
- def test_multiple_files
- person = parse_body(<<-end_body)
- <person>
- <name>David</name>
- <avatars>
- <avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar>
- <avatar type='file' name='you.gif' content_type='image/gif'>#{ActiveSupport::Base64.encode64('DEF')}</avatar>
- </avatars>
- </person>
- end_body
-
- assert_equal "image/jpg", person['person']['avatars']['avatar'].first.content_type
- assert_equal "me.jpg", person['person']['avatars']['avatar'].first.original_filename
- assert_equal "ABC", person['person']['avatars']['avatar'].first.read
-
- assert_equal "image/gif", person['person']['avatars']['avatar'].last.content_type
- assert_equal "you.gif", person['person']['avatars']['avatar'].last.original_filename
- assert_equal "DEF", person['person']['avatars']['avatar'].last.read
- end
-
- private
- def parse_body(body)
- env = { 'rack.input' => StringIO.new(body),
- 'CONTENT_TYPE' => 'application/xml',
- 'CONTENT_LENGTH' => body.size.to_s }
- ActionController::Request.new(env).request_parameters
- end
-end
-
-class LegacyXmlParamsParsingTest < XmlParamsParsingTest
- private
- def parse_body(body)
- env = { 'rack.input' => StringIO.new(body),
- 'HTTP_X_POST_DATA_FORMAT' => 'xml',
- 'CONTENT_LENGTH' => body.size.to_s }
- ActionController::Request.new(env).request_parameters
- end
-end
-
-class JsonParamsParsingTest < ActiveSupport::TestCase
- def test_hash_params_for_application_json
- person = parse_body({:person => {:name => "David"}}.to_json,'application/json')[:person]
- assert_kind_of Hash, person
- assert_equal 'David', person['name']
- end
-
- def test_hash_params_for_application_jsonrequest
- person = parse_body({:person => {:name => "David"}}.to_json,'application/jsonrequest')[:person]
- assert_kind_of Hash, person
- assert_equal 'David', person['name']
- end
-
- private
- def parse_body(body,content_type)
- env = { 'rack.input' => StringIO.new(body),
- 'CONTENT_TYPE' => content_type,
- 'CONTENT_LENGTH' => body.size.to_s }
- ActionController::Request.new(env).request_parameters
- end
-end
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index 63f9827f4a..9f6b45f065 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -67,6 +67,11 @@ class RescueController < ActionController::Base
render :text => 'no way'
end
+ before_filter(:only => :before_filter_raises) { raise 'umm nice' }
+
+ def before_filter_raises
+ end
+
def raises
render :text => 'already rendered'
raise "don't panic!"
@@ -154,6 +159,16 @@ class RescueControllerTest < ActionController::TestCase
end
end
+ def test_rescue_exceptions_raised_by_filters
+ with_rails_root FIXTURE_PUBLIC do
+ with_all_requests_local false do
+ get :before_filter_raises
+ end
+ end
+
+ assert_response :internal_server_error
+ end
+
def test_rescue_action_locally_if_all_requests_local
@controller.expects(:local_request?).never
@controller.expects(:rescue_action_locally).with(@exception)
@@ -367,10 +382,21 @@ class RescueControllerTest < ActionController::TestCase
end
def test_rescue_dispatcher_exceptions
- RescueController.process_with_exception(@request, @response, ActionController::RoutingError.new("Route not found"))
+ env = @request.env
+ env["action_controller.rescue.request"] = @request
+ env["action_controller.rescue.response"] = @response
+
+ RescueController.call_with_exception(env, ActionController::RoutingError.new("Route not found"))
assert_equal "no way", @response.body
end
+ def test_rescue_dispatcher_exceptions_without_request_set
+ @request.env['REQUEST_URI'] = '/no_way'
+ response = RescueController.call_with_exception(@request.env, ActionController::RoutingError.new("Route not found"))
+ assert_kind_of ActionController::Response, response
+ assert_equal "no way", response.body
+ end
+
protected
def with_all_requests_local(local = true)
old_local, ActionController::Base.consider_all_requests_local =
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index d5b6bd6b2a..b981119e1e 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -706,7 +706,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
port_string = port == 80 ? '' : ":#{port}"
protocol = options.delete(:protocol) || "http"
- host = options.delete(:host) || "named.route.test"
+ host = options.delete(:host) || "test.host"
anchor = "##{options.delete(:anchor)}" if options.key?(:anchor)
path = routes.generate(options)
@@ -715,27 +715,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
def request
- @request ||= MockRequest.new(:host => "named.route.test", :method => :get)
- end
- end
-
- class MockRequest
- attr_accessor :path, :path_parameters, :host, :subdomains, :domain, :method
-
- def initialize(values={})
- values.each { |key, value| send("#{key}=", value) }
- if values[:host]
- subdomain, self.domain = values[:host].split(/\./, 2)
- self.subdomains = [subdomain]
- end
- end
-
- def protocol
- "http://"
- end
-
- def host_with_port
- (subdomains * '.') + '.' + domain
+ @request ||= ActionController::TestRequest.new
end
end
@@ -900,7 +880,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
def test_basic_named_route
rs.add_named_route :home, '', :controller => 'content', :action => 'list'
x = setup_for_named_route
- assert_equal("http://named.route.test/",
+ assert_equal("http://test.host/",
x.send(:home_url))
end
@@ -908,7 +888,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
rs.add_named_route :home, '', :controller => 'content', :action => 'list'
x = setup_for_named_route
ActionController::Base.relative_url_root = "/foo"
- assert_equal("http://named.route.test/foo/",
+ assert_equal("http://test.host/foo/",
x.send(:home_url))
assert_equal "/foo/", x.send(:home_path)
ActionController::Base.relative_url_root = nil
@@ -917,14 +897,14 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
def test_named_route_with_option
rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page'
x = setup_for_named_route
- assert_equal("http://named.route.test/page/new%20stuff",
+ assert_equal("http://test.host/page/new%20stuff",
x.send(:page_url, :title => 'new stuff'))
end
def test_named_route_with_default
rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page', :title => 'AboutPage'
x = setup_for_named_route
- assert_equal("http://named.route.test/page/AboutRails",
+ assert_equal("http://test.host/page/AboutRails",
x.send(:page_url, :title => "AboutRails"))
end
@@ -932,21 +912,21 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
def test_named_route_with_name_prefix
rs.add_named_route :page, 'page', :controller => 'content', :action => 'show_page', :name_prefix => 'my_'
x = setup_for_named_route
- assert_equal("http://named.route.test/page",
+ assert_equal("http://test.host/page",
x.send(:my_page_url))
end
def test_named_route_with_path_prefix
rs.add_named_route :page, 'page', :controller => 'content', :action => 'show_page', :path_prefix => 'my'
x = setup_for_named_route
- assert_equal("http://named.route.test/my/page",
+ assert_equal("http://test.host/my/page",
x.send(:page_url))
end
def test_named_route_with_nested_controller
rs.add_named_route :users, 'admin/user', :controller => 'admin/user', :action => 'index'
x = setup_for_named_route
- assert_equal("http://named.route.test/admin/user",
+ assert_equal("http://test.host/admin/user",
x.send(:users_url))
end
@@ -985,7 +965,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
map.root :controller => "hello"
end
x = setup_for_named_route
- assert_equal("http://named.route.test/", x.send(:root_url))
+ assert_equal("http://test.host/", x.send(:root_url))
assert_equal("/", x.send(:root_path))
end
@@ -1001,7 +981,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
# x.send(:article_url, :title => 'hi')
# )
assert_equal(
- "http://named.route.test/page/2005/6/10/hi",
+ "http://test.host/page/2005/6/10/hi",
x.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6)
)
end
@@ -1202,7 +1182,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
assert_equal '/test', rs.generate(:controller => 'post', :action => 'show', :year => nil)
x = setup_for_named_route
- assert_equal("http://named.route.test/test",
+ assert_equal("http://test.host/test",
x.send(:blog_url))
end
@@ -1249,7 +1229,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
assert_equal '/', rs.generate(:controller => 'content')
x = setup_for_named_route
- assert_equal("http://named.route.test/",
+ assert_equal("http://test.host/",
x.send(:home_url))
end
@@ -1591,7 +1571,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
def request
- @request ||= MockRequest.new(:host => "named.routes.test", :method => :get)
+ @request ||= ActionController::TestRequest.new
end
def test_generate_extras
@@ -1692,13 +1672,13 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
def test_named_route_url_method
controller = setup_named_route_test
- assert_equal "http://named.route.test/people/5", controller.send(:show_url, :id => 5)
+ assert_equal "http://test.host/people/5", controller.send(:show_url, :id => 5)
assert_equal "/people/5", controller.send(:show_path, :id => 5)
- assert_equal "http://named.route.test/people", controller.send(:index_url)
+ assert_equal "http://test.host/people", controller.send(:index_url)
assert_equal "/people", controller.send(:index_path)
- assert_equal "http://named.route.test/admin/users", controller.send(:users_url)
+ assert_equal "http://test.host/admin/users", controller.send(:users_url)
assert_equal '/admin/users', controller.send(:users_path)
assert_equal '/admin/users', set.generate(controller.send(:hash_for_users_url), {:controller => 'users', :action => 'index'})
end
@@ -1706,28 +1686,28 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
def test_named_route_url_method_with_anchor
controller = setup_named_route_test
- assert_equal "http://named.route.test/people/5#location", controller.send(:show_url, :id => 5, :anchor => 'location')
+ assert_equal "http://test.host/people/5#location", controller.send(:show_url, :id => 5, :anchor => 'location')
assert_equal "/people/5#location", controller.send(:show_path, :id => 5, :anchor => 'location')
- assert_equal "http://named.route.test/people#location", controller.send(:index_url, :anchor => 'location')
+ assert_equal "http://test.host/people#location", controller.send(:index_url, :anchor => 'location')
assert_equal "/people#location", controller.send(:index_path, :anchor => 'location')
- assert_equal "http://named.route.test/admin/users#location", controller.send(:users_url, :anchor => 'location')
+ assert_equal "http://test.host/admin/users#location", controller.send(:users_url, :anchor => 'location')
assert_equal '/admin/users#location', controller.send(:users_path, :anchor => 'location')
- assert_equal "http://named.route.test/people/go/7/hello/joe/5#location",
+ assert_equal "http://test.host/people/go/7/hello/joe/5#location",
controller.send(:multi_url, 7, "hello", 5, :anchor => 'location')
- assert_equal "http://named.route.test/people/go/7/hello/joe/5?baz=bar#location",
+ assert_equal "http://test.host/people/go/7/hello/joe/5?baz=bar#location",
controller.send(:multi_url, 7, "hello", 5, :baz => "bar", :anchor => 'location')
- assert_equal "http://named.route.test/people?baz=bar#location",
+ assert_equal "http://test.host/people?baz=bar#location",
controller.send(:index_url, :baz => "bar", :anchor => 'location')
end
def test_named_route_url_method_with_port
controller = setup_named_route_test
- assert_equal "http://named.route.test:8080/people/5", controller.send(:show_url, 5, :port=>8080)
+ assert_equal "http://test.host:8080/people/5", controller.send(:show_url, 5, :port=>8080)
end
def test_named_route_url_method_with_host
@@ -1737,30 +1717,30 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
def test_named_route_url_method_with_protocol
controller = setup_named_route_test
- assert_equal "https://named.route.test/people/5", controller.send(:show_url, 5, :protocol => "https")
+ assert_equal "https://test.host/people/5", controller.send(:show_url, 5, :protocol => "https")
end
def test_named_route_url_method_with_ordered_parameters
controller = setup_named_route_test
- assert_equal "http://named.route.test/people/go/7/hello/joe/5",
+ assert_equal "http://test.host/people/go/7/hello/joe/5",
controller.send(:multi_url, 7, "hello", 5)
end
def test_named_route_url_method_with_ordered_parameters_and_hash
controller = setup_named_route_test
- assert_equal "http://named.route.test/people/go/7/hello/joe/5?baz=bar",
+ assert_equal "http://test.host/people/go/7/hello/joe/5?baz=bar",
controller.send(:multi_url, 7, "hello", 5, :baz => "bar")
end
def test_named_route_url_method_with_ordered_parameters_and_empty_hash
controller = setup_named_route_test
- assert_equal "http://named.route.test/people/go/7/hello/joe/5",
+ assert_equal "http://test.host/people/go/7/hello/joe/5",
controller.send(:multi_url, 7, "hello", 5, {})
end
def test_named_route_url_method_with_no_positional_arguments
controller = setup_named_route_test
- assert_equal "http://named.route.test/people?baz=bar",
+ assert_equal "http://test.host/people?baz=bar",
controller.send(:index_url, :baz => "bar")
end
@@ -1896,49 +1876,54 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/people"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("index", request.path_parameters[:action])
+ request.recycle!
- request.method = :post
+ request.env["REQUEST_METHOD"] = "POST"
assert_nothing_raised { set.recognize(request) }
assert_equal("create", request.path_parameters[:action])
+ request.recycle!
- request.method = :put
+ request.env["REQUEST_METHOD"] = "PUT"
assert_nothing_raised { set.recognize(request) }
assert_equal("update", request.path_parameters[:action])
+ request.recycle!
- begin
- request.method = :bacon
+ assert_raises(ActionController::UnknownHttpMethod) {
+ request.env["REQUEST_METHOD"] = "BACON"
set.recognize(request)
- flunk 'Should have raised NotImplemented'
- rescue ActionController::NotImplemented => e
- assert_equal [:get, :post, :put, :delete], e.allowed_methods
- end
+ }
+ request.recycle!
request.path = "/people/5"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("show", request.path_parameters[:action])
assert_equal("5", request.path_parameters[:id])
+ request.recycle!
- request.method = :put
+ request.env["REQUEST_METHOD"] = "PUT"
assert_nothing_raised { set.recognize(request) }
assert_equal("update", request.path_parameters[:action])
assert_equal("5", request.path_parameters[:id])
+ request.recycle!
- request.method = :delete
+ request.env["REQUEST_METHOD"] = "DELETE"
assert_nothing_raised { set.recognize(request) }
assert_equal("destroy", request.path_parameters[:action])
assert_equal("5", request.path_parameters[:id])
+ request.recycle!
begin
- request.method = :post
+ request.env["REQUEST_METHOD"] = "POST"
set.recognize(request)
flunk 'Should have raised MethodNotAllowed'
rescue ActionController::MethodNotAllowed => e
assert_equal [:get, :put, :delete], e.allowed_methods
end
+ request.recycle!
ensure
Object.send(:remove_const, :PeopleController)
@@ -1954,13 +1939,13 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/people"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("people", request.path_parameters[:controller])
assert_equal("index", request.path_parameters[:action])
request.path = "/"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("people", request.path_parameters[:controller])
assert_equal("index", request.path_parameters[:action])
@@ -1978,7 +1963,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/articles/2005/11/05/a-very-interesting-article"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("permalink", request.path_parameters[:action])
assert_equal("2005", request.path_parameters[:year])
@@ -2015,17 +2000,19 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/people/5"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("show", request.path_parameters[:action])
assert_equal("5", request.path_parameters[:id])
+ request.recycle!
- request.method = :put
+ request.env["REQUEST_METHOD"] = "PUT"
assert_nothing_raised { set.recognize(request) }
assert_equal("update", request.path_parameters[:action])
+ request.recycle!
request.path = "/people/5.png"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("show", request.path_parameters[:action])
assert_equal("5", request.path_parameters[:id])
@@ -2050,7 +2037,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
set.draw { |map| map.root :controller => "people" }
request.path = ""
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("people", request.path_parameters[:controller])
assert_equal("index", request.path_parameters[:action])
@@ -2070,7 +2057,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/api/inventory"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("api/products", request.path_parameters[:controller])
assert_equal("inventory", request.path_parameters[:action])
@@ -2090,7 +2077,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/api"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("api/products", request.path_parameters[:controller])
assert_equal("index", request.path_parameters[:action])
@@ -2110,7 +2097,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/prefix/inventory"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("api/products", request.path_parameters[:controller])
assert_equal("inventory", request.path_parameters[:action])
@@ -2246,7 +2233,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/projects/1/milestones"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("milestones", request.path_parameters[:controller])
assert_equal("index", request.path_parameters[:action])
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index 1b7486ad34..5fc79baa44 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -19,7 +19,8 @@ class SendFileController < ActionController::Base
def rescue_action(e) raise end
end
-class SendFileTest < Test::Unit::TestCase
+class SendFileTest < ActionController::TestCase
+ tests SendFileController
include TestFileUtils
Mime::Type.register "image/png", :png unless defined? Mime::PNG
diff --git a/actionpack/test/fixtures/multipart/hello.txt b/actionpack/test/fixtures/multipart/hello.txt
new file mode 100644
index 0000000000..5ab2f8a432
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/hello.txt
@@ -0,0 +1 @@
+Hello \ No newline at end of file
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 7597927f6d..5e2fc20167 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -38,8 +38,6 @@ class AssetTagHelperTest < ActionView::TestCase
@controller.request = @request
ActionView::Helpers::AssetTagHelper::reset_javascript_include_default
- AssetTag::Cache.clear
- AssetCollection::Cache.clear
end
def teardown
@@ -281,6 +279,26 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal copy, source
end
+ def test_caching_image_path_with_caching_and_proc_asset_host_using_request
+ ENV['RAILS_ASSET_ID'] = ''
+ ActionController::Base.asset_host = Proc.new do |source, request|
+ if request.ssl?
+ "#{request.protocol}#{request.host_with_port}"
+ else
+ "#{request.protocol}assets#{source.length}.example.com"
+ end
+ end
+
+ ActionController::Base.perform_caching = true
+
+
+ @controller.request.stubs(:ssl?).returns(false)
+ assert_equal "http://assets15.example.com/images/xml.png", image_path("xml.png")
+
+ @controller.request.stubs(:ssl?).returns(true)
+ assert_equal "http://localhost/images/xml.png", image_path("xml.png")
+ end
+
def test_caching_javascript_include_tag_when_caching_on
ENV["RAILS_ASSET_ID"] = ""
ActionController::Base.asset_host = 'http://a0.example.com'
diff --git a/actionpack/test/template/benchmark_helper_test.rb b/actionpack/test/template/benchmark_helper_test.rb
index 08d453c965..5d2af7cdd9 100644
--- a/actionpack/test/template/benchmark_helper_test.rb
+++ b/actionpack/test/template/benchmark_helper_test.rb
@@ -4,32 +4,25 @@ require 'action_view/helpers/benchmark_helper'
class BenchmarkHelperTest < ActionView::TestCase
tests ActionView::Helpers::BenchmarkHelper
- class MockLogger
- attr_reader :logged
-
- def initialize
- @logged = []
- end
-
- def method_missing(method, *args)
- @logged << [method, args]
- end
+ def teardown
+ controller.logger.send(:clear_buffer)
end
def controller
- @controller ||= Struct.new(:logger).new(MockLogger.new)
+ logger = ActiveSupport::BufferedLogger.new(StringIO.new)
+ logger.auto_flushing = false
+ @controller ||= Struct.new(:logger).new(logger)
end
def test_without_block
assert_raise(LocalJumpError) { benchmark }
- assert controller.logger.logged.empty?
+ assert buffer.empty?
end
def test_defaults
i_was_run = false
benchmark { i_was_run = true }
assert i_was_run
- assert 1, controller.logger.logged.size
assert_last_logged
end
@@ -37,24 +30,57 @@ class BenchmarkHelperTest < ActionView::TestCase
i_was_run = false
benchmark('test_run') { i_was_run = true }
assert i_was_run
- assert 1, controller.logger.logged.size
assert_last_logged 'test_run'
end
- def test_with_message_and_level
+ def test_with_message_and_deprecated_level
i_was_run = false
- benchmark('debug_run', :debug) { i_was_run = true }
+
+ assert_deprecated do
+ benchmark('debug_run', :debug) { i_was_run = true }
+ end
+
assert i_was_run
- assert 1, controller.logger.logged.size
- assert_last_logged 'debug_run', :debug
+ assert_last_logged 'debug_run'
+ end
+
+ def test_within_level
+ controller.logger.level = ActiveSupport::BufferedLogger::DEBUG
+ benchmark('included_debug_run', :level => :debug) { }
+ assert_last_logged 'included_debug_run'
+ end
+
+ def test_outside_level
+ controller.logger.level = ActiveSupport::BufferedLogger::ERROR
+ benchmark('skipped_debug_run', :level => :debug) { }
+ assert_no_match(/skipped_debug_run/, buffer.last)
+ ensure
+ controller.logger.level = ActiveSupport::BufferedLogger::DEBUG
end
+ def test_without_silencing
+ benchmark('debug_run', :silence => false) do
+ controller.logger.info "not silenced!"
+ end
+
+ assert_equal 2, buffer.size
+ end
+
+ def test_with_silencing
+ benchmark('debug_run', :silence => true) do
+ controller.logger.info "silenced!"
+ end
+
+ assert_equal 1, buffer.size
+ end
+
+
private
- def assert_last_logged(message = 'Benchmarking', level = :info)
- last = controller.logger.logged.last
- assert 2, last.size
- assert_equal level, last.first
- assert 1, last[1].size
- assert last[1][0] =~ /^#{message} \(.*\)$/
+ def buffer
+ controller.logger.send(:buffer)
+ end
+
+ def assert_last_logged(message = 'Benchmarking')
+ assert_match(/^#{message} \(.*\)$/, buffer.last)
end
end
diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb
index a68b09bb45..caea1bd643 100644
--- a/actionpack/test/template/compiled_templates_test.rb
+++ b/actionpack/test/template/compiled_templates_test.rb
@@ -31,7 +31,7 @@ uses_mocha 'TestTemplateRecompilation' do
end
def test_compiled_template_will_always_be_recompiled_when_template_is_not_cached
- ActionView::Template.any_instance.expects(:loaded?).times(3).returns(false)
+ ActionView::Template.any_instance.expects(:recompile?).times(3).returns(true)
assert_equal 0, @compiled_templates.instance_methods.size
assert_equal "Hello world!", render(:file => "#{FIXTURE_LOAD_PATH}/test/hello_world.erb")
ActionView::Template.any_instance.expects(:compile!).times(3)
@@ -62,13 +62,14 @@ uses_mocha 'TestTemplateRecompilation' do
def render_with_cache(*args)
view_paths = ActionController::Base.view_paths
- assert view_paths.first.loaded?
+ assert_equal ActionView::Template::EagerPath, view_paths.first.class
ActionView::Base.new(view_paths, {}).render(*args)
end
def render_without_cache(*args)
- view_paths = ActionView::Base.process_view_paths(FIXTURE_LOAD_PATH)
- assert !view_paths.first.loaded?
+ path = ActionView::Template::Path.new(FIXTURE_LOAD_PATH)
+ view_paths = ActionView::Base.process_view_paths(path)
+ assert_equal ActionView::Template::Path, view_paths.first.class
ActionView::Base.new(view_paths, {}).render(*args)
end
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 0387a11de2..4bd897efeb 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -197,7 +197,7 @@ class CachedViewRenderTest < Test::Unit::TestCase
# Ensure view path cache is primed
def setup
view_paths = ActionController::Base.view_paths
- assert view_paths.first.loaded?
+ assert_equal ActionView::Template::EagerPath, view_paths.first.class
setup_view(view_paths)
end
end
@@ -208,8 +208,9 @@ class LazyViewRenderTest < Test::Unit::TestCase
# Test the same thing as above, but make sure the view path
# is not eager loaded
def setup
- view_paths = ActionView::Base.process_view_paths(FIXTURE_LOAD_PATH)
- assert !view_paths.first.loaded?
+ path = ActionView::Template::Path.new(FIXTURE_LOAD_PATH)
+ view_paths = ActionView::Base.process_view_paths(path)
+ assert_equal ActionView::Template::Path, view_paths.first.class
setup_view(view_paths)
end
end
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 9cfd16cc0d..c750f486f9 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,7 @@
*2.3.0/3.0*
+* Added dynamic scopes ala dynamic finders #1648 [Yaroslav Markin]
+
* Fixed that ActiveRecord::Base#new_record? should return false (not nil) for existing records #1219 [Yaroslav Markin]
* I18n the word separator for error messages. Introduces the activerecord.errors.format.separator translation key. #1294 [Akira Matsuda]
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index c428366a04..390c005785 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -51,6 +51,7 @@ module ActiveRecord
autoload :Callbacks, 'active_record/callbacks'
autoload :Dirty, 'active_record/dirty'
autoload :DynamicFinderMatch, 'active_record/dynamic_finder_match'
+ autoload :DynamicScopeMatch, 'active_record/dynamic_scope_match'
autoload :Migration, 'active_record/migration'
autoload :Migrator, 'active_record/migration'
autoload :NamedScope, 'active_record/named_scope'
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index 9d0bf3a308..e4ab69aa1b 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -195,7 +195,7 @@ module ActiveRecord
def preload_has_one_association(records, reflection, preload_options={})
return if records.first.send("loaded_#{reflection.name}?")
- id_to_record_map, ids = construct_id_map(records)
+ id_to_record_map, ids = construct_id_map(records, reflection.options[:primary_key])
options = reflection.options
records.each {|record| record.send("set_#{reflection.name}_target", nil)}
if options[:through]
@@ -229,7 +229,7 @@ module ActiveRecord
options = reflection.options
primary_key_name = reflection.through_reflection_primary_key_name
- id_to_record_map, ids = construct_id_map(records, primary_key_name)
+ id_to_record_map, ids = construct_id_map(records, primary_key_name || reflection.options[:primary_key])
records.each {|record| record.send(reflection.name).loaded}
if options[:through]
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index c154a5087c..86616abf52 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -22,7 +22,7 @@ module ActiveRecord
through_reflection = reflection.through_reflection
source_reflection_names = reflection.source_reflection_names
source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
- super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence :connector => 'or'}?")
+ super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :two_words_connector => ' or ', :last_word_connector => ', or '} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence :two_words_connector => ' or ', :last_word_connector => ', or '}?")
end
end
@@ -2171,7 +2171,7 @@ module ActiveRecord
aliased_table_name,
foreign_key,
parent.aliased_table_name,
- parent.primary_key
+ reflection.options[:primary_key] || parent.primary_key
]
end
when :belongs_to
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index 59f1d3b867..676c4ace61 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -180,7 +180,10 @@ module ActiveRecord
record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
else
- record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
+ unless @owner.new_record?
+ primary_key = @reflection.options[:primary_key] || :id
+ record[@reflection.primary_key_name] = @owner.send(primary_key)
+ end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 43b34125be..4c57897c43 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -764,7 +764,7 @@ module ActiveRecord #:nodoc:
#
# You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
#
- # Careful: although it is often much faster than the alternative,
+ # Note: Although it is often much faster than the alternative,
# <tt>#destroy</tt>, skipping callbacks might bypass business logic in
# your application that ensures referential integrity or performs other
# essential jobs.
@@ -859,7 +859,7 @@ module ActiveRecord #:nodoc:
# reflect that no changes should be made (since they can't be
# persisted).
#
- # Note: the instantiation, callback execution, and deletion of each
+ # Note: Instantiation, callback execution, and deletion of each
# record can be time consuming when you're removing many records at
# once. It generates at least one SQL +DELETE+ query per record (or
# possibly more, to enforce your callbacks). If you want to delete many
@@ -1465,7 +1465,10 @@ module ActiveRecord #:nodoc:
def respond_to?(method_id, include_private = false)
if match = DynamicFinderMatch.match(method_id)
return true if all_attributes_exists?(match.attribute_names)
+ elsif match = DynamicScopeMatch.match(method_id)
+ return true if all_attributes_exists?(match.attribute_names)
end
+
super
end
@@ -1816,11 +1819,12 @@ module ActiveRecord #:nodoc:
# It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+
# is actually <tt>find_all_by_amount(amount, options)</tt>.
#
- # This also enables you to initialize a record if it is not found, such as <tt>find_or_initialize_by_amount(amount)</tt>
- # or <tt>find_or_create_by_user_and_password(user, password)</tt>.
+ # Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that
+ # are turned into scoped(:conditions => ["user_name = ?", user_name]) and scoped(:conditions => ["user_name = ? AND password = ?", user_name, password])
+ # respectively.
#
- # Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
- # attempts to use it do not run through <tt>method_missing</tt>.
+ # Each dynamic finder, scope or initializer/creator is also defined in the class after it is first invoked, so that future
+ # attempts to use it do not run through method_missing.
def method_missing(method_id, *arguments, &block)
if match = DynamicFinderMatch.match(method_id)
attribute_names = match.attribute_names
@@ -1924,6 +1928,22 @@ module ActiveRecord #:nodoc:
}, __FILE__, __LINE__
send(method_id, *arguments, &block)
end
+ elsif match = DynamicScopeMatch.match(method_id)
+ attribute_names = match.attribute_names
+ super unless all_attributes_exists?(attribute_names)
+ if match.scope?
+ self.class_eval %{
+ def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
+ options = args.extract_options! # options = args.extract_options!
+ attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
+ [:#{attribute_names.join(',:')}], args # [:user_name, :password], args
+ ) # )
+ #
+ scoped(:conditions => attributes) # scoped(:conditions => attributes)
+ end # end
+ }, __FILE__, __LINE__
+ send(method_id, *arguments)
+ end
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 84f8c0284e..9387cf8827 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -402,6 +402,10 @@ module ActiveRecord
end
def add_column(table_name, column_name, type, options = {}) #:nodoc:
+ if @connection.respond_to?(:transaction_active?) && @connection.transaction_active?
+ raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction'
+ end
+
alter_table(table_name) do |definition|
definition.column(column_name, type, options)
end
diff --git a/activerecord/lib/active_record/dynamic_scope_match.rb b/activerecord/lib/active_record/dynamic_scope_match.rb
new file mode 100644
index 0000000000..f796ba669a
--- /dev/null
+++ b/activerecord/lib/active_record/dynamic_scope_match.rb
@@ -0,0 +1,25 @@
+module ActiveRecord
+ class DynamicScopeMatch
+ def self.match(method)
+ ds_match = self.new(method)
+ ds_match.scope ? ds_match : nil
+ end
+
+ def initialize(method)
+ @scope = true
+ case method.to_s
+ when /^scoped_by_([_a-zA-Z]\w*)$/
+ names = $1
+ else
+ @scope = nil
+ end
+ @attribute_names = names && names.split('_and_')
+ end
+
+ attr_reader :scope, :attribute_names
+
+ def scope?
+ !@scope.nil?
+ end
+ end
+end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index afbd9fddf9..14099d4176 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -786,4 +786,37 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal Person.find(person.id).agents, person.agents
end
end
+
+ def test_preload_has_many_using_primary_key
+ expected = Firm.find(:first).clients_using_primary_key.to_a
+ firm = Firm.find :first, :include => :clients_using_primary_key
+ assert_no_queries do
+ assert_equal expected, firm.clients_using_primary_key
+ end
+ end
+
+ def test_include_has_many_using_primary_key
+ expected = Firm.find(1).clients_using_primary_key.sort_by &:name
+ firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name'
+ assert_no_queries do
+ assert_equal expected, firm.clients_using_primary_key
+ end
+ end
+
+ def test_preload_has_one_using_primary_key
+ expected = Firm.find(:first).account_using_primary_key
+ firm = Firm.find :first, :include => :account_using_primary_key
+ assert_no_queries do
+ assert_equal expected, firm.account_using_primary_key
+ end
+ end
+
+ def test_include_has_one_using_primary_key
+ expected = Firm.find(1).account_using_primary_key
+ firm = Firm.find(:all, :include => :account_using_primary_key, :order => 'accounts.id').detect {|f| f.id == 1}
+ assert_no_queries do
+ assert_equal expected, firm.account_using_primary_key
+ end
+ end
+
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 20b9acda44..a5ae5cd8ec 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1115,5 +1115,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !client_association.respond_to?(:private_method)
assert client_association.respond_to?(:private_method, true)
end
+
+ def test_creating_using_primary_key
+ firm = Firm.find(:first)
+ client = firm.clients_using_primary_key.create!(:name => 'test')
+ assert_equal firm.name, client.firm_name
+ end
end
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 64e899780c..bab842cf66 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -254,7 +254,7 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_should_use_where_in_query_for_named_scope
- assert_equal Developer.find_all_by_name('Jamis'), Developer.find_all_by_id(Developer.jamises)
+ assert_equal Developer.find_all_by_name('Jamis').to_set, Developer.find_all_by_id(Developer.jamises).to_set
end
def test_size_should_use_count_when_results_are_not_loaded
@@ -278,3 +278,23 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal post.comments.size, Post.scoped(:joins => join).scoped(:joins => join, :conditions => "posts.id = #{post.id}").size
end
end
+
+class DynamicScopeMatchTest < ActiveRecord::TestCase
+ def test_scoped_by_no_match
+ assert_nil ActiveRecord::DynamicScopeMatch.match("not_scoped_at_all")
+ end
+
+ def test_scoped_by
+ match = ActiveRecord::DynamicScopeMatch.match("scoped_by_age_and_sex_and_location")
+ assert_not_nil match
+ assert match.scope?
+ assert_equal %w(age sex location), match.attribute_names
+ end
+end
+
+class DynamicScopeTest < ActiveRecord::TestCase
+ def test_dynamic_scope
+ assert_equal Post.scoped_by_author_id(1).find(1), Post.find(1)
+ assert_equal Post.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, Post.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"})
+ end
+end
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index cf41a20a5c..757cb1da04 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,12 @@
*2.3.0 [Edge]*
+* Object#tap shim for Ruby < 1.8.7. Similar to Object#returning, tap yields self then returns self. [Jeremy Kemper]
+ array.select { ... }.tap(&:inspect).map { ... }
+
+* TimeWithZone#- gives correct result with wrapped DateTime, and with DateTime argument [Geoff Buesing]
+
+* Updated i18n gem to version 0.1.1 #1635 [Yaroslav Markin]
+
* Add :allow_nil option to delegate. #1127 [Sergio Gil]
* Add Benchmark.ms convenience method to benchmark realtime in milliseconds. [Jeremy Kemper]
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 992827f7f4..86e66e0588 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -192,13 +192,8 @@ module ActiveSupport
end
def should_run_callback?(*args)
- if options[:if]
- evaluate_method(options[:if], *args)
- elsif options[:unless]
- !evaluate_method(options[:unless], *args)
- else
- true
- end
+ [options[:if]].flatten.compact.all? { |a| evaluate_method(a, *args) } &&
+ ![options[:unless]].flatten.compact.any? { |a| evaluate_method(a, *args) }
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/misc.rb b/activesupport/lib/active_support/core_ext/object/misc.rb
index 46f9c7d676..4570570bbc 100644
--- a/activesupport/lib/active_support/core_ext/object/misc.rb
+++ b/activesupport/lib/active_support/core_ext/object/misc.rb
@@ -40,6 +40,21 @@ class Object
value
end
+ # Yields <code>x</code> to the block, and then returns <code>x</code>.
+ # The primary purpose of this method is to "tap into" a method chain,
+ # in order to perform operations on intermediate results within the chain.
+ #
+ # (1..10).tap { |x| puts "original: #{x.inspect}" }.to_a.
+ # tap { |x| puts "array: #{x.inspect}" }.
+ # select { |x| x%2 == 0 }.
+ # tap { |x| puts "evens: #{x.inspect}" }.
+ # map { |x| x*x }.
+ # tap { |x| puts "squares: #{x.inspect}" }
+ def tap
+ yield self
+ self
+ end unless Object.respond_to?(:tap)
+
# An elegant way to factor duplication out of options passed to a series of
# method calls. Each method called in the block, with the block variable as
# the receiver, will have its options merged with the default +options+ hash
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index fdb219dbf7..9da4048272 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -16,7 +16,7 @@ module ActiveSupport
protected
# matches YAML-formatted dates
- DATE_REGEX = /^\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[ \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?$/
+ DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[ \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?)$/
# Ensure that ":" and "," are always followed by a space
def convert_json_to_yaml(json) #:nodoc:
diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb
index bd136c2596..f8d12e82b3 100644
--- a/activesupport/lib/active_support/testing/performance.rb
+++ b/activesupport/lib/active_support/testing/performance.rb
@@ -12,7 +12,7 @@ module ActiveSupport
if benchmark = ARGV.include?('--benchmark') # HAX for rake test
{ :benchmark => true,
:runs => 4,
- :metrics => [:process_time, :memory, :objects, :gc_runs, :gc_time],
+ :metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time],
:output => 'tmp/performance' }
else
{ :benchmark => false,
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 72ff684fcc..99be89fdf0 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -199,7 +199,7 @@ module ActiveSupport
# If we're subtracting a Duration of variable length (i.e., years, months, days), move backwards from #time,
# otherwise move backwards #utc, for accuracy when moving across DST boundaries
if other.acts_like?(:time)
- utc - other
+ utc.to_f - other.to_f
elsif duration_of_variable_length?(other)
method_missing(:-, other)
else
diff --git a/activesupport/lib/active_support/vendor.rb b/activesupport/lib/active_support/vendor.rb
index 4525bba559..3d7d52ca71 100644
--- a/activesupport/lib/active_support/vendor.rb
+++ b/activesupport/lib/active_support/vendor.rb
@@ -22,8 +22,8 @@ end
# TODO I18n gem has not been released yet
# begin
-# gem 'i18n', '~> 0.0.1'
+# gem 'i18n', '~> 0.1.1'
# rescue Gem::LoadError
- $:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.0.1"
+ $:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.1.1/lib"
require 'i18n'
# end
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/.gitignore b/activesupport/lib/active_support/vendor/i18n-0.1.1/.gitignore
new file mode 100644
index 0000000000..0f41a39f89
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/.gitignore
@@ -0,0 +1,3 @@
+.DS_Store
+test/rails/fixtures
+doc
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/MIT-LICENSE b/activesupport/lib/active_support/vendor/i18n-0.1.1/MIT-LICENSE
new file mode 100755
index 0000000000..ed8e9ee66d
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008 The Ruby I18n team
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/README.textile b/activesupport/lib/active_support/vendor/i18n-0.1.1/README.textile
new file mode 100644
index 0000000000..a07fc8426d
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/README.textile
@@ -0,0 +1,20 @@
+h1. Ruby I18n gem
+
+I18n and localization solution for Ruby.
+
+For information please refer to http://rails-i18n.org
+
+h2. Authors
+
+* "Matt Aimonetti":http://railsontherun.com
+* "Sven Fuchs":http://www.artweb-design.de
+* "Joshua Harvey":http://www.workingwithrails.com/person/759-joshua-harvey
+* "Saimon Moore":http://saimonmoore.net
+* "Stephan Soller":http://www.arkanis-development.de
+
+h2. License
+
+MIT License. See the included MIT-LICENCE file.
+
+
+
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/Rakefile b/activesupport/lib/active_support/vendor/i18n-0.1.1/Rakefile
new file mode 100644
index 0000000000..2164e13e69
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/Rakefile
@@ -0,0 +1,5 @@
+task :default => [:test]
+
+task :test do
+ ruby "test/all.rb"
+end
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/i18n.gemspec b/activesupport/lib/active_support/vendor/i18n-0.1.1/i18n.gemspec
new file mode 100644
index 0000000000..14294606bd
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/i18n.gemspec
@@ -0,0 +1,27 @@
+Gem::Specification.new do |s|
+ s.name = "i18n"
+ s.version = "0.1.1"
+ s.date = "2008-10-26"
+ s.summary = "Internationalization support for Ruby"
+ s.email = "rails-i18n@googlegroups.com"
+ s.homepage = "http://rails-i18n.org"
+ s.description = "Add Internationalization support to your Ruby application."
+ s.has_rdoc = false
+ s.authors = ['Sven Fuchs', 'Joshua Harvey', 'Matt Aimonetti', 'Stephan Soller', 'Saimon Moore']
+ s.files = [
+ 'i18n.gemspec',
+ 'lib/i18n/backend/simple.rb',
+ 'lib/i18n/exceptions.rb',
+ 'lib/i18n.rb',
+ 'MIT-LICENSE',
+ 'README.textile'
+ ]
+ s.test_files = [
+ 'test/all.rb',
+ 'test/i18n_exceptions_test.rb',
+ 'test/i18n_test.rb',
+ 'test/locale/en.rb',
+ 'test/locale/en.yml',
+ 'test/simple_backend_test.rb'
+ ]
+end
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb
index 2ffe3618b5..b5ad094d0e 100755
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb
@@ -2,39 +2,39 @@
# Sven Fuchs (http://www.artweb-design.de),
# Joshua Harvey (http://www.workingwithrails.com/person/759-joshua-harvey),
# Saimon Moore (http://saimonmoore.net),
-# Stephan Soller (http://www.arkanis-development.de/)
+# Stephan Soller (http://www.arkanis-development.de/)
# Copyright:: Copyright (c) 2008 The Ruby i18n Team
# License:: MIT
require 'i18n/backend/simple'
require 'i18n/exceptions'
-module I18n
+module I18n
@@backend = nil
@@load_path = nil
@@default_locale = :'en'
@@exception_handler = :default_exception_handler
-
+
class << self
# Returns the current backend. Defaults to +Backend::Simple+.
def backend
@@backend ||= Backend::Simple.new
end
-
+
# Sets the current backend. Used to set a custom backend.
- def backend=(backend)
+ def backend=(backend)
@@backend = backend
end
-
- # Returns the current default locale. Defaults to 'en'
+
+ # Returns the current default locale. Defaults to :'en'
def default_locale
- @@default_locale
+ @@default_locale
end
-
+
# Sets the current default locale. Used to set a custom default locale.
- def default_locale=(locale)
- @@default_locale = locale
+ def default_locale=(locale)
+ @@default_locale = locale
end
-
+
# Returns the current locale. Defaults to I18n.default_locale.
def locale
Thread.current[:locale] ||= default_locale
@@ -44,12 +44,12 @@ module I18n
def locale=(locale)
Thread.current[:locale] = locale
end
-
+
# Sets the exception handler.
def exception_handler=(exception_handler)
@@exception_handler = exception_handler
end
-
+
# Allow clients to register paths providing translation data sources. The
# backend defines acceptable sources.
#
@@ -74,25 +74,25 @@ module I18n
def reload!
backend.reload!
end
-
- # Translates, pluralizes and interpolates a given key using a given locale,
+
+ # Translates, pluralizes and interpolates a given key using a given locale,
# scope, and default, as well as interpolation values.
#
# *LOOKUP*
#
- # Translation data is organized as a nested hash using the upper-level keys
- # as namespaces. <em>E.g.</em>, ActionView ships with the translation:
+ # Translation data is organized as a nested hash using the upper-level keys
+ # as namespaces. <em>E.g.</em>, ActionView ships with the translation:
# <tt>:date => {:formats => {:short => "%b %d"}}</tt>.
- #
- # Translations can be looked up at any level of this hash using the key argument
- # and the scope option. <em>E.g.</em>, in this example <tt>I18n.t :date</tt>
+ #
+ # Translations can be looked up at any level of this hash using the key argument
+ # and the scope option. <em>E.g.</em>, in this example <tt>I18n.t :date</tt>
# returns the whole translations hash <tt>{:formats => {:short => "%b %d"}}</tt>.
- #
- # Key can be either a single key or a dot-separated key (both Strings and Symbols
+ #
+ # Key can be either a single key or a dot-separated key (both Strings and Symbols
# work). <em>E.g.</em>, the short format can be looked up using both:
# I18n.t 'date.formats.short'
# I18n.t :'date.formats.short'
- #
+ #
# Scope can be either a single key, a dot-separated key or an array of keys
# or dot-separated keys. Keys and scopes can be combined freely. So these
# examples will all look up the same short date format:
@@ -105,9 +105,9 @@ module I18n
#
# Translations can contain interpolation variables which will be replaced by
# values passed to #translate as part of the options hash, with the keys matching
- # the interpolation variable names.
+ # the interpolation variable names.
#
- # <em>E.g.</em>, with a translation <tt>:foo => "foo {{bar}}"</tt> the option
+ # <em>E.g.</em>, with a translation <tt>:foo => "foo {{bar}}"</tt> the option
# value for the key +bar+ will be interpolated into the translation:
# I18n.t :foo, :bar => 'baz' # => 'foo baz'
#
@@ -116,7 +116,7 @@ module I18n
# Translation data can contain pluralized translations. Pluralized translations
# are arrays of singluar/plural versions of translations like <tt>['Foo', 'Foos']</tt>.
#
- # Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English
+ # Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English
# pluralization rules. Other algorithms can be supported by custom backends.
#
# This returns the singular version of a pluralized translation:
@@ -125,9 +125,9 @@ module I18n
# These both return the plural version of a pluralized translation:
# I18n.t :foo, :count => 0 # => 'Foos'
# I18n.t :foo, :count => 2 # => 'Foos'
- #
- # The <tt>:count</tt> option can be used both for pluralization and interpolation.
- # <em>E.g.</em>, with the translation
+ #
+ # The <tt>:count</tt> option can be used both for pluralization and interpolation.
+ # <em>E.g.</em>, with the translation
# <tt>:foo => ['{{count}} foo', '{{count}} foos']</tt>, count will
# be interpolated to the pluralized translation:
# I18n.t :foo, :count => 1 # => '1 foo'
@@ -137,11 +137,11 @@ module I18n
# This returns the translation for <tt>:foo</tt> or <tt>default</tt> if no translation was found:
# I18n.t :foo, :default => 'default'
#
- # This returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> if no
+ # This returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> if no
# translation for <tt>:foo</tt> was found:
# I18n.t :foo, :default => :bar
#
- # Returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt>
+ # Returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt>
# or <tt>default</tt> if no translations for <tt>:foo</tt> and <tt>:bar</tt> were found.
# I18n.t :foo, :default => [:bar, 'default']
#
@@ -161,9 +161,9 @@ module I18n
rescue I18n::ArgumentError => e
raise e if options[:raise]
send(@@exception_handler, e, locale, key, options)
- end
+ end
alias :t :translate
-
+
# Localizes certain objects, such as dates and numbers to local formatting.
def localize(object, options = {})
locale = options[:locale] || I18n.locale
@@ -171,7 +171,7 @@ module I18n
backend.localize(locale, object, format)
end
alias :l :localize
-
+
protected
# Handles exceptions raised in the backend. All exceptions except for
# MissingTranslationData exceptions are re-raised. When a MissingTranslationData
@@ -181,7 +181,7 @@ module I18n
return exception.message if MissingTranslationData === exception
raise exception
end
-
+
# Merges the given locale, key and scope into a single array of keys.
# Splits keys that contain dots into multiple keys. Makes sure all
# keys are Symbols.
@@ -191,4 +191,4 @@ module I18n
keys.flatten.map { |k| k.to_sym }
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb
index bdda55d3fe..d298b3a85a 100644
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb
@@ -6,21 +6,21 @@ module I18n
INTERPOLATION_RESERVED_KEYS = %w(scope default)
MATCH = /(\\\\)?\{\{([^\}]+)\}\}/
- # Accepts a list of paths to translation files. Loads translations from
+ # Accepts a list of paths to translation files. Loads translations from
# plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
# for details.
def load_translations(*filenames)
filenames.each { |filename| load_file(filename) }
end
-
- # Stores translations for the given locale in memory.
+
+ # Stores translations for the given locale in memory.
# This uses a deep merge for the translations hash, so existing
# translations will be overwritten by new ones only at the deepest
# level of the hash.
def store_translations(locale, data)
merge_translations(locale, data)
end
-
+
def translate(locale, key, options = {})
raise InvalidLocale.new(locale) if locale.nil?
return key.map { |k| translate(locale, k, options) } if key.is_a? Array
@@ -41,13 +41,13 @@ module I18n
entry = interpolate(locale, entry, values)
entry
end
-
- # Acts the same as +strftime+, but returns a localized version of the
- # formatted date string. Takes a key from the date/time formats
- # translations as a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
+
+ # Acts the same as +strftime+, but returns a localized version of the
+ # formatted date string. Takes a key from the date/time formats
+ # translations as a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
def localize(locale, object, format = :default)
raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
-
+
type = object.respond_to?(:sec) ? 'time' : 'date'
# TODO only translate these if format is a String?
formats = translate(locale, :"#{type}.formats")
@@ -57,14 +57,14 @@ module I18n
# TODO only translate these if the format string is actually present
# TODO check which format strings are present, then bulk translate then, then replace them
- format.gsub!(/%a/, translate(locale, :"date.abbr_day_names")[object.wday])
+ format.gsub!(/%a/, translate(locale, :"date.abbr_day_names")[object.wday])
format.gsub!(/%A/, translate(locale, :"date.day_names")[object.wday])
format.gsub!(/%b/, translate(locale, :"date.abbr_month_names")[object.mon])
format.gsub!(/%B/, translate(locale, :"date.month_names")[object.mon])
format.gsub!(/%p/, translate(locale, :"time.#{object.hour < 12 ? :am : :pm}")) if object.respond_to? :hour
object.strftime(format)
end
-
+
def initialized?
@initialized ||= false
end
@@ -79,12 +79,12 @@ module I18n
load_translations(*I18n.load_path)
@initialized = true
end
-
+
def translations
@translations ||= {}
end
-
- # Looks up a translation from the translations hash. Returns nil if
+
+ # Looks up a translation from the translations hash. Returns nil if
# eiher key is nil, or locale, scope or key do not exist as a key in the
# nested translations hash. Splits keys or scopes containing dots
# into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
@@ -101,19 +101,19 @@ module I18n
end
end
end
-
- # Evaluates a default translation.
+
+ # Evaluates a default translation.
# If the given default is a String it is used literally. If it is a Symbol
# it will be translated with the given options. If it is an Array the first
# translation yielded will be returned.
- #
- # <em>I.e.</em>, <tt>default(locale, [:foo, 'default'])</tt> will return +default+ if
+ #
+ # <em>I.e.</em>, <tt>default(locale, [:foo, 'default'])</tt> will return +default+ if
# <tt>translate(locale, :foo)</tt> does not yield a result.
def default(locale, default, options = {})
case default
when String then default
when Symbol then translate locale, default, options
- when Array then default.each do |obj|
+ when Array then default.each do |obj|
result = default(locale, obj, options.dup) and return result
end and nil
end
@@ -135,10 +135,10 @@ module I18n
end
# Interpolates values into a given string.
- #
- # interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
+ #
+ # interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
# # => "file test.txt opened by {{user}}"
- #
+ #
# Note that you have to double escape the <tt>\\</tt> when you want to escape
# the <tt>{{...}}</tt> key in a string (once for the string and once for the
# interpolation).
@@ -167,8 +167,8 @@ module I18n
result.force_encoding(original_encoding) if original_encoding
result
end
-
- # Loads a single translations file by delegating to #load_rb or
+
+ # Loads a single translations file by delegating to #load_rb or
# #load_yml depending on the file extension and directly merges the
# data to the existing translations. Raises I18n::UnknownFileType
# for all other file extensions.
@@ -178,19 +178,19 @@ module I18n
data = send :"load_#{type}", filename # TODO raise a meaningful exception if this does not yield a Hash
data.each { |locale, d| merge_translations(locale, d) }
end
-
+
# Loads a plain Ruby translations file. eval'ing the file must yield
# a Hash containing translation data with locales as toplevel keys.
def load_rb(filename)
eval(IO.read(filename), binding, filename)
end
-
- # Loads a YAML translations file. The data must have locales as
+
+ # Loads a YAML translations file. The data must have locales as
# toplevel keys.
def load_yml(filename)
YAML::load(IO.read(filename))
end
-
+
# Deep merges the given translations hash with the existing translations
# for the given locale
def merge_translations(locale, data)
@@ -202,7 +202,7 @@ module I18n
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
translations[locale].merge!(data, &merger)
end
-
+
# Return a new hash with all keys and nested keys converted to symbols.
def deep_symbolize_keys(hash)
hash.inject({}) { |result, (key, value)|
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/exceptions.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/exceptions.rb
index 0f3eff1071..b5cea7acb4 100644
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/exceptions.rb
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/exceptions.rb
@@ -1,6 +1,6 @@
module I18n
class ArgumentError < ::ArgumentError; end
-
+
class InvalidLocale < ArgumentError
attr_reader :locale
def initialize(locale)
@@ -42,7 +42,7 @@ module I18n
super "reserved key #{key.inspect} used in #{string.inspect}"
end
end
-
+
class UnknownFileType < ArgumentError
attr_reader :type, :filename
def initialize(type, filename)
@@ -50,4 +50,4 @@ module I18n
super "can not load translations from #{filename}, the file type #{type} is not known"
end
end
-end \ No newline at end of file
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/test/all.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/all.rb
new file mode 100644
index 0000000000..353712da49
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/all.rb
@@ -0,0 +1,5 @@
+dir = File.dirname(__FILE__)
+require dir + '/i18n_test.rb'
+require dir + '/simple_backend_test.rb'
+require dir + '/i18n_exceptions_test.rb'
+# *require* dir + '/custom_backend_test.rb' \ No newline at end of file
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_exceptions_test.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_exceptions_test.rb
new file mode 100644
index 0000000000..dfcba6901f
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_exceptions_test.rb
@@ -0,0 +1,100 @@
+$:.unshift "lib"
+
+require 'rubygems'
+require 'test/unit'
+require 'mocha'
+require 'i18n'
+require 'active_support'
+
+class I18nExceptionsTest < Test::Unit::TestCase
+ def test_invalid_locale_stores_locale
+ force_invalid_locale
+ rescue I18n::ArgumentError => e
+ assert_nil e.locale
+ end
+
+ def test_invalid_locale_message
+ force_invalid_locale
+ rescue I18n::ArgumentError => e
+ assert_equal 'nil is not a valid locale', e.message
+ end
+
+ def test_missing_translation_data_stores_locale_key_and_options
+ force_missing_translation_data
+ rescue I18n::ArgumentError => e
+ options = {:scope => :bar}
+ assert_equal 'de', e.locale
+ assert_equal :foo, e.key
+ assert_equal options, e.options
+ end
+
+ def test_missing_translation_data_message
+ force_missing_translation_data
+ rescue I18n::ArgumentError => e
+ assert_equal 'translation missing: de, bar, foo', e.message
+ end
+
+ def test_invalid_pluralization_data_stores_entry_and_count
+ force_invalid_pluralization_data
+ rescue I18n::ArgumentError => e
+ assert_equal [:bar], e.entry
+ assert_equal 1, e.count
+ end
+
+ def test_invalid_pluralization_data_message
+ force_invalid_pluralization_data
+ rescue I18n::ArgumentError => e
+ assert_equal 'translation data [:bar] can not be used with :count => 1', e.message
+ end
+
+ def test_missing_interpolation_argument_stores_key_and_string
+ force_missing_interpolation_argument
+ rescue I18n::ArgumentError => e
+ assert_equal 'bar', e.key
+ assert_equal "{{bar}}", e.string
+ end
+
+ def test_missing_interpolation_argument_message
+ force_missing_interpolation_argument
+ rescue I18n::ArgumentError => e
+ assert_equal 'interpolation argument bar missing in "{{bar}}"', e.message
+ end
+
+ def test_reserved_interpolation_key_stores_key_and_string
+ force_reserved_interpolation_key
+ rescue I18n::ArgumentError => e
+ assert_equal 'scope', e.key
+ assert_equal "{{scope}}", e.string
+ end
+
+ def test_reserved_interpolation_key_message
+ force_reserved_interpolation_key
+ rescue I18n::ArgumentError => e
+ assert_equal 'reserved key "scope" used in "{{scope}}"', e.message
+ end
+
+ private
+ def force_invalid_locale
+ I18n.backend.translate nil, :foo
+ end
+
+ def force_missing_translation_data
+ I18n.backend.store_translations 'de', :bar => nil
+ I18n.backend.translate 'de', :foo, :scope => :bar
+ end
+
+ def force_invalid_pluralization_data
+ I18n.backend.store_translations 'de', :foo => [:bar]
+ I18n.backend.translate 'de', :foo, :count => 1
+ end
+
+ def force_missing_interpolation_argument
+ I18n.backend.store_translations 'de', :foo => "{{bar}}"
+ I18n.backend.translate 'de', :foo, :baz => 'baz'
+ end
+
+ def force_reserved_interpolation_key
+ I18n.backend.store_translations 'de', :foo => "{{scope}}"
+ I18n.backend.translate 'de', :foo, :baz => 'baz'
+ end
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_test.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_test.rb
new file mode 100644
index 0000000000..bbb35ec809
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_test.rb
@@ -0,0 +1,125 @@
+$:.unshift "lib"
+
+require 'rubygems'
+require 'test/unit'
+require 'mocha'
+require 'i18n'
+require 'active_support'
+
+class I18nTest < Test::Unit::TestCase
+ def setup
+ I18n.backend.store_translations :'en', {
+ :currency => {
+ :format => {
+ :separator => '.',
+ :delimiter => ',',
+ }
+ }
+ }
+ end
+
+ def test_uses_simple_backend_set_by_default
+ assert I18n.backend.is_a?(I18n::Backend::Simple)
+ end
+
+ def test_can_set_backend
+ assert_nothing_raised{ I18n.backend = self }
+ assert_equal self, I18n.backend
+ I18n.backend = I18n::Backend::Simple.new
+ end
+
+ def test_uses_en_us_as_default_locale_by_default
+ assert_equal 'en', I18n.default_locale
+ end
+
+ def test_can_set_default_locale
+ assert_nothing_raised{ I18n.default_locale = 'de' }
+ assert_equal 'de', I18n.default_locale
+ I18n.default_locale = 'en'
+ end
+
+ def test_uses_default_locale_as_locale_by_default
+ assert_equal I18n.default_locale, I18n.locale
+ end
+
+ def test_can_set_locale_to_thread_current
+ assert_nothing_raised{ I18n.locale = 'de' }
+ assert_equal 'de', I18n.locale
+ assert_equal 'de', Thread.current[:locale]
+ I18n.locale = 'en'
+ end
+
+ def test_can_set_exception_handler
+ assert_nothing_raised{ I18n.exception_handler = :custom_exception_handler }
+ I18n.exception_handler = :default_exception_handler # revert it
+ end
+
+ def test_uses_custom_exception_handler
+ I18n.exception_handler = :custom_exception_handler
+ I18n.expects(:custom_exception_handler)
+ I18n.translate :bogus
+ I18n.exception_handler = :default_exception_handler # revert it
+ end
+
+ def test_delegates_translate_to_backend
+ I18n.backend.expects(:translate).with 'de', :foo, {}
+ I18n.translate :foo, :locale => 'de'
+ end
+
+ def test_delegates_localize_to_backend
+ I18n.backend.expects(:localize).with 'de', :whatever, :default
+ I18n.localize :whatever, :locale => 'de'
+ end
+
+ def test_translate_given_no_locale_uses_i18n_locale
+ I18n.backend.expects(:translate).with 'en', :foo, {}
+ I18n.translate :foo
+ end
+
+ def test_translate_on_nested_symbol_keys_works
+ assert_equal ".", I18n.t(:'currency.format.separator')
+ end
+
+ def test_translate_with_nested_string_keys_works
+ assert_equal ".", I18n.t('currency.format.separator')
+ end
+
+ def test_translate_with_array_as_scope_works
+ assert_equal ".", I18n.t(:separator, :scope => ['currency.format'])
+ end
+
+ def test_translate_with_array_containing_dot_separated_strings_as_scope_works
+ assert_equal ".", I18n.t(:separator, :scope => ['currency.format'])
+ end
+
+ def test_translate_with_key_array_and_dot_separated_scope_works
+ assert_equal [".", ","], I18n.t(%w(separator delimiter), :scope => 'currency.format')
+ end
+
+ def test_translate_with_dot_separated_key_array_and_scope_works
+ assert_equal [".", ","], I18n.t(%w(format.separator format.delimiter), :scope => 'currency')
+ end
+
+ def test_translate_with_options_using_scope_works
+ I18n.backend.expects(:translate).with('de', :precision, :scope => :"currency.format")
+ I18n.with_options :locale => 'de', :scope => :'currency.format' do |locale|
+ locale.t :precision
+ end
+ end
+
+ # def test_translate_given_no_args_raises_missing_translation_data
+ # assert_equal "translation missing: en, no key", I18n.t
+ # end
+
+ def test_translate_given_a_bogus_key_raises_missing_translation_data
+ assert_equal "translation missing: en, bogus", I18n.t(:bogus)
+ end
+
+ def test_localize_nil_raises_argument_error
+ assert_raises(I18n::ArgumentError) { I18n.l nil }
+ end
+
+ def test_localize_object_raises_argument_error
+ assert_raises(I18n::ArgumentError) { I18n.l Object.new }
+ end
+end
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.rb
new file mode 100644
index 0000000000..6044ce10d9
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.rb
@@ -0,0 +1 @@
+{:'en-Ruby' => {:foo => {:bar => "baz"}}} \ No newline at end of file
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.yml b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.yml
new file mode 100644
index 0000000000..0b298c9c0e
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.yml
@@ -0,0 +1,3 @@
+en-Yaml:
+ foo:
+ bar: baz \ No newline at end of file
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb
new file mode 100644
index 0000000000..e181975f38
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb
@@ -0,0 +1,502 @@
+# encoding: utf-8
+$:.unshift "lib"
+
+require 'rubygems'
+require 'test/unit'
+require 'mocha'
+require 'i18n'
+require 'time'
+require 'yaml'
+
+module I18nSimpleBackendTestSetup
+ def setup_backend
+ # backend_reset_translations!
+ @backend = I18n::Backend::Simple.new
+ @backend.store_translations 'en', :foo => {:bar => 'bar', :baz => 'baz'}
+ @locale_dir = File.dirname(__FILE__) + '/locale'
+ end
+ alias :setup :setup_backend
+
+ # def backend_reset_translations!
+ # I18n::Backend::Simple::ClassMethods.send :class_variable_set, :@@translations, {}
+ # end
+
+ def backend_get_translations
+ # I18n::Backend::Simple::ClassMethods.send :class_variable_get, :@@translations
+ @backend.instance_variable_get :@translations
+ end
+
+ def add_datetime_translations
+ @backend.store_translations :'de', {
+ :date => {
+ :formats => {
+ :default => "%d.%m.%Y",
+ :short => "%d. %b",
+ :long => "%d. %B %Y",
+ },
+ :day_names => %w(Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag),
+ :abbr_day_names => %w(So Mo Di Mi Do Fr Sa),
+ :month_names => %w(Januar Februar März April Mai Juni Juli August September Oktober November Dezember).unshift(nil),
+ :abbr_month_names => %w(Jan Feb Mar Apr Mai Jun Jul Aug Sep Okt Nov Dez).unshift(nil),
+ :order => [:day, :month, :year]
+ },
+ :time => {
+ :formats => {
+ :default => "%a, %d. %b %Y %H:%M:%S %z",
+ :short => "%d. %b %H:%M",
+ :long => "%d. %B %Y %H:%M",
+ },
+ :am => 'am',
+ :pm => 'pm'
+ },
+ :datetime => {
+ :distance_in_words => {
+ :half_a_minute => 'half a minute',
+ :less_than_x_seconds => {
+ :one => 'less than 1 second',
+ :other => 'less than {{count}} seconds'
+ },
+ :x_seconds => {
+ :one => '1 second',
+ :other => '{{count}} seconds'
+ },
+ :less_than_x_minutes => {
+ :one => 'less than a minute',
+ :other => 'less than {{count}} minutes'
+ },
+ :x_minutes => {
+ :one => '1 minute',
+ :other => '{{count}} minutes'
+ },
+ :about_x_hours => {
+ :one => 'about 1 hour',
+ :other => 'about {{count}} hours'
+ },
+ :x_days => {
+ :one => '1 day',
+ :other => '{{count}} days'
+ },
+ :about_x_months => {
+ :one => 'about 1 month',
+ :other => 'about {{count}} months'
+ },
+ :x_months => {
+ :one => '1 month',
+ :other => '{{count}} months'
+ },
+ :about_x_years => {
+ :one => 'about 1 year',
+ :other => 'about {{count}} year'
+ },
+ :over_x_years => {
+ :one => 'over 1 year',
+ :other => 'over {{count}} years'
+ }
+ }
+ }
+ }
+ end
+end
+
+class I18nSimpleBackendTranslationsTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def test_store_translations_adds_translations # no, really :-)
+ @backend.store_translations :'en', :foo => 'bar'
+ assert_equal Hash[:'en', {:foo => 'bar'}], backend_get_translations
+ end
+
+ def test_store_translations_deep_merges_translations
+ @backend.store_translations :'en', :foo => {:bar => 'bar'}
+ @backend.store_translations :'en', :foo => {:baz => 'baz'}
+ assert_equal Hash[:'en', {:foo => {:bar => 'bar', :baz => 'baz'}}], backend_get_translations
+ end
+
+ def test_store_translations_forces_locale_to_sym
+ @backend.store_translations 'en', :foo => 'bar'
+ assert_equal Hash[:'en', {:foo => 'bar'}], backend_get_translations
+ end
+
+ def test_store_translations_converts_keys_to_symbols
+ # backend_reset_translations!
+ @backend.store_translations 'en', 'foo' => {'bar' => 'bar', 'baz' => 'baz'}
+ assert_equal Hash[:'en', {:foo => {:bar => 'bar', :baz => 'baz'}}], backend_get_translations
+ end
+end
+
+class I18nSimpleBackendTranslateTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def test_translate_calls_lookup_with_locale_given
+ @backend.expects(:lookup).with('de', :bar, [:foo]).returns 'bar'
+ @backend.translate 'de', :bar, :scope => [:foo]
+ end
+
+ def test_given_no_keys_it_returns_the_default
+ assert_equal 'default', @backend.translate('en', nil, :default => 'default')
+ end
+
+ def test_translate_given_a_symbol_as_a_default_translates_the_symbol
+ assert_equal 'bar', @backend.translate('en', nil, :scope => [:foo], :default => :bar)
+ end
+
+ def test_translate_given_an_array_as_default_uses_the_first_match
+ assert_equal 'bar', @backend.translate('en', :does_not_exist, :scope => [:foo], :default => [:does_not_exist_2, :bar])
+ end
+
+ def test_translate_given_an_array_of_inexistent_keys_it_raises_missing_translation_data
+ assert_raises I18n::MissingTranslationData do
+ @backend.translate('en', :does_not_exist, :scope => [:foo], :default => [:does_not_exist_2, :does_not_exist_3])
+ end
+ end
+
+ def test_translate_an_array_of_keys_translates_all_of_them
+ assert_equal %w(bar baz), @backend.translate('en', [:bar, :baz], :scope => [:foo])
+ end
+
+ def test_translate_calls_pluralize
+ @backend.expects(:pluralize).with 'en', 'bar', 1
+ @backend.translate 'en', :bar, :scope => [:foo], :count => 1
+ end
+
+ def test_translate_calls_interpolate
+ @backend.expects(:interpolate).with 'en', 'bar', {}
+ @backend.translate 'en', :bar, :scope => [:foo]
+ end
+
+ def test_translate_calls_interpolate_including_count_as_a_value
+ @backend.expects(:interpolate).with 'en', 'bar', {:count => 1}
+ @backend.translate 'en', :bar, :scope => [:foo], :count => 1
+ end
+
+ def test_translate_given_nil_as_a_locale_raises_an_argument_error
+ assert_raises(I18n::InvalidLocale){ @backend.translate nil, :bar }
+ end
+
+ def test_translate_with_a_bogus_key_and_no_default_raises_missing_translation_data
+ assert_raises(I18n::MissingTranslationData){ @backend.translate 'de', :bogus }
+ end
+end
+
+class I18nSimpleBackendLookupTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ # useful because this way we can use the backend with no key for interpolation/pluralization
+ def test_lookup_given_nil_as_a_key_returns_nil
+ assert_nil @backend.send(:lookup, 'en', nil)
+ end
+
+ def test_lookup_given_nested_keys_looks_up_a_nested_hash_value
+ assert_equal 'bar', @backend.send(:lookup, 'en', :bar, [:foo])
+ end
+end
+
+class I18nSimpleBackendPluralizeTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def test_pluralize_given_nil_returns_the_given_entry
+ entry = {:one => 'bar', :other => 'bars'}
+ assert_equal entry, @backend.send(:pluralize, nil, entry, nil)
+ end
+
+ def test_pluralize_given_0_returns_zero_string_if_zero_key_given
+ assert_equal 'zero', @backend.send(:pluralize, nil, {:zero => 'zero', :one => 'bar', :other => 'bars'}, 0)
+ end
+
+ def test_pluralize_given_0_returns_plural_string_if_no_zero_key_given
+ assert_equal 'bars', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 0)
+ end
+
+ def test_pluralize_given_1_returns_singular_string
+ assert_equal 'bar', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 1)
+ end
+
+ def test_pluralize_given_2_returns_plural_string
+ assert_equal 'bars', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 2)
+ end
+
+ def test_pluralize_given_3_returns_plural_string
+ assert_equal 'bars', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 3)
+ end
+
+ def test_interpolate_given_incomplete_pluralization_data_raises_invalid_pluralization_data
+ assert_raises(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, {:one => 'bar'}, 2) }
+ end
+
+ # def test_interpolate_given_a_string_raises_invalid_pluralization_data
+ # assert_raises(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, 'bar', 2) }
+ # end
+ #
+ # def test_interpolate_given_an_array_raises_invalid_pluralization_data
+ # assert_raises(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, ['bar'], 2) }
+ # end
+end
+
+class I18nSimpleBackendInterpolateTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def test_interpolate_given_a_value_hash_interpolates_the_values_to_the_string
+ assert_equal 'Hi David!', @backend.send(:interpolate, nil, 'Hi {{name}}!', :name => 'David')
+ end
+
+ def test_interpolate_given_a_value_hash_interpolates_into_unicode_string
+ assert_equal 'Häi David!', @backend.send(:interpolate, nil, 'Häi {{name}}!', :name => 'David')
+ end
+
+ def test_interpolate_given_nil_as_a_string_returns_nil
+ assert_nil @backend.send(:interpolate, nil, nil, :name => 'David')
+ end
+
+ def test_interpolate_given_an_non_string_as_a_string_returns_nil
+ assert_equal [], @backend.send(:interpolate, nil, [], :name => 'David')
+ end
+
+ def test_interpolate_given_a_values_hash_with_nil_values_interpolates_the_string
+ assert_equal 'Hi !', @backend.send(:interpolate, nil, 'Hi {{name}}!', {:name => nil})
+ end
+
+ def test_interpolate_given_an_empty_values_hash_raises_missing_interpolation_argument
+ assert_raises(I18n::MissingInterpolationArgument) { @backend.send(:interpolate, nil, 'Hi {{name}}!', {}) }
+ end
+
+ def test_interpolate_given_a_string_containing_a_reserved_key_raises_reserved_interpolation_key
+ assert_raises(I18n::ReservedInterpolationKey) { @backend.send(:interpolate, nil, '{{default}}', {:default => nil}) }
+ end
+end
+
+class I18nSimpleBackendLocalizeDateTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def setup
+ @backend = I18n::Backend::Simple.new
+ add_datetime_translations
+ @date = Date.new 2008, 1, 1
+ end
+
+ def test_translate_given_the_short_format_it_uses_it
+ assert_equal '01. Jan', @backend.localize('de', @date, :short)
+ end
+
+ def test_translate_given_the_long_format_it_uses_it
+ assert_equal '01. Januar 2008', @backend.localize('de', @date, :long)
+ end
+
+ def test_translate_given_the_default_format_it_uses_it
+ assert_equal '01.01.2008', @backend.localize('de', @date, :default)
+ end
+
+ def test_translate_given_a_day_name_format_it_returns_a_day_name
+ assert_equal 'Dienstag', @backend.localize('de', @date, '%A')
+ end
+
+ def test_translate_given_an_abbr_day_name_format_it_returns_an_abbrevated_day_name
+ assert_equal 'Di', @backend.localize('de', @date, '%a')
+ end
+
+ def test_translate_given_a_month_name_format_it_returns_a_month_name
+ assert_equal 'Januar', @backend.localize('de', @date, '%B')
+ end
+
+ def test_translate_given_an_abbr_month_name_format_it_returns_an_abbrevated_month_name
+ assert_equal 'Jan', @backend.localize('de', @date, '%b')
+ end
+
+ def test_translate_given_no_format_it_does_not_fail
+ assert_nothing_raised{ @backend.localize 'de', @date }
+ end
+
+ def test_translate_given_an_unknown_format_it_does_not_fail
+ assert_nothing_raised{ @backend.localize 'de', @date, '%x' }
+ end
+
+ def test_localize_nil_raises_argument_error
+ assert_raises(I18n::ArgumentError) { @backend.localize 'de', nil }
+ end
+
+ def test_localize_object_raises_argument_error
+ assert_raises(I18n::ArgumentError) { @backend.localize 'de', Object.new }
+ end
+end
+
+class I18nSimpleBackendLocalizeDateTimeTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def setup
+ @backend = I18n::Backend::Simple.new
+ add_datetime_translations
+ @morning = DateTime.new 2008, 1, 1, 6
+ @evening = DateTime.new 2008, 1, 1, 18
+ end
+
+ def test_translate_given_the_short_format_it_uses_it
+ assert_equal '01. Jan 06:00', @backend.localize('de', @morning, :short)
+ end
+
+ def test_translate_given_the_long_format_it_uses_it
+ assert_equal '01. Januar 2008 06:00', @backend.localize('de', @morning, :long)
+ end
+
+ def test_translate_given_the_default_format_it_uses_it
+ assert_equal 'Di, 01. Jan 2008 06:00:00 +0000', @backend.localize('de', @morning, :default)
+ end
+
+ def test_translate_given_a_day_name_format_it_returns_the_correct_day_name
+ assert_equal 'Dienstag', @backend.localize('de', @morning, '%A')
+ end
+
+ def test_translate_given_an_abbr_day_name_format_it_returns_the_correct_abbrevated_day_name
+ assert_equal 'Di', @backend.localize('de', @morning, '%a')
+ end
+
+ def test_translate_given_a_month_name_format_it_returns_the_correct_month_name
+ assert_equal 'Januar', @backend.localize('de', @morning, '%B')
+ end
+
+ def test_translate_given_an_abbr_month_name_format_it_returns_the_correct_abbrevated_month_name
+ assert_equal 'Jan', @backend.localize('de', @morning, '%b')
+ end
+
+ def test_translate_given_a_meridian_indicator_format_it_returns_the_correct_meridian_indicator
+ assert_equal 'am', @backend.localize('de', @morning, '%p')
+ assert_equal 'pm', @backend.localize('de', @evening, '%p')
+ end
+
+ def test_translate_given_no_format_it_does_not_fail
+ assert_nothing_raised{ @backend.localize 'de', @morning }
+ end
+
+ def test_translate_given_an_unknown_format_it_does_not_fail
+ assert_nothing_raised{ @backend.localize 'de', @morning, '%x' }
+ end
+end
+
+class I18nSimpleBackendLocalizeTimeTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def setup
+ @old_timezone, ENV['TZ'] = ENV['TZ'], 'UTC'
+ @backend = I18n::Backend::Simple.new
+ add_datetime_translations
+ @morning = Time.parse '2008-01-01 6:00 UTC'
+ @evening = Time.parse '2008-01-01 18:00 UTC'
+ end
+
+ def teardown
+ @old_timezone ? ENV['TZ'] = @old_timezone : ENV.delete('TZ')
+ end
+
+ def test_translate_given_the_short_format_it_uses_it
+ assert_equal '01. Jan 06:00', @backend.localize('de', @morning, :short)
+ end
+
+ def test_translate_given_the_long_format_it_uses_it
+ assert_equal '01. Januar 2008 06:00', @backend.localize('de', @morning, :long)
+ end
+
+ # TODO Seems to break on Windows because ENV['TZ'] is ignored. What's a better way to do this?
+ # def test_translate_given_the_default_format_it_uses_it
+ # assert_equal 'Di, 01. Jan 2008 06:00:00 +0000', @backend.localize('de', @morning, :default)
+ # end
+
+ def test_translate_given_a_day_name_format_it_returns_the_correct_day_name
+ assert_equal 'Dienstag', @backend.localize('de', @morning, '%A')
+ end
+
+ def test_translate_given_an_abbr_day_name_format_it_returns_the_correct_abbrevated_day_name
+ assert_equal 'Di', @backend.localize('de', @morning, '%a')
+ end
+
+ def test_translate_given_a_month_name_format_it_returns_the_correct_month_name
+ assert_equal 'Januar', @backend.localize('de', @morning, '%B')
+ end
+
+ def test_translate_given_an_abbr_month_name_format_it_returns_the_correct_abbrevated_month_name
+ assert_equal 'Jan', @backend.localize('de', @morning, '%b')
+ end
+
+ def test_translate_given_a_meridian_indicator_format_it_returns_the_correct_meridian_indicator
+ assert_equal 'am', @backend.localize('de', @morning, '%p')
+ assert_equal 'pm', @backend.localize('de', @evening, '%p')
+ end
+
+ def test_translate_given_no_format_it_does_not_fail
+ assert_nothing_raised{ @backend.localize 'de', @morning }
+ end
+
+ def test_translate_given_an_unknown_format_it_does_not_fail
+ assert_nothing_raised{ @backend.localize 'de', @morning, '%x' }
+ end
+end
+
+class I18nSimpleBackendHelperMethodsTest < Test::Unit::TestCase
+ def setup
+ @backend = I18n::Backend::Simple.new
+ end
+
+ def test_deep_symbolize_keys_works
+ result = @backend.send :deep_symbolize_keys, 'foo' => {'bar' => {'baz' => 'bar'}}
+ expected = {:foo => {:bar => {:baz => 'bar'}}}
+ assert_equal expected, result
+ end
+end
+
+class I18nSimpleBackendLoadTranslationsTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def test_load_translations_with_unknown_file_type_raises_exception
+ assert_raises(I18n::UnknownFileType) { @backend.load_translations "#{@locale_dir}/en.xml" }
+ end
+
+ def test_load_translations_with_ruby_file_type_does_not_raise_exception
+ assert_nothing_raised { @backend.load_translations "#{@locale_dir}/en.rb" }
+ end
+
+ def test_load_rb_loads_data_from_ruby_file
+ data = @backend.send :load_rb, "#{@locale_dir}/en.rb"
+ assert_equal({:'en-Ruby' => {:foo => {:bar => "baz"}}}, data)
+ end
+
+ def test_load_rb_loads_data_from_yaml_file
+ data = @backend.send :load_yml, "#{@locale_dir}/en.yml"
+ assert_equal({'en-Yaml' => {'foo' => {'bar' => 'baz'}}}, data)
+ end
+
+ def test_load_translations_loads_from_different_file_formats
+ @backend = I18n::Backend::Simple.new
+ @backend.load_translations "#{@locale_dir}/en.rb", "#{@locale_dir}/en.yml"
+ expected = {
+ :'en-Ruby' => {:foo => {:bar => "baz"}},
+ :'en-Yaml' => {:foo => {:bar => "baz"}}
+ }
+ assert_equal expected, backend_get_translations
+ end
+end
+
+class I18nSimpleBackendReloadTranslationsTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def setup
+ @backend = I18n::Backend::Simple.new
+ I18n.load_path = [File.dirname(__FILE__) + '/locale/en.yml']
+ assert_nil backend_get_translations
+ @backend.send :init_translations
+ end
+
+ def teardown
+ I18n.load_path = []
+ end
+
+ def test_setup
+ assert_not_nil backend_get_translations
+ end
+
+ def test_reload_translations_unloads_translations
+ @backend.reload!
+ assert_nil backend_get_translations
+ end
+
+ def test_reload_translations_uninitializes_translations
+ @backend.reload!
+ assert_equal @backend.initialized?, false
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 25b8eecef5..2bc2e1eaf0 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -53,10 +53,41 @@ class Person < Record
end
class ConditionalPerson < Record
+ # proc
before_save Proc.new { |r| r.history << [:before_save, :proc] }, :if => Proc.new { |r| true }
before_save Proc.new { |r| r.history << "b00m" }, :if => Proc.new { |r| false }
before_save Proc.new { |r| r.history << [:before_save, :proc] }, :unless => Proc.new { |r| false }
before_save Proc.new { |r| r.history << "b00m" }, :unless => Proc.new { |r| true }
+ # symbol
+ before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :if => :yes
+ before_save Proc.new { |r| r.history << "b00m" }, :if => :no
+ before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :unless => :no
+ before_save Proc.new { |r| r.history << "b00m" }, :unless => :yes
+ # string
+ before_save Proc.new { |r| r.history << [:before_save, :string] }, :if => 'yes'
+ before_save Proc.new { |r| r.history << "b00m" }, :if => 'no'
+ before_save Proc.new { |r| r.history << [:before_save, :string] }, :unless => 'no'
+ before_save Proc.new { |r| r.history << "b00m" }, :unless => 'yes'
+ # Array with conditions
+ before_save Proc.new { |r| r.history << [:before_save, :symbol_array] }, :if => [:yes, :other_yes]
+ before_save Proc.new { |r| r.history << "b00m" }, :if => [:yes, :no]
+ before_save Proc.new { |r| r.history << [:before_save, :symbol_array] }, :unless => [:no, :other_no]
+ before_save Proc.new { |r| r.history << "b00m" }, :unless => [:yes, :no]
+ # Combined if and unless
+ before_save Proc.new { |r| r.history << [:before_save, :combined_symbol] }, :if => :yes, :unless => :no
+ before_save Proc.new { |r| r.history << "b00m" }, :if => :yes, :unless => :yes
+ # Array with different types of conditions
+ before_save Proc.new { |r| r.history << [:before_save, :symbol_proc_string_array] }, :if => [:yes, Proc.new { |r| true }, 'yes']
+ before_save Proc.new { |r| r.history << "b00m" }, :if => [:yes, Proc.new { |r| true }, 'no']
+ # Array with different types of conditions comibned if and unless
+ before_save Proc.new { |r| r.history << [:before_save, :combined_symbol_proc_string_array] },
+ :if => [:yes, Proc.new { |r| true }, 'yes'], :unless => [:no, 'no']
+ before_save Proc.new { |r| r.history << "b00m" }, :if => [:yes, Proc.new { |r| true }, 'no'], :unless => [:no, 'no']
+
+ def yes; true; end
+ def other_yes; true; end
+ def no; false; end
+ def other_no; false; end
def save
run_callbacks(:before_save)
@@ -90,7 +121,16 @@ class ConditionalCallbackTest < Test::Unit::TestCase
person.save
assert_equal [
[:before_save, :proc],
- [:before_save, :proc]
+ [:before_save, :proc],
+ [:before_save, :symbol],
+ [:before_save, :symbol],
+ [:before_save, :string],
+ [:before_save, :string],
+ [:before_save, :symbol_array],
+ [:before_save, :symbol_array],
+ [:before_save, :combined_symbol],
+ [:before_save, :symbol_proc_string_array],
+ [:before_save, :combined_symbol_proc_string_array]
], person.history
end
end
diff --git a/activesupport/test/core_ext/object_ext_test.rb b/activesupport/test/core_ext/object_ext_test.rb
new file mode 100644
index 0000000000..a413d331c4
--- /dev/null
+++ b/activesupport/test/core_ext/object_ext_test.rb
@@ -0,0 +1,8 @@
+require 'abstract_unit'
+
+class ObjectExtTest < Test::Unit::TestCase
+ def test_tap_yields_and_returns_self
+ foo = Object.new
+ assert_equal foo, foo.tap { |x| assert_equal foo, x; :bar }
+ end
+end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index dc36336239..7c8fb7dd94 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -256,6 +256,15 @@ class TimeWithZoneTest < Test::Unit::TestCase
twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] )
assert_equal 86_400.0, twz2 - twz1
end
+
+ def test_minus_with_datetime
+ assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1)
+ end
+
+ def test_minus_with_wrapped_datetime
+ assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - Time.utc(2000, 1, 1)
+ assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1)
+ end
def test_plus_and_minus_enforce_spring_dst_rules
silence_warnings do # silence warnings raised by tzinfo gem
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index 19ae3a01a8..558b03b90d 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -15,7 +15,8 @@ class TestJSONDecoding < Test::Unit::TestCase
# no time zone
%({a: "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"},
# needs to be *exact*
- %({a: " 2007-01-01 01:12:34 Z "}) => {'a' => " 2007-01-01 01:12:34 Z "},
+ %({a: " 2007-01-01 01:12:34 Z "}) => {'a' => " 2007-01-01 01:12:34 Z "},
+ %({a: "2007-01-01 : it's your birthday"}) => {'a' => "2007-01-01 : it's your birthday"},
%([]) => [],
%({}) => {},
%(1) => 1,
diff --git a/ci/ci_build.rb b/ci/ci_build.rb
index 7b9cdceb27..010f78ba09 100755
--- a/ci/ci_build.rb
+++ b/ci/ci_build.rb
@@ -23,6 +23,7 @@ cd "#{root_dir}/activesupport" do
build_results[:activesupport] = system 'rake'
end
+rm_f "#{root_dir}/activerecord/debug.log"
cd "#{root_dir}/activerecord" do
puts
puts "[CruiseControl] Building ActiveRecord with MySQL"
@@ -37,13 +38,12 @@ cd "#{root_dir}/activerecord" do
build_results[:activerecord_postgresql8] = system 'rake test_postgresql'
end
-# Sqlite2 is disabled until tests are fixed
-# cd "#{root_dir}/activerecord" do
-# puts
-# puts "[CruiseControl] Building ActiveRecord with SQLite 2"
-# puts
-# build_results[:activerecord_sqlite] = system 'rake test_sqlite'
-# end
+cd "#{root_dir}/activerecord" do
+ puts
+ puts "[CruiseControl] Building ActiveRecord with SQLite 2"
+ puts
+ build_results[:activerecord_sqlite] = system 'rake test_sqlite'
+end
cd "#{root_dir}/activerecord" do
puts
@@ -59,6 +59,7 @@ cd "#{root_dir}/activemodel" do
build_results[:activemodel] = system 'rake'
end
+rm_f "#{root_dir}/activeresource/debug.log"
cd "#{root_dir}/activeresource" do
puts
puts "[CruiseControl] Building ActiveResource"
diff --git a/ci/ci_setup_notes.txt b/ci/ci_setup_notes.txt
index 86df33c443..b3c5936797 100644
--- a/ci/ci_setup_notes.txt
+++ b/ci/ci_setup_notes.txt
@@ -54,10 +54,14 @@ ci ALL=NOPASSWD: /usr/local/bin/geminstaller, /usr/local/bin/ruby, /usr/loc
* Install/setup nginx:
$ sudo aptitude install nginx
$ sudo vi /etc/nginx/sites-available/default
+# Change server_name entry to match server name
+
# comment two lines and add one to proxy to ccrb:
# root /var/www/nginx-default;
# index index.html index.htm;
proxy_pass http://127.0.0.1:3333;
+
+# also comment default locations for /doc and /images
$ sudo /etc/init.d/nginx start
* Add project to cruise (It will still fail until everything is set up):
@@ -101,6 +105,13 @@ $ sudo aptitude install sqlite sqlite3 libsqlite-dev libsqlite3-dev
$ sudo aptitude install postgresql postgresql-server-dev-8.3
$ sudo su - postgres -c 'createuser -s ci'
+* Install fcgi libraries
+$ sudo apt-get install libfcgi-dev
+
+* Install memcached and start for first time (should start on reboot automatically)
+$ sudo aptitude install memcached
+$ sudo /etc/init.d/memcached start
+
* Install and run GemInstaller to get all dependency gems
$ sudo gem install geminstaller
$ cd ~/.cruise/projects/rails/work
diff --git a/ci/cruise_config.rb b/ci/cruise_config.rb
index 46325f5ebd..325c21397e 100644
--- a/ci/cruise_config.rb
+++ b/ci/cruise_config.rb
@@ -1,5 +1,6 @@
Project.configure do |project|
project.build_command = 'ruby ci/ci_build.rb'
- project.email_notifier.emails = ['thewoolleyman@gmail.com','michael@koziarski.com', 'david@loudthinking.com', 'jeremy@bitsweat.net', 'josh@joshpeek.com', 'pratiknaik@gmail.com']
+# project.email_notifier.emails = ['thewoolleyman@gmail.com']
+ project.email_notifier.emails = ['thewoolleyman@gmail.com','michael@koziarski.com', 'david@loudthinking.com', 'jeremy@bitsweat.net', 'josh@joshpeek.com', 'pratiknaik@gmail.com', 'wycats@gmail.com']
project.email_notifier.from = 'thewoolleyman+railsci@gmail.com'
end
diff --git a/ci/geminstaller.yml b/ci/geminstaller.yml
index 3a1862c3c3..4251518999 100644
--- a/ci/geminstaller.yml
+++ b/ci/geminstaller.yml
@@ -2,13 +2,19 @@
gems:
- name: geminstaller
version: >= 0.4.3
+- name: fcgi
+ version: >= 0.8.7
+- name: memcache-client
+ version: >= 1.5.0
- name: mocha
- version: >= 0.9.0
+ version: >= 0.9.4
- name: mysql
#version: >= 2.7
version: = 2.7
- name: postgres
version: >= 0.7.9.2008.01.28
+- name: rack
+ version: '~> 0.9.0'
- name: rake
version: >= 0.8.1
- name: sqlite-ruby
diff --git a/railties/doc/guides/html/activerecord_validations_callbacks.html b/railties/doc/guides/html/activerecord_validations_callbacks.html
index 7936de209d..0862776f53 100644
--- a/railties/doc/guides/html/activerecord_validations_callbacks.html
+++ b/railties/doc/guides/html/activerecord_validations_callbacks.html
@@ -285,9 +285,19 @@ ul#navMain {
<li><a href="#_callbacks_registration">Callbacks registration</a></li>
- <li><a href="#_registering_callbacks_by_overriding_the_callback_methods">Registering callbacks by overriding the callback methods</a></li>
+ </ul>
+ </li>
+ <li>
+ <a href="#_conditional_callbacks">Conditional callbacks</a>
+ <ul>
- <li><a href="#_registering_callbacks_by_using_macro_style_class_methods">Registering callbacks by using macro-style class methods</a></li>
+ <li><a href="#_using_a_symbol_with_the_tt_if_tt_and_tt_unless_tt_options_2">Using a symbol with the <tt>:if</tt> and <tt>:unless</tt> options</a></li>
+
+ <li><a href="#_using_a_string_with_the_tt_if_tt_and_tt_unless_tt_options_2">Using a string with the <tt>:if</tt> and <tt>:unless</tt> options</a></li>
+
+ <li><a href="#_using_a_proc_object_with_the_tt_if_tt_and_tt_unless_tt_options_2">Using a Proc object with the <tt>:if</tt> and :<tt>unless</tt> options</a></li>
+
+ <li><a href="#_multiple_conditions_for_callbacks">Multiple Conditions for Callbacks</a></li>
</ul>
</li>
@@ -635,7 +645,7 @@ http://www.gnu.org/software/src-highlite -->
<td class="icon">
<img src="./images/icons/note.png" alt="Note" />
</td>
-<td class="content">If you want to validate the presence of a boolean field (where the real values are true and false), you will want to use validates_inclusion_of :field_name, :in =&gt; [true, false] This is due to the way Object#blank? handles boolean values. false.blank? # =&gt; true</td>
+<td class="content">If you want to validate the presence of a boolean field (where the real values are true and false), you will want to use validates_inclusion_of :field_name, :in &#8658; [true, false] This is due to the way Object#blank? handles boolean values. false.blank? # &#8658; true</td>
</tr></table>
</div>
<div class="paragraph"><p>The default error message for <tt>validates_presence_of</tt> is "<em>can&#8217;t be empty</em>".</p></div>
@@ -735,7 +745,7 @@ http://www.gnu.org/software/src-highlite -->
<div class="sectionbody">
<div class="paragraph"><p>Sometimes it will make sense to validate an object just when a given predicate is satisfied. You can do that by using the <tt>:if</tt> and <tt>:unless</tt> options, which can take a symbol, a string or a Ruby Proc. You may use the <tt>:if</tt> option when you want to specify when the validation <strong>should</strong> happen. If you want to specify when the validation <strong>should not</strong> happen, then you may use the <tt>:unless</tt> option.</p></div>
<h3 id="_using_a_symbol_with_the_tt_if_tt_and_tt_unless_tt_options">5.1. Using a symbol with the <tt>:if</tt> and <tt>:unless</tt> options</h3>
-<div class="paragraph"><p>You can associated the <tt>:if</tt> and <tt>:unless</tt> options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option.</p></div>
+<div class="paragraph"><p>You can associate the <tt>:if</tt> and <tt>:unless</tt> options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option.</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
by Lorenzo Bettini
@@ -1055,26 +1065,7 @@ An object of the <tt>ActionView::Helpers::InstanceTag</tt> class.
<div class="sectionbody">
<div class="paragraph"><p>Callbacks are methods that get called at certain moments of an object&#8217;s lifecycle. With callbacks it&#8217;s possible to write code that will run whenever an Active Record object is created, saved, updated, deleted or loaded from the database.</p></div>
<h3 id="_callbacks_registration">9.1. Callbacks registration</h3>
-<div class="paragraph"><p>In order to use the available callbacks, you need to registrate them. There are two ways of doing that.</p></div>
-<h3 id="_registering_callbacks_by_overriding_the_callback_methods">9.2. Registering callbacks by overriding the callback methods</h3>
-<div class="paragraph"><p>You can specify the callback method directly, by overriding it. Let&#8217;s see how it works using the <tt>before_validation</tt> callback, which will surprisingly run right before any validation is done.</p></div>
-<div class="listingblock">
-<div class="content"><!-- Generator: GNU source-highlight 2.9
-by Lorenzo Bettini
-http://www.lorenzobettini.it
-http://www.gnu.org/software/src-highlite -->
-<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">class</span></span> User <span style="color: #990000">&lt;</span> ActiveRecord<span style="color: #990000">::</span>Base
- validates_presence_of <span style="color: #990000">:</span>login<span style="color: #990000">,</span> <span style="color: #990000">:</span>email
-
- protected
- <span style="font-weight: bold"><span style="color: #0000FF">def</span></span> before_validation
- <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="font-weight: bold"><span style="color: #0000FF">self</span></span><span style="color: #990000">.</span>login<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #0000FF">nil</span></span><span style="color: #990000">?</span>
- <span style="font-weight: bold"><span style="color: #0000FF">self</span></span><span style="color: #990000">.</span>login <span style="color: #990000">=</span> email <span style="font-weight: bold"><span style="color: #0000FF">unless</span></span> email<span style="color: #990000">.</span>blank?
- <span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
- <span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
-<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
-<h3 id="_registering_callbacks_by_using_macro_style_class_methods">9.3. Registering callbacks by using macro-style class methods</h3>
-<div class="paragraph"><p>The other way you can register a callback method is by implementing it as an ordinary method, and then using a macro-style class method to register it as a callback. The last example could be written like that:</p></div>
+<div class="paragraph"><p>In order to use the available callbacks, you need to registrate them. You can do that by implementing them as an ordinary methods, and then using a macro-style class method to register then as callbacks.</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
by Lorenzo Bettini
@@ -1103,19 +1094,6 @@ http://www.gnu.org/software/src-highlite -->
before_create <span style="color: #FF0000">{</span><span style="color: #990000">|</span>user<span style="color: #990000">|</span> user<span style="color: #990000">.</span>name <span style="color: #990000">=</span> user<span style="color: #990000">.</span>login<span style="color: #990000">.</span>capitalize <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> user<span style="color: #990000">.</span>name<span style="color: #990000">.</span>blank?<span style="color: #FF0000">}</span>
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
-<div class="paragraph"><p>In Rails, the preferred way of registering callbacks is by using macro-style class methods. The main advantages of using macro-style class methods are:</p></div>
-<div class="ulist"><ul>
-<li>
-<p>
-You can add more than one method for each type of callback. Those methods will be queued for execution at the same order they were registered.
-</p>
-</li>
-<li>
-<p>
-Readability, since your callback declarations will live at the beggining of your models' files.
-</p>
-</li>
-</ul></div>
<div class="admonitionblock">
<table><tr>
<td class="icon">
@@ -1125,10 +1103,56 @@ Readability, since your callback declarations will live at the beggining of your
</tr></table>
</div>
</div>
-<h2 id="_available_callbacks">10. Available callbacks</h2>
+<h2 id="_conditional_callbacks">10. Conditional callbacks</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>Like in validations, we can also make our callbacks conditional, calling then only when a given predicate is satisfied. You can do that by using the <tt>:if</tt> and <tt>:unless</tt> options, which can take a symbol, a string or a Ruby Proc. You may use the <tt>:if</tt> option when you want to specify when the callback <strong>should</strong> get called. If you want to specify when the callback <strong>should not</strong> be called, then you may use the <tt>:unless</tt> option.</p></div>
+<h3 id="_using_a_symbol_with_the_tt_if_tt_and_tt_unless_tt_options_2">10.1. Using a symbol with the <tt>:if</tt> and <tt>:unless</tt> options</h3>
+<div class="paragraph"><p>You can associate the <tt>:if</tt> and <tt>:unless</tt> options with a symbol corresponding to the name of a method that will get called right before the callback. If this method returns <tt>false</tt> the callback won&#8217;t be executed. This is the most common option. Using this form of registration it&#8217;s also possible to register several different methods that should be called to check the if the callback should be executed.</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">class</span></span> Order <span style="color: #990000">&lt;</span> ActiveRecord<span style="color: #990000">::</span>Base
+ before_save <span style="color: #990000">:</span>normalize_card_number<span style="color: #990000">,</span> <span style="color: #990000">:</span><span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="color: #990000">=&gt;</span> <span style="color: #990000">:</span>paid_with_card?
+<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
+<h3 id="_using_a_string_with_the_tt_if_tt_and_tt_unless_tt_options_2">10.2. Using a string with the <tt>:if</tt> and <tt>:unless</tt> options</h3>
+<div class="paragraph"><p>You can also use a string that will be evaluated using <tt>:eval</tt> and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition.</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">class</span></span> Order <span style="color: #990000">&lt;</span> ActiveRecord<span style="color: #990000">::</span>Base
+ before_save <span style="color: #990000">:</span>normalize_card_number<span style="color: #990000">,</span> <span style="color: #990000">:</span><span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="color: #990000">=&gt;</span> <span style="color: #FF0000">"paid_with_card?"</span>
+<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
+<h3 id="_using_a_proc_object_with_the_tt_if_tt_and_tt_unless_tt_options_2">10.3. Using a Proc object with the <tt>:if</tt> and :<tt>unless</tt> options</h3>
+<div class="paragraph"><p>Finally, it&#8217;s possible to associate <tt>:if</tt> and <tt>:unless</tt> with a Ruby Proc object. This option is best suited when writing short validation methods, usually one-liners.</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">class</span></span> Order <span style="color: #990000">&lt;</span> ActiveRecord<span style="color: #990000">::</span>Base
+ before_save <span style="color: #990000">:</span>normalize_card_number<span style="color: #990000">,</span>
+ <span style="color: #990000">:</span><span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="color: #990000">=&gt;</span> Proc<span style="color: #990000">.</span>new <span style="color: #FF0000">{</span> <span style="color: #990000">|</span>order<span style="color: #990000">|</span> order<span style="color: #990000">.</span>paid_with_card? <span style="color: #FF0000">}</span>
+<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
+<h3 id="_multiple_conditions_for_callbacks">10.4. Multiple Conditions for Callbacks</h3>
+<div class="paragraph"><p>When writing conditional callbacks, it&#8217;s possible to mix both <tt>:if</tt> and <tt>:unless</tt> in the same callback declaration.</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">class</span></span> Comment <span style="color: #990000">&lt;</span> ActiveRecord<span style="color: #990000">::</span>Base
+ after_create <span style="color: #990000">:</span>send_email_to_author<span style="color: #990000">,</span> <span style="color: #990000">:</span><span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="color: #990000">=&gt;</span> <span style="color: #990000">:</span>author_wants_emails?<span style="color: #990000">,</span>
+ <span style="color: #990000">:</span><span style="font-weight: bold"><span style="color: #0000FF">unless</span></span> <span style="color: #990000">=&gt;</span> Proc<span style="color: #990000">.</span>new <span style="color: #FF0000">{</span> <span style="color: #990000">|</span>comment<span style="color: #990000">|</span> comment<span style="color: #990000">.</span>post<span style="color: #990000">.</span>ignore_comments? <span style="color: #FF0000">}</span>
+<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
+</div>
+<h2 id="_available_callbacks">11. Available callbacks</h2>
<div class="sectionbody">
<div class="paragraph"><p>Here is a list with all the available Active Record callbacks, listed in the same order in which they will get called during the respective operations.</p></div>
-<h3 id="_callbacks_called_both_when_creating_or_updating_a_record">10.1. Callbacks called both when creating or updating a record.</h3>
+<h3 id="_callbacks_called_both_when_creating_or_updating_a_record">11.1. Callbacks called both when creating or updating a record.</h3>
<div class="ulist"><ul>
<li>
<p>
@@ -1156,7 +1180,7 @@ Readability, since your callback declarations will live at the beggining of your
</p>
</li>
</ul></div>
-<h3 id="_callbacks_called_only_when_creating_a_new_record">10.2. Callbacks called only when creating a new record.</h3>
+<h3 id="_callbacks_called_only_when_creating_a_new_record">11.2. Callbacks called only when creating a new record.</h3>
<div class="ulist"><ul>
<li>
<p>
@@ -1184,7 +1208,7 @@ Readability, since your callback declarations will live at the beggining of your
</p>
</li>
</ul></div>
-<h3 id="_callbacks_called_only_when_updating_an_existing_record">10.3. Callbacks called only when updating an existing record.</h3>
+<h3 id="_callbacks_called_only_when_updating_an_existing_record">11.3. Callbacks called only when updating an existing record.</h3>
<div class="ulist"><ul>
<li>
<p>
@@ -1212,7 +1236,7 @@ Readability, since your callback declarations will live at the beggining of your
</p>
</li>
</ul></div>
-<h3 id="_callbacks_called_when_removing_a_record_from_the_database">10.4. Callbacks called when removing a record from the database.</h3>
+<h3 id="_callbacks_called_when_removing_a_record_from_the_database">11.4. Callbacks called when removing a record from the database.</h3>
<div class="ulist"><ul>
<li>
<p>
@@ -1231,16 +1255,16 @@ Readability, since your callback declarations will live at the beggining of your
</li>
</ul></div>
<div class="paragraph"><p>The <tt>before_destroy</tt> and <tt>after_destroy</tt> callbacks will only be called if you delete the model using either the <tt>destroy</tt> instance method or one of the <tt>destroy</tt> or <tt>destroy_all</tt> class methods of your Active Record class. If you use <tt>delete</tt> or <tt>delete_all</tt> no callback operations will run, since Active Record will not instantiate any objects, accessing the records to be deleted directly in the database.</p></div>
-<h3 id="_the_tt_after_initialize_tt_and_tt_after_find_tt_callbacks">10.5. The <tt>after_initialize</tt> and <tt>after_find</tt> callbacks</h3>
+<h3 id="_the_tt_after_initialize_tt_and_tt_after_find_tt_callbacks">11.5. The <tt>after_initialize</tt> and <tt>after_find</tt> callbacks</h3>
<div class="paragraph"><p>The <tt>after_initialize</tt> callback will be called whenever an Active Record object is instantiated, either by direcly using <tt>new</tt> or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record <tt>initialize</tt> method.</p></div>
<div class="paragraph"><p>The <tt>after_find</tt> callback will be called whenever Active Record loads a record from the database. When used together with <tt>after_initialize</tt> it will run first, since Active Record will first read the record from the database and them create the model object that will hold it.</p></div>
<div class="paragraph"><p>The <tt>after_initialize</tt> and <tt>after_find</tt> callbacks are a bit different from the others, since the only way to register those callbacks is by defining them as methods. If you try to register <tt>after_initialize</tt> or <tt>after_find</tt> using macro-style class methods, they will just be ignored. This behaviour is due to performance reasons, since <tt>after_initialize</tt> and <tt>after_find</tt> will both be called for each record found in the database, significantly slowing down the queries.</p></div>
</div>
-<h2 id="_halting_execution">11. Halting Execution</h2>
+<h2 id="_halting_execution">12. Halting Execution</h2>
<div class="sectionbody">
<div class="paragraph"><p>As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model&#8217;s validations, the registered callbacks and the database operation to be executed. However, if at any moment one of the <tt>before_create</tt>, <tt>before_save</tt>, <tt>before_update</tt> or <tt>before_destroy</tt> callback methods returns a boolean <tt>false</tt> (not <tt>nil</tt>) value, this execution chain will be halted and the desired operation will not complete: your model will not get persisted in the database, or your records will not get deleted and so on.</p></div>
</div>
-<h2 id="_callback_classes">12. Callback classes</h2>
+<h2 id="_callback_classes">13. Callback classes</h2>
<div class="sectionbody">
<div class="paragraph"><p>Sometimes the callback methods that you&#8217;ll write will be useful enough to be reused at other models. Active Record makes it possible to create classes that encapsulate the callback methods, so it becomes very easy to reuse them.</p></div>
<div class="paragraph"><p>Here&#8217;s an example where we create a class with a after_destroy callback for a PictureFile model.</p></div>
@@ -1285,7 +1309,7 @@ http://www.gnu.org/software/src-highlite -->
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
<div class="paragraph"><p>You can declare as many callbacks as you want inside your callback classes.</p></div>
</div>
-<h2 id="_observers">13. Observers</h2>
+<h2 id="_observers">14. Observers</h2>
<div class="sectionbody">
<div class="paragraph"><p>Active Record callbacks are a powerful feature, but they can pollute your model implementation with code that&#8217;s not directly related to the model&#8217;s purpose. In object-oriented software, it&#8217;s always a good idea to design your classes with a single responsibility in the whole system. For example, it wouldn&#8217;t make much sense to have a <tt>User</tt> model with a method that writes data about a login attempt to a log file. Whenever you&#8217;re using callbacks to write code that&#8217;s not directly related to your model class purposes, it may be a good moment to create an Observer.</p></div>
<div class="paragraph"><p>An Active Record Observer is an object that links itself to a model and registers its methods for callbacks. Your model&#8217;s implementation remains clean, while you can reuse the code in the Observer to add behaviour to more than one model class. OK, you may say that we can also do that using callback classes, but it would still force us to add code to our model&#8217;s implementation.</p></div>
@@ -1311,7 +1335,7 @@ http://www.gnu.org/software/src-highlite -->
<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">class</span></span> Auditor <span style="color: #990000">&lt;</span> ActiveRecord<span style="color: #990000">::</span>Observer
observe User<span style="color: #990000">,</span> Registration<span style="color: #990000">,</span> Invoice
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
-<h3 id="_registering_observers">13.1. Registering observers</h3>
+<h3 id="_registering_observers">14.1. Registering observers</h3>
<div class="paragraph"><p>If you paid attention, you may be wondering where Active Record Observers are referenced in our applications, so they get instantiated and begin to interact with our models. For observers to work we need to register them somewhere. The usual place to do that is in our application&#8217;s <strong>config/environment.rb</strong> file. In this file there is a commented-out line where we can define the observers that our application should load at start-up.</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
@@ -1322,10 +1346,10 @@ http://www.gnu.org/software/src-highlite -->
config<span style="color: #990000">.</span>active_record<span style="color: #990000">.</span>observers <span style="color: #990000">=</span> <span style="color: #990000">:</span>registration_observer<span style="color: #990000">,</span> <span style="color: #990000">:</span>auditor</tt></pre></div></div>
<div class="paragraph"><p>You can uncomment the line with <tt>config.active_record.observers</tt> and change the symbols for the name of the observers that should be registered.</p></div>
<div class="paragraph"><p>It&#8217;s also possible to register callbacks in any of the files living at <strong>config/environments/</strong>, if you want an observer to work only in a specific environment. There is not a <tt>config.active_record.observers</tt> line at any of those files, but you can simply add it.</p></div>
-<h3 id="_where_to_put_the_observers_source_files">13.2. Where to put the observers' source files</h3>
+<h3 id="_where_to_put_the_observers_source_files">14.2. Where to put the observers' source files</h3>
<div class="paragraph"><p>By convention, you should always save your observers' source files inside <strong>app/models</strong>.</p></div>
</div>
-<h2 id="_changelog">14. Changelog</h2>
+<h2 id="_changelog">15. Changelog</h2>
<div class="sectionbody">
<div class="paragraph"><p><a href="http://rails.lighthouseapp.com/projects/16213/tickets/26-active-record-validations-and-callbacks">http://rails.lighthouseapp.com/projects/16213/tickets/26-active-record-validations-and-callbacks</a></p></div>
</div>
diff --git a/railties/doc/guides/html/index.html b/railties/doc/guides/html/index.html
index b2dce5cd67..8dc8f6f95b 100644
--- a/railties/doc/guides/html/index.html
+++ b/railties/doc/guides/html/index.html
@@ -348,7 +348,7 @@ of your code.</p></div>
</div></div>
<div class="sidebarblock">
<div class="sidebar-content">
-<div class="sidebar-title"><a href="performance_testing.html">Performance testing Rails Applications</a></div>
+<div class="sidebar-title"><a href="performance_testing.html">Performance Testing Rails Applications</a></div>
<div class="admonitionblock">
<table><tr>
<td class="icon">
diff --git a/railties/doc/guides/html/performance_testing.html b/railties/doc/guides/html/performance_testing.html
index a13c547849..2e6ba0a891 100644
--- a/railties/doc/guides/html/performance_testing.html
+++ b/railties/doc/guides/html/performance_testing.html
@@ -2,7 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
- <title>Performance testing Rails Applications</title>
+ <title>Performance Testing Rails Applications</title>
<!--[if lt IE 8]>
<script src="http://ie7-js.googlecode.com/svn/version/2.0(beta3)/IE8.js" type="text/javascript"></script>
<![endif]-->
@@ -199,32 +199,62 @@ ul#navMain {
<h2>Chapters</h2>
<ol>
<li>
- <a href="#_using_and_understanding_the_log_files">Using and understanding the log files</a>
- </li>
- <li>
- <a href="#_helper_methods">Helper methods</a>
- </li>
- <li>
<a href="#_performance_test_cases">Performance Test Cases</a>
<ul>
+ <li><a href="#_generating_performance_tests">Generating performance tests</a></li>
+
+ <li><a href="#_examples">Examples</a></li>
+
<li><a href="#_modes">Modes</a></li>
<li><a href="#_metrics">Metrics</a></li>
<li><a href="#_understanding_the_output">Understanding the output</a></li>
- <li><a href="#_preparing_ruby_and_ruby_prof">Preparing Ruby and Ruby-prof</a></li>
+ <li><a href="#_tuning_test_runs">Tuning Test Runs</a></li>
- <li><a href="#_generating_performance_test">Generating performance test</a></li>
+ <li><a href="#gc">Installing GC-Patched Ruby</a></li>
</ul>
</li>
<li>
- <a href="#_other_profiling_tools">Other Profiling Tools</a>
+ <a href="#_command_line_tools">Command Line Tools</a>
+ <ul>
+
+ <li><a href="#_benchmarker">benchmarker</a></li>
+
+ <li><a href="#_profiler">profiler</a></li>
+
+ </ul>
</li>
<li>
- <a href="#_commercial_products_dedicated_to_rails_perfomance">Commercial products dedicated to Rails Perfomance</a>
+ <a href="#_helper_methods">Helper methods</a>
+ <ul>
+
+ <li><a href="#_model">Model</a></li>
+
+ <li><a href="#_controller">Controller</a></li>
+
+ <li><a href="#_view">View</a></li>
+
+ </ul>
+ </li>
+ <li>
+ <a href="#_request_logging">Request Logging</a>
+ </li>
+ <li>
+ <a href="#_useful_profiling_tools">Useful Profiling Tools</a>
+ <ul>
+
+ <li><a href="#_rails_plugins_and_gems">Rails Plugins and Gems</a></li>
+
+ <li><a href="#_external">External</a></li>
+
+ </ul>
+ </li>
+ <li>
+ <a href="#_commercial_products">Commercial Products</a>
</li>
<li>
<a href="#_changelog">Changelog</a>
@@ -233,10 +263,10 @@ ul#navMain {
</div>
<div id="content">
- <h1>Performance testing Rails Applications</h1>
+ <h1>Performance Testing Rails Applications</h1>
<div id="preamble">
<div class="sectionbody">
-<div class="paragraph"><p>This guide covers the benchmarking and profiling tactics/tools of Rails and Ruby in general. By referring to this guide, you will be able to:</p></div>
+<div class="paragraph"><p>This guide covers the various ways of performance testing a Ruby on Rails application. By referring to this guide, you will be able to:</p></div>
<div class="ulist"><ul>
<li>
<p>
@@ -245,17 +275,17 @@ Understand the various types of benchmarking and profiling metrics
</li>
<li>
<p>
-Generate performance/benchmarking tests
+Generate performance and benchmarking tests
</p>
</li>
<li>
<p>
-Use GC patched Ruby binary to measure memory usage and object allocation
+Use a GC-patched Ruby binary to measure memory usage and object allocation
</p>
</li>
<li>
<p>
-Understand the information provided by Rails inside the log files
+Understand the benchmarking information provided by Rails inside the log files
</p>
</li>
<li>
@@ -264,77 +294,121 @@ Learn about various tools facilitating benchmarking and profiling
</p>
</li>
</ul></div>
-<div class="paragraph"><p>Performance testing is an integral part of the development cycle. It is very important that you don&#8217;t make your end users wait for too long before the page is completely loaded. Ensuring a plesant browsing experience to the end users and cutting cost of unnecessary hardwares is important for any web application.</p></div>
+<div class="paragraph"><p>Performance testing is an integral part of the development cycle. It is very important that you don&#8217;t make your end users wait for too long before the page is completely loaded. Ensuring a pleasant browsing experience for end users and cutting the cost of unnecessary hardware is important for any non-trivial web application.</p></div>
</div>
</div>
-<h2 id="_using_and_understanding_the_log_files">1. Using and understanding the log files</h2>
+<h2 id="_performance_test_cases">1. Performance Test Cases</h2>
<div class="sectionbody">
-<div class="paragraph"><p>Rails logs files containt basic but very useful information about the time taken to serve every request. A typical log entry looks something like :</p></div>
+<div class="paragraph"><p>Rails performance tests are a special type of integration tests, designed for benchmarking and profiling the test code. With performance tests, you can determine where your application&#8217;s memory or speed problems are coming from, and get a more in-depth picture of those problems.</p></div>
+<div class="paragraph"><p>In a freshly generated Rails application, <tt>test/performance/browsing_test.rb</tt> contains an example of a performance test:</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
-<pre><tt>Processing ItemsController<span style="font-style: italic"><span style="color: #9A1900">#index (for 127.0.0.1 at 2009-01-08 03:06:39) [GET]</span></span>
-Rendering template within layouts<span style="color: #990000">/</span>items
-Rendering items<span style="color: #990000">/</span>index
-Completed <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> 5ms <span style="color: #990000">(</span>View<span style="color: #990000">:</span> <span style="color: #993399">2</span><span style="color: #990000">,</span> DB<span style="color: #990000">:</span> <span style="color: #993399">0</span><span style="color: #990000">)</span> <span style="color: #990000">|</span> <span style="color: #993399">200</span> OK <span style="color: #990000">[</span>http<span style="color: #990000">:</span><span style="color: #FF6600">//0.0.0.0/</span>items<span style="color: #990000">]</span></tt></pre></div></div>
-<div class="paragraph"><p>For this section, we&#8217;re only interested in the last line from that log entry:</p></div>
+<pre><tt><span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'test_helper'</span>
+<span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'performance_test_help'</span>
+
+<span style="font-style: italic"><span style="color: #9A1900"># Profiling results for each test method are written to tmp/performance.</span></span>
+<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> BrowsingTest <span style="color: #990000">&lt;</span> ActionController<span style="color: #990000">::</span>PerformanceTest
+ <span style="font-weight: bold"><span style="color: #0000FF">def</span></span> test_homepage
+ get <span style="color: #FF0000">'/'</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
+<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
+<div class="paragraph"><p>This example is a simple performance test case for profiling a GET request to the application&#8217;s homepage.</p></div>
+<h3 id="_generating_performance_tests">1.1. Generating performance tests</h3>
+<div class="paragraph"><p>Rails provides a generator called <tt>performance_test</tt> for creating new performance tests:</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
-<pre><tt>Completed <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> 5ms <span style="color: #990000">(</span>View<span style="color: #990000">:</span> <span style="color: #993399">2</span><span style="color: #990000">,</span> DB<span style="color: #990000">:</span> <span style="color: #993399">0</span><span style="color: #990000">)</span> <span style="color: #990000">|</span> <span style="color: #993399">200</span> OK <span style="color: #990000">[</span>http<span style="color: #990000">:</span><span style="color: #FF6600">//0.0.0.0/</span>items<span style="color: #990000">]</span></tt></pre></div></div>
-<div class="paragraph"><p>This data is fairly straight forward to understand. Rails uses millisecond(ms) as the metric to measures the time taken. The complete request spent 5 ms inside Rails, out of which 2 ms were spent rendering views and none was spent communication with the database. It&#8217;s safe to assume that the remaining 3 ms were spent inside the controller.</p></div>
-</div>
-<h2 id="_helper_methods">2. Helper methods</h2>
-<div class="sectionbody">
-<div class="paragraph"><p>Rails provides various helper methods inside Active Record, Action Controller and Action View to measure the time taken by a specific code. The method is called <tt>benchmark()</tt> in all three components.</p></div>
+<pre><tt>script/generate performance_test homepage</tt></pre></div></div>
+<div class="paragraph"><p>This generates <tt>homepage_test.rb</tt> in the <tt>test/performance</tt> directory:</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
-<pre><tt>Project<span style="color: #990000">.</span>benchmark<span style="color: #990000">(</span><span style="color: #FF0000">"Creating project"</span><span style="color: #990000">)</span> <span style="font-weight: bold"><span style="color: #0000FF">do</span></span>
- project <span style="color: #990000">=</span> Project<span style="color: #990000">.</span>create<span style="color: #990000">(</span><span style="color: #FF0000">"name"</span> <span style="color: #990000">=&gt;</span> <span style="color: #FF0000">"stuff"</span><span style="color: #990000">)</span>
- project<span style="color: #990000">.</span>create_manager<span style="color: #990000">(</span><span style="color: #FF0000">"name"</span> <span style="color: #990000">=&gt;</span> <span style="color: #FF0000">"David"</span><span style="color: #990000">)</span>
- project<span style="color: #990000">.</span>milestones <span style="color: #990000">&lt;&lt;</span> Milestone<span style="color: #990000">.</span>find<span style="color: #990000">(:</span>all<span style="color: #990000">)</span>
+<pre><tt><span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'test_helper'</span>
+<span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'performance_test_help'</span>
+
+<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> HomepageTest <span style="color: #990000">&lt;</span> ActionController<span style="color: #990000">::</span>PerformanceTest
+ <span style="font-style: italic"><span style="color: #9A1900"># Replace this with your real tests.</span></span>
+ <span style="font-weight: bold"><span style="color: #0000FF">def</span></span> test_homepage
+ get <span style="color: #FF0000">'/'</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
-<div class="paragraph"><p>The above code benchmarks the multiple statments enclosed inside <tt>Project.benchmark("Creating project") do..end</tt> block and prints the results inside log files. The statement inside log files will look like:</p></div>
+<h3 id="_examples">1.2. Examples</h3>
+<div class="paragraph"><p>Let&#8217;s assume your application has the following controller and model:</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
-<pre><tt>Creating projectem <span style="color: #990000">(</span><span style="color: #993399">185</span><span style="color: #990000">.</span>3ms<span style="color: #990000">)</span></tt></pre></div></div>
-<div class="paragraph"><p>Please refer to <a href="http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M001336">API docs</a> for optional options to <tt>benchmark()</tt></p></div>
-<div class="paragraph"><p>Similarly, you could use this helper method inside <a href="http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715">controllers</a> ( Note that it&#8217;s a class method here ):</p></div>
-<div class="listingblock">
-<div class="content"><!-- Generator: GNU source-highlight 2.9
-by Lorenzo Bettini
-http://www.lorenzobettini.it
-http://www.gnu.org/software/src-highlite -->
-<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">def</span></span> process_projects
- <span style="font-weight: bold"><span style="color: #0000FF">self</span></span><span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #0000FF">class</span></span><span style="color: #990000">.</span>benchmark<span style="color: #990000">(</span><span style="color: #FF0000">"Processing projects"</span><span style="color: #990000">)</span> <span style="font-weight: bold"><span style="color: #0000FF">do</span></span>
- Project<span style="color: #990000">.</span>process<span style="color: #990000">(</span>params<span style="color: #990000">[:</span>project_ids<span style="color: #990000">])</span>
- Project<span style="color: #990000">.</span>update_cached_projects
+<pre><tt><span style="font-style: italic"><span style="color: #9A1900"># routes.rb</span></span>
+map<span style="color: #990000">.</span>root <span style="color: #990000">:</span>controller <span style="color: #990000">=&gt;</span> <span style="color: #FF0000">'home'</span>
+map<span style="color: #990000">.</span>resources <span style="color: #990000">:</span>posts
+
+<span style="font-style: italic"><span style="color: #9A1900"># home_controller.rb</span></span>
+<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> HomeController <span style="color: #990000">&lt;</span> ApplicationController
+ <span style="font-weight: bold"><span style="color: #0000FF">def</span></span> dashboard
+ <span style="color: #009900">@users</span> <span style="color: #990000">=</span> User<span style="color: #990000">.</span>last_ten<span style="color: #990000">(:</span><span style="font-weight: bold"><span style="color: #0000FF">include</span></span> <span style="color: #990000">=&gt;</span> <span style="color: #990000">:</span>avatars<span style="color: #990000">)</span>
+ <span style="color: #009900">@posts</span> <span style="color: #990000">=</span> Post<span style="color: #990000">.</span>all_today
+ <span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
+<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
+
+<span style="font-style: italic"><span style="color: #9A1900"># posts_controller.rb</span></span>
+<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> PostsController <span style="color: #990000">&lt;</span> ApplicationController
+ <span style="font-weight: bold"><span style="color: #0000FF">def</span></span> create
+ <span style="color: #009900">@post</span> <span style="color: #990000">=</span> Post<span style="color: #990000">.</span>create<span style="color: #990000">(</span>params<span style="color: #990000">[:</span>post<span style="color: #990000">])</span>
+ redirect_to<span style="color: #990000">(</span><span style="color: #009900">@post</span><span style="color: #990000">)</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
+<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
+
+<span style="font-style: italic"><span style="color: #9A1900"># post.rb</span></span>
+<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> Post <span style="color: #990000">&lt;</span> ActiveRecord<span style="color: #990000">::</span>Base
+ before_save <span style="color: #990000">:</span>recalculate_costly_stats
+
+ <span style="font-weight: bold"><span style="color: #0000FF">def</span></span> slow_method
+ <span style="font-style: italic"><span style="color: #9A1900"># I fire gallzilion queries sleeping all around</span></span>
+ <span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
+
+ private
+
+ <span style="font-weight: bold"><span style="color: #0000FF">def</span></span> recalculate_costly_stats
+ <span style="font-style: italic"><span style="color: #9A1900"># CPU heavy calculations</span></span>
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
-<div class="paragraph"><p>and <a href="http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715">views</a>:</p></div>
+<h4 id="_controller_example">1.2.1. Controller Example</h4>
+<div class="paragraph"><p>Because performance tests are a special kind of integration test, you can use the <tt>get</tt> and <tt>post</tt> methods in them.</p></div>
+<div class="paragraph"><p>Here&#8217;s the performance test for <tt>HomeController#dashboard</tt> and <tt>PostsController#create</tt>:</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
-<pre><tt><span style="color: #FF0000">&lt;% benchmark("Showing projects partial") do %&gt;</span>
- <span style="color: #FF0000">&lt;%= render :partial =&gt;</span> <span style="color: #009900">@projects</span> <span style="color: #990000">%&gt;</span>
-<span style="color: #FF0000">&lt;% end %&gt;</span></tt></pre></div></div>
-</div>
-<h2 id="_performance_test_cases">3. Performance Test Cases</h2>
-<div class="sectionbody">
-<div class="paragraph"><p>Rails provides a very easy way to write performance test cases, which look just like the regular integration tests. Performance tests run a code profiler on your test methods. Profiling output for combinations of each test method, measurement, and output format are written to your <tt>tmp/performance</tt> directory. By default, process_time is measured and both flat and graph_html output formats are written, so you&#8217;ll have two output files per test method.</p></div>
-<div class="paragraph"><p>If you have a look at <tt>test/performance/browsing_test.rb</tt> in a newly created Rails application:</p></div>
+<pre><tt><span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'test_helper'</span>
+<span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'performance_test_help'</span>
+
+<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> PostPerformanceTest <span style="color: #990000">&lt;</span> ActionController<span style="color: #990000">::</span>PerformanceTest
+ <span style="font-weight: bold"><span style="color: #0000FF">def</span></span> setup
+ <span style="font-style: italic"><span style="color: #9A1900"># Application requires logged-in user</span></span>
+ login_as<span style="color: #990000">(:</span>lifo<span style="color: #990000">)</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
+
+ <span style="font-weight: bold"><span style="color: #0000FF">def</span></span> test_homepage
+ get <span style="color: #FF0000">'/dashboard'</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
+
+ <span style="font-weight: bold"><span style="color: #0000FF">def</span></span> test_creating_new_post
+ post <span style="color: #FF0000">'/posts'</span><span style="color: #990000">,</span> <span style="color: #990000">:</span>post <span style="color: #990000">=&gt;</span> <span style="color: #FF0000">{</span> <span style="color: #990000">:</span>body <span style="color: #990000">=&gt;</span> <span style="color: #FF0000">'lifo is fooling you'</span> <span style="color: #FF0000">}</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
+<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
+<div class="paragraph"><p>You can find more details about the <tt>get</tt> and <tt>post</tt> methods in the <a href="../testing_rails_applications.html#mgunderloy">Testing Rails Applications</a> guide.</p></div>
+<h4 id="_model_example">1.2.2. Model Example</h4>
+<div class="paragraph"><p>Even though the performance tests are integration tests and hence closer to the request/response cycle by nature, you can still performance test pure model code.</p></div>
+<div class="paragraph"><p>Performance test for <tt>Post</tt> model:</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
by Lorenzo Bettini
@@ -343,54 +417,59 @@ http://www.gnu.org/software/src-highlite -->
<pre><tt><span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'test_helper'</span>
<span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'performance_test_help'</span>
-<span style="font-style: italic"><span style="color: #9A1900"># Profiling results for each test method are written to tmp/performance.</span></span>
-<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> BrowsingTest <span style="color: #990000">&lt;</span> ActionController<span style="color: #990000">::</span>PerformanceTest
- <span style="font-weight: bold"><span style="color: #0000FF">def</span></span> test_homepage
- get <span style="color: #FF0000">'/'</span>
+<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> PostModelTest <span style="color: #990000">&lt;</span> ActionController<span style="color: #990000">::</span>PerformanceTest
+ <span style="font-weight: bold"><span style="color: #0000FF">def</span></span> test_creation
+ Post<span style="color: #990000">.</span>create <span style="color: #990000">:</span>body <span style="color: #990000">=&gt;</span> <span style="color: #FF0000">'still fooling you'</span><span style="color: #990000">,</span> <span style="color: #990000">:</span>cost <span style="color: #990000">=&gt;</span> <span style="color: #FF0000">'100'</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
+
+ <span style="font-weight: bold"><span style="color: #0000FF">def</span></span> test_slow_method
+ <span style="font-style: italic"><span style="color: #9A1900"># Using posts(:awesome) fixture</span></span>
+ posts<span style="color: #990000">(:</span>awesome<span style="color: #990000">).</span>slow_method
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
-<div class="paragraph"><p>This is an automatically generated example performance test file, for testing performance of homepage(<em>/</em>) of the application.</p></div>
-<h3 id="_modes">3.1. Modes</h3>
-<div class="paragraph"><p>Performance test cases can be run in two modes : Benchmarking and Profling.</p></div>
-<h4 id="_benchmarking">3.1.1. Benchmarking</h4>
-<div class="paragraph"><p>Benchmarking helps you find out how fast are your test cases. Each Test case is run <tt>4 times</tt> in this mode. To run performance tests in benchmarking mode:</p></div>
+<h3 id="_modes">1.3. Modes</h3>
+<div class="paragraph"><p>Performance tests can be run in two modes : Benchmarking and Profiling.</p></div>
+<h4 id="_benchmarking">1.3.1. Benchmarking</h4>
+<div class="paragraph"><p>Benchmarking helps find out how fast each performance test runs. Each test case is run <tt>4 times</tt> in benchmarking mode.</p></div>
+<div class="paragraph"><p>To run performance tests in benchmarking mode:</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><tt>$ rake <span style="font-weight: bold"><span style="color: #0000FF">test</span></span><span style="color: #990000">:</span>benchmark</tt></pre></div></div>
-<h4 id="_profiling">3.1.2. Profiling</h4>
-<div class="paragraph"><p>Profiling helps introspect into your test cases and figure out which are the slow parts. Each Test case is run <tt>1 time</tt> in this mode. To run performance tests in profiling mode:</p></div>
+<h4 id="_profiling">1.3.2. Profiling</h4>
+<div class="paragraph"><p>Profiling helps you see the details of a performance test and provide an in-depth picture of the slow and memory hungry parts. Each test case is run <tt>1 time</tt> in profiling mode.</p></div>
+<div class="paragraph"><p>To run performance tests in profiling mode:</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><tt>$ rake <span style="font-weight: bold"><span style="color: #0000FF">test</span></span><span style="color: #990000">:</span>profile</tt></pre></div></div>
-<h3 id="_metrics">3.2. Metrics</h3>
-<div class="paragraph"><p>Benchmarking and profiling run performance test cases in various modes to help precisely figure out the where the problem lies.</p></div>
-<h4 id="_wall_time">3.2.1. Wall Time</h4>
-<div class="paragraph"><p>Measures the real world time elapsed during the test run. It is affected by any other processes concurrently running on the system.</p></div>
+<h3 id="_metrics">1.4. Metrics</h3>
+<div class="paragraph"><p>Benchmarking and profiling run performance tests in various modes described below.</p></div>
+<h4 id="_wall_time">1.4.1. Wall Time</h4>
+<div class="paragraph"><p>Wall time measures the real world time elapsed during the test run. It is affected by any other processes concurrently running on the system.</p></div>
<div class="paragraph"><p>Mode : Benchmarking</p></div>
-<h4 id="_process_time">3.2.2. Process Time</h4>
-<div class="paragraph"><p>Measures the time taken by the process. It is unaffected by any other processes running concurrently on the same system. Hence, process time is likely to be constant for any given performance test, irrespective of the machine load.</p></div>
+<h4 id="_process_time">1.4.2. Process Time</h4>
+<div class="paragraph"><p>Process time measures the time taken by the process. It is unaffected by any other processes running concurrently on the same system. Hence, process time is likely to be constant for any given performance test, irrespective of the machine load.</p></div>
<div class="paragraph"><p>Mode : Profiling</p></div>
-<h4 id="_memory">3.2.3. Memory</h4>
-<div class="paragraph"><p>Measures the amount of memory used for the performance test case.</p></div>
-<div class="paragraph"><p>Mode : Benchmarking, Profiling [Requires specially compiled Ruby]</p></div>
-<h4 id="_objects">3.2.4. Objects</h4>
-<div class="paragraph"><p>Measures the number of objects allocated for the performance test case.</p></div>
-<div class="paragraph"><p>Mode : Benchmarking, Profiling [Requires specially compiled Ruby]</p></div>
-<h4 id="_gc_runs">3.2.5. GC Runs</h4>
-<div class="paragraph"><p>Measures the number of times GC was invoked for the performance test case.</p></div>
-<div class="paragraph"><p>Mode : Benchmarking [Requires specially compiled Ruby]</p></div>
-<h4 id="_gc_time">3.2.6. GC Time</h4>
-<div class="paragraph"><p>Measures the amount of time spent in GC for the performance test case.</p></div>
-<div class="paragraph"><p>Mode : Benchmarking [Requires specially compiled Ruby]</p></div>
-<h3 id="_understanding_the_output">3.3. Understanding the output</h3>
-<div class="paragraph"><p>Performance tests generate different outputs inside <tt>tmp/performance</tt> directory based on the mode it is run in and the metric.</p></div>
-<h4 id="_benchmarking_2">3.3.1. Benchmarking</h4>
+<h4 id="_memory">1.4.3. Memory</h4>
+<div class="paragraph"><p>Memory measures the amount of memory used for the performance test case.</p></div>
+<div class="paragraph"><p>Mode : Benchmarking, Profiling [<a href="#gc">Requires GC-Patched Ruby</a>]</p></div>
+<h4 id="_objects">1.4.4. Objects</h4>
+<div class="paragraph"><p>Objects measures the number of objects allocated for the performance test case.</p></div>
+<div class="paragraph"><p>Mode : Benchmarking, Profiling [<a href="#gc">Requires GC-Patched Ruby</a>]</p></div>
+<h4 id="_gc_runs">1.4.5. GC Runs</h4>
+<div class="paragraph"><p>GC Runs measures the number of times GC was invoked for the performance test case.</p></div>
+<div class="paragraph"><p>Mode : Benchmarking [<a href="#gc">Requires GC-Patched Ruby</a>]</p></div>
+<h4 id="_gc_time">1.4.6. GC Time</h4>
+<div class="paragraph"><p>GC Time measures the amount of time spent in GC for the performance test case.</p></div>
+<div class="paragraph"><p>Mode : Benchmarking [<a href="#gc">Requires GC-Patched Ruby</a>]</p></div>
+<h3 id="_understanding_the_output">1.5. Understanding the output</h3>
+<div class="paragraph"><p>Performance tests generate different outputs inside <tt>tmp/performance</tt> directory depending on their mode and metric.</p></div>
+<h4 id="_benchmarking_2">1.5.1. Benchmarking</h4>
<div class="paragraph"><p>In benchmarking mode, performance tests generate two types of outputs :</p></div>
<h5 id="_command_line">Command line</h5>
<div class="paragraph"><p>This is the primary form of output in benchmarking mode. Example :</p></div>
@@ -406,7 +485,7 @@ http://www.gnu.org/software/src-highlite -->
gc_runs<span style="color: #990000">:</span> <span style="color: #993399">0</span>
gc_time<span style="color: #990000">:</span> <span style="color: #993399">19</span> ms</tt></pre></div></div>
<h5 id="_csv_files">CSV files</h5>
-<div class="paragraph"><p>Performance tests results are also appended to <tt>.csv</tt> files inside <tt>tmp/performance/&lt;Class&gt;#&lt;test&gt;_&lt;metric&gt;.csv</tt> file. For example, running the default <tt>BrowsingTest#test_homepage</tt> will generate following five files :</p></div>
+<div class="paragraph"><p>Performance test results are also appended to <tt>.csv</tt> files inside <tt>tmp/performance</tt>. For example, running the default <tt>BrowsingTest#test_homepage</tt> will generate following five files :</p></div>
<div class="ulist"><ul>
<li>
<p>
@@ -434,8 +513,8 @@ BrowsingTest#test_homepage_wall_time.csv
</p>
</li>
</ul></div>
-<div class="paragraph"><p>As the results are appended to these files each time the performance tests are run in benchmarking mode, it enables you gather data over a sustainable period of time which can be very helpful with various performance analysis.</p></div>
-<div class="paragraph"><p>Sample output of +BrowsingTest#test_homepage_wall_time.csv + :</p></div>
+<div class="paragraph"><p>As the results are appended to these files each time the performance tests are run in benchmarking mode, you can collect data over a period of time. This can be very helpful in analyzing the effects of code changes.</p></div>
+<div class="paragraph"><p>Sample output of <tt>BrowsingTest#test_homepage_wall_time.csv</tt>:</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
by Lorenzo Bettini
@@ -452,9 +531,10 @@ http://www.gnu.org/software/src-highlite -->
<span style="color: #993399">0.00740450000000004</span><span style="color: #990000">,</span><span style="color: #993399">2009</span>-<span style="color: #993399">01</span>-09T03<span style="color: #990000">:</span><span style="color: #993399">54</span><span style="color: #990000">:</span>47Z<span style="color: #990000">,,</span><span style="color: #993399">2.3</span><span style="color: #990000">.</span><span style="color: #993399">0</span><span style="color: #990000">.</span>master<span style="color: #990000">.</span><span style="color: #993399">859e150</span><span style="color: #990000">,</span>ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6.110</span><span style="color: #990000">,</span>i686-darwin<span style="color: #993399">9.0</span><span style="color: #990000">.</span><span style="color: #993399">0</span>
<span style="color: #993399">0.00603150000000008</span><span style="color: #990000">,</span><span style="color: #993399">2009</span>-<span style="color: #993399">01</span>-09T03<span style="color: #990000">:</span><span style="color: #993399">54</span><span style="color: #990000">:</span>57Z<span style="color: #990000">,,</span><span style="color: #993399">2.3</span><span style="color: #990000">.</span><span style="color: #993399">0</span><span style="color: #990000">.</span>master<span style="color: #990000">.</span><span style="color: #993399">859e150</span><span style="color: #990000">,</span>ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6.111</span><span style="color: #990000">,</span>i686-darwin<span style="color: #993399">9.1</span><span style="color: #990000">.</span><span style="color: #993399">0</span>
<span style="color: #993399">0.00771250000000012</span><span style="color: #990000">,</span><span style="color: #993399">2009</span>-<span style="color: #993399">01</span>-09T15<span style="color: #990000">:</span><span style="color: #993399">46</span><span style="color: #990000">:</span>03Z<span style="color: #990000">,,</span><span style="color: #993399">2.3</span><span style="color: #990000">.</span><span style="color: #993399">0</span><span style="color: #990000">.</span>master<span style="color: #990000">.</span><span style="color: #993399">859e150</span><span style="color: #990000">,</span>ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6.110</span><span style="color: #990000">,</span>i686-darwin<span style="color: #993399">9.0</span><span style="color: #990000">.</span><span style="color: #993399">0</span></tt></pre></div></div>
-<h4 id="_profiling_2">3.3.2. Profiling</h4>
+<h4 id="_profiling_2">1.5.2. Profiling</h4>
+<div class="paragraph"><p>In profiling mode, you can choose from four types of output.</p></div>
<h5 id="_command_line_2">Command line</h5>
-<div class="paragraph"><p>This is the very basic form of output in profiling mode. Example :</p></div>
+<div class="paragraph"><p>This is a very basic form of output in profiling mode:</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
by Lorenzo Bettini
@@ -465,104 +545,283 @@ http://www.gnu.org/software/src-highlite -->
memory<span style="color: #990000">:</span> <span style="color: #993399">832.13</span> KB
objects<span style="color: #990000">:</span> <span style="color: #993399">7882</span></tt></pre></div></div>
<h5 id="_flat">Flat</h5>
-<div class="paragraph"><p>Flat output shows the total amount of time spent in each method. <a href="http://ruby-prof.rubyforge.org/files/examples/flat_txt.html">Check ruby prof documentation for a better explaination</a>.</p></div>
+<div class="paragraph"><p>Flat output shows the total amount of time spent in each method. <a href="http://ruby-prof.rubyforge.org/files/examples/flat_txt.html">Check ruby prof documentation for a better explanation</a>.</p></div>
<h5 id="_graph">Graph</h5>
-<div class="paragraph"><p>Graph output shows how long each method takes to run, which methods call it and which methods it calls. <a href="http://ruby-prof.rubyforge.org/files/examples/graph_txt.html">Check ruby prof documentation for a better explaination</a>.</p></div>
+<div class="paragraph"><p>Graph output shows how long each method takes to run, which methods call it and which methods it calls. <a href="http://ruby-prof.rubyforge.org/files/examples/graph_txt.html">Check ruby prof documentation for a better explanation</a>.</p></div>
<h5 id="_tree">Tree</h5>
-<div class="paragraph"><p>Tree output is profiling information in calltree format for use by kcachegrind and similar tools.</p></div>
-<h3 id="_preparing_ruby_and_ruby_prof">3.4. Preparing Ruby and Ruby-prof</h3>
-<div class="paragraph"><p>Before we go ahead, Rails performance testing requires you to build a special Ruby binary with some super powers - GC patch for measuring GC Runs/Time. This process is very straight forward. If you&#8217;ve never compiled a Ruby binary before, you can follow the following steps to build a ruby binary inside your home directory:</p></div>
-<h4 id="_compile">3.4.1. Compile</h4>
+<div class="paragraph"><p>Tree output is profiling information in calltree format for use by <a href="http://kcachegrind.sourceforge.net/html/Home.html">kcachegrind</a> and similar tools.</p></div>
+<h3 id="_tuning_test_runs">1.6. Tuning Test Runs</h3>
+<div class="paragraph"><p>By default, each performance test is run <tt>4 times</tt> in benchmarking mode and <tt>1 time</tt> in profiling. However, test runs can easily be configured.</p></div>
+<div class="admonitionblock">
+<table><tr>
+<td class="icon">
+<img src="./images/icons/caution.png" alt="Caution" />
+</td>
+<td class="content">Performance test configurability is not yet enabled in Rails. But it will be soon.</td>
+</tr></table>
+</div>
+<h3 id="gc">1.7. Installing GC-Patched Ruby</h3>
+<div class="paragraph"><p>To get the best from Rails performance tests, you need to build a special Ruby binary with some super powers - <a href="http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch">GC patch</a> for measuring GC Runs/Time and memory/object allocation.</p></div>
+<div class="paragraph"><p>The process is fairly straight forward. If you&#8217;ve never compiled a Ruby binary before, follow these steps to build a ruby binary inside your home directory:</p></div>
+<h4 id="_installation">1.7.1. Installation</h4>
+<div class="paragraph"><p>Compile Ruby and apply this <a href="http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch">GC Patch</a>:</p></div>
+<h4 id="_download_and_extract">1.7.2. Download and Extract</h4>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><tt><span style="color: #990000">[</span>lifo@null <span style="color: #990000">~]</span>$ mkdir rubygc
-<span style="color: #990000">[</span>lifo@null <span style="color: #990000">~]</span>$ wget ftp<span style="color: #990000">:</span>//ftp<span style="color: #990000">.</span>ruby-lang<span style="color: #990000">.</span>org/pub/ruby<span style="color: #990000">/</span><span style="color: #993399">1.8</span>/ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6</span>-p<span style="color: #993399">111</span><span style="color: #990000">.</span>tar<span style="color: #990000">.</span>gz
-<span style="color: #990000">[</span>lifo@null <span style="color: #990000">~]</span>$ tar -xzvf ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6</span>-p<span style="color: #993399">111</span><span style="color: #990000">.</span>tar<span style="color: #990000">.</span>gz
-<span style="color: #990000">[</span>lifo@null <span style="color: #990000">~]</span>$ cd ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6</span>-p<span style="color: #993399">111</span>
-<span style="color: #990000">[</span>lifo@null ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6</span>-p<span style="color: #993399">111</span><span style="color: #990000">]</span>$ curl http<span style="color: #990000">:</span>//rubyforge<span style="color: #990000">.</span>org/tracker/download<span style="color: #990000">.</span>php<span style="color: #990000">/</span><span style="color: #993399">1814</span><span style="color: #990000">/</span><span style="color: #993399">7062</span><span style="color: #990000">/</span><span style="color: #993399">17676</span><span style="color: #990000">/</span><span style="color: #993399">3291</span>/ruby186gc<span style="color: #990000">.</span>patch <span style="color: #990000">|</span> patch -p<span style="color: #993399">0</span>
-<span style="color: #990000">[</span>lifo@null ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6</span>-p<span style="color: #993399">111</span><span style="color: #990000">]</span>$ <span style="color: #990000">.</span>/configure --prefix<span style="color: #990000">=</span>/Users/lifo/rubygc
-<span style="color: #990000">[</span>lifo@null ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6</span>-p<span style="color: #993399">111</span><span style="color: #990000">]</span>$ make <span style="color: #990000">&amp;&amp;</span> make install</tt></pre></div></div>
-<h4 id="_prepare_aliases">3.4.2. Prepare aliases</h4>
-<div class="paragraph"><p>Add the following lines in your ~/.profile for convenience:</p></div>
+<span style="color: #990000">[</span>lifo@null <span style="color: #990000">~]</span>$ wget <span style="color: #990000">&lt;</span>download the latest stable ruby from ftp<span style="color: #990000">:</span>//ftp<span style="color: #990000">.</span>ruby-lang<span style="color: #990000">.</span>org/pub/ruby<span style="color: #990000">&gt;</span>
+<span style="color: #990000">[</span>lifo@null <span style="color: #990000">~]</span>$ tar -xzvf <span style="color: #990000">&lt;</span>ruby-version<span style="color: #990000">.</span>tar<span style="color: #990000">.</span>gz<span style="color: #990000">&gt;</span>
+<span style="color: #990000">[</span>lifo@null <span style="color: #990000">~]</span>$ cd <span style="color: #990000">&lt;</span>ruby-version<span style="color: #990000">&gt;</span></tt></pre></div></div>
+<h4 id="_apply_the_patch">1.7.3. Apply the patch</h4>
<div class="listingblock">
-<div class="content">
-<pre><tt>alias gcruby='/Users/lifo/rubygc/bin/ruby'
-alias gcrake='/Users/lifo/rubygc/bin/rake'
-alias gcgem='/Users/lifo/rubygc/bin/gem'
-alias gcirb='/Users/lifo/rubygc/bin/irb'
-alias gcrails='/Users/lifo/rubygc/bin/rails'</tt></pre>
-</div></div>
-<h4 id="_install_rubygems_and_some_basic_gems">3.4.3. Install rubygems and some basic gems</h4>
-<div class="paragraph"><p>Download <a href="http://rubyforge.org/projects/rubygems">Rubygems</a> and install it from source. Afterwards, install rake. rails, ruby-prof and rack gems:</p></div>
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt><span style="color: #990000">[</span>lifo@null ruby-version<span style="color: #990000">]</span>$ curl http<span style="color: #990000">:</span>//rubyforge<span style="color: #990000">.</span>org/tracker/download<span style="color: #990000">.</span>php<span style="color: #990000">/</span><span style="color: #993399">1814</span><span style="color: #990000">/</span><span style="color: #993399">7062</span><span style="color: #990000">/</span><span style="color: #993399">17676</span><span style="color: #990000">/</span><span style="color: #993399">3291</span>/ruby186gc<span style="color: #990000">.</span>patch <span style="color: #990000">|</span> patch -p<span style="color: #993399">0</span></tt></pre></div></div>
+<h4 id="_configure_and_install">1.7.4. Configure and Install</h4>
+<div class="paragraph"><p>The following will install ruby in your home directory&#8217;s <tt>/rubygc</tt> directory. Make sure to replace <tt>&lt;homedir&gt;</tt> with a full patch to your actual home directory.</p></div>
<div class="listingblock">
-<div class="content">
-<pre><tt>[lifo@null ~]$ gcgem install rake
-[lifo@null ~]$ gcgem install rails
-[lifo@null ~]$ gcgem install ruby-prof
-[lifo@null ~]$ gcgem install rack</tt></pre>
-</div></div>
-<h4 id="_install_mysql_gem">3.4.4. Install MySQL gem</h4>
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt><span style="color: #990000">[</span>lifo@null ruby-version<span style="color: #990000">]</span>$ <span style="color: #990000">.</span>/configure --prefix<span style="color: #990000">=/&lt;</span>homedir<span style="color: #990000">&gt;</span>/rubygc
+<span style="color: #990000">[</span>lifo@null ruby-version<span style="color: #990000">]</span>$ make <span style="color: #990000">&amp;&amp;</span> make install</tt></pre></div></div>
+<h4 id="_prepare_aliases">1.7.5. Prepare aliases</h4>
+<div class="paragraph"><p>For convenience, add the following lines in your <tt>~/.profile</tt>:</p></div>
<div class="listingblock">
<div class="content">
-<pre><tt>[lifo@null ~]$ gcgem install mysql</tt></pre>
+<pre><tt>alias gcruby='~/rubygc/bin/ruby'
+alias gcrake='~/rubygc/bin/rake'
+alias gcgem='~/rubygc/bin/gem'
+alias gcirb='~/rubygc/bin/irb'
+alias gcrails='~/rubygc/bin/rails'</tt></pre>
</div></div>
-<div class="paragraph"><p>If this fails, you can try to install it manually:</p></div>
+<h4 id="_install_rubygems_and_dependency_gems">1.7.6. Install rubygems and dependency gems</h4>
+<div class="paragraph"><p>Download <a href="http://rubyforge.org/projects/rubygems">Rubygems</a> and install it from source. Rubygem&#8217;s README file should have necessary installation instructions.</p></div>
+<div class="paragraph"><p>Additionally, install the following gems :</p></div>
+<div class="ulist"><ul>
+<li>
+<p>
+<tt>rake</tt>
+</p>
+</li>
+<li>
+<p>
+<tt>rails</tt>
+</p>
+</li>
+<li>
+<p>
+<tt>ruby-prof</tt>
+</p>
+</li>
+<li>
+<p>
+<tt>rack</tt>
+</p>
+</li>
+<li>
+<p>
+<tt>mysql</tt>
+</p>
+</li>
+</ul></div>
+<div class="paragraph"><p>If installing <tt>mysql</tt> fails, you can try to install it manually:</p></div>
<div class="listingblock">
-<div class="content">
-<pre><tt>[lifo@null ~]$ cd /Users/lifo/rubygc/lib/ruby/gems/1.8/gems/mysql-2.7/
-[lifo@null mysql-2.7]$ gcruby extconf.rb --with-mysql-config
-[lifo@null mysql-2.7]$ make &amp;&amp; make install</tt></pre>
-</div></div>
-<h3 id="_generating_performance_test">3.5. Generating performance test</h3>
-<div class="paragraph"><p>Rails provides a generator for creating new performance tests:</p></div>
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt><span style="color: #990000">[</span>lifo@null mysql<span style="color: #990000">]</span>$ gcruby extconf<span style="color: #990000">.</span>rb --with-mysql-config
+<span style="color: #990000">[</span>lifo@null mysql<span style="color: #990000">]</span>$ make <span style="color: #990000">&amp;&amp;</span> make install</tt></pre></div></div>
+<div class="paragraph"><p>And you&#8217;re ready to go. Don&#8217;t forget to use <tt>gcruby</tt> and <tt>gcrake</tt> aliases when running the performance tests.</p></div>
+</div>
+<h2 id="_command_line_tools">2. Command Line Tools</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>Writing performance test cases could be an overkill when you are looking for one time tests. Rails ships with two command line tools that enable quick and dirty performance testing:</p></div>
+<h3 id="_benchmarker">2.1. benchmarker</h3>
+<div class="paragraph"><p><tt>benchmarker</tt> is a wrapper around Ruby&#8217;s <a href="http://ruby-doc.org/core/classes/Benchmark.html">Benchmark</a> module.</p></div>
+<div class="paragraph"><p>Usage:</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
-<pre><tt><span style="color: #990000">[</span>lifo@null application <span style="color: #990000">(</span>master<span style="color: #990000">)]</span>$ script/generate performance_test homepage</tt></pre></div></div>
-<div class="paragraph"><p>This will generate <tt>test/performance/homepage_test.rb</tt>:</p></div>
+<pre><tt>$ script/performance/benchmarker <span style="color: #990000">[</span><span style="font-weight: bold"><span style="color: #0000FF">times</span></span><span style="color: #990000">]</span> <span style="color: #FF0000">'Person.expensive_way'</span> <span style="color: #FF0000">'Person.another_expensive_way'</span> <span style="color: #990000">...</span></tt></pre></div></div>
+<div class="paragraph"><p>Examples:</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 2.9
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
-<pre><tt><span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'test_helper'</span>
-<span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'performance_test_help'</span>
-
-<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> HomepageTest <span style="color: #990000">&lt;</span> ActionController<span style="color: #990000">::</span>PerformanceTest
- <span style="font-style: italic"><span style="color: #9A1900"># Replace this with your real tests.</span></span>
- <span style="font-weight: bold"><span style="color: #0000FF">def</span></span> test_homepage
- get <span style="color: #FF0000">'/'</span>
+<pre><tt>$ script/performance/benchmarker <span style="color: #993399">10</span> <span style="color: #FF0000">'Item.all'</span> <span style="color: #FF0000">'CouchItem.all'</span></tt></pre></div></div>
+<div class="paragraph"><p>If the <tt>[times]</tt> argument is omitted, supplied methods are run just once:</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt>$ script/performance/benchmarker <span style="color: #FF0000">'Item.first'</span> <span style="color: #FF0000">'Item.last'</span></tt></pre></div></div>
+<h3 id="_profiler">2.2. profiler</h3>
+<div class="paragraph"><p><tt>profiler</tt> is a wrapper around <a href="http://ruby-prof.rubyforge.org/">ruby-prof</a> gem.</p></div>
+<div class="paragraph"><p>Usage:</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt>$ script/performance/profiler <span style="color: #FF0000">'Person.expensive_method(10)'</span> <span style="color: #990000">[</span><span style="font-weight: bold"><span style="color: #0000FF">times</span></span><span style="color: #990000">]</span> <span style="color: #990000">[</span>flat<span style="color: #990000">|</span>graph<span style="color: #990000">|</span>graph_html<span style="color: #990000">]</span></tt></pre></div></div>
+<div class="paragraph"><p>Examples:</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt>$ script/performance/profiler <span style="color: #FF0000">'Item.all'</span></tt></pre></div></div>
+<div class="paragraph"><p>This will profile <tt>Item.all</tt> in <tt>RubyProf::WALL_TIME</tt> measure mode. By default, it prints flat output to the shell.</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt>$ script/performance/profiler <span style="color: #FF0000">'Item.all'</span> <span style="color: #993399">10</span> graph</tt></pre></div></div>
+<div class="paragraph"><p>This will profile <tt>10.times { Item.all }</tt> with <tt>RubyProf::WALL_TIME</tt> measure mode and print graph output to the shell.</p></div>
+<div class="paragraph"><p>If you want to store the output in a file:</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt>$ script/performance/profiler <span style="color: #FF0000">'Item.all'</span> <span style="color: #993399">10</span> graph <span style="color: #993399">2</span><span style="color: #990000">&gt;</span> graph<span style="color: #990000">.</span>txt</tt></pre></div></div>
+</div>
+<h2 id="_helper_methods">3. Helper methods</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>Rails provides various helper methods inside Active Record, Action Controller and Action View to measure the time taken by a given piece of code. The method is called <tt>benchmark()</tt> in all the three components.</p></div>
+<h3 id="_model">3.1. Model</h3>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt>Project<span style="color: #990000">.</span>benchmark<span style="color: #990000">(</span><span style="color: #FF0000">"Creating project"</span><span style="color: #990000">)</span> <span style="font-weight: bold"><span style="color: #0000FF">do</span></span>
+ project <span style="color: #990000">=</span> Project<span style="color: #990000">.</span>create<span style="color: #990000">(</span><span style="color: #FF0000">"name"</span> <span style="color: #990000">=&gt;</span> <span style="color: #FF0000">"stuff"</span><span style="color: #990000">)</span>
+ project<span style="color: #990000">.</span>create_manager<span style="color: #990000">(</span><span style="color: #FF0000">"name"</span> <span style="color: #990000">=&gt;</span> <span style="color: #FF0000">"David"</span><span style="color: #990000">)</span>
+ project<span style="color: #990000">.</span>milestones <span style="color: #990000">&lt;&lt;</span> Milestone<span style="color: #990000">.</span>find<span style="color: #990000">(:</span>all<span style="color: #990000">)</span>
+<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
+<div class="paragraph"><p>This benchmarks the code enclosed in the <tt>Project.benchmark("Creating project") do..end</tt> block and prints the result to the log file:</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt>Creating project <span style="color: #990000">(</span><span style="color: #993399">185</span><span style="color: #990000">.</span>3ms<span style="color: #990000">)</span></tt></pre></div></div>
+<div class="paragraph"><p>Please refer to the <a href="http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M001336">API docs</a> for additional options to <tt>benchmark()</tt></p></div>
+<h3 id="_controller">3.2. Controller</h3>
+<div class="paragraph"><p>Similarly, you could use this helper method inside <a href="http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715">controllers</a></p></div>
+<div class="admonitionblock">
+<table><tr>
+<td class="icon">
+<img src="./images/icons/note.png" alt="Note" />
+</td>
+<td class="content"><tt>benchmark</tt> is a class method inside controllers</td>
+</tr></table>
+</div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">def</span></span> process_projects
+ <span style="font-weight: bold"><span style="color: #0000FF">self</span></span><span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #0000FF">class</span></span><span style="color: #990000">.</span>benchmark<span style="color: #990000">(</span><span style="color: #FF0000">"Processing projects"</span><span style="color: #990000">)</span> <span style="font-weight: bold"><span style="color: #0000FF">do</span></span>
+ Project<span style="color: #990000">.</span>process<span style="color: #990000">(</span>params<span style="color: #990000">[:</span>project_ids<span style="color: #990000">])</span>
+ Project<span style="color: #990000">.</span>update_cached_projects
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
-<div class="paragraph"><p>Which you can modify to suit your needs.</p></div>
+<h3 id="_view">3.3. View</h3>
+<div class="paragraph"><p>And in <a href="http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715">views</a>:</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt><span style="color: #FF0000">&lt;% benchmark("Showing projects partial") do %&gt;</span>
+ <span style="color: #FF0000">&lt;%= render :partial =&gt;</span> <span style="color: #009900">@projects</span> <span style="color: #990000">%&gt;</span>
+<span style="color: #FF0000">&lt;% end %&gt;</span></tt></pre></div></div>
</div>
-<h2 id="_other_profiling_tools">4. Other Profiling Tools</h2>
+<h2 id="_request_logging">4. Request Logging</h2>
<div class="sectionbody">
+<div class="paragraph"><p>Rails log files contain very useful information about the time taken to serve each request. Here&#8217;s a typical log file entry:</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt>Processing ItemsController<span style="font-style: italic"><span style="color: #9A1900">#index (for 127.0.0.1 at 2009-01-08 03:06:39) [GET]</span></span>
+Rendering template within layouts<span style="color: #990000">/</span>items
+Rendering items<span style="color: #990000">/</span>index
+Completed <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> 5ms <span style="color: #990000">(</span>View<span style="color: #990000">:</span> <span style="color: #993399">2</span><span style="color: #990000">,</span> DB<span style="color: #990000">:</span> <span style="color: #993399">0</span><span style="color: #990000">)</span> <span style="color: #990000">|</span> <span style="color: #993399">200</span> OK <span style="color: #990000">[</span>http<span style="color: #990000">:</span><span style="color: #FF6600">//0.0.0.0/</span>items<span style="color: #990000">]</span></tt></pre></div></div>
+<div class="paragraph"><p>For this section, we&#8217;re only interested in the last line:</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight 2.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt>Completed <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> 5ms <span style="color: #990000">(</span>View<span style="color: #990000">:</span> <span style="color: #993399">2</span><span style="color: #990000">,</span> DB<span style="color: #990000">:</span> <span style="color: #993399">0</span><span style="color: #990000">)</span> <span style="color: #990000">|</span> <span style="color: #993399">200</span> OK <span style="color: #990000">[</span>http<span style="color: #990000">:</span><span style="color: #FF6600">//0.0.0.0/</span>items<span style="color: #990000">]</span></tt></pre></div></div>
+<div class="paragraph"><p>This data is fairly straightforward to understand. Rails uses millisecond(ms) as the metric to measures the time taken. The complete request spent 5 ms inside Rails, out of which 2 ms were spent rendering views and none was spent communication with the database. It&#8217;s safe to assume that the remaining 3 ms were spent inside the controller.</p></div>
+<div class="paragraph"><p>Michael Koziarski has an <a href="http://www.therailsway.com/2009/1/6/requests-per-second">interesting blog post</a> explaining the importance of using milliseconds as the metric.</p></div>
+</div>
+<h2 id="_useful_profiling_tools">5. Useful Profiling Tools</h2>
+<div class="sectionbody">
+<h3 id="_rails_plugins_and_gems">5.1. Rails Plugins and Gems</h3>
<div class="ulist"><ul>
<li>
<p>
-<a href="http://www.hpl.hp.com/research/linux/httperf/">httperf</a>
+<a href="http://rails-analyzer.rubyforge.org/">Rails Analyzer</a>
</p>
</li>
<li>
<p>
-<a href="http://rails-analyzer.rubyforge.org/">Rails Analyzer</a>
+<a href="http://www.flyingmachinestudios.com/projects/">Palmist</a>
</p>
</li>
<li>
<p>
-<a href="http://www.flyingmachinestudios.com/projects/">Palmist</a>
+<a href="http://github.com/josevalim/rails-footnotes/tree/master">Rails Footnotes</a>
+</p>
+</li>
+<li>
+<p>
+<a href="http://github.com/dsboulder/query_reviewer/tree/master">Query Reviewer</a>
+</p>
+</li>
+</ul></div>
+<h3 id="_external">5.2. External</h3>
+<div class="ulist"><ul>
+<li>
+<p>
+<a href="http://www.hpl.hp.com/research/linux/httperf">httperf</a>
+</p>
+</li>
+<li>
+<p>
+<a href="http://httpd.apache.org/docs/2.2/programs/ab.html">ab</a>
+</p>
+</li>
+<li>
+<p>
+<a href="http://jakarta.apache.org/jmeter">JMeter</a>
</p>
</li>
</ul></div>
</div>
-<h2 id="_commercial_products_dedicated_to_rails_perfomance">5. Commercial products dedicated to Rails Perfomance</h2>
+<h2 id="_commercial_products">6. Commercial Products</h2>
<div class="sectionbody">
+<div class="paragraph"><p>Rails has been lucky to have three startups dedicated to Rails specific performance tools:</p></div>
<div class="ulist"><ul>
<li>
<p>
@@ -581,13 +840,13 @@ http://www.gnu.org/software/src-highlite -->
</li>
</ul></div>
</div>
-<h2 id="_changelog">6. Changelog</h2>
+<h2 id="_changelog">7. Changelog</h2>
<div class="sectionbody">
<div class="paragraph"><p><a href="http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/4">Lighthouse ticket</a></p></div>
<div class="ulist"><ul>
<li>
<p>
-January 9, 2009: Rewrite by Pratik
+January 9, 2009: Complete rewrite by Pratik
</p>
</li>
<li>
diff --git a/railties/doc/guides/source/activerecord_validations_callbacks.txt b/railties/doc/guides/source/activerecord_validations_callbacks.txt
index 7d37df1ed2..29bff0c7b3 100644
--- a/railties/doc/guides/source/activerecord_validations_callbacks.txt
+++ b/railties/doc/guides/source/activerecord_validations_callbacks.txt
@@ -359,7 +359,7 @@ Sometimes it will make sense to validate an object just when a given predicate i
=== Using a symbol with the +:if+ and +:unless+ options
-You can associated the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option.
+You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option.
[source, ruby]
------------------------------------------------------------------
@@ -620,29 +620,7 @@ Callbacks are methods that get called at certain moments of an object's lifecycl
=== Callbacks registration
-In order to use the available callbacks, you need to registrate them. There are two ways of doing that.
-
-=== Registering callbacks by overriding the callback methods
-
-You can specify the callback method directly, by overriding it. Let's see how it works using the +before_validation+ callback, which will surprisingly run right before any validation is done.
-
-[source, ruby]
-------------------------------------------------------------------
-class User < ActiveRecord::Base
- validates_presence_of :login, :email
-
- protected
- def before_validation
- if self.login.nil?
- self.login = email unless email.blank?
- end
- end
-end
-------------------------------------------------------------------
-
-=== Registering callbacks by using macro-style class methods
-
-The other way you can register a callback method is by implementing it as an ordinary method, and then using a macro-style class method to register it as a callback. The last example could be written like that:
+In order to use the available callbacks, you need to registrate them. You can do that by implementing them as an ordinary methods, and then using a macro-style class method to register then as callbacks.
[source, ruby]
------------------------------------------------------------------
@@ -671,12 +649,57 @@ class User < ActiveRecord::Base
end
------------------------------------------------------------------
-In Rails, the preferred way of registering callbacks is by using macro-style class methods. The main advantages of using macro-style class methods are:
+CAUTION: Remember to always declare the callback methods as being protected or private. These methods should never be public, otherwise it will be possible to call them from code outside the model, violating object encapsulation and exposing implementation details.
-* You can add more than one method for each type of callback. Those methods will be queued for execution at the same order they were registered.
-* Readability, since your callback declarations will live at the beggining of your models' files.
+== Conditional callbacks
-CAUTION: Remember to always declare the callback methods as being protected or private. These methods should never be public, otherwise it will be possible to call them from code outside the model, violating object encapsulation and exposing implementation details.
+Like in validations, we can also make our callbacks conditional, calling then only when a given predicate is satisfied. You can do that by using the +:if+ and +:unless+ options, which can take a symbol, a string or a Ruby Proc. You may use the +:if+ option when you want to specify when the callback *should* get called. If you want to specify when the callback *should not* be called, then you may use the +:unless+ option.
+
+=== Using a symbol with the +:if+ and +:unless+ options
+
+You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. If this method returns +false+ the callback won't be executed. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check the if the callback should be executed.
+
+[source, ruby]
+------------------------------------------------------------------
+class Order < ActiveRecord::Base
+ before_save :normalize_card_number, :if => :paid_with_card?
+end
+------------------------------------------------------------------
+
+=== Using a string with the +:if+ and +:unless+ options
+
+You can also use a string that will be evaluated using +:eval+ and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition.
+
+[source, ruby]
+------------------------------------------------------------------
+class Order < ActiveRecord::Base
+ before_save :normalize_card_number, :if => "paid_with_card?"
+end
+------------------------------------------------------------------
+
+=== Using a Proc object with the +:if+ and :+unless+ options
+
+Finally, it's possible to associate +:if+ and +:unless+ with a Ruby Proc object. This option is best suited when writing short validation methods, usually one-liners.
+
+[source, ruby]
+------------------------------------------------------------------
+class Order < ActiveRecord::Base
+ before_save :normalize_card_number,
+ :if => Proc.new { |order| order.paid_with_card? }
+end
+------------------------------------------------------------------
+
+=== Multiple Conditions for Callbacks
+
+When writing conditional callbacks, it's possible to mix both +:if+ and +:unless+ in the same callback declaration.
+
+[source, ruby]
+------------------------------------------------------------------
+class Comment < ActiveRecord::Base
+ after_create :send_email_to_author, :if => :author_wants_emails?,
+ :unless => Proc.new { |comment| comment.post.ignore_comments? }
+end
+------------------------------------------------------------------
== Available callbacks
diff --git a/railties/doc/guides/source/index.txt b/railties/doc/guides/source/index.txt
index 130beedf08..61be44c63e 100644
--- a/railties/doc/guides/source/index.txt
+++ b/railties/doc/guides/source/index.txt
@@ -113,7 +113,7 @@ ways of achieving this and how to understand what is happening "behind the scene
of your code.
***********************************************************
-.link:performance_testing.html[Performance testing Rails Applications]
+.link:performance_testing.html[Performance Testing Rails Applications]
***********************************************************
CAUTION: link:http://rails.lighthouseapp.com/projects/16213/tickets/4[Lighthouse Ticket]
diff --git a/railties/doc/guides/source/performance_testing.txt b/railties/doc/guides/source/performance_testing.txt
index b741ddfd00..84a42cecde 100644
--- a/railties/doc/guides/source/performance_testing.txt
+++ b/railties/doc/guides/source/performance_testing.txt
@@ -1,108 +1,164 @@
-Performance testing Rails Applications
+Performance Testing Rails Applications
======================================
-This guide covers the benchmarking and profiling tactics/tools of Rails and Ruby in general. By referring to this guide, you will be able to:
+This guide covers the various ways of performance testing a Ruby on Rails application. By referring to this guide, you will be able to:
* Understand the various types of benchmarking and profiling metrics
-* Generate performance/benchmarking tests
-* Use GC patched Ruby binary to measure memory usage and object allocation
-* Understand the information provided by Rails inside the log files
+* Generate performance and benchmarking tests
+* Use a GC-patched Ruby binary to measure memory usage and object allocation
+* Understand the benchmarking information provided by Rails inside the log files
* Learn about various tools facilitating benchmarking and profiling
-Performance testing is an integral part of the development cycle. It is very important that you don't make your end users wait for too long before the page is completely loaded. Ensuring a plesant browsing experience to the end users and cutting cost of unnecessary hardwares is important for any web application.
+Performance testing is an integral part of the development cycle. It is very important that you don't make your end users wait for too long before the page is completely loaded. Ensuring a pleasant browsing experience for end users and cutting the cost of unnecessary hardware is important for any non-trivial web application.
-== Using and understanding the log files ==
-
-Rails logs files containt basic but very useful information about the time taken to serve every request. A typical log entry looks something like :
+== Performance Test Cases ==
-[source, ruby]
-----------------------------------------------------------------------------
-Processing ItemsController#index (for 127.0.0.1 at 2009-01-08 03:06:39) [GET]
-Rendering template within layouts/items
-Rendering items/index
-Completed in 5ms (View: 2, DB: 0) | 200 OK [http://0.0.0.0/items]
-----------------------------------------------------------------------------
+Rails performance tests are a special type of integration tests, designed for benchmarking and profiling the test code. With performance tests, you can determine where your application's memory or speed problems are coming from, and get a more in-depth picture of those problems.
-For this section, we're only interested in the last line from that log entry:
+In a freshly generated Rails application, +test/performance/browsing_test.rb+ contains an example of a performance test:
[source, ruby]
----------------------------------------------------------------------------
-Completed in 5ms (View: 2, DB: 0) | 200 OK [http://0.0.0.0/items]
+require 'test_helper'
+require 'performance_test_help'
+
+# Profiling results for each test method are written to tmp/performance.
+class BrowsingTest < ActionController::PerformanceTest
+ def test_homepage
+ get '/'
+ end
+end
----------------------------------------------------------------------------
-This data is fairly straight forward to understand. Rails uses millisecond(ms) as the metric to measures the time taken. The complete request spent 5 ms inside Rails, out of which 2 ms were spent rendering views and none was spent communication with the database. It's safe to assume that the remaining 3 ms were spent inside the controller.
+This example is a simple performance test case for profiling a GET request to the application's homepage.
-== Helper methods ==
+=== Generating performance tests ===
-Rails provides various helper methods inside Active Record, Action Controller and Action View to measure the time taken by a specific code. The method is called +benchmark()+ in all three components.
+Rails provides a generator called +performance_test+ for creating new performance tests:
-[source, ruby]
+[source, shell]
----------------------------------------------------------------------------
-Project.benchmark("Creating project") do
- project = Project.create("name" => "stuff")
- project.create_manager("name" => "David")
- project.milestones << Milestone.find(:all)
-end
+script/generate performance_test homepage
----------------------------------------------------------------------------
-The above code benchmarks the multiple statments enclosed inside +Project.benchmark("Creating project") do..end+ block and prints the results inside log files. The statement inside log files will look like:
+This generates +homepage_test.rb+ in the +test/performance+ directory:
[source, ruby]
----------------------------------------------------------------------------
-Creating projectem (185.3ms)
+require 'test_helper'
+require 'performance_test_help'
+
+class HomepageTest < ActionController::PerformanceTest
+ # Replace this with your real tests.
+ def test_homepage
+ get '/'
+ end
+end
----------------------------------------------------------------------------
-Please refer to http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M001336[API docs] for optional options to +benchmark()+
+=== Examples ===
-Similarly, you could use this helper method inside http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715[controllers] ( Note that it's a class method here ):
+Let's assume your application has the following controller and model:
[source, ruby]
----------------------------------------------------------------------------
-def process_projects
- self.class.benchmark("Processing projects") do
- Project.process(params[:project_ids])
- Project.update_cached_projects
+# routes.rb
+map.root :controller => 'home'
+map.resources :posts
+
+# home_controller.rb
+class HomeController < ApplicationController
+ def dashboard
+ @users = User.last_ten(:include => :avatars)
+ @posts = Post.all_today
+ end
+end
+
+# posts_controller.rb
+class PostsController < ApplicationController
+ def create
+ @post = Post.create(params[:post])
+ redirect_to(@post)
+ end
+end
+
+# post.rb
+class Post < ActiveRecord::Base
+ before_save :recalculate_costly_stats
+
+ def slow_method
+ # I fire gallzilion queries sleeping all around
+ end
+
+ private
+
+ def recalculate_costly_stats
+ # CPU heavy calculations
end
end
----------------------------------------------------------------------------
-and http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715[views]:
+==== Controller Example ====
+
+Because performance tests are a special kind of integration test, you can use the +get+ and +post+ methods in them.
+
+Here's the performance test for +HomeController#dashboard+ and +PostsController#create+:
[source, ruby]
----------------------------------------------------------------------------
-<% benchmark("Showing projects partial") do %>
- <%= render :partial => @projects %>
-<% end %>
+require 'test_helper'
+require 'performance_test_help'
+
+class PostPerformanceTest < ActionController::PerformanceTest
+ def setup
+ # Application requires logged-in user
+ login_as(:lifo)
+ end
+
+ def test_homepage
+ get '/dashboard'
+ end
+
+ def test_creating_new_post
+ post '/posts', :post => { :body => 'lifo is fooling you' }
+ end
+end
----------------------------------------------------------------------------
-== Performance Test Cases ==
+You can find more details about the +get+ and +post+ methods in the link:../testing_rails_applications.html#mgunderloy[Testing Rails Applications] guide.
+
+==== Model Example ====
-Rails provides a very easy way to write performance test cases, which look just like the regular integration tests. Performance tests run a code profiler on your test methods. Profiling output for combinations of each test method, measurement, and output format are written to your +tmp/performance+ directory. By default, process_time is measured and both flat and graph_html output formats are written, so you'll have two output files per test method.
+Even though the performance tests are integration tests and hence closer to the request/response cycle by nature, you can still performance test pure model code.
-If you have a look at +test/performance/browsing_test.rb+ in a newly created Rails application:
+Performance test for +Post+ model:
[source, ruby]
----------------------------------------------------------------------------
require 'test_helper'
require 'performance_test_help'
-# Profiling results for each test method are written to tmp/performance.
-class BrowsingTest < ActionController::PerformanceTest
- def test_homepage
- get '/'
+class PostModelTest < ActionController::PerformanceTest
+ def test_creation
+ Post.create :body => 'still fooling you', :cost => '100'
+ end
+
+ def test_slow_method
+ # Using posts(:awesome) fixture
+ posts(:awesome).slow_method
end
end
----------------------------------------------------------------------------
-This is an automatically generated example performance test file, for testing performance of homepage('/') of the application.
-
=== Modes ===
-Performance test cases can be run in two modes : Benchmarking and Profling.
+Performance tests can be run in two modes : Benchmarking and Profiling.
==== Benchmarking ====
-Benchmarking helps you find out how fast are your test cases. Each Test case is run +4 times+ in this mode. To run performance tests in benchmarking mode:
+Benchmarking helps find out how fast each performance test runs. Each test case is run +4 times+ in benchmarking mode.
+
+To run performance tests in benchmarking mode:
[source, shell]
----------------------------------------------------------------------------
@@ -111,7 +167,9 @@ $ rake test:benchmark
==== Profiling ====
-Profiling helps introspect into your test cases and figure out which are the slow parts. Each Test case is run +1 time+ in this mode. To run performance tests in profiling mode:
+Profiling helps you see the details of a performance test and provide an in-depth picture of the slow and memory hungry parts. Each test case is run +1 time+ in profiling mode.
+
+To run performance tests in profiling mode:
[source, shell]
----------------------------------------------------------------------------
@@ -120,47 +178,47 @@ $ rake test:profile
=== Metrics ===
-Benchmarking and profiling run performance test cases in various modes to help precisely figure out the where the problem lies.
+Benchmarking and profiling run performance tests in various modes described below.
==== Wall Time ====
-Measures the real world time elapsed during the test run. It is affected by any other processes concurrently running on the system.
+Wall time measures the real world time elapsed during the test run. It is affected by any other processes concurrently running on the system.
Mode : Benchmarking
==== Process Time ====
-Measures the time taken by the process. It is unaffected by any other processes running concurrently on the same system. Hence, process time is likely to be constant for any given performance test, irrespective of the machine load.
+Process time measures the time taken by the process. It is unaffected by any other processes running concurrently on the same system. Hence, process time is likely to be constant for any given performance test, irrespective of the machine load.
Mode : Profiling
==== Memory ====
-Measures the amount of memory used for the performance test case.
+Memory measures the amount of memory used for the performance test case.
-Mode : Benchmarking, Profiling [Requires specially compiled Ruby]
+Mode : Benchmarking, Profiling [xref:gc[Requires GC-Patched Ruby]]
==== Objects ====
-Measures the number of objects allocated for the performance test case.
+Objects measures the number of objects allocated for the performance test case.
-Mode : Benchmarking, Profiling [Requires specially compiled Ruby]
+Mode : Benchmarking, Profiling [xref:gc[Requires GC-Patched Ruby]]
==== GC Runs ====
-Measures the number of times GC was invoked for the performance test case.
+GC Runs measures the number of times GC was invoked for the performance test case.
-Mode : Benchmarking [Requires specially compiled Ruby]
+Mode : Benchmarking [xref:gc[Requires GC-Patched Ruby]]
==== GC Time ====
-Measures the amount of time spent in GC for the performance test case.
+GC Time measures the amount of time spent in GC for the performance test case.
-Mode : Benchmarking [Requires specially compiled Ruby]
+Mode : Benchmarking [xref:gc[Requires GC-Patched Ruby]]
=== Understanding the output ===
-Performance tests generate different outputs inside +tmp/performance+ directory based on the mode it is run in and the metric.
+Performance tests generate different outputs inside +tmp/performance+ directory depending on their mode and metric.
==== Benchmarking ====
@@ -182,7 +240,7 @@ BrowsingTest#test_homepage (31 ms warmup)
===== CSV files =====
-Performance tests results are also appended to +.csv+ files inside +tmp/performance/<Class>#<test>_<metric>.csv+ file. For example, running the default +BrowsingTest#test_homepage+ will generate following five files :
+Performance test results are also appended to +.csv+ files inside +tmp/performance+. For example, running the default +BrowsingTest#test_homepage+ will generate following five files :
- BrowsingTest#test_homepage_gc_runs.csv
- BrowsingTest#test_homepage_gc_time.csv
@@ -190,9 +248,9 @@ Performance tests results are also appended to +.csv+ files inside +tmp/performa
- BrowsingTest#test_homepage_objects.csv
- BrowsingTest#test_homepage_wall_time.csv
-As the results are appended to these files each time the performance tests are run in benchmarking mode, it enables you gather data over a sustainable period of time which can be very helpful with various performance analysis.
+As the results are appended to these files each time the performance tests are run in benchmarking mode, you can collect data over a period of time. This can be very helpful in analyzing the effects of code changes.
-Sample output of +BrowsingTest#test_homepage_wall_time.csv + :
+Sample output of +BrowsingTest#test_homepage_wall_time.csv+:
[source, shell]
----------------------------------------------------------------------------
@@ -211,9 +269,11 @@ measurement,created_at,app,rails,ruby,platform
==== Profiling ====
+In profiling mode, you can choose from four types of output.
+
===== Command line =====
-This is the very basic form of output in profiling mode. Example :
+This is a very basic form of output in profiling mode:
[source, shell]
----------------------------------------------------------------------------
@@ -225,103 +285,250 @@ BrowsingTest#test_homepage (58 ms warmup)
===== Flat =====
-Flat output shows the total amount of time spent in each method. http://ruby-prof.rubyforge.org/files/examples/flat_txt.html[Check ruby prof documentation for a better explaination].
+Flat output shows the total amount of time spent in each method. http://ruby-prof.rubyforge.org/files/examples/flat_txt.html[Check ruby prof documentation for a better explanation].
===== Graph =====
-Graph output shows how long each method takes to run, which methods call it and which methods it calls. http://ruby-prof.rubyforge.org/files/examples/graph_txt.html[Check ruby prof documentation for a better explaination].
+Graph output shows how long each method takes to run, which methods call it and which methods it calls. http://ruby-prof.rubyforge.org/files/examples/graph_txt.html[Check ruby prof documentation for a better explanation].
===== Tree =====
-Tree output is profiling information in calltree format for use by kcachegrind and similar tools.
+Tree output is profiling information in calltree format for use by http://kcachegrind.sourceforge.net/html/Home.html[kcachegrind] and similar tools.
+
+=== Tuning Test Runs ===
+
+By default, each performance test is run +4 times+ in benchmarking mode and +1 time+ in profiling. However, test runs can easily be configured.
+
+CAUTION: Performance test configurability is not yet enabled in Rails. But it will be soon.
+
+[[gc]]
+=== Installing GC-Patched Ruby ===
+
+To get the best from Rails performance tests, you need to build a special Ruby binary with some super powers - http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch[GC patch] for measuring GC Runs/Time and memory/object allocation.
+
+The process is fairly straight forward. If you've never compiled a Ruby binary before, follow these steps to build a ruby binary inside your home directory:
-=== Preparing Ruby and Ruby-prof ===
+==== Installation ====
-Before we go ahead, Rails performance testing requires you to build a special Ruby binary with some super powers - GC patch for measuring GC Runs/Time. This process is very straight forward. If you've never compiled a Ruby binary before, you can follow the following steps to build a ruby binary inside your home directory:
+Compile Ruby and apply this http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch[GC Patch]:
-==== Compile ====
+==== Download and Extract ====
[source, shell]
----------------------------------------------------------------------------
[lifo@null ~]$ mkdir rubygc
-[lifo@null ~]$ wget ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.6-p111.tar.gz
-[lifo@null ~]$ tar -xzvf ruby-1.8.6-p111.tar.gz
-[lifo@null ~]$ cd ruby-1.8.6-p111
-[lifo@null ruby-1.8.6-p111]$ curl http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch | patch -p0
-[lifo@null ruby-1.8.6-p111]$ ./configure --prefix=/Users/lifo/rubygc
-[lifo@null ruby-1.8.6-p111]$ make && make install
+[lifo@null ~]$ wget <download the latest stable ruby from ftp://ftp.ruby-lang.org/pub/ruby>
+[lifo@null ~]$ tar -xzvf <ruby-version.tar.gz>
+[lifo@null ~]$ cd <ruby-version>
+----------------------------------------------------------------------------
+
+==== Apply the patch ====
+
+[source, shell]
+----------------------------------------------------------------------------
+[lifo@null ruby-version]$ curl http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch | patch -p0
+----------------------------------------------------------------------------
+
+==== Configure and Install ====
+
+The following will install ruby in your home directory's +/rubygc+ directory. Make sure to replace +<homedir>+ with a full patch to your actual home directory.
+
+[source, shell]
+----------------------------------------------------------------------------
+[lifo@null ruby-version]$ ./configure --prefix=/<homedir>/rubygc
+[lifo@null ruby-version]$ make && make install
----------------------------------------------------------------------------
==== Prepare aliases ====
-Add the following lines in your ~/.profile for convenience:
+For convenience, add the following lines in your +~/.profile+:
----------------------------------------------------------------------------
-alias gcruby='/Users/lifo/rubygc/bin/ruby'
-alias gcrake='/Users/lifo/rubygc/bin/rake'
-alias gcgem='/Users/lifo/rubygc/bin/gem'
-alias gcirb='/Users/lifo/rubygc/bin/irb'
-alias gcrails='/Users/lifo/rubygc/bin/rails'
+alias gcruby='~/rubygc/bin/ruby'
+alias gcrake='~/rubygc/bin/rake'
+alias gcgem='~/rubygc/bin/gem'
+alias gcirb='~/rubygc/bin/irb'
+alias gcrails='~/rubygc/bin/rails'
----------------------------------------------------------------------------
-==== Install rubygems and some basic gems ====
+==== Install rubygems and dependency gems ====
-Download http://rubyforge.org/projects/rubygems[Rubygems] and install it from source. Afterwards, install rake. rails, ruby-prof and rack gems:
+Download http://rubyforge.org/projects/rubygems[Rubygems] and install it from source. Rubygem's README file should have necessary installation instructions.
+Additionally, install the following gems :
+
+ * +rake+
+ * +rails+
+ * +ruby-prof+
+ * +rack+
+ * +mysql+
+
+If installing +mysql+ fails, you can try to install it manually:
+
+[source, shell]
+----------------------------------------------------------------------------
+[lifo@null mysql]$ gcruby extconf.rb --with-mysql-config
+[lifo@null mysql]$ make && make install
+----------------------------------------------------------------------------
+
+And you're ready to go. Don't forget to use +gcruby+ and +gcrake+ aliases when running the performance tests.
+
+== Command Line Tools ==
+
+Writing performance test cases could be an overkill when you are looking for one time tests. Rails ships with two command line tools that enable quick and dirty performance testing:
+
+=== benchmarker ===
+
++benchmarker+ is a wrapper around Ruby's http://ruby-doc.org/core/classes/Benchmark.html[Benchmark] module.
+
+Usage:
+
+[source, shell]
----------------------------------------------------------------------------
-[lifo@null ~]$ gcgem install rake
-[lifo@null ~]$ gcgem install rails
-[lifo@null ~]$ gcgem install ruby-prof
-[lifo@null ~]$ gcgem install rack
+$ script/performance/benchmarker [times] 'Person.expensive_way' 'Person.another_expensive_way' ...
----------------------------------------------------------------------------
-==== Install MySQL gem ====
+Examples:
+[source, shell]
----------------------------------------------------------------------------
-[lifo@null ~]$ gcgem install mysql
+$ script/performance/benchmarker 10 'Item.all' 'CouchItem.all'
----------------------------------------------------------------------------
-If this fails, you can try to install it manually:
+If the +[times]+ argument is omitted, supplied methods are run just once:
+[source, shell]
----------------------------------------------------------------------------
-[lifo@null ~]$ cd /Users/lifo/rubygc/lib/ruby/gems/1.8/gems/mysql-2.7/
-[lifo@null mysql-2.7]$ gcruby extconf.rb --with-mysql-config
-[lifo@null mysql-2.7]$ make && make install
+$ script/performance/benchmarker 'Item.first' 'Item.last'
----------------------------------------------------------------------------
-=== Generating performance test ===
+=== profiler ===
+
++profiler+ is a wrapper around http://ruby-prof.rubyforge.org/[ruby-prof] gem.
+
+Usage:
-Rails provides a generator for creating new performance tests:
+[source, shell]
+----------------------------------------------------------------------------
+$ script/performance/profiler 'Person.expensive_method(10)' [times] [flat|graph|graph_html]
+----------------------------------------------------------------------------
+
+Examples:
[source, shell]
----------------------------------------------------------------------------
-[lifo@null application (master)]$ script/generate performance_test homepage
+$ script/performance/profiler 'Item.all'
----------------------------------------------------------------------------
-This will generate +test/performance/homepage_test.rb+:
+This will profile +Item.all+ in +RubyProf::WALL_TIME+ measure mode. By default, it prints flat output to the shell.
+
+[source, shell]
+----------------------------------------------------------------------------
+$ script/performance/profiler 'Item.all' 10 graph
+----------------------------------------------------------------------------
+
+This will profile +10.times { Item.all }+ with +RubyProf::WALL_TIME+ measure mode and print graph output to the shell.
+
+If you want to store the output in a file:
+
+[source, shell]
+----------------------------------------------------------------------------
+$ script/performance/profiler 'Item.all' 10 graph 2> graph.txt
+----------------------------------------------------------------------------
+
+== Helper methods ==
+
+Rails provides various helper methods inside Active Record, Action Controller and Action View to measure the time taken by a given piece of code. The method is called +benchmark()+ in all the three components.
+
+=== Model ===
[source, ruby]
----------------------------------------------------------------------------
-require 'test_helper'
-require 'performance_test_help'
+Project.benchmark("Creating project") do
+ project = Project.create("name" => "stuff")
+ project.create_manager("name" => "David")
+ project.milestones << Milestone.find(:all)
+end
+----------------------------------------------------------------------------
-class HomepageTest < ActionController::PerformanceTest
- # Replace this with your real tests.
- def test_homepage
- get '/'
+This benchmarks the code enclosed in the +Project.benchmark("Creating project") do..end+ block and prints the result to the log file:
+
+[source, ruby]
+----------------------------------------------------------------------------
+Creating project (185.3ms)
+----------------------------------------------------------------------------
+
+Please refer to the http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M001336[API docs] for additional options to +benchmark()+
+
+=== Controller ===
+
+Similarly, you could use this helper method inside http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715[controllers]
+
+NOTE: +benchmark+ is a class method inside controllers
+
+[source, ruby]
+----------------------------------------------------------------------------
+def process_projects
+ self.class.benchmark("Processing projects") do
+ Project.process(params[:project_ids])
+ Project.update_cached_projects
end
end
----------------------------------------------------------------------------
-Which you can modify to suit your needs.
+=== View ===
+
+And in http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715[views]:
+
+[source, ruby]
+----------------------------------------------------------------------------
+<% benchmark("Showing projects partial") do %>
+ <%= render :partial => @projects %>
+<% end %>
+----------------------------------------------------------------------------
-== Other Profiling Tools ==
+== Request Logging ==
+
+Rails log files contain very useful information about the time taken to serve each request. Here's a typical log file entry:
+
+[source, ruby]
+----------------------------------------------------------------------------
+Processing ItemsController#index (for 127.0.0.1 at 2009-01-08 03:06:39) [GET]
+Rendering template within layouts/items
+Rendering items/index
+Completed in 5ms (View: 2, DB: 0) | 200 OK [http://0.0.0.0/items]
+----------------------------------------------------------------------------
+
+For this section, we're only interested in the last line:
+
+[source, ruby]
+----------------------------------------------------------------------------
+Completed in 5ms (View: 2, DB: 0) | 200 OK [http://0.0.0.0/items]
+----------------------------------------------------------------------------
+
+This data is fairly straightforward to understand. Rails uses millisecond(ms) as the metric to measures the time taken. The complete request spent 5 ms inside Rails, out of which 2 ms were spent rendering views and none was spent communication with the database. It's safe to assume that the remaining 3 ms were spent inside the controller.
+
+Michael Koziarski has an http://www.therailsway.com/2009/1/6/requests-per-second[interesting blog post] explaining the importance of using milliseconds as the metric.
+
+== Useful Profiling Tools ==
+
+=== Rails Plugins and Gems ===
-* http://www.hpl.hp.com/research/linux/httperf/[httperf]
* http://rails-analyzer.rubyforge.org/[Rails Analyzer]
* http://www.flyingmachinestudios.com/projects/[Palmist]
+* http://github.com/josevalim/rails-footnotes/tree/master[Rails Footnotes]
+* http://github.com/dsboulder/query_reviewer/tree/master[Query Reviewer]
+
+=== External ===
+
+* http://www.hpl.hp.com/research/linux/httperf[httperf]
+* http://httpd.apache.org/docs/2.2/programs/ab.html[ab]
+* http://jakarta.apache.org/jmeter[JMeter]
+
+== Commercial Products ==
+
+Rails has been lucky to have three startups dedicated to Rails specific performance tools:
-== Commercial products dedicated to Rails Perfomance ==
* http://www.newrelic.com[New Relic]
* http://www.fiveruns.com[Fiveruns]
* http://scoutapp.com[Scout]
@@ -330,6 +537,6 @@ Which you can modify to suit your needs.
http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/4[Lighthouse ticket]
-* January 9, 2009: Rewrite by Pratik
+* January 9, 2009: Complete rewrite by Pratik
* October 17, 2008: First revision by Pratik
-* September 6, 2008: Initial version by Matthew Bergman \ No newline at end of file
+* September 6, 2008: Initial version by Matthew Bergman
diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb
index 637fe74313..619701460d 100644
--- a/railties/lib/initializer.rb
+++ b/railties/lib/initializer.rb
@@ -370,8 +370,9 @@ Run `rake gems:install` to install the missing gems.
def load_view_paths
if configuration.frameworks.include?(:action_view)
if configuration.cache_classes
- ActionController::Base.view_paths.load if configuration.frameworks.include?(:action_controller)
- ActionMailer::Base.template_root.load if configuration.frameworks.include?(:action_mailer)
+ view_path = ActionView::Template::EagerPath.new(configuration.view_path)
+ ActionController::Base.view_paths = view_path if configuration.frameworks.include?(:action_controller)
+ ActionMailer::Base.template_root = view_path if configuration.frameworks.include?(:action_mailer)
end
end
end
@@ -473,7 +474,7 @@ Run `rake gems:install` to install the missing gems.
# set to use Configuration#view_path.
def initialize_framework_views
if configuration.frameworks.include?(:action_view)
- view_path = ActionView::PathSet::Path.new(configuration.view_path, false)
+ view_path = ActionView::Template::Path.new(configuration.view_path)
ActionMailer::Base.template_root ||= view_path if configuration.frameworks.include?(:action_mailer)
ActionController::Base.view_paths = view_path if configuration.frameworks.include?(:action_controller) && ActionController::Base.view_paths.empty?
end
@@ -536,7 +537,7 @@ Run `rake gems:install` to install the missing gems.
end
def initialize_metal
- configuration.middleware.use Rails::Rack::Metal
+ configuration.middleware.insert_before(:"ActionController::VerbPiggybacking", Rails::Rack::Metal)
end
# Initializes framework-specific settings for each of the loaded frameworks
diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb
index 94d34cda39..e1b422716d 100644
--- a/railties/lib/rails/backtrace_cleaner.rb
+++ b/railties/lib/rails/backtrace_cleaner.rb
@@ -2,7 +2,7 @@ module Rails
class BacktraceCleaner < ActiveSupport::BacktraceCleaner
ERB_METHOD_SIG = /:in `_run_erb_.*/
- VENDOR_DIRS = %w( vendor/plugins vendor/gems vendor/rails )
+ VENDOR_DIRS = %w( vendor/gems vendor/rails )
SERVER_DIRS = %w( lib/mongrel bin/mongrel
lib/passenger bin/passenger-spawn-server
lib/rack )
@@ -15,11 +15,12 @@ module Rails
def initialize
super
- add_filter { |line| line.sub(RAILS_ROOT, '') }
+ add_filter { |line| line.sub("#{RAILS_ROOT}/", '') }
add_filter { |line| line.sub(ERB_METHOD_SIG, '') }
add_filter { |line| line.sub('./', '/') } # for tests
add_filter { |line| line.sub(/(#{GEMS_DIR})\/gems\/([a-z]+)-([0-9.]+)\/(.*)/, '\2 (\3) \4')} # http://gist.github.com/30430
add_silencer { |line| ALL_NOISE.any? { |dir| line.include?(dir) } }
+ add_silencer { |line| line =~ %r(vendor/plugins/[^\/]+/lib) }
end
end
diff --git a/railties/lib/rails_generator/commands.rb b/railties/lib/rails_generator/commands.rb
index cacb3807d6..299044c3d7 100644
--- a/railties/lib/rails_generator/commands.rb
+++ b/railties/lib/rails_generator/commands.rb
@@ -294,7 +294,7 @@ HELP
file(relative_source, relative_destination, template_options) do |file|
# Evaluate any assignments in a temporary, throwaway binding.
vars = template_options[:assigns] || {}
- b = binding
+ b = template_options[:binding] || binding
vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }
# Render the source file with the temporary binding.
diff --git a/railties/lib/test_help.rb b/railties/lib/test_help.rb
index b5c92c1790..ee24ea3a45 100644
--- a/railties/lib/test_help.rb
+++ b/railties/lib/test_help.rb
@@ -3,7 +3,6 @@
silence_warnings { RAILS_ENV = "test" }
require 'test/unit'
-require 'active_support/test_case'
require 'action_controller/test_case'
require 'action_view/test_case'
require 'action_controller/integration'
diff --git a/railties/test/console_app_test.rb b/railties/test/console_app_test.rb
index cbaf230594..f419fe0d8d 100644
--- a/railties/test/console_app_test.rb
+++ b/railties/test/console_app_test.rb
@@ -14,6 +14,15 @@ require 'console_app'
Test::Unit.run = false
class ConsoleAppTest < Test::Unit::TestCase
+ def test_app_method_should_return_integration_session
+ assert_nothing_thrown do
+ console_session = app
+ assert_not_nil console_session
+ assert_instance_of ActionController::Integration::Session,
+ console_session
+ end
+ end
+
uses_mocha 'console reload test' do
def test_reload_should_fire_preparation_callbacks
a = b = c = nil
diff --git a/railties/test/error_page_test.rb b/railties/test/error_page_test.rb
index 844f889aad..f819e468e8 100644
--- a/railties/test/error_page_test.rb
+++ b/railties/test/error_page_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
require 'action_controller'
-require 'action_controller/test_process'
+require 'action_controller/test_case'
RAILS_ENV = "test"
CURRENT_DIR = File.expand_path(File.dirname(__FILE__))
@@ -22,13 +22,10 @@ ActionController::Routing::Routes.draw do |map|
map.connect ':controller/:action/:id'
end
-class ErrorPageControllerTest < Test::Unit::TestCase
+class ErrorPageControllerTest < ActionController::TestCase
def setup
- @controller = ErrorPageController.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
-
ActionController::Base.consider_all_requests_local = false
+ rescue_action_in_public!
end
def test_500_error_page_instructs_system_administrator_to_check_log_file
@@ -38,6 +35,6 @@ class ErrorPageControllerTest < Test::Unit::TestCase
end
get :crash
expected_log_file = "#{RAILS_ENV}.log"
- assert_not_nil @response.body.index(expected_log_file)
+ assert_not_nil @response.body.index(expected_log_file), @response.body
end
end
diff --git a/railties/test/fcgi_dispatcher_test.rb b/railties/test/fcgi_dispatcher_test.rb
index cc054c24aa..c469c5dd01 100644
--- a/railties/test/fcgi_dispatcher_test.rb
+++ b/railties/test/fcgi_dispatcher_test.rb
@@ -1,10 +1,9 @@
require 'abstract_unit'
begin
+require 'action_controller'
require 'fcgi_handler'
-module ActionController; module Routing; module Routes; end end end
-
class RailsFCGIHandlerTest < Test::Unit::TestCase
def setup
@log = StringIO.new
@@ -131,19 +130,11 @@ class RailsFCGIHandlerSignalsTest < Test::Unit::TestCase
end
end
- class ::Dispatcher
- class << self
- attr_accessor :signal
- alias_method :old_dispatch, :dispatch
- def dispatch(cgi)
- signal ? Process.kill(signal, $$) : old_dispatch
- end
- end
- end
-
def setup
@log = StringIO.new
@handler = RailsFCGIHandler.new(@log)
+ @dispatcher = mock
+ Dispatcher.stubs(:new).returns(@dispatcher)
end
def test_interrupted_via_HUP_when_not_in_request
@@ -159,19 +150,6 @@ class RailsFCGIHandlerSignalsTest < Test::Unit::TestCase
assert_equal :reload, @handler.when_ready
end
- def test_interrupted_via_HUP_when_in_request
- cgi = mock
- FCGI.expects(:each_cgi).once.yields(cgi)
- Dispatcher.expects(:signal).times(2).returns('HUP')
-
- @handler.expects(:reload!).once
- @handler.expects(:close_connection).never
- @handler.expects(:exit).never
-
- @handler.process!
- assert_equal :reload, @handler.when_ready
- end
-
def test_interrupted_via_USR1_when_not_in_request
cgi = mock
FCGI.expects(:each_cgi).once.yields(cgi)
@@ -186,19 +164,6 @@ class RailsFCGIHandlerSignalsTest < Test::Unit::TestCase
assert_nil @handler.when_ready
end
- def test_interrupted_via_USR1_when_in_request
- cgi = mock
- FCGI.expects(:each_cgi).once.yields(cgi)
- Dispatcher.expects(:signal).times(2).returns('USR1')
-
- @handler.expects(:reload!).never
- @handler.expects(:close_connection).with(cgi).once
- @handler.expects(:exit).never
-
- @handler.process!
- assert_equal :exit, @handler.when_ready
- end
-
def test_restart_via_USR2_when_in_request
cgi = mock
FCGI.expects(:each_cgi).once.yields(cgi)
@@ -217,7 +182,7 @@ class RailsFCGIHandlerSignalsTest < Test::Unit::TestCase
def test_interrupted_via_TERM
cgi = mock
FCGI.expects(:each_cgi).once.yields(cgi)
- Dispatcher.expects(:signal).times(2).returns('TERM')
+ ::Rack::Handler::FastCGI.expects(:serve).once.returns('TERM')
@handler.expects(:reload!).never
@handler.expects(:close_connection).never
@@ -238,7 +203,7 @@ class RailsFCGIHandlerSignalsTest < Test::Unit::TestCase
cgi = mock
error = RuntimeError.new('foo')
FCGI.expects(:each_cgi).once.yields(cgi)
- Dispatcher.expects(:dispatch).once.with(cgi).raises(error)
+ ::Rack::Handler::FastCGI.expects(:serve).once.raises(error)
@handler.expects(:dispatcher_error).with(error, regexp_matches(/^unhandled/))
@handler.process!
end
@@ -254,7 +219,7 @@ class RailsFCGIHandlerSignalsTest < Test::Unit::TestCase
cgi = mock
error = SignalException.new('USR2')
FCGI.expects(:each_cgi).once.yields(cgi)
- Dispatcher.expects(:dispatch).once.with(cgi).raises(error)
+ ::Rack::Handler::FastCGI.expects(:serve).once.raises(error)
@handler.expects(:dispatcher_error).with(error, regexp_matches(/^stopping/))
@handler.process!
end
@@ -284,7 +249,7 @@ class RailsFCGIHandlerPeriodicGCTest < Test::Unit::TestCase
cgi = mock
FCGI.expects(:each_cgi).times(10).yields(cgi)
- Dispatcher.expects(:dispatch).times(10).with(cgi)
+ Dispatcher.expects(:new).times(10)
@handler.expects(:run_gc!).never
9.times { @handler.process! }
diff --git a/railties/test/gem_dependency_test.rb b/railties/test/gem_dependency_test.rb
index 30fd899fea..6c1f0961a1 100644
--- a/railties/test/gem_dependency_test.rb
+++ b/railties/test/gem_dependency_test.rb
@@ -9,33 +9,33 @@ Rails::VendorGemSourceIndex.silence_spec_warnings = true
uses_mocha "Plugin Tests" do
class GemDependencyTest < Test::Unit::TestCase
def setup
- @gem = Rails::GemDependency.new "hpricot"
- @gem_with_source = Rails::GemDependency.new "hpricot", :source => "http://code.whytheluckystiff.net"
- @gem_with_version = Rails::GemDependency.new "hpricot", :version => "= 0.6"
- @gem_with_lib = Rails::GemDependency.new "aws-s3", :lib => "aws/s3"
- @gem_without_load = Rails::GemDependency.new "hpricot", :lib => false
+ @gem = Rails::GemDependency.new "xhpricotx"
+ @gem_with_source = Rails::GemDependency.new "xhpricotx", :source => "http://code.whytheluckystiff.net"
+ @gem_with_version = Rails::GemDependency.new "xhpricotx", :version => "= 0.6"
+ @gem_with_lib = Rails::GemDependency.new "xaws-s3x", :lib => "aws/s3"
+ @gem_without_load = Rails::GemDependency.new "xhpricotx", :lib => false
end
def test_configuration_adds_gem_dependency
config = Rails::Configuration.new
- config.gem "aws-s3", :lib => "aws/s3", :version => "0.4.0"
- assert_equal [["install", "aws-s3", "--version", '"= 0.4.0"']], config.gems.collect(&:install_command)
+ config.gem "xaws-s3x", :lib => "aws/s3", :version => "0.4.0"
+ assert_equal [["install", "xaws-s3x", "--version", '"= 0.4.0"']], config.gems.collect(&:install_command)
end
def test_gem_creates_install_command
- assert_equal %w(install hpricot), @gem.install_command
+ assert_equal %w(install xhpricotx), @gem.install_command
end
def test_gem_with_source_creates_install_command
- assert_equal %w(install hpricot --source http://code.whytheluckystiff.net), @gem_with_source.install_command
+ assert_equal %w(install xhpricotx --source http://code.whytheluckystiff.net), @gem_with_source.install_command
end
def test_gem_with_version_creates_install_command
- assert_equal ["install", "hpricot", "--version", '"= 0.6"'], @gem_with_version.install_command
+ assert_equal ["install", "xhpricotx", "--version", '"= 0.6"'], @gem_with_version.install_command
end
def test_gem_creates_unpack_command
- assert_equal %w(unpack hpricot), @gem.unpack_command
+ assert_equal %w(unpack xhpricotx), @gem.unpack_command
end
def test_gem_with_version_unpack_install_command
@@ -43,7 +43,7 @@ uses_mocha "Plugin Tests" do
mock_spec = mock()
mock_spec.stubs(:version).returns('0.6')
@gem_with_version.stubs(:specification).returns(mock_spec)
- assert_equal ["unpack", "hpricot", "--version", '= 0.6'], @gem_with_version.unpack_command
+ assert_equal ["unpack", "xhpricotx", "--version", '= 0.6'], @gem_with_version.unpack_command
end
def test_gem_adds_load_paths