aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG36
-rw-r--r--actionpack/lib/action_controller/base/base.rb2
-rw-r--r--actionpack/lib/action_controller/base/http_authentication.rb196
-rw-r--r--actionpack/lib/action_controller/base/layout.rb2
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb4
-rw-r--r--actionpack/lib/action_controller/routing/generation/url_rewriter.rb9
-rw-r--r--actionpack/lib/action_controller/testing/process.rb67
-rwxr-xr-xactionpack/lib/action_dispatch/http/request.rb31
-rw-r--r--actionpack/lib/action_dispatch/middleware/rewindable_input.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb6
-rw-r--r--actionpack/lib/action_view/base.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb59
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb45
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb2
-rw-r--r--actionpack/lib/action_view/locale/en.yml13
-rw-r--r--actionpack/lib/action_view/paths.rb3
-rw-r--r--actionpack/lib/action_view/template/template.rb56
-rw-r--r--actionpack/test/abstract_unit.rb4
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb42
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb130
-rw-r--r--actionpack/test/controller/render_test.rb8
-rw-r--r--actionpack/test/controller/session/cookie_store_test.rb33
-rw-r--r--actionpack/test/controller/session/test_session_test.rb58
-rw-r--r--actionpack/test/controller/test_test.rb23
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb17
-rw-r--r--actionpack/test/dispatch/rack_test.rb24
-rw-r--r--actionpack/test/dispatch/request_test.rb56
-rw-r--r--actionpack/test/fixtures/replies.yml2
-rw-r--r--actionpack/test/fixtures/test/hello_world.da.html.erb1
-rw-r--r--actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb1
-rw-r--r--actionpack/test/template/form_options_helper_test.rb42
-rw-r--r--actionpack/test/template/number_helper_i18n_test.rb24
-rw-r--r--actionpack/test/template/render_test.rb15
-rw-r--r--actionpack/test/template/text_helper_test.rb23
-rw-r--r--activerecord/Rakefile8
-rwxr-xr-xactiverecord/lib/active_record/base.rb29
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb2
-rwxr-xr-xactiverecord/test/cases/base_test.rb7
-rw-r--r--activerecord/test/cases/connection_test_mysql.rb26
-rw-r--r--activerecord/test/cases/copy_table_test_sqlite.rb11
-rw-r--r--activerecord/test/cases/method_scoping_test.rb10
-rw-r--r--activerecord/test/cases/named_scope_test.rb20
-rw-r--r--activerecord/test/connections/jdbc_jdbcderby/connection.rb18
-rw-r--r--activerecord/test/connections/jdbc_jdbch2/connection.rb18
-rw-r--r--activerecord/test/connections/jdbc_jdbchsqldb/connection.rb18
-rw-r--r--activerecord/test/connections/jdbc_jdbcmysql/connection.rb26
-rw-r--r--activerecord/test/connections/jdbc_jdbcpostgresql/connection.rb26
-rw-r--r--activerecord/test/connections/jdbc_jdbcsqlite3/connection.rb25
-rw-r--r--activerecord/test/models/post.rb6
-rw-r--r--activerecord/test/models/topic.rb2
-rw-r--r--activerecord/test/schema/schema.rb5
-rw-r--r--activeresource/lib/active_resource/base.rb6
-rw-r--r--activeresource/test/base/equality_test.rb9
-rw-r--r--activeresource/test/base_test.rb15
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb2
-rw-r--r--activesupport/lib/active_support/dependencies.rb19
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb4
-rwxr-xr-xactivesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb5
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb8
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb22
-rw-r--r--activesupport/test/caching_test.rb12
-rw-r--r--ci/geminstaller.yml4
-rw-r--r--railties/CHANGELOG4
-rw-r--r--railties/lib/commands/dbconsole.rb18
-rw-r--r--railties/lib/initializer.rb3
-rw-r--r--railties/lib/tasks/gems.rake4
-rw-r--r--railties/lib/tasks/misc.rake1
69 files changed, 1230 insertions, 216 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 26e40e76d5..e9e18a8f6b 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,41 @@
*2.3.0 [Edge]*
+* Added grouped_options_for_select helper method for wrapping option tags in optgroups. #977 [Jon Crawford]
+
+* Implement HTTP Digest authentication. #1230 [Gregg Kellogg, Pratik Naik] Example :
+
+ class DummyDigestController < ActionController::Base
+ USERS = { "lifo" => 'world' }
+
+ before_filter :authenticate
+
+ def index
+ render :text => "Hello Secret"
+ end
+
+ private
+
+ def authenticate
+ authenticate_or_request_with_http_digest("Super Secret") do |username|
+ # Return the user's password
+ USERS[username]
+ end
+ end
+ end
+
+* Improved i18n support for the number_to_human_size helper. Changes the storage_units translation data; update your translations accordingly. #1634 [Yaroslav Markin]
+ storage_units:
+ # %u is the storage unit, %n is the number (default: 2 MB)
+ format: "%n %u"
+ units:
+ byte:
+ one: "Byte"
+ other: "Bytes"
+ kb: "KB"
+ mb: "MB"
+ gb: "GB"
+ tb: "TB"
+
* 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]
diff --git a/actionpack/lib/action_controller/base/base.rb b/actionpack/lib/action_controller/base/base.rb
index c9c23008cd..a01d8f25cc 100644
--- a/actionpack/lib/action_controller/base/base.rb
+++ b/actionpack/lib/action_controller/base/base.rb
@@ -630,7 +630,7 @@ module ActionController #:nodoc:
end
def session_enabled?
- request.session_options && request.session_options[:disabled] != false
+ ActiveSupport::Deprecation.warn("Sessions are now lazy loaded. So if you don't access them, consider them disabled.", caller)
end
self.view_paths = []
diff --git a/actionpack/lib/action_controller/base/http_authentication.rb b/actionpack/lib/action_controller/base/http_authentication.rb
index 2ed810db7d..5d915fda08 100644
--- a/actionpack/lib/action_controller/base/http_authentication.rb
+++ b/actionpack/lib/action_controller/base/http_authentication.rb
@@ -1,42 +1,42 @@
module ActionController
module HttpAuthentication
# Makes it dead easy to do HTTP Basic authentication.
- #
+ #
# Simple Basic example:
- #
+ #
# class PostsController < ApplicationController
# USER_NAME, PASSWORD = "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_basic do |user_name, password|
+ # authenticate_or_request_with_http_basic do |user_name, password|
# user_name == USER_NAME && password == PASSWORD
# end
# end
# end
- #
- #
- # Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication,
+ #
+ #
+ # Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication,
# the regular HTML interface is protected by a session approach:
- #
+ #
# class ApplicationController < ActionController::Base
# before_filter :set_account, :authenticate
- #
+ #
# protected
# def set_account
# @account = Account.find_by_url_name(request.subdomains.first)
# end
- #
+ #
# def authenticate
# case request.format
# when Mime::XML, Mime::ATOM
@@ -54,24 +54,48 @@ module ActionController
# end
# end
# end
- #
- #
+ #
# In your integration tests, you can do something like this:
- #
+ #
# def test_access_granted_from_xml
# get(
- # "/notes/1.xml", nil,
+ # "/notes/1.xml", nil,
# :authorization => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
# )
- #
+ #
# assert_equal 200, status
# end
- #
- #
+ #
+ # Simple Digest example:
+ #
+ # 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 |username|
+ # USERS[username]
+ # end
+ # end
+ # end
+ #
+ # NOTE: The +authenticate_or_request_with_http_digest+ 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.
+ #
# On shared hosts, Apache sometimes doesn't pass authentication headers to
# FCGI instances. If your environment matches this description and you cannot
# authenticate, try this rule in your Apache setup:
- #
+ #
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Basic
extend self
@@ -99,14 +123,14 @@ module ActionController
def user_name_and_password(request)
decode_credentials(request).split(/:/, 2)
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
-
+
def decode_credentials(request)
ActiveSupport::Base64.decode64(authorization(request).split.last || '')
end
@@ -120,5 +144,131 @@ 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)
+ authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm)
+ end
+
+ # Authenticate with HTTP Digest, returns true or false
+ def authenticate_with_http_digest(realm = "Application", &password_procedure)
+ HttpAuthentication::Digest.authenticate(self, realm, &password_procedure)
+ 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
+ end
+
+ # Returns false on a valid response, true otherwise
+ def authenticate(controller, realm, &password_procedure)
+ authorization(controller.request) && validate_digest_response(controller.request, realm, &password_procedure)
+ 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(request, realm, &password_procedure)
+ credentials = decode_credentials_header(request)
+ valid_nonce = validate_nonce(request, credentials[:nonce])
+
+ if valid_nonce && realm == credentials[:realm] && opaque(request.session.session_id) == credentials[:opaque]
+ password = password_procedure.call(credentials[:username])
+ expected = expected_response(request.env['REQUEST_METHOD'], request.url, credentials, password)
+ expected == credentials[:response]
+ end
+ 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_header(request)
+ decode_credentials(authorization(request))
+ end
+
+ def decode_credentials(header)
+ header.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)
+ session_id = controller.request.session.session_id
+ controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce(session_id)}", opaque="#{opaque(session_id)}")
+ end
+
+ def authentication_request(controller, realm, message = nil)
+ message ||= "HTTP Digest: Access denied.\n"
+ 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(session_id, time = Time.now)
+ 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
+ nonce(request.session.session_id, t) == value && (t - Time.now.to_i).abs <= 10 * 60
+ end
+
+ # Opaque based on digest of session_id
+ def opaque(session_id)
+ Base64.encode64(::Digest::MD5::hexdigest(session_id)).gsub("\n", '')
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/base/layout.rb b/actionpack/lib/action_controller/base/layout.rb
index 926ae26f92..88a15aa6ca 100644
--- a/actionpack/lib/action_controller/base/layout.rb
+++ b/actionpack/lib/action_controller/base/layout.rb
@@ -188,7 +188,7 @@ module ActionController #:nodoc:
end
def layout_list #:nodoc:
- Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] }
+ Array(view_paths).sum([]) { |path| Dir["#{path.to_str}/layouts/**/*"] }
end
memoize :layout_list
diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
index c7992d7769..c1be264ffb 100644
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ b/actionpack/lib/action_controller/caching/sweeping.rb
@@ -87,9 +87,9 @@ module ActionController #:nodoc:
__send__(action_callback_method_name) if respond_to?(action_callback_method_name, true)
end
- def method_missing(method, *arguments)
+ def method_missing(method, *arguments, &block)
return if @controller.nil?
- @controller.__send__(method, *arguments)
+ @controller.__send__(method, *arguments, &block)
end
end
end
diff --git a/actionpack/lib/action_controller/routing/generation/url_rewriter.rb b/actionpack/lib/action_controller/routing/generation/url_rewriter.rb
index d86e2db67d..bb6cb437b7 100644
--- a/actionpack/lib/action_controller/routing/generation/url_rewriter.rb
+++ b/actionpack/lib/action_controller/routing/generation/url_rewriter.rb
@@ -92,15 +92,12 @@ module ActionController
# end
# end
module UrlWriter
- # The default options for urls written by this writer. Typically a <tt>:host</tt>
- # pair is provided.
- mattr_accessor :default_url_options
- self.default_url_options = {}
-
def self.included(base) #:nodoc:
ActionController::Routing::Routes.install_helpers(base)
base.mattr_accessor :default_url_options
- base.default_url_options ||= default_url_options
+
+ # The default options for urls written by this writer. Typically a <tt>:host</tt> pair is provided.
+ base.default_url_options ||= {}
end
# Generate a url based on the options provided, default_url_options and the
diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb
index 199ffb702c..38e37c7a18 100644
--- a/actionpack/lib/action_controller/testing/process.rb
+++ b/actionpack/lib/action_controller/testing/process.rb
@@ -15,7 +15,7 @@ module ActionController #:nodoc:
end
def reset_session
- @session = TestSession.new
+ @session.reset
end
# Wraps raw_post in a StringIO.
@@ -35,7 +35,6 @@ module ActionController #:nodoc:
def port=(number)
@env["SERVER_PORT"] = number.to_i
- port(true)
end
def action=(action_name)
@@ -49,8 +48,6 @@ module ActionController #:nodoc:
@env["REQUEST_URI"] = value
@request_uri = nil
@path = nil
- request_uri(true)
- path(true)
end
def request_uri=(uri)
@@ -58,9 +55,13 @@ module ActionController #:nodoc:
@path = uri.split("?").first
end
+ def request_method=(method)
+ @request_method = method
+ end
+
def accept=(mime_types)
@env["HTTP_ACCEPT"] = Array(mime_types).collect { |mime_types| mime_types.to_s }.join(",")
- accepts(true)
+ @accepts = nil
end
def if_modified_since=(last_modified)
@@ -76,11 +77,11 @@ module ActionController #:nodoc:
end
def request_uri(*args)
- @request_uri || super
+ @request_uri || super()
end
def path(*args)
- @path || super
+ @path || super()
end
def assign_parameters(controller_path, action, parameters)
@@ -107,7 +108,7 @@ module ActionController #:nodoc:
def recycle!
self.query_parameters = {}
self.path_parameters = {}
- unmemoize_all
+ @headers, @request_method, @accepts, @content_type = nil, nil, nil, nil
end
def user_agent=(user_agent)
@@ -279,38 +280,62 @@ module ActionController #:nodoc:
end
end
- class TestSession #:nodoc:
+ class TestSession < Hash #:nodoc:
attr_accessor :session_id
def initialize(attributes = nil)
- @session_id = ''
- @attributes = attributes.nil? ? nil : attributes.stringify_keys
- @saved_attributes = nil
+ reset_session_id
+ replace_attributes(attributes)
+ end
+
+ def reset
+ reset_session_id
+ replace_attributes({ })
end
def data
- @attributes ||= @saved_attributes || {}
+ to_hash
end
def [](key)
- data[key.to_s]
+ super(key.to_s)
end
def []=(key, value)
- data[key.to_s] = value
+ super(key.to_s, value)
end
- def update
- @saved_attributes = @attributes
+ def update(hash = nil)
+ if hash.nil?
+ ActiveSupport::Deprecation.warn('use replace instead', caller)
+ replace({})
+ else
+ super(hash)
+ end
end
- def delete
- @attributes = nil
+ def delete(key = nil)
+ if key.nil?
+ ActiveSupport::Deprecation.warn('use clear instead', caller)
+ clear
+ else
+ super(key.to_s)
+ end
end
def close
- update
- delete
+ ActiveSupport::Deprecation.warn('sessions should no longer be closed', caller)
+ end
+
+ private
+
+ def reset_session_id
+ @session_id = ''
+ end
+
+ def replace_attributes(attributes = nil)
+ attributes ||= {}
+ replace(attributes.stringify_keys)
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 0da7daacf2..366ac26421 100755
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -7,7 +7,6 @@ require 'action_controller/cgi_ext'
module ActionDispatch
class Request < Rack::Request
- extend ActiveSupport::Memoizable
%w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
@@ -35,7 +34,6 @@ module ActionDispatch
def request_method
HTTP_METHOD_LOOKUP[super] || raise(ActionController::UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
end
- memoize :request_method
# Returns the HTTP request \method used for action processing as a
# lowercase symbol, such as <tt>:post</tt>. (Unlike #request_method, this
@@ -77,7 +75,6 @@ module ActionDispatch
def headers
Http::Headers.new(@env)
end
- memoize :headers
# Returns the content length of the request as an integer.
def content_length
@@ -89,17 +86,19 @@ module ActionDispatch
# For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
def content_type
- if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
- Mime::Type.lookup($1.strip.downcase)
- else
- nil
+ @content_type ||= begin
+ if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
+ Mime::Type.lookup($1.strip.downcase)
+ else
+ nil
+ end
end
end
- memoize :content_type
# Returns the accepted MIME type for the request.
def accepts
- header = @env['HTTP_ACCEPT'].to_s.strip
+ @accepts ||= begin
+ header = @env['HTTP_ACCEPT'].to_s.strip
fallback = xhr? ? Mime::JS : Mime::HTML
@@ -113,14 +112,12 @@ module ActionDispatch
ret
end
end
- memoize :accepts
def if_modified_since
if since = env['HTTP_IF_MODIFIED_SINCE']
Time.rfc2822(since) rescue nil
end
end
- memoize :if_modified_since
def if_none_match
env['HTTP_IF_NONE_MATCH']
@@ -172,7 +169,7 @@ module ActionDispatch
def formats
@formats =
if ActionController::Base.use_accept_header
- ret = Array(Mime[parameters[:format]] || accepts)
+ Array(Mime[parameters[:format]] || accepts)
else
[format]
end
@@ -263,25 +260,21 @@ EOM
@env['REMOTE_ADDR']
end
- memoize :remote_ip
# Returns the lowercase name of the HTTP server software.
def server_software
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
end
- memoize :server_software
# Returns the complete URL used for this request.
def url
protocol + host_with_port + request_uri
end
- memoize :url
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
def protocol
ssl? ? 'https://' : 'http://'
end
- memoize :protocol
# Is this an SSL request?
def ssl?
@@ -301,14 +294,12 @@ EOM
def host
raw_host_with_port.sub(/:\d+$/, '')
end
- memoize :host
# Returns a \host:\port string for this request, such as "example.com" or
# "example.com:8080".
def host_with_port
"#{host}#{port_string}"
end
- memoize :host_with_port
# Returns the port number of this request as an integer.
def port
@@ -318,7 +309,6 @@ EOM
standard_port
end
end
- memoize :port
# Returns the standard \port number for this request's protocol.
def standard_port
@@ -356,7 +346,6 @@ EOM
def query_string
@env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].split('?', 2)[1] || '')
end
- memoize :query_string
# Returns the request URI, accounting for server idiosyncrasies.
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
@@ -382,7 +371,6 @@ EOM
end
end
end
- memoize :request_uri
# Returns the interpreted \path to requested resource after all the installation
# directory of this application was taken into account.
@@ -391,7 +379,6 @@ EOM
path.sub!(/\A#{ActionController::Base.relative_url_root}/, '')
path
end
- memoize :path
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
diff --git a/actionpack/lib/action_dispatch/middleware/rewindable_input.rb b/actionpack/lib/action_dispatch/middleware/rewindable_input.rb
index ac2194eead..725414efc4 100644
--- a/actionpack/lib/action_dispatch/middleware/rewindable_input.rb
+++ b/actionpack/lib/action_dispatch/middleware/rewindable_input.rb
@@ -3,12 +3,12 @@ module ActionDispatch
class RewindableIO < ActiveSupport::BasicObject
def initialize(io)
@io = io
- @rewindable = io.is_a?(StringIO)
+ @rewindable = io.is_a?(::StringIO)
end
def method_missing(method, *args, &block)
unless @rewindable
- @io = StringIO.new(@io.read)
+ @io = ::StringIO.new(@io.read)
@rewindable = true
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index e745997dda..879d98fbdb 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -102,8 +102,10 @@ module ActionDispatch
response = @app.call(env)
session_data = env[ENV_SESSION_KEY]
- if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?)
- options = env[ENV_SESSION_OPTIONS_KEY]
+ options = env[ENV_SESSION_OPTIONS_KEY]
+
+ if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
+ session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
if session_data.is_a?(AbstractStore::SessionHash)
sid = session_data.id
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 293fbca7cf..ec93f66a88 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -93,12 +93,14 @@ module ActionDispatch
status, headers, body = @app.call(env)
session_data = env[ENV_SESSION_KEY]
- if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?)
+ options = env[ENV_SESSION_OPTIONS_KEY]
+
+ if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
+ session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
session_data = marshal(session_data.to_hash)
raise CookieOverflow if session_data.size > MAX
- options = env[ENV_SESSION_OPTIONS_KEY]
cookie = Hash.new
cookie[:value] = session_data
unless options[:expire_after].nil?
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 80b8ea6a4c..2f7cfeb88e 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -227,7 +227,7 @@ module ActionView #:nodoc:
def view_paths=(paths)
@view_paths = self.class.process_view_paths(paths)
end
-
+
# Access the current template being rendered.
# Returns a ActionView::Template object.
def template
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 9ed50a9653..54c82cbd1d 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -277,6 +277,62 @@ module ActionView
end
end
+ # Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but
+ # wraps them with <tt><optgroup></tt> tags.
+ #
+ # Parameters:
+ # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
+ # <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
+ # nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
+ # Ex. ["North America",[["United States","US"],["Canada","CA"]]]
+ # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
+ # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
+ # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
+ # * +prompt+ - set to true or a prompt string. When the select element doesn’t have a value yet, this
+ # prepends an option with a generic prompt — "Please select" — or the given prompt string.
+ #
+ # Sample usage (Array):
+ # grouped_options = [
+ # ['North America',
+ # [['United States','US'],'Canada']],
+ # ['Europe',
+ # ['Denmark','Germany','France']]
+ # ]
+ # grouped_options_for_select(grouped_options)
+ #
+ # Sample usage (Hash):
+ # grouped_options = {
+ # 'North America' => [['United States','US], 'Canada'],
+ # 'Europe' => ['Denmark','Germany','France']
+ # }
+ # grouped_options_for_select(grouped_options)
+ #
+ # Possible output:
+ # <optgroup label="Europe">
+ # <option value="Denmark">Denmark</option>
+ # <option value="Germany">Germany</option>
+ # <option value="France">France</option>
+ # </optgroup>
+ # <optgroup label="North America">
+ # <option value="US">United States</option>
+ # <option value="Canada">Canada</option>
+ # </optgroup>
+ #
+ # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
+ # wrap the output in an appropriate <tt><select></tt> tag.
+ def grouped_options_for_select(grouped_options, selected_key = nil, prompt = nil)
+ body = ''
+ body << content_tag(:option, prompt, :value => "") if prompt
+
+ grouped_options = grouped_options.sort if grouped_options.is_a?(Hash)
+
+ grouped_options.each do |group|
+ body << content_tag(:optgroup, options_for_select(group[1], selected_key), :label => group[0])
+ end
+
+ body
+ end
+
# Returns a string of option tags for pretty much any time zone in the
# world. Supply a TimeZone name as +selected+ to have it marked as the
# selected option tag. You can also supply an array of TimeZone objects
@@ -349,8 +405,9 @@ module ActionView
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
value = value(object)
+ selected_value = options.has_key?(:selected) ? options[:selected] : value
content_tag(
- "select", add_options(options_from_collection_for_select(collection, value_method, text_method, value), options, value), html_options
+ "select", add_options(options_from_collection_for_select(collection, value_method, text_method, selected_value), options, value), html_options
)
end
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 3e734ccaab..e622f97b9e 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -220,6 +220,8 @@ module ActionView
end
end
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
+
# Formats the bytes in +size+ into a more understandable representation
# (e.g., giving it 1500 yields 1.5 KB). This method is useful for
# reporting file sizes to users. This method returns nil if
@@ -247,7 +249,7 @@ module ActionView
# number_to_human_size(1234567, 2) # => 1.18 MB
# number_to_human_size(483989, 0) # => 473 KB
def number_to_human_size(number, *args)
- return number.nil? ? nil : pluralize(number.to_i, "Byte") if number.to_i < 1024
+ return nil if number.nil?
options = args.extract_options!
options.symbolize_keys!
@@ -255,7 +257,6 @@ module ActionView
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
human = I18n.translate(:'number.human.format', :locale => options[:locale], :raise => true) rescue {}
defaults = defaults.merge(human)
- storage_units = I18n.translate(:'number.human.storage_units', :locale => options[:locale], :raise => true)
unless args.empty?
ActiveSupport::Deprecation.warn('number_to_human_size takes an option hash ' +
@@ -267,22 +268,32 @@ module ActionView
separator ||= (options[:separator] || defaults[:separator])
delimiter ||= (options[:delimiter] || defaults[:delimiter])
- max_exp = storage_units.size - 1
- number = Float(number)
- exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024
- exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
- number /= 1024 ** exponent
- unit = storage_units[exponent]
+ storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
- begin
- escaped_separator = Regexp.escape(separator)
- number_with_precision(number,
- :precision => precision,
- :separator => separator,
- :delimiter => delimiter
- ).sub(/(\d)(#{escaped_separator}[1-9]*)?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') + " #{unit}"
- rescue
- number
+ if number.to_i < 1024
+ unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
+ storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
+ else
+ max_exp = STORAGE_UNITS.size - 1
+ number = Float(number)
+ exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024
+ exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
+ number /= 1024 ** exponent
+
+ unit_key = STORAGE_UNITS[exponent]
+ unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
+
+ begin
+ escaped_separator = Regexp.escape(separator)
+ formatted_number = number_with_precision(number,
+ :precision => precision,
+ :separator => separator,
+ :delimiter => delimiter
+ ).sub(/(\d)(#{escaped_separator}[1-9]*)?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
+ storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
+ rescue
+ number
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 1d9e4fe9b8..b1eb6891fa 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -107,7 +107,7 @@ module ActionView
text
else
match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
- text.gsub(/(#{match})/i, options[:highlighter])
+ text.gsub(/(#{match})(?!(?:[^<]*?)?(?:["'])[^<>]*>)/i, options[:highlighter])
end
end
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index a880fd83ef..afe35691bc 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -44,7 +44,18 @@
# separator:
delimiter: ""
precision: 1
- storage_units: [Bytes, KB, MB, GB, TB]
+ storage_units:
+ # Storage units output formatting.
+ # %u is the storage unit, %n is the number (default: 2 MB)
+ format: "%n %u"
+ units:
+ byte:
+ one: "Byte"
+ other: "Bytes"
+ kb: "KB"
+ mb: "MB"
+ gb: "GB"
+ tb: "TB"
# Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()
datetime:
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
index 4c3a226ac6..d88296daa6 100644
--- a/actionpack/lib/action_view/paths.rb
+++ b/actionpack/lib/action_view/paths.rb
@@ -51,6 +51,9 @@ module ActionView #:nodoc:
each do |load_path|
if template = load_path.find_by_parts(template_path, format)
return template
+ # Try to find html version if the format is javascript
+ elsif format == :js && template = load_path["#{template_path}.html"]
+ return template
end
end
diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb
index 235a95a0f3..4b4b80d48c 100644
--- a/actionpack/lib/action_view/template/template.rb
+++ b/actionpack/lib/action_view/template/template.rb
@@ -111,13 +111,14 @@ module ActionView #:nodoc:
@@exempt_from_layout.merge(regexps)
end
- attr_accessor :filename, :load_path, :base_path, :name, :format, :extension
+ attr_accessor :filename, :load_path, :base_path
+ attr_accessor :locale, :name, :format, :extension
delegate :to_s, :to => :path
def initialize(template_path, load_paths = [])
template_path = template_path.dup
@load_path, @filename = find_full_path(template_path, load_paths)
- @base_path, @name, @format, @extension = split(template_path)
+ @base_path, @name, @locale, @format, @extension = split(template_path)
@base_path.to_s.gsub!(/\/$/, '') # Push to split method
# Extend with partial super powers
@@ -155,17 +156,17 @@ module ActionView #:nodoc:
memoize :mime_type
def path
- [base_path, [name, format, extension].compact.join('.')].compact.join('/')
+ [base_path, [name, locale, format, extension].compact.join('.')].compact.join('/')
end
memoize :path
def path_without_extension
- [base_path, [name, format].compact.join('.')].compact.join('/')
+ [base_path, [name, locale, format].compact.join('.')].compact.join('/')
end
memoize :path_without_extension
def path_without_format_and_extension
- [base_path, name].compact.join('/')
+ [base_path, [name, locale].compact.join('.')].compact.join('/')
end
memoize :path_without_format_and_extension
@@ -213,6 +214,10 @@ module ActionView #:nodoc:
!Template.registered_template_handler(extension).nil?
end
+ def valid_locale?(locale)
+ I18n.available_locales.include?(locale.to_sym)
+ end
+
def find_full_path(path, load_paths)
load_paths = Array(load_paths) + [nil]
load_paths.each do |load_path|
@@ -223,19 +228,42 @@ module ActionView #:nodoc:
end
# Returns file split into an array
- # [base_path, name, format, extension]
+ # [base_path, name, locale, format, extension]
def split(file)
- if m = file.match(/^(.*\/)?([^\.]+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/)
- if valid_extension?(m[5]) # Multipart formats
- [m[1], m[2], "#{m[3]}.#{m[4]}", m[5]]
- elsif valid_extension?(m[4]) # Single format
- [m[1], m[2], m[3], m[4]]
- elsif valid_extension?(m[3]) # No format
- [m[1], m[2], nil, m[3]]
+ if m = file.match(/^(.*\/)?([^\.]+)\.(.*)$/)
+ base_path = m[1]
+ name = m[2]
+ extensions = m[3]
+ else
+ return
+ end
+
+ locale = nil
+ format = nil
+ extension = nil
+
+ if m = extensions.match(/^(\w+)?\.?(\w+)?\.?(\w+)?\.?/)
+ if valid_locale?(m[1]) && m[2] && valid_extension?(m[3]) # All three
+ locale = m[1]
+ format = m[2]
+ extension = m[3]
+ elsif m[1] && m[2] && valid_extension?(m[3]) # Multipart formats
+ format = "#{m[1]}.#{m[2]}"
+ extension = m[3]
+ elsif valid_locale?(m[1]) && valid_extension?(m[2]) # locale and extension
+ locale = m[1]
+ extension = m[2]
+ elsif valid_extension?(m[2]) # format and extension
+ format = m[1]
+ extension = m[2]
+ elsif valid_extension?(m[1]) # Just extension
+ extension = m[1]
else # No extension
- [m[1], m[2], m[3], nil]
+ format = m[1]
end
end
+
+ [base_path, name, locale, format, extension]
end
end
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 67c51f01bd..12b60ddf3c 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -31,6 +31,10 @@ ActionController::Routing::Routes.reload rescue nil
ActionController::Base.session_store = nil
+# Register danish language for testing
+I18n.backend.store_translations 'da', {}
+ORIGINAL_LOCALES = I18n.available_locales
+
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
ActionController::Base.view_paths = FIXTURE_LOAD_PATH
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index 08a25bfdb8..fbc94a0df7 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -1,35 +1,35 @@
require 'abstract_unit'
-class DummyController < ActionController::Base
- before_filter :authenticate, :only => :index
- before_filter :authenticate_with_request, :only => :display
+class HttpBasicAuthenticationTest < ActionController::TestCase
+ class DummyController < ActionController::Base
+ before_filter :authenticate, :only => :index
+ before_filter :authenticate_with_request, :only => :display
- def index
- render :text => "Hello Secret"
- end
+ def index
+ render :text => "Hello Secret"
+ end
- def display
- render :text => 'Definitely Maybe'
- end
+ def display
+ render :text => 'Definitely Maybe'
+ end
- private
+ private
- def authenticate
- authenticate_or_request_with_http_basic do |username, password|
- username == 'lifo' && password == 'world'
+ def authenticate
+ authenticate_or_request_with_http_basic do |username, password|
+ username == 'lifo' && password == 'world'
+ end
end
- end
- def authenticate_with_request
- if authenticate_with_http_basic { |username, password| username == 'pretty' && password == 'please' }
- @logged_in = true
- else
- request_http_basic_authentication("SuperSecret")
+ def authenticate_with_request
+ if authenticate_with_http_basic { |username, password| username == 'pretty' && password == 'please' }
+ @logged_in = true
+ else
+ request_http_basic_authentication("SuperSecret")
+ end
end
end
-end
-class HttpBasicAuthenticationTest < ActionController::TestCase
AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION']
tests DummyController
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..59f7a403b5
--- /dev/null
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -0,0 +1,130 @@
+require 'abstract_unit'
+
+class HttpDigestAuthenticationTest < ActionController::TestCase
+ class DummyDigestController < ActionController::Base
+ before_filter :authenticate, :only => :index
+ before_filter :authenticate_with_request, :only => :display
+
+ USERS = { 'lifo' => 'world', 'pretty' => 'please' }
+
+ def index
+ render :text => "Hello Secret"
+ end
+
+ def display
+ render :text => 'Definitely Maybe'
+ end
+
+ private
+
+ def authenticate
+ authenticate_or_request_with_http_digest("SuperSecret") do |username|
+ # Return the password
+ USERS[username]
+ end
+ end
+
+ def authenticate_with_request
+ if authenticate_with_http_digest("SuperSecret") { |username| USERS[username] }
+ @logged_in = true
+ else
+ request_http_digest_authentication("SuperSecret", "Authentication Failed")
+ end
+ end
+ end
+
+ AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION']
+
+ tests DummyDigestController
+
+ AUTH_HEADERS.each do |header|
+ test "successful authentication with #{header.downcase}" do
+ @request.env[header] = encode_credentials(:username => 'lifo', :password => 'world')
+ get :index
+
+ assert_response :success
+ assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}"
+ end
+ end
+
+ AUTH_HEADERS.each do |header|
+ test "unsuccessful authentication with #{header.downcase}" do
+ @request.env[header] = encode_credentials(:username => 'h4x0r', :password => 'world')
+ get :index
+
+ assert_response :unauthorized
+ assert_equal "HTTP Digest: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}"
+ end
+ end
+
+ test "authentication request without credential" do
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "Authentication Failed", @response.body
+ credentials = decode_credentials(@response.headers['WWW-Authenticate'])
+ assert_equal 'SuperSecret', credentials[:realm]
+ end
+
+ test "authentication request with invalid password" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo')
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "Authentication Failed", @response.body
+ end
+
+ test "authentication request with invalid nonce" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please', :nonce => "xxyyzz")
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "Authentication Failed", @response.body
+ end
+
+ test "authentication request with invalid opaque" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo', :opaque => "xxyyzz")
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "Authentication Failed", @response.body
+ end
+
+ test "authentication request with invalid realm" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo', :realm => "NotSecret")
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "Authentication Failed", @response.body
+ end
+
+ test "authentication request with valid credential" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ private
+
+ def encode_credentials(options)
+ options.reverse_merge!(:nc => "00000001", :cnonce => "0a4f113b")
+ password = options.delete(:password)
+
+ # Perform unautheticated get to retrieve digest parameters to use on subsequent request
+ get :index
+
+ assert_response :unauthorized
+
+ credentials = decode_credentials(@response.headers['WWW-Authenticate'])
+ credentials.merge!(options)
+ credentials.merge!(:uri => "http://#{@request.host}#{@request.env['REQUEST_URI']}")
+ ActionController::HttpAuthentication::Digest.encode_credentials("GET", credentials, password)
+ end
+
+ def decode_credentials(header)
+ ActionController::HttpAuthentication::Digest.decode_credentials(@response.headers['WWW-Authenticate'])
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index a339bb524c..72b33629ff 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -274,6 +274,9 @@ class TestController < ActionController::Base
def render_explicit_html_template
end
+ def render_implicit_html_template_from_xhr_request
+ end
+
def formatted_html_erb
end
@@ -1010,6 +1013,11 @@ class RenderTest < ActionController::TestCase
end
end
+ def test_should_implicitly_render_html_template_from_xhr_request
+ get :render_implicit_html_template_from_xhr_request, :format => :js
+ assert_equal "Hello HTML!", @response.body
+ end
+
def test_should_render_formatted_template
get :formatted_html_erb
assert_equal 'formatted html erb', @response.body
diff --git a/actionpack/test/controller/session/cookie_store_test.rb b/actionpack/test/controller/session/cookie_store_test.rb
index d77be31c9a..3a1a9854c3 100644
--- a/actionpack/test/controller/session/cookie_store_test.rb
+++ b/actionpack/test/controller/session/cookie_store_test.rb
@@ -11,8 +11,7 @@ class CookieStoreTest < ActionController::IntegrationTest
Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, 'SHA1')
- SignedBar = "BAh7BjoIZm9vIghiYXI%3D--" +
- "fef868465920f415f2c0652d6910d3af288a0367"
+ SignedBar = "BAh7BjoIZm9vIghiYXI%3D--fef868465920f415f2c0652d6910d3af288a0367"
class TestController < ActionController::Base
def no_session_access
@@ -177,6 +176,36 @@ class CookieStoreTest < ActionController::IntegrationTest
end
end
+ def test_session_store_with_expire_after
+ app = ActionController::Session::CookieStore.new(DispatcherApp, :key => SessionKey, :secret => SessionSecret, :expire_after => 5.hours)
+ @integration_session = open_session(app)
+
+ with_test_route_set do
+ # First request accesses the session
+ time = Time.local(2008, 4, 24)
+ Time.stubs(:now).returns(time)
+ expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT")
+
+ cookies[SessionKey] = SignedBar
+
+ get '/set_session_value'
+ assert_response :success
+
+ cookie_body = response.body
+ assert_equal ["_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; httponly"], headers['Set-Cookie']
+
+ # Second request does not access the session
+ time = Time.local(2008, 4, 25)
+ Time.stubs(:now).returns(time)
+ expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT")
+
+ get '/no_session_access'
+ assert_response :success
+
+ assert_equal ["_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; httponly"], headers['Set-Cookie']
+ end
+ end
+
private
def with_test_route_set
with_routing do |set|
diff --git a/actionpack/test/controller/session/test_session_test.rb b/actionpack/test/controller/session/test_session_test.rb
new file mode 100644
index 0000000000..83103be3ec
--- /dev/null
+++ b/actionpack/test/controller/session/test_session_test.rb
@@ -0,0 +1,58 @@
+require 'abstract_unit'
+require 'stringio'
+
+class ActionController::TestSessionTest < ActiveSupport::TestCase
+
+ def test_calling_delete_without_parameters_raises_deprecation_warning_and_calls_to_clear_test_session
+ assert_deprecated(/use clear instead/){ ActionController::TestSession.new.delete }
+ end
+
+ def test_calling_update_without_parameters_raises_deprecation_warning_and_calls_to_clear_test_session
+ assert_deprecated(/use replace instead/){ ActionController::TestSession.new.update }
+ end
+
+ def test_calling_close_raises_deprecation_warning
+ assert_deprecated(/sessions should no longer be closed/){ ActionController::TestSession.new.close }
+ end
+
+ def test_defaults
+ session = ActionController::TestSession.new
+ assert_equal({}, session.data)
+ assert_equal('', session.session_id)
+ end
+
+ def test_ctor_allows_setting
+ session = ActionController::TestSession.new({:one => 'one', :two => 'two'})
+ assert_equal('one', session[:one])
+ assert_equal('two', session[:two])
+ end
+
+ def test_setting_session_item_sets_item
+ session = ActionController::TestSession.new
+ session[:key] = 'value'
+ assert_equal('value', session[:key])
+ end
+
+ def test_calling_delete_removes item
+ session = ActionController::TestSession.new
+ session[:key] = 'value'
+ assert_equal('value', session[:key])
+ session.delete(:key)
+ assert_nil(session[:key])
+ end
+
+ def test_calling_update_with_params_passes_to_attributes
+ session = ActionController::TestSession.new()
+ session.update('key' => 'value')
+ assert_equal('value', session[:key])
+ end
+
+ def test_clear_emptys_session
+ params = {:one => 'one', :two => 'two'}
+ session = ActionController::TestSession.new({:one => 'one', :two => 'two'})
+ session.clear
+ assert_nil(session[:one])
+ assert_nil(session[:two])
+ end
+
+end \ No newline at end of file
diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb
index 5708a1768f..d378188b43 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_test.rb
@@ -23,6 +23,11 @@ class TestTest < ActionController::TestCase
render :text => 'Success'
end
+ def reset_the_session
+ reset_session
+ render :text => 'ignore me'
+ end
+
def render_raw_post
raise ActiveSupport::TestCase::Assertion, "#raw_post is blank" if request.raw_post.blank?
render :text => request.raw_post
@@ -171,6 +176,24 @@ XML
assert_equal 'value2', session[:symbol]
end
+ def test_session_is_cleared_from_controller_after_reset_session
+ process :set_session
+ process :reset_the_session
+ assert_equal Hash.new, @controller.session.to_hash
+ end
+
+ def test_session_is_cleared_from_response_after_reset_session
+ process :set_session
+ process :reset_the_session
+ assert_equal Hash.new, @response.session.to_hash
+ end
+
+ def test_session_is_cleared_from_request_after_reset_session
+ process :set_session
+ process :reset_the_session
+ assert_equal Hash.new, @request.session.to_hash
+ end
+
def test_process_with_request_uri_with_no_params
process :test_uri
assert_equal "/test_test/test/test_uri", @response.body
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index e9d372544e..09a8356fec 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -303,7 +303,6 @@ class UrlWriterTests < ActionController::TestCase
def test_named_routes_with_nil_keys
ActionController::Routing::Routes.clear!
- add_host!
ActionController::Routing::Routes.draw do |map|
map.main '', :controller => 'posts'
map.resources :posts
@@ -311,6 +310,8 @@ class UrlWriterTests < ActionController::TestCase
end
# We need to create a new class in order to install the new named route.
kls = Class.new { include ActionController::UrlWriter }
+ kls.default_url_options[:host] = 'www.basecamphq.com'
+
controller = kls.new
params = {:action => :index, :controller => :posts, :format => :xml}
assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params))
@@ -337,6 +338,20 @@ class UrlWriterTests < ActionController::TestCase
ensure
ActionController::Routing::Routes.load!
end
+
+ def test_multiple_includes_maintain_distinct_options
+ first_class = Class.new { include ActionController::UrlWriter }
+ second_class = Class.new { include ActionController::UrlWriter }
+
+ first_host, second_host = 'firsthost.com', 'secondhost.com'
+
+ first_class.default_url_options[:host] = first_host
+ second_class.default_url_options[:host] = second_host
+
+ assert_equal first_class.default_url_options[:host], first_host
+ assert_equal second_class.default_url_options[:host], second_host
+ end
+
private
def extract_params(url)
url.split('?', 2).last.split('&')
diff --git a/actionpack/test/dispatch/rack_test.rb b/actionpack/test/dispatch/rack_test.rb
index df465eba91..a9a9f815da 100644
--- a/actionpack/test/dispatch/rack_test.rb
+++ b/actionpack/test/dispatch/rack_test.rb
@@ -68,55 +68,55 @@ class RackRequestTest < BaseRackTest
test "http host" do
@env.delete "HTTP_X_FORWARDED_HOST"
@env['HTTP_HOST'] = "rubyonrails.org:8080"
- assert_equal "rubyonrails.org", @request.host(true)
- assert_equal "rubyonrails.org:8080", @request.host_with_port(true)
+ assert_equal "rubyonrails.org", @request.host
+ assert_equal "rubyonrails.org:8080", @request.host_with_port
@env['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
- assert_equal "www.secondhost.org", @request.host(true)
+ assert_equal "www.secondhost.org", @request.host
end
test "http host with default port overrides server port" do
@env.delete "HTTP_X_FORWARDED_HOST"
@env['HTTP_HOST'] = "rubyonrails.org"
- assert_equal "rubyonrails.org", @request.host_with_port(true)
+ assert_equal "rubyonrails.org", @request.host_with_port
end
test "host with port defaults to server name if no host headers" do
@env.delete "HTTP_X_FORWARDED_HOST"
@env.delete "HTTP_HOST"
- assert_equal "glu.ttono.us:8007", @request.host_with_port(true)
+ assert_equal "glu.ttono.us:8007", @request.host_with_port
end
test "host with port falls back to server addr if necessary" do
@env.delete "HTTP_X_FORWARDED_HOST"
@env.delete "HTTP_HOST"
@env.delete "SERVER_NAME"
- assert_equal "207.7.108.53", @request.host(true)
- assert_equal 8007, @request.port(true)
- assert_equal "207.7.108.53:8007", @request.host_with_port(true)
+ assert_equal "207.7.108.53", @request.host
+ assert_equal 8007, @request.port
+ assert_equal "207.7.108.53:8007", @request.host_with_port
end
test "host with port if http standard port is specified" do
@env['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:80"
- assert_equal "glu.ttono.us", @request.host_with_port(true)
+ assert_equal "glu.ttono.us", @request.host_with_port
end
test "host with port if https standard port is specified" do
@env['HTTP_X_FORWARDED_PROTO'] = "https"
@env['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:443"
- assert_equal "glu.ttono.us", @request.host_with_port(true)
+ assert_equal "glu.ttono.us", @request.host_with_port
end
test "host if ipv6 reference" do
@env.delete "HTTP_X_FORWARDED_HOST"
@env['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]"
- assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host(true)
+ assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
end
test "host if ipv6 reference with port" do
@env.delete "HTTP_X_FORWARDED_HOST"
@env['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]:8008"
- assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host(true)
+ assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
end
test "cgi environment variables" do
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 6262763b81..60f71f03ce 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -14,53 +14,53 @@ class RequestTest < ActiveSupport::TestCase
assert_equal '0.0.0.0', @request.remote_ip
@request.remote_addr = '1.2.3.4'
- assert_equal '1.2.3.4', @request.remote_ip(true)
+ assert_equal '1.2.3.4', @request.remote_ip
@request.remote_addr = '1.2.3.4,3.4.5.6'
- assert_equal '1.2.3.4', @request.remote_ip(true)
+ assert_equal '1.2.3.4', @request.remote_ip
@request.env['HTTP_CLIENT_IP'] = '2.3.4.5'
- assert_equal '1.2.3.4', @request.remote_ip(true)
+ assert_equal '1.2.3.4', @request.remote_ip
@request.remote_addr = '192.168.0.1'
- assert_equal '2.3.4.5', @request.remote_ip(true)
+ assert_equal '2.3.4.5', @request.remote_ip
@request.env.delete 'HTTP_CLIENT_IP'
@request.remote_addr = '1.2.3.4'
@request.env['HTTP_X_FORWARDED_FOR'] = '3.4.5.6'
- assert_equal '1.2.3.4', @request.remote_ip(true)
+ assert_equal '1.2.3.4', @request.remote_ip
@request.remote_addr = '127.0.0.1'
@request.env['HTTP_X_FORWARDED_FOR'] = '3.4.5.6'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,3.4.5.6'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = '172.16.0.1,3.4.5.6'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = '192.168.0.1,3.4.5.6'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = '10.0.0.1,3.4.5.6'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = '10.0.0.1, 10.0.0.1, 3.4.5.6'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,3.4.5.6'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,192.168.0.1'
- assert_equal 'unknown', @request.remote_ip(true)
+ assert_equal 'unknown', @request.remote_ip
@request.env['HTTP_X_FORWARDED_FOR'] = '9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4'
- assert_equal '3.4.5.6', @request.remote_ip(true)
+ assert_equal '3.4.5.6', @request.remote_ip
@request.env['HTTP_CLIENT_IP'] = '8.8.8.8'
e = assert_raises(ActionController::ActionControllerError) {
- @request.remote_ip(true)
+ @request.remote_ip
}
assert_match /IP spoofing attack/, e.message
assert_match /HTTP_X_FORWARDED_FOR="9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4"/, e.message
@@ -72,11 +72,11 @@ class RequestTest < ActiveSupport::TestCase
# leap of faith to assume that their proxies are ever going to set the
# HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly.
ActionController::Base.ip_spoofing_check = false
- assert_equal('8.8.8.8', @request.remote_ip(true))
+ assert_equal('8.8.8.8', @request.remote_ip)
ActionController::Base.ip_spoofing_check = true
@request.env['HTTP_X_FORWARDED_FOR'] = '8.8.8.8, 9.9.9.9'
- assert_equal '8.8.8.8', @request.remote_ip(true)
+ assert_equal '8.8.8.8', @request.remote_ip
@request.env.delete 'HTTP_CLIENT_IP'
@request.env.delete 'HTTP_X_FORWARDED_FOR'
@@ -189,8 +189,8 @@ class RequestTest < ActiveSupport::TestCase
@request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1"
@request.env['SCRIPT_NAME'] = "/path/dispatch.rb"
@request.set_REQUEST_URI nil
- assert_equal "/path/of/some/uri?mapped=1", @request.request_uri(true)
- assert_equal "/of/some/uri", @request.path(true)
+ assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
+ assert_equal "/of/some/uri", @request.path
ActionController::Base.relative_url_root = nil
@request.env['PATH_INFO'] = "/path/of/some/uri"
@@ -225,12 +225,12 @@ class RequestTest < ActiveSupport::TestCase
@request.set_REQUEST_URI '/hieraki/dispatch.cgi'
ActionController::Base.relative_url_root = '/hieraki'
- assert_equal "/dispatch.cgi", @request.path(true)
+ assert_equal "/dispatch.cgi", @request.path
ActionController::Base.relative_url_root = nil
@request.set_REQUEST_URI '/hieraki/dispatch.cgi'
ActionController::Base.relative_url_root = '/foo'
- assert_equal "/hieraki/dispatch.cgi", @request.path(true)
+ assert_equal "/hieraki/dispatch.cgi", @request.path
ActionController::Base.relative_url_root = nil
# This test ensures that Rails uses REQUEST_URI over PATH_INFO
@@ -238,8 +238,8 @@ class RequestTest < ActiveSupport::TestCase
@request.env['REQUEST_URI'] = "/some/path"
@request.env['PATH_INFO'] = "/another/path"
@request.env['SCRIPT_NAME'] = "/dispatch.cgi"
- assert_equal "/some/path", @request.request_uri(true)
- assert_equal "/some/path", @request.path(true)
+ assert_equal "/some/path", @request.request_uri
+ assert_equal "/some/path", @request.path
end
test "host with default port" do
@@ -258,10 +258,10 @@ class RequestTest < ActiveSupport::TestCase
assert_equal nil, @request.server_software(true)
@request.env['SERVER_SOFTWARE'] = 'Apache3.422'
- assert_equal 'apache', @request.server_software(true)
+ assert_equal 'apache', @request.server_software
@request.env['SERVER_SOFTWARE'] = 'lighttpd(1.1.4)'
- assert_equal 'lighttpd', @request.server_software(true)
+ assert_equal 'lighttpd', @request.server_software
end
test "xml http request" do
@@ -299,13 +299,13 @@ class RequestTest < ActiveSupport::TestCase
test "invalid http method raises exception" do
assert_raises(ActionController::UnknownHttpMethod) do
self.request_method = :random_method
+ @request.request_method
end
end
test "allow method hacking on post" do
[:get, :head, :options, :put, :post, :delete].each do |method|
self.request_method = method
- @request.request_method(true)
assert_equal(method == :head ? :get : method, @request.method)
end
end
@@ -313,7 +313,7 @@ class RequestTest < ActiveSupport::TestCase
test "invalid method hacking on post raises exception" do
assert_raises(ActionController::UnknownHttpMethod) do
self.request_method = :_random_method
- @request.request_method(true)
+ @request.request_method
end
end
@@ -404,6 +404,6 @@ class RequestTest < ActiveSupport::TestCase
protected
def request_method=(method)
@request.env['REQUEST_METHOD'] = method.to_s.upcase
- @request.request_method(true)
+ @request.request_method = nil # Reset the ivar cache
end
end
diff --git a/actionpack/test/fixtures/replies.yml b/actionpack/test/fixtures/replies.yml
index a17d2fc42b..66020b706a 100644
--- a/actionpack/test/fixtures/replies.yml
+++ b/actionpack/test/fixtures/replies.yml
@@ -12,4 +12,4 @@ another:
developer_id: 1
content: Nuh uh!
created_at: <%= 1.hour.ago.to_s(:db) %>
- updated_at: nil \ No newline at end of file
+ updated_at: nil
diff --git a/actionpack/test/fixtures/test/hello_world.da.html.erb b/actionpack/test/fixtures/test/hello_world.da.html.erb
new file mode 100644
index 0000000000..10ec443291
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello_world.da.html.erb
@@ -0,0 +1 @@
+Hey verden \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb b/actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb
new file mode 100644
index 0000000000..4a11845cfe
--- /dev/null
+++ b/actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb
@@ -0,0 +1 @@
+Hello HTML! \ No newline at end of file
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 86a0bb6a79..86b321e6e5 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -143,6 +143,32 @@ uses_mocha "FormOptionsHelperTest" do
)
end
+ def test_grouped_options_for_select_with_array
+ assert_dom_equal(
+ "<optgroup label=\"North America\"><option value=\"US\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"Europe\"><option value=\"GB\">Great Britain</option>\n<option value=\"Germany\">Germany</option></optgroup>",
+ grouped_options_for_select([
+ ["North America",
+ [['United States','US'],"Canada"]],
+ ["Europe",
+ [["Great Britain","GB"], "Germany"]]
+ ])
+ )
+ end
+
+ def test_grouped_options_for_select_with_selected_and_prompt
+ assert_dom_equal(
+ "<option value=\"\">Choose a product...</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
+ grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", "Choose a product...")
+ )
+ end
+
+ def test_optgroups_with_with_options_with_hash
+ assert_dom_equal(
+ "<optgroup label=\"Europe\"><option value=\"Denmark\">Denmark</option>\n<option value=\"Germany\">Germany</option></optgroup><optgroup label=\"North America\"><option value=\"United States\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup>",
+ grouped_options_for_select({'North America' => ['United States','Canada'], 'Europe' => ['Denmark','Germany']})
+ )
+ end
+
def test_time_zone_options_no_parms
opts = time_zone_options_for_select
assert_dom_equal "<option value=\"A\">A</option>\n" +
@@ -473,6 +499,22 @@ uses_mocha "FormOptionsHelperTest" do
assert_dom_equal expected, collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true, :name => 'post[author_name][]' }, :multiple => true)
end
+ def test_collection_select_with_blank_and_selected
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ @post = Post.new
+ @post.author_name = "Babe"
+
+ assert_dom_equal(
+ %{<select id="post_author_name" name="post[author_name]"><option value=""></option>\n<option value="&lt;Abe&gt;" selected="selected">&lt;Abe&gt;</option>\n<option value="Babe">Babe</option>\n<option value="Cabe">Cabe</option></select>},
+ collection_select("post", "author_name", @posts, "author_name", "author_name", {:include_blank => true, :selected => "<Abe>"})
+ )
+ end
+
def test_time_zone_select
@firm = Firm.new("D")
html = time_zone_select( "firm", "time_zone" )
diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb
index 2528bead36..3fdf991a44 100644
--- a/actionpack/test/template/number_helper_i18n_test.rb
+++ b/actionpack/test/template/number_helper_i18n_test.rb
@@ -10,7 +10,9 @@ class NumberHelperI18nTests < Test::Unit::TestCase
@number_defaults = { :precision => 3, :delimiter => ',', :separator => '.' }
@currency_defaults = { :unit => '$', :format => '%u%n', :precision => 2 }
@human_defaults = { :precision => 1 }
- @human_storage_units_defaults = %w(Bytes KB MB GB TB)
+ @human_storage_units_format_default = "%n %u"
+ @human_storage_units_units_byte_other = "Bytes"
+ @human_storage_units_units_kb_other = "KB"
@percentage_defaults = { :delimiter => '' }
@precision_defaults = { :delimiter => '' }
@@ -48,10 +50,22 @@ class NumberHelperI18nTests < Test::Unit::TestCase
I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults)
I18n.expects(:translate).with(:'number.human.format', :locale => 'en',
:raise => true).returns(@human_defaults)
- I18n.expects(:translate).with(:'number.human.storage_units', :locale => 'en',
- :raise => true).returns(@human_storage_units_defaults)
- # can't be called with 1 because this directly returns without calling I18n.translate
- number_to_human_size(1025, :locale => 'en')
+ I18n.expects(:translate).with(:'number.human.storage_units.format', :locale => 'en',
+ :raise => true).returns(@human_storage_units_format_default)
+ I18n.expects(:translate).with(:'number.human.storage_units.units.kb', :locale => 'en', :count => 2,
+ :raise => true).returns(@human_storage_units_units_kb_other)
+ # 2KB
+ number_to_human_size(2048, :locale => 'en')
+
+ I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults)
+ I18n.expects(:translate).with(:'number.human.format', :locale => 'en',
+ :raise => true).returns(@human_defaults)
+ I18n.expects(:translate).with(:'number.human.storage_units.format', :locale => 'en',
+ :raise => true).returns(@human_storage_units_format_default)
+ I18n.expects(:translate).with(:'number.human.storage_units.units.byte', :locale => 'en', :count => 42,
+ :raise => true).returns(@human_storage_units_units_byte_other)
+ # 42 Bytes
+ number_to_human_size(42, :locale => 'en')
end
end
end
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 47348ebdb8..83de0815f4 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -5,6 +5,13 @@ module RenderTestCases
def setup_view(paths)
@assigns = { :secret => 'in the sauce' }
@view = ActionView::Base.new(paths, @assigns)
+
+ # Reload and register danish language for testing
+ I18n.reload!
+ I18n.backend.store_translations 'da', {}
+
+ # Ensure original are still the same since we are reindexing view paths
+ assert_equal ORIGINAL_LOCALES, I18n.available_locales
end
def test_render_file
@@ -19,6 +26,14 @@ module RenderTestCases
assert_equal "Hello world!", @view.render(:file => "test/hello_world")
end
+ def test_render_file_with_localization
+ old_locale = I18n.locale
+ I18n.locale = :da
+ assert_equal "Hey verden", @view.render(:file => "test/hello_world")
+ ensure
+ I18n.locale = old_locale
+ end
+
def test_render_file_at_top_level
assert_equal 'Elastica', @view.render(:file => '/shared')
end
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index a6200fbdd7..564845779f 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -122,6 +122,29 @@ class TextHelperTest < ActionView::TestCase
)
end
+ def test_highlight_with_html
+ assert_equal(
+ "<p>This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ highlight("<p>This is a beautiful morning, but also a beautiful day</p>", "beautiful")
+ )
+ assert_equal(
+ "<p>This is a <em><strong class=\"highlight\">beautiful</strong></em> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ highlight("<p>This is a <em>beautiful</em> morning, but also a beautiful day</p>", "beautiful")
+ )
+ assert_equal(
+ "<p>This is a <em class=\"error\"><strong class=\"highlight\">beautiful</strong></em> morning, but also a <strong class=\"highlight\">beautiful</strong> <span class=\"last\">day</span></p>",
+ highlight("<p>This is a <em class=\"error\">beautiful</em> morning, but also a beautiful <span class=\"last\">day</span></p>", "beautiful")
+ )
+ assert_equal(
+ "<p class=\"beautiful\">This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ highlight("<p class=\"beautiful\">This is a beautiful morning, but also a beautiful day</p>", "beautiful")
+ )
+ assert_equal(
+ "<p>This is a <strong class=\"highlight\">beautiful</strong> <a href=\"http://example.com/beautiful\#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ highlight("<p>This is a beautiful <a href=\"http://example.com/beautiful\#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a beautiful day</p>", "beautiful")
+ )
+ end
+
def test_excerpt
assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", 5))
assert_equal("This is a...", excerpt("This is a beautiful morning", "this", 5))
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index f47674d5b7..1c7e2603ee 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -32,9 +32,13 @@ task :default => :test
desc 'Run mysql, sqlite, and postgresql tests'
task :test => %w(test_mysql test_sqlite3 test_postgresql)
-for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase )
+for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb )
Rake::TestTask.new("test_#{adapter}") { |t|
- t.libs << "test" << "test/connections/native_#{adapter}"
+ if adapter =~ /jdbc/
+ t.libs << "test" << "test/connections/jdbc_#{adapter}"
+ else
+ t.libs << "test" << "test/connections/native_#{adapter}"
+ end
adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
t.test_files=Dir.glob( "test/cases/**/*_test{,_#{adapter_short}}.rb" ).sort
t.verbose = true
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index ebc0b7783f..0efccb66ee 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -927,7 +927,7 @@ module ActiveRecord #:nodoc:
#
# ==== Parameters
#
- # * +id+ - The id of the object you wish to update a counter on.
+ # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
# * +counters+ - An Array of Hashes containing the names of the fields
# to update as keys and the amount to update the field by as values.
#
@@ -941,12 +941,27 @@ module ActiveRecord #:nodoc:
# # SET comment_count = comment_count - 1,
# # action_count = action_count + 1
# # WHERE id = 5
+ #
+ # # For the Posts with id of 10 and 15, increment the comment_count by 1
+ # Post.update_counters [10, 15], :comment_count => 1
+ # # Executes the following SQL:
+ # # UPDATE posts
+ # # SET comment_count = comment_count + 1,
+ # # WHERE id IN (10, 15)
def update_counters(id, counters)
updates = counters.inject([]) { |list, (counter_name, increment)|
sign = increment < 0 ? "-" : "+"
list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
}.join(", ")
- update_all(updates, "#{connection.quote_column_name(primary_key)} = #{quote_value(id)}")
+
+ if id.is_a?(Array)
+ ids_list = id.map {|i| quote_value(i)}.join(', ')
+ condition = "IN (#{ids_list})"
+ else
+ condition = "= #{quote_value(id)}"
+ end
+
+ update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}")
end
# Increment a number field by one, usually representing a count.
@@ -1700,7 +1715,7 @@ module ActiveRecord #:nodoc:
end
join
end
- joins.flatten.uniq
+ joins.flatten.map{|j| j.strip}.uniq
else
joins.collect{|j| safe_to_array(j)}.flatten.uniq
end
@@ -2097,7 +2112,11 @@ module ActiveRecord #:nodoc:
(hash[method].keys + params.keys).uniq.each do |key|
merge = hash[method][key] && params[key] # merge if both scopes have the same key
if key == :conditions && merge
- hash[method][key] = merge_conditions(params[key], hash[method][key])
+ if params[key].is_a?(Hash) && hash[method][key].is_a?(Hash)
+ hash[method][key] = merge_conditions(hash[method][key].deep_merge(params[key]))
+ else
+ hash[method][key] = merge_conditions(params[key], hash[method][key])
+ end
elsif key == :include && merge
hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
elsif key == :joins && merge
@@ -2107,7 +2126,7 @@ module ActiveRecord #:nodoc:
end
end
else
- hash[method] = params.merge(hash[method])
+ hash[method] = hash[method].merge(params)
end
else
hash[method] = params
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index b2345fd571..9300df28ee 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -152,6 +152,7 @@ module ActiveRecord
# * <tt>:password</tt> - Defaults to nothing.
# * <tt>:database</tt> - The name of the database. No default, must be provided.
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
+ # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
@@ -563,8 +564,6 @@ module ActiveRecord
private
def connect
- @connection.reconnect = true if @connection.respond_to?(:reconnect=)
-
encoding = @config[:encoding]
if encoding
@connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
@@ -575,6 +574,10 @@ module ActiveRecord
end
@connection.real_connect(*@connection_options)
+
+ # reconnect must be set after real_connect is called, because real_connect sets it to false internally
+ @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
+
configure_connection
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 9387cf8827..5390f49f04 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -306,7 +306,7 @@ module ActiveRecord
end
def copy_table(from, to, options = {}) #:nodoc:
- options = options.merge(:id => !columns(from).detect{|c| c.name == 'id'}.nil?)
+ options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s))
create_table(to, options) do |definition|
@definition = definition
columns(from).each do |column|
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 0f03dae829..973bb567bd 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -639,6 +639,13 @@ class BasicsTest < ActiveRecord::TestCase
category.reload
assert_not_nil category.categorizations_count
assert_equal 4, category.categorizations_count
+
+ category_2 = categories(:technology)
+ count_1, count_2 = (category.categorizations_count || 0), (category_2.categorizations_count || 0)
+ Category.update_counters([category.id, category_2.id], "categorizations_count" => 2)
+ category.reload; category_2.reload
+ assert_equal count_1 + 2, category.categorizations_count
+ assert_equal count_2 + 2, category_2.categorizations_count
end
def test_update_all
diff --git a/activerecord/test/cases/connection_test_mysql.rb b/activerecord/test/cases/connection_test_mysql.rb
index 40ddcf5420..f79ee2f1f7 100644
--- a/activerecord/test/cases/connection_test_mysql.rb
+++ b/activerecord/test/cases/connection_test_mysql.rb
@@ -2,9 +2,24 @@ require "cases/helper"
class MysqlConnectionTest < ActiveRecord::TestCase
def setup
+ super
@connection = ActiveRecord::Base.connection
end
+ def test_mysql_reconnect_attribute_after_connection_with_reconnect_true
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => true}))
+ assert ActiveRecord::Base.connection.raw_connection.reconnect
+ end
+ end
+
+ def test_mysql_reconnect_attribute_after_connection_with_reconnect_false
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => false}))
+ assert !ActiveRecord::Base.connection.raw_connection.reconnect
+ end
+ end
+
def test_no_automatic_reconnection_after_timeout
assert @connection.active?
@connection.update('set @@wait_timeout=1')
@@ -27,4 +42,15 @@ class MysqlConnectionTest < ActiveRecord::TestCase
@connection.verify!
assert @connection.active?
end
+
+ private
+
+ def run_without_connection
+ original_connection = ActiveRecord::Base.remove_connection
+ begin
+ yield original_connection
+ ensure
+ ActiveRecord::Base.establish_connection(original_connection)
+ end
+ end
end
diff --git a/activerecord/test/cases/copy_table_test_sqlite.rb b/activerecord/test/cases/copy_table_test_sqlite.rb
index f0cfb67866..72bd7e2dab 100644
--- a/activerecord/test/cases/copy_table_test_sqlite.rb
+++ b/activerecord/test/cases/copy_table_test_sqlite.rb
@@ -46,6 +46,17 @@ class CopyTableTest < ActiveRecord::TestCase
test_copy_table('developers_projects', 'programmers_projects')
end
+ def test_copy_table_with_id_col_that_is_not_primary_key
+ test_copy_table('goofy_string_id', 'goofy_string_id2') do |from, to, options|
+ original_id = @connection.columns('goofy_string_id').detect{|col| col.name == 'id' }
+ copied_id = @connection.columns('goofy_string_id2').detect{|col| col.name == 'id' }
+ assert_equal original_id.type, copied_id.type
+ assert_equal original_id.sql_type, copied_id.sql_type
+ assert_equal original_id.limit, copied_id.limit
+ assert_equal original_id.primary, copied_id.primary
+ end
+ end
+
protected
def copy_table(from, to, options = {})
@connection.copy_table(from, to, {:temporary => true}.merge(options))
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 80a06116ad..71e2ce8790 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -186,6 +186,16 @@ class MethodScopingTest < ActiveRecord::TestCase
assert_equal authors(:david).attributes, scoped_authors.first.attributes
end
+ def test_scoped_find_strips_spaces_from_string_joins_and_eliminates_duplicate_string_joins
+ scoped_authors = Author.with_scope(:find => { :joins => ' INNER JOIN posts ON posts.author_id = authors.id '}) do
+ Author.find(:all, :select => 'DISTINCT authors.*', :joins => ['INNER JOIN posts ON posts.author_id = authors.id'], :conditions => 'posts.id = 1')
+ end
+ assert scoped_authors.include?(authors(:david))
+ assert !scoped_authors.include?(authors(:mary))
+ assert_equal 1, scoped_authors.size
+ assert_equal authors(:david).attributes, scoped_authors.first.attributes
+ end
+
def test_scoped_count_include
# with the include, will retrieve only developers for the given project
Developer.with_scope(:find => { :include => :projects }) do
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index bab842cf66..e1e27fa130 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -277,6 +277,26 @@ class NamedScopeTest < ActiveRecord::TestCase
post = Post.find(1)
assert_equal post.comments.size, Post.scoped(:joins => join).scoped(:joins => join, :conditions => "posts.id = #{post.id}").size
end
+
+ def test_chanining_should_use_latest_conditions_when_creating
+ post1 = Topic.rejected.approved.new
+ assert post1.approved?
+
+ post2 = Topic.approved.rejected.new
+ assert ! post2.approved?
+ end
+
+ def test_chanining_should_use_latest_conditions_when_searching
+ # Normal hash conditions
+ assert_equal Topic.all(:conditions => {:approved => true}), Topic.rejected.approved.all
+ assert_equal Topic.all(:conditions => {:approved => false}), Topic.approved.rejected.all
+
+ # Nested hash conditions with same keys
+ assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.all
+
+ # Nested hash conditions with different keys
+ assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).all.uniq
+ end
end
class DynamicScopeMatchTest < ActiveRecord::TestCase
diff --git a/activerecord/test/connections/jdbc_jdbcderby/connection.rb b/activerecord/test/connections/jdbc_jdbcderby/connection.rb
new file mode 100644
index 0000000000..222ef5db38
--- /dev/null
+++ b/activerecord/test/connections/jdbc_jdbcderby/connection.rb
@@ -0,0 +1,18 @@
+print "Using Derby via JRuby, activerecord-jdbc-adapter and activerecord-jdbcderby-adapter\n"
+require_dependency 'models/course'
+require 'logger'
+ActiveRecord::Base.logger = Logger.new("debug.log")
+
+ActiveRecord::Base.configurations = {
+ 'arunit' => {
+ :adapter => 'jdbcderby',
+ :database => 'activerecord_unittest'
+ },
+ 'arunit2' => {
+ :adapter => 'jdbcderby',
+ :database => 'activerecord_unittest2'
+ }
+}
+
+ActiveRecord::Base.establish_connection 'arunit'
+Course.establish_connection 'arunit2'
diff --git a/activerecord/test/connections/jdbc_jdbch2/connection.rb b/activerecord/test/connections/jdbc_jdbch2/connection.rb
new file mode 100644
index 0000000000..9d2875e8e7
--- /dev/null
+++ b/activerecord/test/connections/jdbc_jdbch2/connection.rb
@@ -0,0 +1,18 @@
+print "Using H2 via JRuby, activerecord-jdbc-adapter and activerecord-jdbch2-adapter\n"
+require_dependency 'models/course'
+require 'logger'
+ActiveRecord::Base.logger = Logger.new("debug.log")
+
+ActiveRecord::Base.configurations = {
+ 'arunit' => {
+ :adapter => 'jdbch2',
+ :database => 'activerecord_unittest'
+ },
+ 'arunit2' => {
+ :adapter => 'jdbch2',
+ :database => 'activerecord_unittest2'
+ }
+}
+
+ActiveRecord::Base.establish_connection 'arunit'
+Course.establish_connection 'arunit2'
diff --git a/activerecord/test/connections/jdbc_jdbchsqldb/connection.rb b/activerecord/test/connections/jdbc_jdbchsqldb/connection.rb
new file mode 100644
index 0000000000..fa943c2c76
--- /dev/null
+++ b/activerecord/test/connections/jdbc_jdbchsqldb/connection.rb
@@ -0,0 +1,18 @@
+print "Using HSQLDB via JRuby, activerecord-jdbc-adapter and activerecord-jdbchsqldb-adapter\n"
+require_dependency 'models/course'
+require 'logger'
+ActiveRecord::Base.logger = Logger.new("debug.log")
+
+ActiveRecord::Base.configurations = {
+ 'arunit' => {
+ :adapter => 'jdbchsqldb',
+ :database => 'activerecord_unittest'
+ },
+ 'arunit2' => {
+ :adapter => 'jdbchsqldb',
+ :database => 'activerecord_unittest2'
+ }
+}
+
+ActiveRecord::Base.establish_connection 'arunit'
+Course.establish_connection 'arunit2'
diff --git a/activerecord/test/connections/jdbc_jdbcmysql/connection.rb b/activerecord/test/connections/jdbc_jdbcmysql/connection.rb
new file mode 100644
index 0000000000..e2517a50eb
--- /dev/null
+++ b/activerecord/test/connections/jdbc_jdbcmysql/connection.rb
@@ -0,0 +1,26 @@
+print "Using MySQL via JRuby, activerecord-jdbc-adapter and activerecord-jdbcmysql-adapter\n"
+require_dependency 'models/course'
+require 'logger'
+
+ActiveRecord::Base.logger = Logger.new("debug.log")
+
+# GRANT ALL PRIVILEGES ON activerecord_unittest.* to 'rails'@'localhost';
+# GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost';
+
+ActiveRecord::Base.configurations = {
+ 'arunit' => {
+ :adapter => 'jdbcmysql',
+ :username => 'rails',
+ :encoding => 'utf8',
+ :database => 'activerecord_unittest',
+ },
+ 'arunit2' => {
+ :adapter => 'jdbcmysql',
+ :username => 'rails',
+ :database => 'activerecord_unittest2'
+ }
+}
+
+ActiveRecord::Base.establish_connection 'arunit'
+Course.establish_connection 'arunit2'
+
diff --git a/activerecord/test/connections/jdbc_jdbcpostgresql/connection.rb b/activerecord/test/connections/jdbc_jdbcpostgresql/connection.rb
new file mode 100644
index 0000000000..0685da4433
--- /dev/null
+++ b/activerecord/test/connections/jdbc_jdbcpostgresql/connection.rb
@@ -0,0 +1,26 @@
+print "Using Postgrsql via JRuby, activerecord-jdbc-adapter and activerecord-postgresql-adapter\n"
+require_dependency 'models/course'
+require 'logger'
+
+ActiveRecord::Base.logger = Logger.new("debug.log")
+
+# createuser rails --createdb --no-superuser --no-createrole
+# createdb -O rails activerecord_unittest
+# createdb -O rails activerecord_unittest2
+
+ActiveRecord::Base.configurations = {
+ 'arunit' => {
+ :adapter => 'jdbcpostgresql',
+ :username => ENV['USER'] || 'rails',
+ :database => 'activerecord_unittest'
+ },
+ 'arunit2' => {
+ :adapter => 'jdbcpostgresql',
+ :username => ENV['USER'] || 'rails',
+ :database => 'activerecord_unittest2'
+ }
+}
+
+ActiveRecord::Base.establish_connection 'arunit'
+Course.establish_connection 'arunit2'
+
diff --git a/activerecord/test/connections/jdbc_jdbcsqlite3/connection.rb b/activerecord/test/connections/jdbc_jdbcsqlite3/connection.rb
new file mode 100644
index 0000000000..26d4676ff3
--- /dev/null
+++ b/activerecord/test/connections/jdbc_jdbcsqlite3/connection.rb
@@ -0,0 +1,25 @@
+print "Using SQLite3 via JRuby, activerecord-jdbc-adapter and activerecord-jdbcsqlite3-adapter\n"
+require_dependency 'models/course'
+require 'logger'
+ActiveRecord::Base.logger = Logger.new("debug.log")
+
+class SqliteError < StandardError
+end
+
+BASE_DIR = FIXTURES_ROOT
+sqlite_test_db = "#{BASE_DIR}/fixture_database.sqlite3"
+sqlite_test_db2 = "#{BASE_DIR}/fixture_database_2.sqlite3"
+
+def make_connection(clazz, db_file)
+ ActiveRecord::Base.configurations = { clazz.name => { :adapter => 'jdbcsqlite3', :database => db_file, :timeout => 5000 } }
+ unless File.exist?(db_file)
+ puts "SQLite3 database not found at #{db_file}. Rebuilding it."
+ sqlite_command = %Q{sqlite3 "#{db_file}" "create table a (a integer); drop table a;"}
+ puts "Executing '#{sqlite_command}'"
+ raise SqliteError.new("Seems that there is no sqlite3 executable available") unless system(sqlite_command)
+ end
+ clazz.establish_connection(clazz.name)
+end
+
+make_connection(ActiveRecord::Base, sqlite_test_db)
+make_connection(Course, sqlite_test_db2)
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index e0d8be676a..388fff8fba 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -17,6 +17,12 @@ class Post < ActiveRecord::Base
has_one :last_comment, :class_name => 'Comment', :order => 'id desc'
+ named_scope :with_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'SpecialComment'} }
+ named_scope :with_very_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'VerySpecialComment'} }
+ named_scope :with_post, lambda {|post_id|
+ { :joins => :comments, :conditions => {:comments => {:post_id => post_id} } }
+ }
+
has_many :comments, :order => "body" do
def find_most_recent
find(:first, :order => "id DESC")
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 39ca1bf42a..08bb24ed03 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -4,6 +4,8 @@ class Topic < ActiveRecord::Base
{ :conditions => ['written_on < ?', time] }
}
named_scope :approved, :conditions => {:approved => true}
+ named_scope :rejected, :conditions => {:approved => false}
+
named_scope :by_lifo, :conditions => {:author_name => 'lifo'}
named_scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}}
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 094932d375..d44faf04cc 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -154,6 +154,11 @@ ActiveRecord::Schema.define do
t.string :name
end
+ create_table :goofy_string_id, :force => true, :id => false do |t|
+ t.string :id, :null => false
+ t.string :info
+ end
+
create_table :items, :force => true do |t|
t.column :name, :integer
end
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index 4192fab525..94418fb559 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -746,8 +746,8 @@ module ActiveResource
# # => true
#
def ==(other)
- other.equal?(self) || (other.instance_of?(self.class) && !other.new? && other.id == id)
- end
+ other.equal?(self) || (other.instance_of?(self.class) && other.id == id && other.prefix_options == prefix_options)
+ end
# Tests for equality (delegates to ==).
def eql?(other)
@@ -1006,7 +1006,7 @@ module ActiveResource
# Takes a response from a typical create post and pulls the ID out
def id_from_response(response)
- response['Location'][/\/([^\/]*?)(\.\w+)?$/, 1]
+ response['Location'][/\/([^\/]*?)(\.\w+)?$/, 1] if response['Location']
end
def element_path(options = nil)
diff --git a/activeresource/test/base/equality_test.rb b/activeresource/test/base/equality_test.rb
index 1fb8938e95..84f1a7b998 100644
--- a/activeresource/test/base/equality_test.rb
+++ b/activeresource/test/base/equality_test.rb
@@ -40,4 +40,13 @@ class BaseEqualityTest < Test::Unit::TestCase
assert_equal resource.id.hash, resource.hash
end
end
+
+ def test_with_prefix_options
+ assert_equal @one == @one, @one.eql?(@one)
+ assert_equal @one == @one.dup, @one.eql?(@one.dup)
+ new_one = @one.dup
+ new_one.prefix_options = {:foo => 'bar'}
+ assert_not_equal @one, new_one
+ end
+
end
diff --git a/activeresource/test/base_test.rb b/activeresource/test/base_test.rb
index d37a6d4ed2..e22388f4a7 100644
--- a/activeresource/test/base_test.rb
+++ b/activeresource/test/base_test.rb
@@ -627,6 +627,12 @@ class BaseTest < Test::Unit::TestCase
assert_equal '1', p.__send__(:id_from_response, resp)
end
+ def test_id_from_response_without_location
+ p = Person.new
+ resp = {}
+ assert_equal nil, p.__send__(:id_from_response, resp)
+ end
+
def test_create_with_custom_prefix
matzs_house = StreetAddress.new(:person_id => 1)
matzs_house.save
@@ -670,7 +676,6 @@ class BaseTest < Test::Unit::TestCase
assert_equal person, person.reload
end
-
def test_create
rick = Person.create(:name => 'Rick')
assert rick.valid?
@@ -687,6 +692,14 @@ class BaseTest < Test::Unit::TestCase
assert_raises(ActiveResource::ResourceConflict) { Person.create(:name => 'Rick') }
end
+ def test_create_without_location
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.post "/people.xml", {}, nil, 201
+ end
+ person = Person.create(:name => 'Rick')
+ assert_equal nil, person.id
+ end
+
def test_clone
matz = Person.find(1)
matz_c = matz.clone
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index 621358d701..d83e259a2a 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -41,7 +41,7 @@ module ActiveSupport
value
else
# forcing the value to be immutable
- value.dup
+ value.duplicable? ? value.dup : value
end
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 7ce9adec2c..2badad5f5f 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -51,6 +51,9 @@ module ActiveSupport #:nodoc:
mattr_accessor :constant_watch_stack
self.constant_watch_stack = []
+ mattr_accessor :constant_watch_stack_mutex
+ self.constant_watch_stack_mutex = Mutex.new
+
# Module includes this module
module ModuleConstMissing #:nodoc:
def self.included(base) #:nodoc:
@@ -509,7 +512,9 @@ module ActiveSupport #:nodoc:
[mod_name, initial_constants]
end
- constant_watch_stack.concat watch_frames
+ constant_watch_stack_mutex.synchronize do
+ constant_watch_stack.concat watch_frames
+ end
aborting = true
begin
@@ -526,8 +531,10 @@ module ActiveSupport #:nodoc:
new_constants = mod.local_constant_names - prior_constants
# Make sure no other frames takes credit for these constants.
- constant_watch_stack.each do |frame_name, constants|
- constants.concat new_constants if frame_name == mod_name
+ constant_watch_stack_mutex.synchronize do
+ constant_watch_stack.each do |frame_name, constants|
+ constants.concat new_constants if frame_name == mod_name
+ end
end
new_constants.collect do |suffix|
@@ -549,8 +556,10 @@ module ActiveSupport #:nodoc:
# Remove the stack frames that we added.
if defined?(watch_frames) && ! watch_frames.blank?
frame_ids = watch_frames.collect { |frame| frame.object_id }
- constant_watch_stack.delete_if do |watch_frame|
- frame_ids.include? watch_frame.object_id
+ constant_watch_stack_mutex.synchronize do
+ constant_watch_stack.delete_if do |watch_frame|
+ frame_ids.include? watch_frame.object_id
+ end
end
end
end
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 25ea505813..66aab9e562 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -92,6 +92,10 @@ module ActiveSupport
dup.merge!(other_hash)
end
+ def inspect
+ "#<OrderedHash #{self.to_hash.inspect}>"
+ end
+
private
def sync_keys!
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb
index b5ad094d0e..76361bed90 100755
--- a/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb
@@ -45,6 +45,11 @@ module I18n
Thread.current[:locale] = locale
end
+ # Returns an array of locales for which translations are available
+ def available_locales
+ backend.available_locales
+ end
+
# Sets the exception handler.
def exception_handler=(exception_handler)
@@exception_handler = exception_handler
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb
index d298b3a85a..b54164d496 100644
--- a/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb
@@ -69,6 +69,12 @@ module I18n
@initialized ||= false
end
+ # Returns an array of locales for which translations are available
+ def available_locales
+ init_translations unless initialized?
+ translations.keys
+ end
+
def reload!
@initialized = false
@translations = nil
@@ -76,7 +82,7 @@ module I18n
protected
def init_translations
- load_translations(*I18n.load_path)
+ load_translations(*I18n.load_path.flatten)
@initialized = true
end
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
index e181975f38..8ba7036abf 100644
--- 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
@@ -124,6 +124,16 @@ class I18nSimpleBackendTranslationsTest < Test::Unit::TestCase
end
end
+class I18nSimpleBackendAvailableLocalesTest < Test::Unit::TestCase
+ def test_available_locales
+ @backend = I18n::Backend::Simple.new
+ @backend.store_translations 'de', :foo => 'bar'
+ @backend.store_translations 'en', :foo => 'foo'
+
+ assert_equal ['de', 'en'], @backend.available_locales.map{|locale| locale.to_s }.sort
+ end
+end
+
class I18nSimpleBackendTranslateTest < Test::Unit::TestCase
include I18nSimpleBackendTestSetup
@@ -472,6 +482,18 @@ class I18nSimpleBackendLoadTranslationsTest < Test::Unit::TestCase
end
end
+class I18nSimpleBackendLoadPathTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def test_nested_load_paths_do_not_break_locale_loading
+ @backend = I18n::Backend::Simple.new
+ I18n.load_path = [[File.dirname(__FILE__) + '/locale/en.yml']]
+ assert_nil backend_get_translations
+ assert_nothing_raised { @backend.send :init_translations }
+ assert_not_nil backend_get_translations
+ end
+end
+
class I18nSimpleBackendReloadTranslationsTest < Test::Unit::TestCase
include I18nSimpleBackendTestSetup
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index e8e0b41d4d..4e212f1661 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -81,6 +81,11 @@ module CacheStoreBehavior
assert_equal({:a => "b"}, @cache.read('foo'))
end
+ def test_should_read_and_write_integer
+ @cache.write('foo', 1)
+ assert_equal 1, @cache.read('foo')
+ end
+
def test_should_read_and_write_nil
@cache.write('foo', nil)
assert_equal nil, @cache.read('foo')
@@ -200,6 +205,13 @@ uses_memcached 'memcached backed store' do
end
end
+ def test_local_cache_should_read_and_write_integer
+ @cache.with_local_cache do
+ @cache.write('foo', 1)
+ assert_equal 1, @cache.read('foo')
+ end
+ end
+
def test_local_cache_of_delete
@cache.with_local_cache do
@cache.write('foo', 'bar')
diff --git a/ci/geminstaller.yml b/ci/geminstaller.yml
index 4251518999..d11bf04485 100644
--- a/ci/geminstaller.yml
+++ b/ci/geminstaller.yml
@@ -11,8 +11,8 @@ gems:
- name: mysql
#version: >= 2.7
version: = 2.7
-- name: postgres
- version: >= 0.7.9.2008.01.28
+- name: pg
+ version: >= 0.7.9.2008.10.13
- name: rack
version: '~> 0.9.0'
- name: rake
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index b36f57f75d..5b932ac197 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,8 +1,8 @@
*2.3.0 [Edge]*
-* Remove script/performance/profiler in favour of performance integration tests. [Pratik Naik]
+* Remove script/performance/request in favour of the performance integration tests. [Pratik Naik]
- To continue using script/performance/profiler, install the request_profiler plugin :
+ To continue using script/performance/request, install the request_profiler plugin :
script/plugin install git://github.com/rails/request_profiler.git
diff --git a/railties/lib/commands/dbconsole.rb b/railties/lib/commands/dbconsole.rb
index 06848d3c91..8002264f7e 100644
--- a/railties/lib/commands/dbconsole.rb
+++ b/railties/lib/commands/dbconsole.rb
@@ -3,12 +3,23 @@ require 'yaml'
require 'optparse'
include_password = false
+options = {}
OptionParser.new do |opt|
opt.banner = "Usage: dbconsole [options] [environment]"
opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v|
include_password = true
end
+
+ opt.on("--mode [MODE]", ['html', 'list', 'line', 'column'],
+ "Automatically put the sqlite3 database in the specified mode (html, list, line, column).") do |mode|
+ options['mode'] = mode
+ end
+
+ opt.on("-h", "--header") do |h|
+ options['header'] = h
+ end
+
opt.parse!(ARGV)
abort opt.to_s unless (0..1).include?(ARGV.size)
end
@@ -60,8 +71,13 @@ when "sqlite"
exec(find_cmd('sqlite'), config["database"])
when "sqlite3"
- exec(find_cmd('sqlite3'), config["database"])
+ args = []
+
+ args << "-#{options['mode']}" if options['mode']
+ args << "-header" if options['header']
+ args << config['database']
+ exec(find_cmd('sqlite3'), *args)
else
abort "Unknown command-line client for #{config['database']}. Submit a Rails patch to add support!"
end
diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb
index be04873855..da064c86aa 100644
--- a/railties/lib/initializer.rb
+++ b/railties/lib/initializer.rb
@@ -302,7 +302,7 @@ module Rails
if unloaded_gems.size > 0
@gems_dependencies_loaded = false
# don't print if the gems rake tasks are being run
- unless $rails_gem_installer
+ unless $rails_rake_task
abort <<-end_error
Missing these required gems:
#{unloaded_gems.map { |gem| "#{gem.name} #{gem.requirement}" } * "\n "}
@@ -379,6 +379,7 @@ Run `rake gems:install` to install the missing gems.
# Eager load application classes
def load_application_classes
+ return if $rails_rake_task
if configuration.cache_classes
configuration.eager_load_paths.each do |load_path|
matcher = /\A#{Regexp.escape(load_path)}(.*)\.rb\Z/
diff --git a/railties/lib/tasks/gems.rake b/railties/lib/tasks/gems.rake
index 754e3ba5c9..e6731ab78c 100644
--- a/railties/lib/tasks/gems.rake
+++ b/railties/lib/tasks/gems.rake
@@ -17,13 +17,13 @@ end
namespace :gems do
task :base do
- $rails_gem_installer = true
+ $rails_rake_task = true
Rake::Task[:environment].invoke
end
desc "Build any native extensions for unpacked gems"
task :build do
- $rails_gem_installer = true
+ $rails_rake_task = true
require 'rails/gem_builder'
Dir[File.join(Rails::GemDependency.unpacked_path, '*')].each do |gem_dir|
spec_file = File.join(gem_dir, '.specification')
diff --git a/railties/lib/tasks/misc.rake b/railties/lib/tasks/misc.rake
index 411750bf40..9e6f96db5b 100644
--- a/railties/lib/tasks/misc.rake
+++ b/railties/lib/tasks/misc.rake
@@ -1,5 +1,6 @@
task :default => :test
task :environment do
+ $rails_rake_task = true
require(File.join(RAILS_ROOT, 'config', 'environment'))
end