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.rb28
-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/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
103 files changed, 2588 insertions, 1110 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..fc8d25f665 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -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/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