diff options
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 |