Merge commit 'mainstream/master'

Conflicts:

	actionpack/lib/action_controller/request.rb
	actionpack/lib/action_controller/resources.rb
This commit is contained in:
Pratik Naik
2008-08-14 16:31:14 +01:00
78 changed files with 1561 additions and 889 deletions

View File

@@ -216,7 +216,7 @@ module ActionMailer #:nodoc:
# * <tt>:domain</tt> - If you need to specify a HELO domain, you can do it here.
# * <tt>:user_name</tt> - If your mail server requires authentication, set the username in this setting.
# * <tt>:password</tt> - If your mail server requires authentication, set the password in this setting.
# * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the authentication type here.
# * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the authentication type here.
# This is a symbol and one of <tt>:plain</tt>, <tt>:login</tt>, <tt>:cram_md5</tt>.
#
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
@@ -233,10 +233,10 @@ module ActionMailer #:nodoc:
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with <tt>delivery_method :test</tt>. Most useful
# for unit and functional testing.
#
# * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also
# * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also
# pick a different charset from inside a method with +charset+.
# * <tt>default_content_type</tt> - The default content type used for the main part of the message. Defaults to "text/plain". You
# can also pick a different content type from inside a method with +content_type+.
# can also pick a different content type from inside a method with +content_type+.
# * <tt>default_mime_version</tt> - The default mime version used for the message. Defaults to <tt>1.0</tt>. You
# can also pick a different value from inside a method with +mime_version+.
# * <tt>default_implicit_parts_order</tt> - When a message is built implicitly (i.e. multiple parts are assembled from templates
@@ -253,9 +253,6 @@ module ActionMailer #:nodoc:
class_inheritable_accessor :view_paths
cattr_accessor :logger
cattr_accessor :template_extensions
@@template_extensions = ['erb', 'builder', 'rhtml', 'rxml']
@@smtp_settings = {
:address => "localhost",
:port => 25,
@@ -414,15 +411,10 @@ module ActionMailer #:nodoc:
new.deliver!(mail)
end
# Register a template extension so mailer templates written in a
# templating language other than rhtml or rxml are supported.
# To use this, include in your template-language plugin's init
# code or on a per-application basis, this can be invoked from
# <tt>config/environment.rb</tt>:
#
# ActionMailer::Base.register_template_extension('haml')
def register_template_extension(extension)
template_extensions << extension
ActiveSupport::Deprecation.warn(
"ActionMailer::Base.register_template_extension has been deprecated." +
"Use ActionView::Base.register_template_extension instead", caller)
end
def template_root
@@ -455,16 +447,18 @@ module ActionMailer #:nodoc:
# "the_template_file.text.html.erb", etc.). Only do this if parts
# have not already been specified manually.
if @parts.empty?
templates = Dir.glob("#{template_path}/#{@template}.*")
templates.each do |path|
basename = File.basename(path)
template_regex = Regexp.new("^([^\\\.]+)\\\.([^\\\.]+\\\.[^\\\.]+)\\\.(" + template_extensions.join('|') + ")$")
next unless md = template_regex.match(basename)
template_name = basename
content_type = md.captures[1].gsub('.', '/')
@parts << Part.new(:content_type => content_type,
:disposition => "inline", :charset => charset,
:body => render_message(template_name, @body))
Dir.glob("#{template_path}/#{@template}.*").each do |path|
template = template_root["#{mailer_name}/#{File.basename(path)}"]
# Skip unless template has a multipart format
next unless template.multipart?
@parts << Part.new(
:content_type => template.content_type,
:disposition => "inline",
:charset => charset,
:body => render_message(template, @body)
)
end
unless @parts.empty?
@content_type = "multipart/alternative"
@@ -477,7 +471,7 @@ module ActionMailer #:nodoc:
# normal template exists (or if there were no implicit parts) we render
# it.
template_exists = @parts.empty?
template_exists ||= Dir.glob("#{template_path}/#{@template}.*").any? { |i| File.basename(i).split(".").length == 2 }
template_exists ||= template_root["#{mailer_name}/#{@template}"]
@body = render_message(@template, @body) if template_exists
# Finally, if there are other message parts and a textual body exists,
@@ -538,7 +532,7 @@ module ActionMailer #:nodoc:
def render(opts)
body = opts.delete(:body)
if opts[:file] && opts[:file] !~ /\//
if opts[:file] && (opts[:file] !~ /\// && !opts[:file].respond_to?(:render))
opts[:file] = "#{mailer_name}/#{opts[:file]}"
end
initialize_template_class(body).render(opts)

View File

@@ -219,7 +219,7 @@ class TestMailer < ActionMailer::Base
end
attachment :content_type => "application/octet-stream",:filename => "test.txt", :body => "test abcdefghijklmnopqstuvwxyz"
end
def nested_multipart_with_body(recipient)
recipients recipient
subject "nested multipart with body"
@@ -321,7 +321,7 @@ class ActionMailerTest < Test::Unit::TestCase
assert_nothing_raised { created = TestMailer.create_nested_multipart(@recipient)}
assert_equal 2,created.parts.size
assert_equal 2,created.parts.first.parts.size
assert_equal "multipart/mixed", created.content_type
assert_equal "multipart/alternative", created.parts.first.content_type
assert_equal "bar", created.parts.first.header['foo'].to_s
@@ -366,7 +366,7 @@ class ActionMailerTest < Test::Unit::TestCase
assert_not_nil ActionMailer::Base.deliveries.first
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
end
def test_custom_template
expected = new_mail
expected.to = @recipient
@@ -382,7 +382,6 @@ class ActionMailerTest < Test::Unit::TestCase
end
def test_custom_templating_extension
#
# N.b., custom_templating_extension.text.plain.haml is expected to be in fixtures/test_mailer directory
expected = new_mail
expected.to = @recipient
@@ -390,18 +389,10 @@ class ActionMailerTest < Test::Unit::TestCase
expected.body = "Hello there, \n\nMr. #{@recipient}"
expected.from = "system@loudthinking.com"
expected.date = Time.local(2004, 12, 12)
# Stub the render method so no alternative renderers need be present.
ActionView::Base.any_instance.stubs(:render).returns("Hello there, \n\nMr. #{@recipient}")
# If the template is not registered, there should be no parts.
created = nil
assert_nothing_raised { created = TestMailer.create_custom_templating_extension(@recipient) }
assert_not_nil created
assert_equal 0, created.parts.length
ActionMailer::Base.register_template_extension('haml')
# Now that the template is registered, there should be one part. The text/plain part.
created = nil
assert_nothing_raised { created = TestMailer.create_custom_templating_extension(@recipient) }
@@ -428,7 +419,7 @@ class ActionMailerTest < Test::Unit::TestCase
assert_not_nil ActionMailer::Base.deliveries.first
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
end
def test_cc_bcc
expected = new_mail
expected.to = @recipient
@@ -550,7 +541,7 @@ class ActionMailerTest < Test::Unit::TestCase
TestMailer.deliver_signed_up(@recipient)
assert_equal 1, ActionMailer::Base.deliveries.size
end
def test_doesnt_raise_errors_when_raise_delivery_errors_is_false
ActionMailer::Base.raise_delivery_errors = false
TestMailer.any_instance.expects(:perform_delivery_test).raises(Exception)
@@ -670,7 +661,7 @@ EOF
assert_not_nil ActionMailer::Base.deliveries.first
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
end
def test_utf8_body_is_not_quoted
@recipient = "Foo áëô îü <extended@example.net>"
expected = new_mail "utf-8"
@@ -760,7 +751,7 @@ EOF
mail = TestMailer.create_multipart_with_mime_version(@recipient)
assert_equal "1.1", mail.mime_version
end
def test_multipart_with_utf8_subject
mail = TestMailer.create_multipart_with_utf8_subject(@recipient)
assert_match(/\nSubject: =\?utf-8\?Q\?Foo_.*?\?=/, mail.encoded)
@@ -825,7 +816,7 @@ EOF
mail = TestMailer.create_implicitly_multipart_example(@recipient, 'iso-8859-1')
assert_equal "multipart/alternative", mail.header['content-type'].body
assert_equal 'iso-8859-1', mail.parts[0].sub_header("content-type", "charset")
assert_equal 'iso-8859-1', mail.parts[1].sub_header("content-type", "charset")
assert_equal 'iso-8859-1', mail.parts[2].sub_header("content-type", "charset")
@@ -852,7 +843,7 @@ EOF
assert_equal "line #1\nline #2\nline #3\nline #4\n\n", mail.parts[0].body
assert_equal "<p>line #1</p>\n<p>line #2</p>\n<p>line #3</p>\n<p>line #4</p>\n\n", mail.parts[1].body
end
def test_headers_removed_on_smtp_delivery
ActionMailer::Base.delivery_method = :smtp
TestMailer.deliver_cc_bcc(@recipient)

View File

@@ -7,8 +7,14 @@
* Update Prototype to 1.6.0.2 #599 [Patrick Joyce]
* Conditional GET utility methods. [Jeremy Kemper]
* etag!([:admin, post, current_user]) sets the ETag response header and returns head(:not_modified) if it matches the If-None-Match request header.
* last_modified!(post.updated_at) sets Last-Modified and returns head(:not_modified) if it's no later than If-Modified-Since.
response.last_modified = @post.updated_at
response.etag = [:admin, @post, current_user]
if request.fresh?(response)
head :not_modified
else
# render ...
end
* All 2xx requests are considered successful [Josh Peek]

View File

@@ -87,11 +87,11 @@ module ActionController
#
def assert_template(expected = nil, message=nil)
clean_backtrace do
rendered = @response.rendered_template
rendered = @response.rendered_template.to_s
msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
assert_block(msg) do
if expected.nil?
@response.rendered_template.nil?
@response.rendered_template.blank?
else
rendered.to_s.match(expected)
end

View File

@@ -428,11 +428,7 @@ module ActionController #:nodoc:
# By default, all methods defined in ActionController::Base and included modules are hidden.
# More methods can be hidden using <tt>hide_actions</tt>.
def hidden_actions
unless read_inheritable_attribute(:hidden_actions)
write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods.map { |m| m.to_s })
end
read_inheritable_attribute(:hidden_actions)
read_inheritable_attribute(:hidden_actions) || write_inheritable_attribute(:hidden_actions, [])
end
# Hide each of the given methods from being callable as actions.
@@ -1199,7 +1195,7 @@ module ActionController #:nodoc:
end
def perform_action
if self.class.action_methods.include?(action_name)
if action_methods.include?(action_name)
send(action_name)
default_render unless performed?
elsif respond_to? :method_missing
@@ -1208,7 +1204,7 @@ module ActionController #:nodoc:
elsif template_exists? && template_public?
default_render
else
raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.to_a.sort.to_sentence}", caller
raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence}", caller
end
end
@@ -1234,7 +1230,15 @@ module ActionController #:nodoc:
end
def self.action_methods
@action_methods ||= Set.new(public_instance_methods.map { |m| m.to_s }) - hidden_actions
@action_methods ||=
# All public instance methods of this class, including ancestors
public_instance_methods(true).map { |m| m.to_s }.to_set -
# Except for public instance methods of Base and its ancestors
Base.public_instance_methods(true).map { |m| m.to_s } +
# Be sure to include shadowed public instance methods of this class
public_instance_methods(false).map { |m| m.to_s } -
# And always exclude explicitly hidden actions
hidden_actions
end
def add_variables_to_assigns

View File

@@ -43,7 +43,7 @@ module ActionController #:nodoc:
:session_path => "/", # available to all paths in app
:session_key => "_session_id",
:cookie_only => true
} unless const_defined?(:DEFAULT_SESSION_OPTIONS)
}
def initialize(cgi, session_options = {})
@cgi = cgi
@@ -61,53 +61,14 @@ module ActionController #:nodoc:
end
end
# The request body is an IO input stream. If the RAW_POST_DATA environment
# variable is already set, wrap it in a StringIO.
def body
if raw_post = env['RAW_POST_DATA']
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
StringIO.new(raw_post)
else
@cgi.stdinput
end
end
def query_parameters
@query_parameters ||= self.class.parse_query_parameters(query_string)
end
def request_parameters
@request_parameters ||= parse_formatted_request_parameters
def body_stream #:nodoc:
@cgi.stdinput
end
def cookies
@cgi.cookies.freeze
end
def host_with_port_without_standard_port_handling
if forwarded = env["HTTP_X_FORWARDED_HOST"]
forwarded.split(/,\s?/).last
elsif http_host = env['HTTP_HOST']
http_host
elsif server_name = env['SERVER_NAME']
server_name
else
"#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
end
end
def host
host_with_port_without_standard_port_handling.sub(/:\d+$/, '')
end
def port
if host_with_port_without_standard_port_handling =~ /:(\d+)$/
$1.to_i
else
standard_port
end
end
def session
unless defined?(@session)
if @session_options == false

View File

@@ -109,16 +109,17 @@ module ActionController #:nodoc:
update_options! options
end
# override these to return true in appropriate subclass
def before?
self.class == BeforeFilter
false
end
def after?
self.class == AfterFilter
false
end
def around?
self.class == AroundFilter
false
end
# Make sets of strings from :only/:except options
@@ -170,6 +171,10 @@ module ActionController #:nodoc:
:around
end
def around?
true
end
def call(controller, &block)
if should_run_callback?(controller)
method = filter_responds_to_before_and_after? ? around_proc : self.method
@@ -212,6 +217,10 @@ module ActionController #:nodoc:
:before
end
def before?
true
end
def call(controller, &block)
super
if controller.send!(:performed?)
@@ -224,6 +233,10 @@ module ActionController #:nodoc:
def type
:after
end
def after?
true
end
end
# Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do

View File

@@ -1,31 +1,33 @@
require 'active_support/memoizable'
module ActionController
module Http
class Headers < ::Hash
def initialize(constructor = {})
if constructor.is_a?(Hash)
extend ActiveSupport::Memoizable
def initialize(*args)
if args.size == 1 && args[0].is_a?(Hash)
super()
update(constructor)
update(args[0])
else
super(constructor)
super
end
end
def [](header_name)
if include?(header_name)
super
super
else
super(normalize_header(header_name))
super(env_name(header_name))
end
end
private
# Takes an HTTP header name and returns it in the
# format
def normalize_header(header_name)
# Converts a HTTP header name to an environment variable name.
def env_name(header_name)
"HTTP_#{header_name.upcase.gsub(/-/, '_')}"
end
memoize :env_name
end
end
end
end

View File

@@ -3,7 +3,7 @@ require 'action_controller/session/cookie_store'
module ActionController #:nodoc:
class RackRequest < AbstractRequest #:nodoc:
attr_accessor :env, :session_options
attr_accessor :session_options
attr_reader :cgi
class SessionFixationAttempt < StandardError #:nodoc:
@@ -15,7 +15,7 @@ module ActionController #:nodoc:
:session_path => "/", # available to all paths in app
:session_key => "_session_id",
:cookie_only => true
} unless const_defined?(:DEFAULT_SESSION_OPTIONS)
}
def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
@session_options = session_options
@@ -30,35 +30,21 @@ module ActionController #:nodoc:
SERVER_NAME SERVER_PROTOCOL
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
define_method(env.sub(/^HTTP_/n, '').downcase) do
@env[env]
end
end
# The request body is an IO input stream. If the RAW_POST_DATA environment
# variable is already set, wrap it in a StringIO.
def body
if raw_post = env['RAW_POST_DATA']
StringIO.new(raw_post)
else
@env['rack.input']
end
def body_stream #:nodoc:
@env['rack.input']
end
def key?(key)
@env.key?(key)
end
def query_parameters
@query_parameters ||= self.class.parse_query_parameters(query_string)
end
def request_parameters
@request_parameters ||= parse_formatted_request_parameters
end
def cookies
return {} unless @env["HTTP_COOKIE"]
@@ -70,34 +56,6 @@ module ActionController #:nodoc:
@env["rack.request.cookie_hash"]
end
def host_with_port_without_standard_port_handling
if forwarded = @env["HTTP_X_FORWARDED_HOST"]
forwarded.split(/,\s?/).last
elsif http_host = @env['HTTP_HOST']
http_host
elsif server_name = @env['SERVER_NAME']
server_name
else
"#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
end
end
def host
host_with_port_without_standard_port_handling.sub(/:\d+$/, '')
end
def port
if host_with_port_without_standard_port_handling =~ /:(\d+)$/
$1.to_i
else
standard_port
end
end
def remote_addr
@env['REMOTE_ADDR']
end
def server_port
@env['SERVER_PORT'].to_i
end

View File

@@ -2,35 +2,35 @@ require 'tempfile'
require 'stringio'
require 'strscan'
module ActionController
# HTTP methods which are accepted by default.
ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete options ))
require 'active_support/memoizable'
module ActionController
# CgiRequest and TestRequest provide concrete implementations.
class AbstractRequest
extend ActiveSupport::Memoizable
def self.relative_url_root=(*args)
ActiveSupport::Deprecation.warn(
"ActionController::AbstractRequest.relative_url_root= has been renamed." +
"You can now set it with config.action_controller.relative_url_root=", caller)
end
# The hash of CGI-like environment variables for this request, such as
#
# { 'SERVER_PROTOCOL' => 'HTTP/1.1', 'HTTP_ACCEPT_LANGUAGE' => 'en-us', ... }
HTTP_METHODS = %w(get head put post delete options)
HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h }
# The hash of environment variables for this request,
# such as { 'RAILS_ENV' => 'production' }.
attr_reader :env
# The true HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
# UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
def request_method
@request_method ||= begin
method = ((@env['REQUEST_METHOD'] == 'POST' && !parameters[:_method].blank?) ? parameters[:_method].to_s : @env['REQUEST_METHOD']).downcase
if ACCEPTED_HTTP_METHODS.include?(method)
method.to_sym
else
raise UnknownHttpMethod, "#{method}, accepted HTTP methods are #{ACCEPTED_HTTP_METHODS.to_a.to_sentence}"
end
end
method = @env['REQUEST_METHOD']
method = parameters[:_method] if method == 'POST' && !parameters[:_method].blank?
HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
end
memoize :request_method
# The HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
# Note, HEAD is returned as <tt>:get</tt> since the two are functionally
@@ -69,34 +69,60 @@ module ActionController
#
# request.headers["Content-Type"] # => "text/plain"
def headers
@headers ||= ActionController::Http::Headers.new(@env)
ActionController::Http::Headers.new(@env)
end
memoize :headers
# Returns the content length of the request as an integer.
def content_length
@content_length ||= env['CONTENT_LENGTH'].to_i
@env['CONTENT_LENGTH'].to_i
end
memoize :content_length
# The MIME type of the HTTP request, such as Mime::XML.
#
# For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
def content_type
@content_type ||= Mime::Type.lookup(content_type_without_parameters)
Mime::Type.lookup(content_type_without_parameters)
end
memoize :content_type
# Returns the accepted MIME type for the request.
def accepts
@accepts ||=
begin
header = @env['HTTP_ACCEPT'].to_s.strip
header = @env['HTTP_ACCEPT'].to_s.strip
if header.empty?
[content_type, Mime::ALL].compact
else
Mime::Type.parse(header)
end
end
if header.empty?
[content_type, Mime::ALL].compact
else
Mime::Type.parse(header)
end
end
memoize :accepts
def if_modified_since
if since = env['HTTP_IF_MODIFIED_SINCE']
Time.rfc2822(since)
end
end
memoize :if_modified_since
def if_none_match
env['HTTP_IF_NONE_MATCH']
end
def not_modified?(modified_at)
if_modified_since && modified_at && if_modified_since >= modified_at
end
def etag_matches?(etag)
if_none_match && if_none_match == etag
end
# Check response freshness (Last-Modified and ETag) against request
# If-Modified-Since and If-None-Match conditions.
def fresh?(response)
not_modified?(response.last_modified) || etag_matches?(response.etag)
end
# Returns the Mime type for the \format used in the request.
@@ -105,7 +131,7 @@ module ActionController
# GET /posts/5.xhtml | request.format => Mime::HTML
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
def format
@format ||= begin
@format ||=
if parameters[:format]
Mime::Type.lookup_by_extension(parameters[:format])
elsif ActionController::Base.use_accept_header
@@ -115,7 +141,6 @@ module ActionController
else
Mime::Type.lookup_by_extension("html")
end
end
end
@@ -203,22 +228,26 @@ 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?
@@ -226,19 +255,36 @@ EOM
end
# Returns the \host for this request, such as "example.com".
def host
def raw_host_with_port
if forwarded = env["HTTP_X_FORWARDED_HOST"]
forwarded.split(/,\s?/).last
else
env['HTTP_HOST'] || env['SERVER_NAME'] || "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
end
end
# Returns the host for this request, such as example.com.
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_with_port ||= host + port_string
"#{host}#{port_string}"
end
memoize :host_with_port
# Returns the port number of this request as an integer.
def port
@port_as_int ||= @env['SERVER_PORT'].to_i
if raw_host_with_port =~ /:(\d+)$/
$1.to_i
else
standard_port
end
end
memoize :port
# Returns the standard \port number for this request's protocol.
def standard_port
@@ -251,7 +297,7 @@ EOM
# Returns a \port suffix like ":8080" if the \port number of this request
# is not the default HTTP \port 80 or HTTPS \port 443.
def port_string
(port == standard_port) ? '' : ":#{port}"
port == standard_port ? '' : ":#{port}"
end
# Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
@@ -280,6 +326,7 @@ EOM
@env['QUERY_STRING'] || ''
end
end
memoize :query_string
# Returns the request URI, accounting for server idiosyncrasies.
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
@@ -289,21 +336,23 @@ EOM
(%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
else
# Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
uri = @env['PATH_INFO']
uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil?
unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty?
uri << '?' << env_qs
uri = @env['PATH_INFO'].to_s
if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
uri = uri.sub(/#{script_filename}\//, '')
end
if uri.nil?
env_qs = @env['QUERY_STRING'].to_s
uri += "?#{env_qs}" unless env_qs.empty?
if uri.blank?
@env.delete('REQUEST_URI')
uri
else
@env['REQUEST_URI'] = uri
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.
@@ -314,6 +363,7 @@ EOM
path.sub!(%r/^#{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.
@@ -350,19 +400,41 @@ EOM
@path_parameters ||= {}
end
# The request body is an IO input stream. If the RAW_POST_DATA environment
# variable is already set, wrap it in a StringIO.
def body
if raw_post = env['RAW_POST_DATA']
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
StringIO.new(raw_post)
else
body_stream
end
end
def remote_addr
@env['REMOTE_ADDR']
end
def referrer
@env['HTTP_REFERER']
end
alias referer referrer
def query_parameters
@query_parameters ||= self.class.parse_query_parameters(query_string)
end
def request_parameters
@request_parameters ||= parse_formatted_request_parameters
end
#--
# Must be implemented in the concrete request
#++
# The request \body as an IO input stream.
def body
end
def query_parameters #:nodoc:
end
def request_parameters #:nodoc:
def body_stream #:nodoc:
end
def cookies #:nodoc:
@@ -389,8 +461,9 @@ EOM
# The raw content type string with its parameters stripped off.
def content_type_without_parameters
@content_type_without_parameters ||= self.class.extract_content_type_without_parameters(content_type_with_parameters)
self.class.extract_content_type_without_parameters(content_type_with_parameters)
end
memoize :content_type_without_parameters
private
def content_type_from_legacy_post_data_format_header

View File

@@ -481,8 +481,7 @@ module ActionController
resource.collection_methods.each do |method, actions|
actions.each do |action|
action_options = action_options_for(action, resource, method)
map.named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}.:format", action_options)
map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
end
end
end
@@ -495,18 +494,15 @@ module ActionController
index_route_name << "_index"
end
map.named_route(index_route_name, resource.path, index_action_options)
map.named_route("formatted_#{index_route_name}", "#{resource.path}.:format", index_action_options)
map_named_routes(map, index_route_name, resource.path, index_action_options)
create_action_options = action_options_for("create", resource)
map.connect(resource.path, create_action_options)
map.connect("#{resource.path}.:format", create_action_options)
map_unnamed_routes(map, resource.path, create_action_options)
end
def map_default_singleton_actions(map, resource)
create_action_options = action_options_for("create", resource)
map.connect(resource.path, create_action_options)
map.connect("#{resource.path}.:format", create_action_options)
map_unnamed_routes(map, resource.path, create_action_options)
end
def map_new_actions(map, resource)
@@ -514,11 +510,9 @@ module ActionController
actions.each do |action|
action_options = action_options_for(action, resource, method)
if action == :new
map.named_route("new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options)
map.named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}.:format", action_options)
map_named_routes(map, "new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options)
else
map.named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
map.named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}.:format", action_options)
map_named_routes(map, "#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
end
end
end
@@ -532,22 +526,28 @@ module ActionController
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
action_path ||= Base.resources_path_names[action] || action
map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}.:format",action_options)
map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
end
end
show_action_options = action_options_for("show", resource)
map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", show_action_options)
map_named_routes(map, "#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
update_action_options = action_options_for("update", resource)
map.connect(resource.member_path, update_action_options)
map.connect("#{resource.member_path}.:format", update_action_options)
map_unnamed_routes(map, resource.member_path, update_action_options)
destroy_action_options = action_options_for("destroy", resource)
map.connect(resource.member_path, destroy_action_options)
map.connect("#{resource.member_path}.:format", destroy_action_options)
map_unnamed_routes(map, resource.member_path, destroy_action_options)
end
def map_unnamed_routes(map, path_without_format, options)
map.connect(path_without_format, options)
map.connect("#{path_without_format}.:format", options)
end
def map_named_routes(map, name, path_without_format, options)
map.named_route(name, path_without_format, options)
map.named_route("formatted_#{name}", "#{path_without_format}.:format", options)
end
def add_conditions_for(conditions, method)
@@ -574,4 +574,4 @@ end
class ActionController::Routing::RouteSet::Mapper
include ActionController::Resources
end
end

View File

@@ -37,12 +37,20 @@ module ActionController # :nodoc:
attr_accessor :body
# The headers of the response, as a Hash. It maps header names to header values.
attr_accessor :headers
attr_accessor :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout
attr_accessor :session, :cookies, :assigns, :template, :layout
attr_accessor :redirected_to, :redirected_to_method_params
def initialize
@body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
end
def status; headers['Status'] end
def status=(status) headers['Status'] = status end
def location; headers['Location'] end
def location=(url) headers['Location'] = url end
# Sets the HTTP response's content MIME type. For example, in the controller
# you could write this:
#
@@ -70,11 +78,23 @@ module ActionController # :nodoc:
charset.blank? ? nil : charset.strip.split("=")[1]
end
def redirect(to_url, response_status)
self.headers["Status"] = response_status
self.headers["Location"] = to_url
def last_modified
Time.rfc2822(headers['Last-Modified'])
end
self.body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
def last_modified=(utc_time)
headers['Last-Modified'] = utc_time.httpdate
end
def etag; headers['ETag'] end
def etag=(etag)
headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
end
def redirect(url, status)
self.status = status
self.location = url
self.body = "<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>"
end
def prepare!
@@ -83,38 +103,20 @@ module ActionController # :nodoc:
set_content_length!
end
# Sets the Last-Modified response header. Returns whether it's older than
# the If-Modified-Since request header.
def last_modified!(utc_time)
headers['Last-Modified'] ||= utc_time.httpdate
if request && since = request.headers['HTTP_IF_MODIFIED_SINCE']
utc_time <= Time.rfc2822(since)
end
end
# Sets the ETag response header. Returns whether it matches the
# If-None-Match request header.
def etag!(tag)
headers['ETag'] ||= %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(tag))}")
if request && request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag']
true
end
end
private
def handle_conditional_get!
if nonempty_ok_response?
set_conditional_cache_control!
if etag!(body)
headers['Status'] = '304 Not Modified'
self.etag ||= body
if request && request.etag_matches?(etag)
self.status = '304 Not Modified'
self.body = ''
end
end
end
def nonempty_ok_response?
status = headers['Status']
ok = !status || status[0..2] == '200'
ok && body.is_a?(String) && !body.empty?
end

View File

@@ -2,7 +2,8 @@ module ActionController
module Routing
class Segment #:nodoc:
RESERVED_PCHAR = ':@&=+$,;'
UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
# TODO: Convert :is_optional accessor to read only
attr_accessor :is_optional

View File

@@ -129,7 +129,7 @@ class CGI::Session::CookieStore
private
# Marshal a session hash into safe cookie data. Include an integrity hash.
def marshal(session)
data = ActiveSupport::Base64.encode64(Marshal.dump(session)).chop
data = ActiveSupport::Base64.encode64s(Marshal.dump(session))
"#{data}--#{generate_digest(data)}"
end

View File

@@ -23,7 +23,7 @@ module ActionController #:nodoc:
class TestRequest < AbstractRequest #:nodoc:
attr_accessor :cookies, :session_options
attr_accessor :query_parameters, :request_parameters, :path, :session, :env
attr_accessor :query_parameters, :request_parameters, :path, :session
attr_accessor :host, :user_agent
def initialize(query_parameters = nil, request_parameters = nil, session = nil)
@@ -42,7 +42,7 @@ module ActionController #:nodoc:
end
# Wraps raw_post in a StringIO.
def body
def body_stream #:nodoc:
StringIO.new(raw_post)
end
@@ -54,7 +54,7 @@ module ActionController #:nodoc:
def port=(number)
@env["SERVER_PORT"] = number.to_i
@port_as_int = nil
port(true)
end
def action=(action_name)
@@ -68,6 +68,8 @@ module ActionController #:nodoc:
@env["REQUEST_URI"] = value
@request_uri = nil
@path = nil
request_uri(true)
path(true)
end
def request_uri=(uri)
@@ -77,21 +79,26 @@ module ActionController #:nodoc:
def accept=(mime_types)
@env["HTTP_ACCEPT"] = Array(mime_types).collect { |mime_types| mime_types.to_s }.join(",")
accepts(true)
end
def if_modified_since=(last_modified)
@env["HTTP_IF_MODIFIED_SINCE"] = last_modified
end
def if_none_match=(etag)
@env["HTTP_IF_NONE_MATCH"] = etag
end
def remote_addr=(addr)
@env['REMOTE_ADDR'] = addr
end
def remote_addr
@env['REMOTE_ADDR']
end
def request_uri
def request_uri(*args)
@request_uri || super
end
def path
def path(*args)
@path || super
end
@@ -113,17 +120,13 @@ module ActionController #:nodoc:
end
end
@parameters = nil # reset TestRequest#parameters to use the new path_parameters
end
end
def recycle!
self.request_parameters = {}
self.query_parameters = {}
self.path_parameters = {}
@request_method, @accepts, @content_type = nil, nil, nil
end
def referer
@env["HTTP_REFERER"]
unmemoize_all
end
private
@@ -448,10 +451,13 @@ module ActionController #:nodoc:
end
def method_missing(selector, *args)
return @controller.send!(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
return super
if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
@controller.send(selector, *args)
else
super
end
end
# Shortcut for <tt>ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type)</tt>:
#
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')

View File

@@ -300,6 +300,8 @@ module ActionView #:nodoc:
# # => 'users/legacy.rhtml'
#
def pick_template(template_path)
return template_path if template_path.respond_to?(:render)
path = template_path.sub(/^\//, '')
if m = path.match(/(.*)\.(\w+)$/)
template_file_name, template_file_extension = m[1], m[2]
@@ -343,7 +345,8 @@ module ActionView #:nodoc:
ActiveSupport::Deprecation.warn("use_full_path option has been deprecated and has no affect.", caller)
end
if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) && !template_path.include?("/")
if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) &&
template_path.is_a?(String) && !template_path.include?("/")
raise ActionViewError, <<-END_ERROR
Due to changes in ActionMailer, you need to provide the mailer_name along with the template name.

View File

@@ -463,7 +463,7 @@ module ActionView
end
private
COMPUTED_PUBLIC_PATHS = ActiveSupport::Cache::MemoryStore.new.silence!.threadsafe!
COMPUTED_PUBLIC_PATHS = ActiveSupport::Cache::MemoryStore.new.silence!
# Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
# Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
@@ -618,6 +618,11 @@ module ActionView
def write_asset_file_contents(joined_asset_path, asset_paths)
FileUtils.mkdir_p(File.dirname(joined_asset_path))
File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
# Set mtime to the latest of the combined files to allow for
# consistent ETag without a shared filesystem.
mt = asset_paths.map { |p| File.mtime(File.join(ASSETS_DIR, p)) }.max
File.utime(mt, mt, joined_asset_path)
end
def collect_asset_files(*path)

View File

@@ -13,9 +13,6 @@ module ActionView
# the select_month method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead of
# "date[month]".
module DateHelper
include ActionView::Helpers::TagHelper
DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX')
# Reports the approximate distance in time between two Time or Date objects or integers as seconds.
# Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs
# Distances are reported based on the following table:
@@ -52,7 +49,7 @@ module ActionView
# distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute
# distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
# distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
# distance_of_time_in_words(from_time, from_time + 4.years + 15.days + 30.minutes + 5.seconds) # => over 4 years
# distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => over 4 years
#
# to_time = Time.now + 6.years + 19.days
# distance_of_time_in_words(from_time, to_time, true) # => over 6 years
@@ -109,19 +106,36 @@ module ActionView
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
# Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based
# attribute (identified by +method+) on an object assigned to the template (identified by +object+). It's
# possible to tailor the selects through the +options+ hash, which accepts all the keys that each of the
# individual select builders do (like <tt>:use_month_numbers</tt> for select_month) as well as a range of discard
# options. The discard options are <tt>:discard_year</tt>, <tt>:discard_month</tt> and <tt>:discard_day</tt>. Set
# to true, they'll drop the respective select. Discarding the month select will also automatically discard the
# day select. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an
# array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. Symbols may be omitted
# and the respective select is not included.
# attribute (identified by +method+) on an object assigned to the template (identified by +object+). You can
# the output in the +options+ hash.
#
# Pass the <tt>:default</tt> option to set the default date. Use a Time object or a Hash of <tt>:year</tt>,
# <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt>, and <tt>:second</tt>.
#
# Passing <tt>:disabled => true</tt> as part of the +options+ will make elements inaccessible for change.
# ==== Options
# * <tt>:use_month_numbers</tt> - Set to true if you want to use month numbers rather than month names (e.g.
# "2" instead of "February").
# * <tt>:use_short_month</tt> - Set to true if you want to use the abbreviated month name instead of the full
# name (e.g. "Feb" instead of "February").
# * <tt>:add_month_number</tt> - Set to true if you want to show both, the month's number and name (e.g.
# "2 - February" instead of "February").
# * <tt>:use_month_names</tt> - Set to an array with 12 month names if you want to customize month names.
# Note: You can also use Rails' new i18n functionality for this.
# * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
# * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Time.now.year - 5</tt>.
# * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Time.now.year + 5</tt>.
# * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day
# as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the
# first of the given month in order to not create invalid dates like 31 February.
# * <tt>:discard_month</tt> - Set to true if you don't want to show a month select. This includes the month
# as a hidden field instead of showing a select field. Also note that this implicitly sets :discard_day to true.
# * <tt>:discard_year</tt> - Set to true if you don't want to show a year select. This includes the year
# as a hidden field instead of showing a select field.
# * <tt>:order</tt> - Set to an array containing <tt>:day</tt>, <tt>:month</tt> and <tt>:year</tt> do
# customize the order in which the select fields are shown. If you leave out any of the symbols, the respective
# select will not be shown (like when you set <tt>:discard_xxx => true</tt>. Defaults to the order defined in
# the respective locale (e.g. [:year, :month, :day] in the en-US locale that ships with Rails).
# * <tt>:include_blank</tt> - Include a blank option in every select field so it's possible to set empty
# dates.
# * <tt>:default</tt> - Set a default date if the affected date isn't set or is nil.
# * <tt>:disabled</tt> - Set to true if you want show the select fields as disabled.
#
# If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
#
@@ -165,9 +179,9 @@ module ActionView
InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options)
end
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified
# time-based attribute (identified by +method+) on an object assigned to the template (identified by +object+).
# You can include the seconds with <tt>:include_seconds</tt>.
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a
# specified time-based attribute (identified by +method+) on an object assigned to the template (identified by
# +object+). You can include the seconds with <tt>:include_seconds</tt>.
#
# This method will also generate 3 input hidden tags, for the actual year, month and day unless the option
# <tt>:ignore_date</tt> is set to +true+.
@@ -178,7 +192,8 @@ module ActionView
# # Creates a time select tag that, when POSTed, will be stored in the post variable in the sunrise attribute
# time_select("post", "sunrise")
#
# # Creates a time select tag that, when POSTed, will be stored in the order variable in the submitted attribute
# # Creates a time select tag that, when POSTed, will be stored in the order variable in the submitted
# # attribute
# time_select("order", "submitted")
#
# # Creates a time select tag that, when POSTed, will be stored in the mail variable in the sent_at attribute
@@ -210,7 +225,8 @@ module ActionView
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
# # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on attribute
# # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on
# # attribute
# datetime_select("post", "written_on")
#
# # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
@@ -230,12 +246,12 @@ module ActionView
InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
end
# Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
# It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol,
# it will be appended onto the <tt>:order</tt> passed in. You can also add <tt>:date_separator</tt>,
# <tt>:datetime_separator</tt> and <tt>:time_separator</tt> keys to the +options+ to control visual display of
# the elements.
# Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the
# +datetime+. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with
# an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not
# supply a Symbol, it will be appended onto the <tt>:order</tt> passed in. You can also add
# <tt>:date_separator</tt>, <tt>:datetime_separator</tt> and <tt>:time_separator</tt> keys to the +options+ to
# control visual display of the elements.
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
@@ -270,14 +286,13 @@ module ActionView
# select_datetime(my_date_time, :prefix => 'payday')
#
def select_datetime(datetime = Time.current, options = {}, html_options = {})
separator = options[:datetime_separator] || ''
select_date(datetime, options, html_options) + separator + select_time(datetime, options, html_options)
DateTimeSelector.new(datetime, options, html_options).select_datetime
end
# Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
# It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
# will be appended onto the <tt>:order</tt> passed in.
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol,
# it will be appended onto the <tt>:order</tt> passed in.
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
@@ -307,12 +322,7 @@ module ActionView
# select_date(my_date, :prefix => 'payday')
#
def select_date(date = Date.current, options = {}, html_options = {})
options.reverse_merge!(:order => [], :date_separator => '')
[:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) }
options[:order].inject([]) { |s, o|
s << self.send("select_#{o}", date, options, html_options)
}.join(options[:date_separator])
DateTimeSelector.new(date, options, html_options).select_date
end
# Returns a set of html select-tags (one for hour and minute)
@@ -343,9 +353,7 @@ module ActionView
# select_time(my_time, :time_separator => ':', :include_seconds => true)
#
def select_time(datetime = Time.current, options = {}, html_options = {})
separator = options[:time_separator] || ''
select_hour(datetime, options, html_options) + separator + select_minute(datetime, options, html_options) +
(options[:include_seconds] ? separator + select_second(datetime, options, html_options) : '')
DateTimeSelector.new(datetime, options, html_options).select_time
end
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
@@ -366,15 +374,12 @@ module ActionView
# select_second(my_time, :field_name => 'interval')
#
def select_second(datetime, options = {}, html_options = {})
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) : ''
options[:use_hidden] ?
(options[:include_seconds] ? _date_hidden_html(options[:field_name] || 'second', val, options) : '') :
_date_select_html(options[:field_name] || 'second', _date_build_options(val), options, html_options)
DateTimeSelector.new(datetime, options, html_options).select_second
end
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
# Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute selected
# The <tt>minute</tt> can also be substituted for a minute number.
# Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute
# selected. The <tt>minute</tt> can also be substituted for a minute number.
# Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
#
# ==== Examples
@@ -391,11 +396,7 @@ module ActionView
# select_minute(my_time, :field_name => 'stride')
#
def select_minute(datetime, options = {}, html_options = {})
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.min) : ''
options[:use_hidden] ?
_date_hidden_html(options[:field_name] || 'minute', val, options) :
_date_select_html(options[:field_name] || 'minute',
_date_build_options(val, :step => options[:minute_step]), options, html_options)
DateTimeSelector.new(datetime, options, html_options).select_minute
end
# Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
@@ -416,9 +417,7 @@ module ActionView
# select_minute(my_time, :field_name => 'stride')
#
def select_hour(datetime, options = {}, html_options = {})
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) : ''
options[:use_hidden] ? _date_hidden_html(options[:field_name] || 'hour', val, options) :
_date_select_html(options[:field_name] || 'hour', _date_build_options(val, :end => 23), options, html_options)
DateTimeSelector.new(datetime, options, html_options).select_hour
end
# Returns a select tag with options for each of the days 1 through 31 with the current day selected.
@@ -439,11 +438,7 @@ module ActionView
# select_day(my_time, :field_name => 'due')
#
def select_day(date, options = {}, html_options = {})
val = date ? (date.kind_of?(Fixnum) ? date : date.day) : ''
options[:use_hidden] ? _date_hidden_html(options[:field_name] || 'day', val, options) :
_date_select_html(options[:field_name] || 'day',
_date_build_options(val, :start => 1, :end => 31, :leading_zeros => false),
options, html_options)
DateTimeSelector.new(date, options, html_options).select_day
end
# Returns a select tag with options for each of the months January through December with the current month
@@ -481,36 +476,7 @@ module ActionView
# select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...))
#
def select_month(date, options = {}, html_options = {})
locale = options[:locale]
val = date ? (date.kind_of?(Fixnum) ? date : date.month) : ''
if options[:use_hidden]
_date_hidden_html(options[:field_name] || 'month', val, options)
else
month_options = []
month_names = options[:use_month_names] || begin
key = options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names'
I18n.translate key, :locale => locale
end
month_names.unshift(nil) if month_names.size < 13
1.upto(12) do |month_number|
month_name = if options[:use_month_numbers]
month_number
elsif options[:add_month_numbers]
month_number.to_s + ' - ' + month_names[month_number]
else
month_names[month_number]
end
month_options << ((val == month_number) ?
content_tag(:option, month_name, :value => month_number, :selected => "selected") :
content_tag(:option, month_name, :value => month_number)
)
month_options << "\n"
end
_date_select_html(options[:field_name] || 'month', month_options.join, options, html_options)
end
DateTimeSelector.new(date, options, html_options).select_month
end
# Returns a select tag with options for each of the five years on each side of the current, which is selected.
@@ -537,158 +503,369 @@ module ActionView
# select_year(2006, :start_year => 2000, :end_year => 2010)
#
def select_year(date, options = {}, html_options = {})
if !date || date == 0
val = ''
middle_year = Date.today.year
elsif date.kind_of?(Fixnum)
val = middle_year = date
DateTimeSelector.new(date, options, html_options).select_year
end
end
class DateTimeSelector #:nodoc:
extend ActiveSupport::Memoizable
include ActionView::Helpers::TagHelper
DEFAULT_PREFIX = 'date'.freeze unless const_defined?('DEFAULT_PREFIX')
POSITION = {
:year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6
}.freeze unless const_defined?('POSITION')
def initialize(datetime, options = {}, html_options = {})
@options = options.dup
@html_options = html_options.dup
@datetime = datetime
end
def select_datetime
# TODO: Remove tag conditional
# Ideally we could just join select_date and select_date for the tag case
if @options[:tag] && @options[:ignore_date]
select_time
elsif @options[:tag]
order = date_order.dup
order -= [:hour, :minute, :second]
@options[:discard_year] ||= true unless order.include?(:year)
@options[:discard_month] ||= true unless order.include?(:month)
@options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
@options[:discard_minute] ||= true if @options[:discard_hour]
@options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute]
# If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
# valid (otherwise it could be 31 and february wouldn't be a valid date)
if @options[:discard_day] && !@options[:discard_month]
@datetime = @datetime.change(:day => 1)
end
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
order += [:hour, :minute, :second] unless @options[:discard_hour]
build_selects_from_types(order)
else
val = middle_year = date.year
"#{select_date}#{@options[:datetime_separator]}#{select_time}"
end
end
def select_date
order = date_order.dup
# TODO: Remove tag conditional
if @options[:tag]
@options[:discard_hour] = true
@options[:discard_minute] = true
@options[:discard_second] = true
@options[:discard_year] ||= true unless order.include?(:year)
@options[:discard_month] ||= true unless order.include?(:month)
@options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
# If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
# valid (otherwise it could be 31 and february wouldn't be a valid date)
if @options[:discard_day] && !@options[:discard_month]
@datetime = @datetime.change(:day => 1)
end
end
if options[:use_hidden]
_date_hidden_html(options[:field_name] || 'year', val, options)
else
options[:start_year] ||= middle_year - 5
options[:end_year] ||= middle_year + 5
step = options[:start_year] < options[:end_year] ? 1 : -1
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
_date_select_html(options[:field_name] || 'year',
_date_build_options(val,
:start => options[:start_year],
:end => options[:end_year],
:step => step,
:leading_zeros => false
), options, html_options)
build_selects_from_types(order)
end
def select_time
order = []
# TODO: Remove tag conditional
if @options[:tag]
@options[:discard_month] = true
@options[:discard_year] = true
@options[:discard_day] = true
@options[:discard_second] ||= true unless @options[:include_seconds]
order += [:year, :month, :day] unless @options[:ignore_date]
end
order += [:hour, :minute]
order << :second if @options[:include_seconds]
build_selects_from_types(order)
end
def select_second
if @options[:use_hidden] || @options[:discard_second]
build_hidden(:second, sec) if @options[:include_seconds]
else
build_options_and_select(:second, sec)
end
end
def select_minute
if @options[:use_hidden] || @options[:discard_minute]
build_hidden(:minute, min)
else
build_options_and_select(:minute, min, :step => @options[:minute_step])
end
end
def select_hour
if @options[:use_hidden] || @options[:discard_hour]
build_hidden(:hour, hour)
else
build_options_and_select(:hour, hour, :end => 23)
end
end
def select_day
if @options[:use_hidden] || @options[:discard_day]
build_hidden(:day, day)
else
build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false)
end
end
def select_month
if @options[:use_hidden] || @options[:discard_month]
build_hidden(:month, month)
else
month_options = []
1.upto(12) do |month_number|
options = { :value => month_number }
options[:selected] = "selected" if month == month_number
month_options << content_tag(:option, month_name(month_number), options) + "\n"
end
build_select(:month, month_options.join)
end
end
def select_year
if !@datetime || @datetime == 0
val = ''
middle_year = Date.today.year
else
val = middle_year = year
end
if @options[:use_hidden] || @options[:discard_year]
build_hidden(:year, val)
else
options = {}
options[:start] = @options[:start_year] || middle_year - 5
options[:end] = @options[:end_year] || middle_year + 5
options[:step] = options[:start] < options[:end] ? 1 : -1
options[:leading_zeros] = false
build_options_and_select(:year, val, options)
end
end
private
def _date_build_options(selected, options={})
options.reverse_merge!(:start => 0, :end => 59, :step => 1, :leading_zeros => true)
%w( sec min hour day month year ).each do |method|
define_method(method) do
@datetime.kind_of?(Fixnum) ? @datetime : @datetime.send(method) if @datetime
end
end
# Returns translated month names, but also ensures that a custom month
# name array has a leading nil element
def month_names
month_names = @options[:use_month_names] || translated_month_names
month_names.unshift(nil) if month_names.size < 13
month_names
end
memoize :month_names
# Returns translated month names
# => [nil, "January", "February", "March",
# "April", "May", "June", "July",
# "August", "September", "October",
# "November", "December"]
#
# If :use_short_month option is set
# => [nil, "Jan", "Feb", "Mar", "Apr", "May", "Jun",
# "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
def translated_month_names
begin
key = @options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names'
I18n.translate(key, :locale => @options[:locale])
end
end
# Lookup month name for number
# month_name(1) => "January"
#
# If :use_month_numbers option is passed
# month_name(1) => 1
#
# If :add_month_numbers option is passed
# month_name(1) => "1 - January"
def month_name(number)
if @options[:use_month_numbers]
number
elsif @options[:add_month_numbers]
"#{number} - #{month_names[number]}"
else
month_names[number]
end
end
def date_order
@options[:order] || translated_date_order
end
memoize :date_order
def translated_date_order
begin
I18n.translate(:'date.order', :locale => @options[:locale]) || []
end
end
# Build full select tag from date type and options
def build_options_and_select(type, selected, options = {})
build_select(type, build_options(selected, options))
end
# Build select option html from date value and options
# build_options(15, :start => 1, :end => 31)
# => "<option value="1">1</option>
# <option value=\"2\">2</option>
# <option value=\"3\">3</option>..."
def build_options(selected, options = {})
start = options.delete(:start) || 0
stop = options.delete(:end) || 59
step = options.delete(:step) || 1
leading_zeros = options.delete(:leading_zeros).nil? ? true : false
select_options = []
(options[:start] || 0).step((options[:end] || 59), options[:step] || 1) do |i|
value = options[:leading_zeros] ? sprintf("%02d", i) : i
start.step(stop, step) do |i|
value = leading_zeros ? sprintf("%02d", i) : i
tag_options = { :value => value }
tag_options[:selected] = "selected" if selected == i
select_options << content_tag(:option, value, tag_options)
end
select_options.join("\n") + "\n"
end
def _date_select_html(type, html_options, options, select_tag_options = {})
_date_name_and_id_from_options(options, type)
select_options = {:id => options[:id], :name => options[:name]}
select_options.merge!(:disabled => 'disabled') if options[:disabled]
select_options.merge!(select_tag_options) unless select_tag_options.empty?
# Builds select tag from date type and html select options
# build_select(:month, "<option value="1">January</option>...")
# => "<select id="post_written_on_2i" name="post[written_on(2i)]">
# <option value="1">January</option>...
# </select>"
def build_select(type, select_options_as_html)
select_options = {
:id => input_id_from_type(type),
:name => input_name_from_type(type)
}.merge(@html_options)
select_options.merge!(:disabled => 'disabled') if @options[:disabled]
select_html = "\n"
select_html << content_tag(:option, '', :value => '') + "\n" if options[:include_blank]
select_html << html_options.to_s
select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
select_html << select_options_as_html.to_s
content_tag(:select, select_html, select_options) + "\n"
end
def _date_hidden_html(type, value, options)
_date_name_and_id_from_options(options, type)
hidden_html = tag(:input, :type => "hidden", :id => options[:id], :name => options[:name], :value => value) + "\n"
# Builds hidden input tag for date part and value
# build_hidden(:year, 2008)
# => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />"
def build_hidden(type, value)
tag(:input, {
:type => "hidden",
:id => input_id_from_type(type),
:name => input_name_from_type(type),
:value => value
}) + "\n"
end
def _date_name_and_id_from_options(options, type)
options[:name] = (options[:prefix] || DEFAULT_PREFIX) + (options[:discard_type] ? '' : "[#{type}]")
options[:id] = options[:name].gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
# Returns the name attribute for the input tag
# => post[written_on(1i)]
def input_name_from_type(type)
prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX
prefix += "[#{@options[:index]}]" if @options[:index]
field_name = @options[:field_name] || type
if @options[:include_position]
field_name += "(#{ActionView::Helpers::DateTimeSelector::POSITION[type]}i)"
end
@options[:discard_type] ? prefix : "#{prefix}[#{field_name}]"
end
# Returns the id attribute for the input tag
# => "post_written_on_1i"
def input_id_from_type(type)
input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
end
# Given an ordering of datetime components, create the selection html
# and join them with their appropriate seperators
def build_selects_from_types(order)
select = ''
order.reverse.each do |type|
separator = separator(type) unless type == order.first # don't add on last field
select.insert(0, separator.to_s + send("select_#{type}").to_s)
end
select
end
# Returns the separator for a given datetime component
def separator(type)
case type
when :month, :day
@options[:date_separator]
when :hour
(@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
when :minute
@options[:time_separator]
when :second
@options[:include_seconds] ? @options[:time_separator] : ""
end
end
end
class InstanceTag #:nodoc:
include DateHelper
def to_date_select_tag(options = {}, html_options = {})
date_or_time_select(options.merge(:discard_hour => true), html_options)
datetime_selector(options, html_options).select_date
end
def to_time_select_tag(options = {}, html_options = {})
date_or_time_select(options.merge(:discard_year => true, :discard_month => true), html_options)
datetime_selector(options, html_options).select_time
end
def to_datetime_select_tag(options = {}, html_options = {})
date_or_time_select(options, html_options)
datetime_selector(options, html_options).select_datetime
end
private
def date_or_time_select(options, html_options = {})
locale = options[:locale]
def datetime_selector(options, html_options)
datetime = value(object) || default_datetime(options)
defaults = { :discard_type => true }
options = defaults.merge(options)
datetime = value(object)
datetime ||= default_time_from_options(options[:default]) unless options[:include_blank]
options = options.dup
options[:field_name] = @method_name
options[:include_position] = true
options[:prefix] ||= @object_name
options[:index] ||= @auto_index
options[:datetime_separator] ||= ' &mdash; '
options[:time_separator] ||= ' : '
position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
order = options[:order] ||= I18n.translate(:'date.order', :locale => locale)
# Discard explicit and implicit by not being included in the :order
discard = {}
discard[:year] = true if options[:discard_year] or !order.include?(:year)
discard[:month] = true if options[:discard_month] or !order.include?(:month)
discard[:day] = true if options[:discard_day] or discard[:month] or !order.include?(:day)
discard[:hour] = true if options[:discard_hour]
discard[:minute] = true if options[:discard_minute] or discard[:hour]
discard[:second] = true unless options[:include_seconds] && !discard[:minute]
# If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are valid
# (otherwise it could be 31 and february wouldn't be a valid date)
if datetime && discard[:day] && !discard[:month]
datetime = datetime.change(:day => 1)
end
# Maintain valid dates by including hidden fields for discarded elements
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
# Ensure proper ordering of :hour, :minute and :second
[:hour, :minute, :second].each { |o| order.delete(o); order.push(o) }
date_or_time_select = ''
order.reverse.each do |param|
# Send hidden fields for discarded elements once output has started
# This ensures AR can reconstruct valid dates using ParseDate
next if discard[param] && (date_or_time_select.empty? || options[:ignore_date])
date_or_time_select.insert(0,
self.send("select_#{param}",
datetime,
options_with_prefix(position[param], options.merge(:use_hidden => discard[param])),
html_options))
date_or_time_select.insert(0,
case param
when :hour then (discard[:year] && discard[:day] ? "" : " &mdash; ")
when :minute then " : "
when :second then options[:include_seconds] ? " : " : ""
else ""
end)
end
date_or_time_select
DateTimeSelector.new(datetime, options.merge(:tag => true), html_options)
end
def options_with_prefix(position, options)
prefix = "#{@object_name}"
if options[:index]
prefix << "[#{options[:index]}]"
elsif @auto_index
prefix << "[#{@auto_index}]"
end
options.merge(:prefix => "#{prefix}[#{@method_name}(#{position}i)]")
end
def default_datetime(options)
return if options[:include_blank]
def default_time_from_options(default)
case default
case options[:default]
when nil
Time.current
when Date, Time
default
options[:default]
else
default = options[:default].dup
# Rename :minute and :second to :min and :sec
default[:min] ||= default[:minute]
default[:sec] ||= default[:second]
@@ -699,8 +876,11 @@ module ActionView
default[key] ||= time.send(key)
end
Time.utc_time(default[:year], default[:month], default[:day], default[:hour], default[:min], default[:sec])
end
Time.utc_time(
default[:year], default[:month], default[:day],
default[:hour], default[:min], default[:sec]
)
end
end
end

View File

@@ -71,9 +71,9 @@ module ActionView
def number_to_currency(number, options = {})
options.symbolize_keys!
defaults, currency = I18n.translate([:'number.format', :'number.currency.format'],
:locale => options[:locale]) || [{},{}]
defaults = defaults.merge(currency)
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :raise => true) rescue {}
defaults = defaults.merge(currency)
precision = options[:precision] || defaults[:precision]
unit = options[:unit] || defaults[:unit]
@@ -109,9 +109,9 @@ module ActionView
def number_to_percentage(number, options = {})
options.symbolize_keys!
defaults, percentage = I18n.translate([:'number.format', :'number.percentage.format'],
:locale => options[:locale]) || [{},{}]
defaults = defaults.merge(percentage)
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :raise => true) rescue {}
defaults = defaults.merge(percentage)
precision = options[:precision] || defaults[:precision]
separator = options[:separator] || defaults[:separator]
@@ -151,7 +151,7 @@ module ActionView
options = args.extract_options!
options.symbolize_keys!
defaults = I18n.translate(:'number.format', :locale => options[:locale]) || {}
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
unless args.empty?
ActiveSupport::Deprecation.warn('number_with_delimiter takes an option hash ' +
@@ -195,9 +195,10 @@ module ActionView
options = args.extract_options!
options.symbolize_keys!
defaults, precision_defaults = I18n.translate([:'number.format', :'number.precision.format'],
:locale => options[:locale]) || [{},{}]
defaults = defaults.merge(precision_defaults)
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale],
:raise => true) rescue {}
defaults = defaults.merge(precision_defaults)
unless args.empty?
ActiveSupport::Deprecation.warn('number_with_precision takes an option hash ' +
@@ -209,12 +210,14 @@ module ActionView
separator ||= (options[:separator] || defaults[:separator])
delimiter ||= (options[:delimiter] || defaults[:delimiter])
rounded_number = (Float(number) * (10 ** precision)).round.to_f / 10 ** precision
number_with_delimiter("%01.#{precision}f" % rounded_number,
:separator => separator,
:delimiter => delimiter)
rescue
number
begin
rounded_number = (Float(number) * (10 ** precision)).round.to_f / 10 ** precision
number_with_delimiter("%01.#{precision}f" % rounded_number,
:separator => separator,
:delimiter => delimiter)
rescue
number
end
end
STORAGE_UNITS = %w( Bytes KB MB GB TB ).freeze
@@ -251,8 +254,8 @@ module ActionView
options = args.extract_options!
options.symbolize_keys!
defaults, human = I18n.translate([:'number.format', :'number.human.format'],
:locale => options[:locale]) || [{},{}]
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)
unless args.empty?
@@ -272,13 +275,16 @@ module ActionView
number /= 1024 ** exponent
unit = STORAGE_UNITS[exponent]
number_with_precision(number,
:precision => precision,
:separator => separator,
:delimiter => delimiter
).sub(/(\d)(#{Regexp.escape(separator)}[1-9]*)?0+\z/, '\1') + " #{unit}"
rescue
number
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
end
end
end
end

View File

@@ -558,7 +558,7 @@ module ActionView
[-\w]+ # subdomain or domain
(?:\.[-\w]+)* # remaining subdomains or domain
(?::\d+)? # port
(?:/(?:(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$])))*)* # path
(?:/(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$]))*)* # path
(?:\?[\w\+@%&=.;-]+)? # query string
(?:\#[\w\-]*)? # trailing anchor
)

View File

@@ -442,7 +442,7 @@ module ActionView
# # => <a href="mailto:me@domain.com">me@domain.com</a>
#
# mail_to "me@domain.com", "My email", :encode => "javascript"
# # => <script type="text/javascript">eval(unescape('%64%6f%63...%6d%65%6e'))</script>
# # => <script type="text/javascript">eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script>
#
# mail_to "me@domain.com", "My email", :encode => "hex"
# # => <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
@@ -476,7 +476,7 @@ module ActionView
"document.write('#{content_tag("a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
string << sprintf("%%%x", c)
end
"<script type=\"#{Mime::JS}\">eval(unescape('#{string}'))</script>"
"<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>"
elsif encode == "hex"
email_address_encoded = ''
email_address_obfuscated.each_byte do |c|

View File

@@ -146,7 +146,7 @@ module ActionView
def find_partial_path(partial_path)
if partial_path.include?('/')
"#{File.dirname(partial_path)}/_#{File.basename(partial_path)}"
File.join(File.dirname(partial_path), "_#{File.basename(partial_path)}")
elsif respond_to?(:controller)
"#{controller.class.controller_path}/_#{partial_path}"
else

View File

@@ -31,10 +31,10 @@ module ActionView
view.send(:evaluate_assigns)
view.send(:set_controller_content_type, mime_type) if respond_to?(:mime_type)
view.send(:execute, method(local_assigns), local_assigns)
view.send(:execute, method_name(local_assigns), local_assigns)
end
def method(local_assigns)
def method_name(local_assigns)
if local_assigns && local_assigns.any?
local_assigns_keys = "locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}"
end
@@ -44,7 +44,7 @@ module ActionView
private
# Compile and evaluate the template's code (if necessary)
def compile(local_assigns)
render_symbol = method(local_assigns)
render_symbol = method_name(local_assigns)
@@mutex.synchronize do
if recompile?(render_symbol)

View File

@@ -22,6 +22,14 @@ module ActionView #:nodoc:
end
memoize :format_and_extension
def multipart?
format && format.include?('.')
end
def content_type
format.gsub('.', '/')
end
def mime_type
Mime::Type.lookup_by_extension(format) if format
end
@@ -84,7 +92,7 @@ module ActionView #:nodoc:
# [base_path, name, format, extension]
def split(file)
if m = file.match(/^(.*\/)?([^\.]+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/)
if m[5] # Mulipart formats
if m[5] # Multipart formats
[m[1], m[2], "#{m[3]}.#{m[4]}", m[5]]
elsif m[4] # Single format
[m[1], m[2], m[3], m[4]]

View File

@@ -17,6 +17,8 @@ unless defined?(ActionMailer)
end
end
ActionMailer::Base.template_root = FIXTURE_LOAD_PATH
class AssertSelectTest < Test::Unit::TestCase
class AssertSelectController < ActionController::Base
def response_with=(content)
@@ -69,11 +71,10 @@ class AssertSelectTest < Test::Unit::TestCase
ActionMailer::Base.deliveries = []
end
def teardown
ActionMailer::Base.deliveries.clear
end
def assert_failure(message, &block)
e = assert_raises(AssertionFailedError, &block)
assert_match(message, e.message) if Regexp === message
@@ -91,7 +92,6 @@ class AssertSelectTest < Test::Unit::TestCase
assert_failure(/Expected at least 1 element matching \"p\", found 0/) { assert_select "p" }
end
def test_equality_true_false
render_html %Q{<div id="1"></div><div id="2"></div>}
assert_nothing_raised { assert_select "div" }
@@ -102,7 +102,6 @@ class AssertSelectTest < Test::Unit::TestCase
assert_nothing_raised { assert_select "p", false }
end
def test_equality_string_and_regexp
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
assert_nothing_raised { assert_select "div", "foo" }
@@ -116,7 +115,6 @@ class AssertSelectTest < Test::Unit::TestCase
assert_raises(AssertionFailedError) { assert_select "p", :text=>/foobar/ }
end
def test_equality_of_html
render_html %Q{<p>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</p>}
text = "\"This is not a big problem,\" he said."
@@ -135,7 +133,6 @@ class AssertSelectTest < Test::Unit::TestCase
assert_raises(AssertionFailedError) { assert_select "pre", :html=>text }
end
def test_counts
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
assert_nothing_raised { assert_select "div", 2 }
@@ -166,7 +163,6 @@ class AssertSelectTest < Test::Unit::TestCase
end
end
def test_substitution_values
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
assert_select "div#?", /\d+/ do |elements|
@@ -181,7 +177,6 @@ class AssertSelectTest < Test::Unit::TestCase
end
end
def test_nested_assert_select
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
assert_select "div" do |elements|
@@ -200,7 +195,7 @@ class AssertSelectTest < Test::Unit::TestCase
assert_select "#3", false
end
end
assert_failure(/Expected at least 1 element matching \"#4\", found 0\./) do
assert_select "div" do
assert_select "#4"
@@ -208,7 +203,6 @@ class AssertSelectTest < Test::Unit::TestCase
end
end
def test_assert_select_text_match
render_html %Q{<div id="1"><span>foo</span></div><div id="2"><span>bar</span></div>}
assert_select "div" do
@@ -225,7 +219,6 @@ class AssertSelectTest < Test::Unit::TestCase
end
end
# With single result.
def test_assert_select_from_rjs_with_single_result
render_rjs do |page|
@@ -255,19 +248,16 @@ class AssertSelectTest < Test::Unit::TestCase
end
end
#
# Test css_select.
#
def test_css_select
render_html %Q{<div id="1"></div><div id="2"></div>}
assert 2, css_select("div").size
assert 0, css_select("p").size
end
def test_nested_css_select
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
assert_select "div#?", /\d+/ do |elements|
@@ -286,7 +276,6 @@ class AssertSelectTest < Test::Unit::TestCase
end
end
# With one result.
def test_css_select_from_rjs_with_single_result
render_rjs do |page|
@@ -309,12 +298,10 @@ class AssertSelectTest < Test::Unit::TestCase
assert_equal 1, css_select("#2").size
end
#
# Test assert_select_rjs.
#
# Test that we can pick up all statements in the result.
def test_assert_select_rjs_picks_up_all_statements
render_rjs do |page|
@@ -381,7 +368,6 @@ class AssertSelectTest < Test::Unit::TestCase
assert_raises(AssertionFailedError) { assert_select_rjs "test4" }
end
def test_assert_select_rjs_for_replace
render_rjs do |page|
page.replace "test1", "<div id=\"1\">foo</div>"
@@ -479,7 +465,7 @@ class AssertSelectTest < Test::Unit::TestCase
end
end
end
# Simple hide
def test_assert_select_rjs_for_hide
render_rjs do |page|
@@ -500,7 +486,7 @@ class AssertSelectTest < Test::Unit::TestCase
end
end
end
# Simple toggle
def test_assert_select_rjs_for_toggle
render_rjs do |page|
@@ -521,7 +507,7 @@ class AssertSelectTest < Test::Unit::TestCase
end
end
end
# Non-positioned insert.
def test_assert_select_rjs_for_nonpositioned_insert
render_rjs do |page|
@@ -568,7 +554,7 @@ class AssertSelectTest < Test::Unit::TestCase
assert_select "div", 4
end
end
# Simple selection from a single result.
def test_nested_assert_select_rjs_with_single_result
render_rjs do |page|
@@ -600,7 +586,6 @@ class AssertSelectTest < Test::Unit::TestCase
end
end
def test_feed_item_encoded
render_xml <<-EOF
<rss version="2.0">
@@ -654,7 +639,6 @@ EOF
end
end
#
# Test assert_select_email
#
@@ -670,7 +654,6 @@ EOF
end
end
protected
def render_html(html)
@controller.response_with = html

View File

@@ -109,7 +109,7 @@ class PageCachingTest < Test::Unit::TestCase
uses_mocha("should_cache_ok_at_custom_path") do
def test_should_cache_ok_at_custom_path
@request.expects(:path).returns("/index.html")
@request.stubs(:path).returns("/index.html")
get :ok
assert_response :ok
assert File.exist?("#{FILE_STORE_PATH}/index.html")

View File

@@ -75,7 +75,7 @@ class CgiRequestTest < BaseCgiTest
assert_equal "rubyonrails.org:8080", @request.host_with_port
@request_hash['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
assert_equal "www.secondhost.org", @request.host
assert_equal "www.secondhost.org", @request.host(true)
end
def test_http_host_with_default_port_overrides_server_port

View File

@@ -128,23 +128,23 @@ class AcceptBasedContentTypeTest < ActionController::TestCase
def test_render_default_content_types_for_respond_to
@request.env["HTTP_ACCEPT"] = Mime::HTML.to_s
@request.accept = Mime::HTML.to_s
get :render_default_content_types_for_respond_to
assert_equal Mime::HTML, @response.content_type
@request.env["HTTP_ACCEPT"] = Mime::JS.to_s
@request.accept = Mime::JS.to_s
get :render_default_content_types_for_respond_to
assert_equal Mime::JS, @response.content_type
end
def test_render_default_content_types_for_respond_to_with_template
@request.env["HTTP_ACCEPT"] = Mime::XML.to_s
@request.accept = Mime::XML.to_s
get :render_default_content_types_for_respond_to
assert_equal Mime::XML, @response.content_type
end
def test_render_default_content_types_for_respond_to_with_overwrite
@request.env["HTTP_ACCEPT"] = Mime::RSS.to_s
@request.accept = Mime::RSS.to_s
get :render_default_content_types_for_respond_to
assert_equal Mime::XML, @response.content_type
end

View File

@@ -177,7 +177,7 @@ class MimeControllerTest < Test::Unit::TestCase
end
def test_html
@request.env["HTTP_ACCEPT"] = "text/html"
@request.accept = "text/html"
get :js_or_html
assert_equal 'HTML', @response.body
@@ -189,7 +189,7 @@ class MimeControllerTest < Test::Unit::TestCase
end
def test_all
@request.env["HTTP_ACCEPT"] = "*/*"
@request.accept = "*/*"
get :js_or_html
assert_equal 'HTML', @response.body # js is not part of all
@@ -201,13 +201,13 @@ class MimeControllerTest < Test::Unit::TestCase
end
def test_xml
@request.env["HTTP_ACCEPT"] = "application/xml"
@request.accept = "application/xml"
get :html_xml_or_rss
assert_equal 'XML', @response.body
end
def test_js_or_html
@request.env["HTTP_ACCEPT"] = "text/javascript, text/html"
@request.accept = "text/javascript, text/html"
get :js_or_html
assert_equal 'JS', @response.body
@@ -232,7 +232,7 @@ class MimeControllerTest < Test::Unit::TestCase
'JSON' => %w(application/json text/x-json)
}.each do |body, content_types|
content_types.each do |content_type|
@request.env['HTTP_ACCEPT'] = content_type
@request.accept = content_type
get :json_or_yaml
assert_equal body, @response.body
end
@@ -240,7 +240,7 @@ class MimeControllerTest < Test::Unit::TestCase
end
def test_js_or_anything
@request.env["HTTP_ACCEPT"] = "text/javascript, */*"
@request.accept = "text/javascript, */*"
get :js_or_html
assert_equal 'JS', @response.body
@@ -252,34 +252,34 @@ class MimeControllerTest < Test::Unit::TestCase
end
def test_using_defaults
@request.env["HTTP_ACCEPT"] = "*/*"
@request.accept = "*/*"
get :using_defaults
assert_equal "text/html", @response.content_type
assert_equal 'Hello world!', @response.body
@request.env["HTTP_ACCEPT"] = "text/javascript"
@request.accept = "text/javascript"
get :using_defaults
assert_equal "text/javascript", @response.content_type
assert_equal '$("body").visualEffect("highlight");', @response.body
@request.env["HTTP_ACCEPT"] = "application/xml"
@request.accept = "application/xml"
get :using_defaults
assert_equal "application/xml", @response.content_type
assert_equal "<p>Hello world!</p>\n", @response.body
end
def test_using_defaults_with_type_list
@request.env["HTTP_ACCEPT"] = "*/*"
@request.accept = "*/*"
get :using_defaults_with_type_list
assert_equal "text/html", @response.content_type
assert_equal 'Hello world!', @response.body
@request.env["HTTP_ACCEPT"] = "text/javascript"
@request.accept = "text/javascript"
get :using_defaults_with_type_list
assert_equal "text/javascript", @response.content_type
assert_equal '$("body").visualEffect("highlight");', @response.body
@request.env["HTTP_ACCEPT"] = "application/xml"
@request.accept = "application/xml"
get :using_defaults_with_type_list
assert_equal "application/xml", @response.content_type
assert_equal "<p>Hello world!</p>\n", @response.body
@@ -298,55 +298,55 @@ class MimeControllerTest < Test::Unit::TestCase
end
def test_synonyms
@request.env["HTTP_ACCEPT"] = "application/javascript"
@request.accept = "application/javascript"
get :js_or_html
assert_equal 'JS', @response.body
@request.env["HTTP_ACCEPT"] = "application/x-xml"
@request.accept = "application/x-xml"
get :html_xml_or_rss
assert_equal "XML", @response.body
end
def test_custom_types
@request.env["HTTP_ACCEPT"] = "application/crazy-xml"
@request.accept = "application/crazy-xml"
get :custom_type_handling
assert_equal "application/crazy-xml", @response.content_type
assert_equal 'Crazy XML', @response.body
@request.env["HTTP_ACCEPT"] = "text/html"
@request.accept = "text/html"
get :custom_type_handling
assert_equal "text/html", @response.content_type
assert_equal 'HTML', @response.body
end
def test_xhtml_alias
@request.env["HTTP_ACCEPT"] = "application/xhtml+xml,application/xml"
@request.accept = "application/xhtml+xml,application/xml"
get :html_or_xml
assert_equal 'HTML', @response.body
end
def test_firefox_simulation
@request.env["HTTP_ACCEPT"] = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
@request.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
get :html_or_xml
assert_equal 'HTML', @response.body
end
def test_handle_any
@request.env["HTTP_ACCEPT"] = "*/*"
@request.accept = "*/*"
get :handle_any
assert_equal 'HTML', @response.body
@request.env["HTTP_ACCEPT"] = "text/javascript"
@request.accept = "text/javascript"
get :handle_any
assert_equal 'Either JS or XML', @response.body
@request.env["HTTP_ACCEPT"] = "text/xml"
@request.accept = "text/xml"
get :handle_any
assert_equal 'Either JS or XML', @response.body
end
def test_handle_any_any
@request.env["HTTP_ACCEPT"] = "*/*"
@request.accept = "*/*"
get :handle_any_any
assert_equal 'HTML', @response.body
end
@@ -357,31 +357,31 @@ class MimeControllerTest < Test::Unit::TestCase
end
def test_handle_any_any_explicit_html
@request.env["HTTP_ACCEPT"] = "text/html"
@request.accept = "text/html"
get :handle_any_any
assert_equal 'HTML', @response.body
end
def test_handle_any_any_javascript
@request.env["HTTP_ACCEPT"] = "text/javascript"
@request.accept = "text/javascript"
get :handle_any_any
assert_equal 'Whatever you ask for, I got it', @response.body
end
def test_handle_any_any_xml
@request.env["HTTP_ACCEPT"] = "text/xml"
@request.accept = "text/xml"
get :handle_any_any
assert_equal 'Whatever you ask for, I got it', @response.body
end
def test_rjs_type_skips_layout
@request.env["HTTP_ACCEPT"] = "text/javascript"
@request.accept = "text/javascript"
get :all_types_with_layout
assert_equal 'RJS for all_types_with_layout', @response.body
end
def test_html_type_with_layout
@request.env["HTTP_ACCEPT"] = "text/html"
@request.accept = "text/html"
get :all_types_with_layout
assert_equal '<html><div id="html">HTML for all_types_with_layout</div></html>', @response.body
end
@@ -460,7 +460,7 @@ class MimeControllerTest < Test::Unit::TestCase
end
def test_format_with_custom_response_type_and_request_headers
@request.env["HTTP_ACCEPT"] = "text/iphone"
@request.accept = "text/iphone"
get :iphone_with_html_response_type
assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
assert_equal "text/html", @response.content_type
@@ -470,7 +470,7 @@ class MimeControllerTest < Test::Unit::TestCase
get :iphone_with_html_response_type_without_layout
assert_equal '<html><div id="html_missing">Hello future from Firefox!</div></html>', @response.body
@request.env["HTTP_ACCEPT"] = "text/iphone"
@request.accept = "text/iphone"
assert_raises(ActionView::MissingTemplate) { get :iphone_with_html_response_type_without_layout }
end
end
@@ -522,7 +522,7 @@ class MimeControllerLayoutsTest < Test::Unit::TestCase
get :index
assert_equal '<html><div id="html">Hello Firefox</div></html>', @response.body
@request.env["HTTP_ACCEPT"] = "text/iphone"
@request.accept = "text/iphone"
get :index
assert_equal 'Hello iPhone', @response.body
end
@@ -533,7 +533,7 @@ class MimeControllerLayoutsTest < Test::Unit::TestCase
get :index
assert_equal 'Super Firefox', @response.body
@request.env["HTTP_ACCEPT"] = "text/iphone"
@request.accept = "text/iphone"
get :index
assert_equal '<html><div id="super_iphone">Super iPhone</div></html>', @response.body
end

View File

@@ -136,6 +136,10 @@ class NewRenderTestController < ActionController::Base
render :partial => "partial_only", :layout => true
end
def partial_with_counter
render :partial => "counter", :locals => { :counter_counter => 5 }
end
def partial_with_locals
render :partial => "customer", :locals => { :customer => Customer.new("david") }
end
@@ -741,6 +745,11 @@ EOS
assert_equal "<title>Talking to the layout</title>\nAction was here!", @response.body
end
def test_partial_with_counter
get :partial_with_counter
assert_equal "5", @response.body
end
def test_partials_list
get :partials_list
assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body

View File

@@ -64,58 +64,61 @@ end
class RackRequestTest < BaseRackTest
def test_proxy_request
assert_equal 'glu.ttono.us', @request.host_with_port
assert_equal 'glu.ttono.us', @request.host_with_port(true)
end
def test_http_host
@env.delete "HTTP_X_FORWARDED_HOST"
@env['HTTP_HOST'] = "rubyonrails.org:8080"
assert_equal "rubyonrails.org:8080", @request.host_with_port
assert_equal "rubyonrails.org", @request.host(true)
assert_equal "rubyonrails.org:8080", @request.host_with_port(true)
@env['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
assert_equal "www.secondhost.org", @request.host
assert_equal "www.secondhost.org", @request.host(true)
end
def test_http_host_with_default_port_overrides_server_port
@env.delete "HTTP_X_FORWARDED_HOST"
@env['HTTP_HOST'] = "rubyonrails.org"
assert_equal "rubyonrails.org", @request.host_with_port
assert_equal "rubyonrails.org", @request.host_with_port(true)
end
def test_host_with_port_defaults_to_server_name_if_no_host_headers
@env.delete "HTTP_X_FORWARDED_HOST"
@env.delete "HTTP_HOST"
assert_equal "glu.ttono.us:8007", @request.host_with_port
assert_equal "glu.ttono.us:8007", @request.host_with_port(true)
end
def test_host_with_port_falls_back_to_server_addr_if_necessary
@env.delete "HTTP_X_FORWARDED_HOST"
@env.delete "HTTP_HOST"
@env.delete "SERVER_NAME"
assert_equal "207.7.108.53:8007", @request.host_with_port
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)
end
def test_host_with_port_if_http_standard_port_is_specified
@env['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:80"
assert_equal "glu.ttono.us", @request.host_with_port
assert_equal "glu.ttono.us", @request.host_with_port(true)
end
def test_host_with_port_if_https_standard_port_is_specified
@env['HTTP_X_FORWARDED_PROTO'] = "https"
@env['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:443"
assert_equal "glu.ttono.us", @request.host_with_port
assert_equal "glu.ttono.us", @request.host_with_port(true)
end
def test_host_if_ipv6_reference
@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
assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host(true)
end
def test_host_if_ipv6_reference_with_port
@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
assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host(true)
end
def test_cgi_environment_variables

View File

@@ -15,9 +15,14 @@ class TestController < ActionController::Base
end
def conditional_hello
etag! [:foo, 123]
last_modified! Time.now.utc.beginning_of_day
render :action => 'hello_world' unless performed?
response.last_modified = Time.now.utc.beginning_of_day
response.etag = [:foo, 123]
if request.fresh?(response)
head :not_modified
else
render :action => 'hello_world'
end
end
def render_hello_world
@@ -428,7 +433,7 @@ class RenderTest < Test::Unit::TestCase
end
def test_should_render_formatted_html_erb_template_with_faulty_accepts_header
@request.env["HTTP_ACCEPT"] = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, appliction/x-shockwave-flash, */*"
@request.accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, appliction/x-shockwave-flash, */*"
get :formatted_xml_erb
assert_equal '<test>passed formatted html erb</test>', @response.body
end
@@ -490,16 +495,16 @@ class EtagRenderTest < Test::Unit::TestCase
end
def test_render_against_etag_request_should_304_when_match
@request.headers["HTTP_IF_NONE_MATCH"] = etag_for("hello david")
@request.if_none_match = etag_for("hello david")
get :render_hello_world_from_variable
assert_equal "304 Not Modified", @response.headers['Status']
assert_equal "304 Not Modified", @response.status
assert @response.body.empty?
end
def test_render_against_etag_request_should_200_when_no_match
@request.headers["HTTP_IF_NONE_MATCH"] = etag_for("hello somewhere else")
@request.if_none_match = etag_for("hello somewhere else")
get :render_hello_world_from_variable
assert_equal "200 OK", @response.headers['Status']
assert_equal "200 OK", @response.status
assert !@response.body.empty?
end
@@ -508,13 +513,13 @@ class EtagRenderTest < Test::Unit::TestCase
expected_etag = etag_for('hello david')
assert_equal expected_etag, @response.headers['ETag']
@request.headers["HTTP_IF_NONE_MATCH"] = expected_etag
@request.if_none_match = expected_etag
get :render_hello_world_from_variable
assert_equal "304 Not Modified", @response.headers['Status']
assert_equal "304 Not Modified", @response.status
@request.headers["HTTP_IF_NONE_MATCH"] = "\"diftag\""
@request.if_none_match = "\"diftag\""
get :render_hello_world_from_variable
assert_equal "200 OK", @response.headers['Status']
assert_equal "200 OK", @response.status
end
def render_with_404_shouldnt_have_etag
@@ -557,17 +562,17 @@ class LastModifiedRenderTest < Test::Unit::TestCase
end
def test_request_not_modified
@request.headers["HTTP_IF_MODIFIED_SINCE"] = @last_modified
@request.if_modified_since = @last_modified
get :conditional_hello
assert_equal "304 Not Modified", @response.headers['Status']
assert_equal "304 Not Modified", @response.status
assert @response.body.blank?, @response.body
assert_equal @last_modified, @response.headers['Last-Modified']
end
def test_request_modified
@request.headers["HTTP_IF_MODIFIED_SINCE"] = 'Thu, 16 Jul 2008 00:00:00 GMT'
@request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT'
get :conditional_hello
assert_equal "200 OK", @response.headers['Status']
assert_equal "200 OK", @response.status
assert !@response.body.blank?
assert_equal @last_modified, @response.headers['Last-Modified']
end

View File

@@ -15,57 +15,57 @@ class RequestTest < Test::Unit::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
assert_equal '1.2.3.4', @request.remote_ip(true)
@request.env['HTTP_CLIENT_IP'] = '2.3.4.5'
assert_equal '1.2.3.4', @request.remote_ip
assert_equal '1.2.3.4', @request.remote_ip(true)
@request.remote_addr = '192.168.0.1'
assert_equal '2.3.4.5', @request.remote_ip
assert_equal '2.3.4.5', @request.remote_ip(true)
@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
assert_equal '1.2.3.4', @request.remote_ip(true)
@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
assert_equal '3.4.5.6', @request.remote_ip(true)
@request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,3.4.5.6'
assert_equal '3.4.5.6', @request.remote_ip
assert_equal '3.4.5.6', @request.remote_ip(true)
@request.env['HTTP_X_FORWARDED_FOR'] = '172.16.0.1,3.4.5.6'
assert_equal '3.4.5.6', @request.remote_ip
assert_equal '3.4.5.6', @request.remote_ip(true)
@request.env['HTTP_X_FORWARDED_FOR'] = '192.168.0.1,3.4.5.6'
assert_equal '3.4.5.6', @request.remote_ip
assert_equal '3.4.5.6', @request.remote_ip(true)
@request.env['HTTP_X_FORWARDED_FOR'] = '10.0.0.1,3.4.5.6'
assert_equal '3.4.5.6', @request.remote_ip
assert_equal '3.4.5.6', @request.remote_ip(true)
@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
assert_equal '3.4.5.6', @request.remote_ip(true)
@request.env['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,3.4.5.6'
assert_equal '3.4.5.6', @request.remote_ip
assert_equal '3.4.5.6', @request.remote_ip(true)
@request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,192.168.0.1'
assert_equal 'unknown', @request.remote_ip
assert_equal 'unknown', @request.remote_ip(true)
@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
assert_equal '3.4.5.6', @request.remote_ip(true)
@request.env['HTTP_CLIENT_IP'] = '8.8.8.8'
e = assert_raises(ActionController::ActionControllerError) {
@request.remote_ip
@request.remote_ip(true)
}
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
assert_match /HTTP_CLIENT_IP="8.8.8.8"/, e.message
@request.env['HTTP_X_FORWARDED_FOR'] = '8.8.8.8, 9.9.9.9'
assert_equal '8.8.8.8', @request.remote_ip
assert_equal '8.8.8.8', @request.remote_ip(true)
@request.env.delete 'HTTP_CLIENT_IP'
@request.env.delete 'HTTP_X_FORWARDED_FOR'
@@ -168,58 +168,58 @@ class RequestTest < Test::Unit::TestCase
ActionController::Base.relative_url_root = nil
# The following tests are for when REQUEST_URI is not supplied (as in IIS)
@request.set_REQUEST_URI nil
@request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1"
@request.env['SCRIPT_NAME'] = nil #"/path/dispatch.rb"
@request.set_REQUEST_URI nil
assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
assert_equal "/path/of/some/uri", @request.path
ActionController::Base.relative_url_root = '/path'
@request.set_REQUEST_URI nil
@request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1"
@request.env['SCRIPT_NAME'] = "/path/dispatch.rb"
assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
assert_equal "/of/some/uri", @request.path
@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)
ActionController::Base.relative_url_root = nil
@request.set_REQUEST_URI nil
@request.env['PATH_INFO'] = "/path/of/some/uri"
@request.env['SCRIPT_NAME'] = nil
@request.set_REQUEST_URI nil
assert_equal "/path/of/some/uri", @request.request_uri
assert_equal "/path/of/some/uri", @request.path
@request.set_REQUEST_URI nil
@request.env['PATH_INFO'] = "/"
@request.set_REQUEST_URI nil
assert_equal "/", @request.request_uri
assert_equal "/", @request.path
@request.set_REQUEST_URI nil
@request.env['PATH_INFO'] = "/?m=b"
@request.set_REQUEST_URI nil
assert_equal "/?m=b", @request.request_uri
assert_equal "/", @request.path
@request.set_REQUEST_URI nil
@request.env['PATH_INFO'] = "/"
@request.env['SCRIPT_NAME'] = "/dispatch.cgi"
@request.set_REQUEST_URI nil
assert_equal "/", @request.request_uri
assert_equal "/", @request.path
ActionController::Base.relative_url_root = '/hieraki'
@request.set_REQUEST_URI nil
@request.env['PATH_INFO'] = "/hieraki/"
@request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
@request.set_REQUEST_URI nil
assert_equal "/hieraki/", @request.request_uri
assert_equal "/", @request.path
ActionController::Base.relative_url_root = nil
@request.set_REQUEST_URI '/hieraki/dispatch.cgi'
ActionController::Base.relative_url_root = '/hieraki'
assert_equal "/dispatch.cgi", @request.path
assert_equal "/dispatch.cgi", @request.path(true)
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
assert_equal "/hieraki/dispatch.cgi", @request.path(true)
ActionController::Base.relative_url_root = nil
# This test ensures that Rails uses REQUEST_URI over PATH_INFO
@@ -227,8 +227,8 @@ class RequestTest < Test::Unit::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
assert_equal "/some/path", @request.path
assert_equal "/some/path", @request.request_uri(true)
assert_equal "/some/path", @request.path(true)
end
def test_host_with_default_port
@@ -244,13 +244,13 @@ class RequestTest < Test::Unit::TestCase
end
def test_server_software
assert_equal nil, @request.server_software
assert_equal nil, @request.server_software(true)
@request.env['SERVER_SOFTWARE'] = 'Apache3.422'
assert_equal 'apache', @request.server_software
assert_equal 'apache', @request.server_software(true)
@request.env['SERVER_SOFTWARE'] = 'lighttpd(1.1.4)'
assert_equal 'lighttpd', @request.server_software
assert_equal 'lighttpd', @request.server_software(true)
end
def test_xml_http_request
@@ -280,44 +280,44 @@ class RequestTest < Test::Unit::TestCase
def test_symbolized_request_methods
[:get, :post, :put, :delete].each do |method|
set_request_method_to method
self.request_method = method
assert_equal method, @request.method
end
end
def test_invalid_http_method_raises_exception
set_request_method_to :random_method
assert_raises(ActionController::UnknownHttpMethod) do
@request.method
self.request_method = :random_method
end
end
def test_allow_method_hacking_on_post
set_request_method_to :post
self.request_method = :post
[:get, :head, :options, :put, :post, :delete].each do |method|
@request.instance_eval { @parameters = { :_method => method } ; @request_method = nil }
@request.instance_eval { @parameters = { :_method => method.to_s } ; @request_method = nil }
@request.request_method(true)
assert_equal(method == :head ? :get : method, @request.method)
end
end
def test_invalid_method_hacking_on_post_raises_exception
set_request_method_to :post
self.request_method = :post
@request.instance_eval { @parameters = { :_method => :random_method } ; @request_method = nil }
assert_raises(ActionController::UnknownHttpMethod) do
@request.method
@request.request_method(true)
end
end
def test_restrict_method_hacking
@request.instance_eval { @parameters = { :_method => 'put' } }
[:get, :put, :delete].each do |method|
set_request_method_to method
self.request_method = method
assert_equal method, @request.method
end
end
def test_head_masquarading_as_get
set_request_method_to :head
def test_head_masquerading_as_get
self.request_method = :head
assert_equal :get, @request.method
assert @request.get?
assert @request.head?
@@ -339,9 +339,16 @@ class RequestTest < Test::Unit::TestCase
end
def test_nil_format
@request.instance_eval { @parameters = { :format => nil } }
ActionController::Base.use_accept_header, old =
false, ActionController::Base.use_accept_header
@request.instance_eval { @parameters = {} }
@request.env["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest"
assert @request.xhr?
assert_equal Mime::JS, @request.format
ensure
ActionController::Base.use_accept_header = old
end
def test_content_type
@@ -384,9 +391,9 @@ class RequestTest < Test::Unit::TestCase
end
protected
def set_request_method_to(method)
def request_method=(method)
@request.env['REQUEST_METHOD'] = method.to_s.upcase
@request.instance_eval { @request_method = nil }
@request.request_method(true)
end
end

View File

@@ -0,0 +1 @@
top level partial html

View File

@@ -0,0 +1 @@
top level partial

View File

@@ -0,0 +1 @@
<%= counter_counter %>

View File

@@ -425,7 +425,8 @@ class AssetTagHelperTest < ActionView::TestCase
stylesheet_link_tag(:all, :cache => true)
)
assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
expected = Dir["#{ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR}/*.css"].map { |p| File.mtime(p) }.max
assert_equal expected, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
assert_dom_equal(
%(<link href="http://a0.example.com/stylesheets/money.css" media="screen" rel="stylesheet" type="text/css" />),

View File

@@ -3,22 +3,22 @@ require 'abstract_unit'
class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase
include ActionView::Helpers::DateHelper
attr_reader :request
def setup
@from = Time.mktime(2004, 6, 6, 21, 45, 0)
end
uses_mocha 'date_helper_distance_of_time_in_words_i18n_test' do
# distance_of_time_in_words
def test_distance_of_time_in_words_calls_i18n
{ # with include_seconds
[2.seconds, true] => [:'less_than_x_seconds', 5],
[9.seconds, true] => [:'less_than_x_seconds', 10],
[19.seconds, true] => [:'less_than_x_seconds', 20],
[30.seconds, true] => [:'half_a_minute', nil],
[59.seconds, true] => [:'less_than_x_minutes', 1],
[60.seconds, true] => [:'x_minutes', 1],
[2.seconds, true] => [:'less_than_x_seconds', 5],
[9.seconds, true] => [:'less_than_x_seconds', 10],
[19.seconds, true] => [:'less_than_x_seconds', 20],
[30.seconds, true] => [:'half_a_minute', nil],
[59.seconds, true] => [:'less_than_x_minutes', 1],
[60.seconds, true] => [:'x_minutes', 1],
# without include_seconds
[29.seconds, false] => [:'less_than_x_minutes', 1],
@@ -38,7 +38,7 @@ class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase
def assert_distance_of_time_in_words_translates_key(passed, expected)
diff, include_seconds = *passed
key, count = *expected
key, count = *expected
to = @from + diff
options = {:locale => 'en-US', :scope => :'datetime.distance_in_words'}
@@ -49,11 +49,11 @@ class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase
end
end
end
class DateHelperSelectTagsI18nTests < Test::Unit::TestCase
include ActionView::Helpers::DateHelper
attr_reader :request
uses_mocha 'date_helper_select_tags_i18n_tests' do
def setup
I18n.stubs(:translate).with(:'date.month_names', :locale => 'en-US').returns Date::MONTHNAMES

View File

@@ -557,11 +557,8 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_incomplete_order
expected = %(<select id="date_first_day" name="date[first][day]">\n)
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
expected << "</select>\n"
expected << %(<select id="date_first_year" name="date[first][year]">\n)
# NOTE: modified this test because of minimal API change
expected = %(<select id="date_first_year" name="date[first][year]">\n)
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -569,6 +566,10 @@ class DateHelperTest < ActionView::TestCase
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
expected << "</select>\n"
expected << %(<select id="date_first_day" name="date[first][day]">\n)
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
expected << "</select>\n"
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :order => [:day])
end
@@ -909,6 +910,10 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), { :datetime_separator => "&mdash;", :date_separator => "/", :time_separator => ":", :start_year => 2003, :end_year => 2005, :prefix => "date[first]"}, :class => 'selector')
end
def test_select_datetime_should_work_with_date
assert_nothing_raised { select_datetime(Date.today) }
end
def test_select_time
expected = %(<select id="date_hour" name="date[hour]">\n)
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
@@ -986,31 +991,8 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {:include_seconds => false}, :class => 'selector')
end
uses_mocha 'TestDatetimeAndTimeSelectUseTimeCurrentAsDefault' do
def test_select_datetime_uses_time_current_as_default
time = stub(:year => 2004, :month => 6, :day => 15, :hour => 16, :min => 35, :sec => 0)
Time.expects(:current).returns time
expects(:select_date).with(time, anything, anything).returns('')
expects(:select_time).with(time, anything, anything).returns('')
select_datetime
end
def test_select_time_uses_time_current_as_default
time = stub(:year => 2004, :month => 6, :day => 15, :hour => 16, :min => 35, :sec => 0)
Time.expects(:current).returns time
expects(:select_hour).with(time, anything, anything).returns('')
expects(:select_minute).with(time, anything, anything).returns('')
select_time
end
def test_select_date_uses_date_current_as_default
date = stub(:year => 2004, :month => 6, :day => 15)
Date.expects(:current).returns date
expects(:select_year).with(date, anything, anything).returns('')
expects(:select_month).with(date, anything, anything).returns('')
expects(:select_day).with(date, anything, anything).returns('')
select_date
end
def test_select_time_should_work_with_date
assert_nothing_raised { select_time(Date.today) }
end
def test_date_select
@@ -1231,6 +1213,30 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
def test_date_select_with_separator
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
expected << " / "
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
expected << "</select>\n"
expected << " / "
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
expected << "</select>\n"
assert_dom_equal expected, date_select("post", "written_on", { :date_separator => " / " })
end
def test_time_select
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
@@ -1330,6 +1336,33 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
def test_time_select_with_separator
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n)
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) }
expected << "</select>\n"
expected << " - "
expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n)
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) }
expected << "</select>\n"
expected << " - "
expected << %(<select id="post_written_on_6i" name="post[written_on(6i)]">\n)
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 35}>#{sprintf("%02d", i)}</option>\n) }
expected << "</select>\n"
assert_dom_equal expected, time_select("post", "written_on", { :time_separator => " - ", :include_seconds => true })
end
def test_datetime_select
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 16, 35)
@@ -1412,6 +1445,47 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
def test_datetime_select_with_separators
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
expected << " / "
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
expected << "</select>\n"
expected << " / "
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
expected << "</select>\n"
expected << " , "
expected << %(<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n)
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) }
expected << "</select>\n"
expected << " - "
expected << %(<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n)
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) }
expected << "</select>\n"
expected << " - "
expected << %(<select id="post_updated_at_6i" name="post[updated_at(6i)]">\n)
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 35}>#{sprintf("%02d", i)}</option>\n) }
expected << "</select>\n"
assert_dom_equal expected, datetime_select("post", "updated_at", { :date_separator => " / ", :datetime_separator => " , ", :time_separator => " - ", :include_seconds => true })
end
def test_date_select_with_zero_value_and_no_start_year
expected = %(<select id="date_first_year" name="date[first][year]">\n)
(Date.today.year-5).upto(Date.today.year+1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
@@ -1814,26 +1888,151 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, datetime_select("post", "updated_at", {}, :class => 'selector')
end
uses_mocha 'TestInstanceTagDefaultTimeFromOptions' do
def test_instance_tag_default_time_from_options_uses_time_current_as_default_when_hash_passed_as_arg
dummy_instance_tag = ActionView::Helpers::InstanceTag.new(1,2,3)
Time.expects(:current).returns Time.now
dummy_instance_tag.send!(:default_time_from_options, :hour => 2)
end
def test_date_select_should_not_change_passed_options_hash
@post = Post.new
@post.updated_at = Time.local(2008, 7, 16, 23, 30)
def test_instance_tag_default_time_from_options_respects_hash_arg_settings_when_time_falls_in_system_local_dst_spring_gap
with_env_tz('US/Central') do
dummy_instance_tag = ActionView::Helpers::InstanceTag.new(1,2,3)
Time.stubs(:now).returns Time.local(2006, 4, 2, 1)
assert_equal 2, dummy_instance_tag.send!(:default_time_from_options, :hour => 2).hour
end
end
options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
:include_blank => false,
:ignore_date => false,
:include_seconds => true
}
date_select(@post, :updated_at, options)
def test_instance_tag_default_time_from_options_handles_far_future_date
dummy_instance_tag = ActionView::Helpers::InstanceTag.new(1,2,3)
time = dummy_instance_tag.send!(:default_time_from_options, :year => 2050, :month => 2, :day => 10, :hour => 15, :min => 30, :sec => 45)
assert_equal 2050, time.year
end
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
:include_blank => false,
:ignore_date => false,
:include_seconds => true
}, options)
end
def test_datetime_select_should_not_change_passed_options_hash
@post = Post.new
@post.updated_at = Time.local(2008, 7, 16, 23, 30)
options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
:include_blank => false,
:ignore_date => false,
:include_seconds => true
}
datetime_select(@post, :updated_at, options)
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
:include_blank => false,
:ignore_date => false,
:include_seconds => true
}, options)
end
def test_time_select_should_not_change_passed_options_hash
@post = Post.new
@post.updated_at = Time.local(2008, 7, 16, 23, 30)
options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
:include_blank => false,
:ignore_date => false,
:include_seconds => true
}
time_select(@post, :updated_at, options)
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
:include_blank => false,
:ignore_date => false,
:include_seconds => true
}, options)
end
def test_select_date_should_not_change_passed_options_hash
options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
:include_blank => false,
:ignore_date => false,
:include_seconds => true
}
select_date(Date.today, options)
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
:include_blank => false,
:ignore_date => false,
:include_seconds => true
}, options)
end
def test_select_datetime_should_not_change_passed_options_hash
options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
:include_blank => false,
:ignore_date => false,
:include_seconds => true
}
select_datetime(Time.now, options)
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
:include_blank => false,
:ignore_date => false,
:include_seconds => true
}, options)
end
def test_select_time_should_not_change_passed_options_hash
options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
:include_blank => false,
:ignore_date => false,
:include_seconds => true
}
select_time(Time.now, options)
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
:include_blank => false,
:ignore_date => false,
:include_seconds => true
}, options)
end
protected

View File

@@ -18,35 +18,35 @@ class NumberHelperI18nTests < Test::Unit::TestCase
end
def test_number_to_currency_translates_currency_formats
I18n.expects(:translate).with(
[:'number.format', :'number.currency.format'], :locale => 'en-US'
).returns([@number_defaults, @currency_defaults])
I18n.expects(:translate).with(:'number.format', :locale => 'en-US', :raise => true).returns(@number_defaults)
I18n.expects(:translate).with(:'number.currency.format', :locale => 'en-US',
:raise => true).returns(@currency_defaults)
number_to_currency(1, :locale => 'en-US')
end
def test_number_with_precision_translates_number_formats
I18n.expects(:translate).with(
[:'number.format', :'number.precision.format'], :locale => 'en-US'
).returns([@number_defaults, @precision_defaults])
I18n.expects(:translate).with(:'number.format', :locale => 'en-US', :raise => true).returns(@number_defaults)
I18n.expects(:translate).with(:'number.precision.format', :locale => 'en-US',
:raise => true).returns(@precision_defaults)
number_with_precision(1, :locale => 'en-US')
end
def test_number_with_delimiter_translates_number_formats
I18n.expects(:translate).with(:'number.format', :locale => 'en-US').returns(@number_defaults)
I18n.expects(:translate).with(:'number.format', :locale => 'en-US', :raise => true).returns(@number_defaults)
number_with_delimiter(1, :locale => 'en-US')
end
def test_number_to_percentage_translates_number_formats
I18n.expects(:translate).with(
[:'number.format', :'number.percentage.format'], :locale => 'en-US'
).returns([@number_defaults, @percentage_defaults])
I18n.expects(:translate).with(:'number.format', :locale => 'en-US', :raise => true).returns(@number_defaults)
I18n.expects(:translate).with(:'number.percentage.format', :locale => 'en-US',
:raise => true).returns(@percentage_defaults)
number_to_percentage(1, :locale => 'en-US')
end
def test_number_to_human_size_translates_human_formats
I18n.expects(:translate).with(
[:'number.format', :'number.human.format'], :locale => 'en-US'
).returns([@number_defaults, @human_defaults])
I18n.expects(:translate).with(:'number.format', :locale => 'en-US', :raise => true).returns(@number_defaults)
I18n.expects(:translate).with(:'number.human.format', :locale => 'en-US',
:raise => true).returns(@human_defaults)
# can't be called with 1 because this directly returns without calling I18n.translate
number_to_human_size(1025, :locale => 'en-US')
end

View File

@@ -19,6 +19,10 @@ class ViewRenderTest < Test::Unit::TestCase
assert_equal "Hello world!", @view.render("test/hello_world")
end
def test_render_file_at_top_level
assert_equal 'Elastica', @view.render('/shared')
end
def test_render_file_with_full_path
template_path = File.join(File.dirname(__FILE__), '../fixtures/test/hello_world.erb')
assert_equal "Hello world!", @view.render(:file => template_path)
@@ -47,6 +51,24 @@ class ViewRenderTest < Test::Unit::TestCase
assert_equal "only partial", @view.render(:partial => "test/partial_only")
end
def test_render_partial_with_format
assert_equal 'partial html', @view.render(:partial => 'test/partial')
end
def test_render_partial_at_top_level
# file fixtures/_top_level_partial_only.erb (not fixtures/test)
assert_equal 'top level partial', @view.render(:partial => '/top_level_partial_only')
end
def test_render_partial_with_format_at_top_level
# file fixtures/_top_level_partial.html.erb (not fixtures/test, with format extension)
assert_equal 'top level partial html', @view.render(:partial => '/top_level_partial')
end
def test_render_partial_with_locals
assert_equal "5", @view.render(:partial => "test/counter", :locals => { :counter_counter => 5 })
end
def test_render_partial_with_errors
assert_raise(ActionView::TemplateError) { @view.render(:partial => "test/raise") }
end
@@ -54,14 +76,14 @@ class ViewRenderTest < Test::Unit::TestCase
def test_render_partial_collection
assert_equal "Hello: davidHello: mary", @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), Customer.new("mary") ])
end
def test_render_partial_collection_as
assert_equal "david david davidmary mary mary",
assert_equal "david david davidmary mary mary",
@view.render(:partial => "test/customer_with_var", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :customer)
end
def test_render_partial_collection_without_as
assert_equal "local_inspector,local_inspector_counter,object",
assert_equal "local_inspector,local_inspector_counter,object",
@view.render(:partial => "test/local_inspector", :collection => [ Customer.new("mary") ])
end

View File

@@ -277,7 +277,11 @@ class UrlHelperTest < ActionView::TestCase
end
def test_mail_to_with_javascript
assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript")
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript")
end
def test_mail_to_with_javascript_unicode
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%22%3e%c3%ba%6e%69%63%6f%64%65%3c%2f%61%3e%27%29%3b'))</script>", mail_to("unicode@example.com", "únicode", :encode => "javascript")
end
def test_mail_with_options
@@ -301,8 +305,8 @@ class UrlHelperTest < ActionView::TestCase
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#46;&#99;&#111;&#109;</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)")
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex", :replace_at => "(at)")
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#40;&#100;&#111;&#116;&#41;&#99;&#111;&#109;</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)")
assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
end
def protect_against_forgery?

View File

@@ -344,7 +344,7 @@ module ActiveRecord
callback(:before_add, record)
yield(record) if block_given?
@target ||= [] unless loaded?
@target << record
@target << record unless @reflection.options[:uniq] && @target.include?(record)
callback(:after_add, record)
record
end

View File

@@ -2599,7 +2599,7 @@ module ActiveRecord #:nodoc:
removed_attributes = attributes.keys - safe_attributes.keys
if removed_attributes.any?
logger.debug "WARNING: Can't mass-assign these protected attributes: #{removed_attributes.join(', ')}"
log_protected_attribute_removal(removed_attributes)
end
safe_attributes
@@ -2614,6 +2614,10 @@ module ActiveRecord #:nodoc:
end
end
def log_protected_attribute_removal(*attributes)
logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}"
end
# The primary key and inheritance column can never be set by mass-assignment for security reasons.
def attributes_protected_by_default
default = [ self.class.primary_key, self.class.inheritance_column ]
@@ -2627,8 +2631,15 @@ module ActiveRecord #:nodoc:
quoted = {}
connection = self.class.connection
attribute_names.each do |name|
if column = column_for_attribute(name)
quoted[name] = connection.quote(read_attribute(name), column) unless !include_primary_key && column.primary
if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
value = read_attribute(name)
# We need explicit to_yaml because quote() does not properly convert Time/Date fields to YAML.
if value && self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))
value = value.to_yaml
end
quoted[name] = connection.quote(value, column)
end
end
include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)

View File

@@ -211,7 +211,7 @@ module ActiveRecord
sql << " ORDER BY #{options[:order]} " if options[:order]
add_limit!(sql, options, scope)
sql << ')' if use_workaround
sql << ') AS #{aggregate_alias}_subquery' if use_workaround
sql
end

View File

@@ -134,7 +134,9 @@ module ActiveRecord
def update_with_dirty
if partial_updates?
update_without_dirty(changed)
# Serialized attributes should always be written in case they've been
# changed in place.
update_without_dirty(changed | self.class.serialized_attributes.keys)
else
update_without_dirty
end

View File

@@ -349,6 +349,27 @@ module ActiveRecord
end
end
# MigrationProxy is used to defer loading of the actual migration classes
# until they are needed
class MigrationProxy
attr_accessor :name, :version, :filename
delegate :migrate, :announce, :write, :to=>:migration
private
def migration
@migration ||= load_migration
end
def load_migration
load(filename)
name.constantize
end
end
class Migrator#:nodoc:
class << self
def migrate(migrations_path, target_version = nil)
@@ -437,7 +458,7 @@ module ActiveRecord
runnable.pop if down? && !target.nil?
runnable.each do |migration|
Base.logger.info "Migrating to #{migration} (#{migration.version})"
Base.logger.info "Migrating to #{migration.name} (#{migration.version})"
# On our way up, we skip migrating the ones we've already migrated
# On our way down, we skip reverting the ones we've never migrated
@@ -470,11 +491,10 @@ module ActiveRecord
raise DuplicateMigrationNameError.new(name.camelize)
end
load(file)
klasses << returning(name.camelize.constantize) do |klass|
class << klass; attr_accessor :version end
klass.version = version
klasses << returning(MigrationProxy.new) do |migration|
migration.name = name.camelize
migration.version = version
migration.filename = file
end
end

View File

@@ -103,7 +103,7 @@ module ActiveRecord
attr_reader :proxy_scope, :proxy_options
[].methods.each do |m|
unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?|any?)/
unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?|any?|respond_to?)/
delegate m, :to => :proxy_found
end
end
@@ -140,6 +140,10 @@ module ActiveRecord
@found ? @found.empty? : count.zero?
end
def respond_to?(method)
super || @proxy_scope.respond_to?(method)
end
def any?
if block_given?
proxy_found.any? { |*block_args| yield(*block_args) }

View File

@@ -9,7 +9,7 @@ require 'models/topic'
require 'models/reply'
class CascadedEagerLoadingTest < ActiveRecord::TestCase
fixtures :authors, :mixins, :companies, :posts, :topics
fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments, :categorizations
def test_eager_association_loading_with_cascaded_two_levels
authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id")

View File

@@ -21,7 +21,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
fixtures :posts, :comments, :authors, :categories, :categories_posts,
:companies, :accounts, :tags, :taggings, :people, :readers,
:owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books,
:developers, :projects
:developers, :projects, :developers_projects
def test_loading_with_one_association
posts = Post.find(:all, :include => :comments)

View File

@@ -70,7 +70,7 @@ end
class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,
:parrots, :pirates, :treasures, :price_estimates
:parrots, :pirates, :treasures, :price_estimates, :tags, :taggings
def test_has_and_belongs_to_many
david = Developer.find(1)
@@ -299,6 +299,17 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 3, projects(:active_record, :reload).developers.size
end
def test_uniq_option_prevents_duplicate_push
project = projects(:active_record)
project.developers << developers(:jamis)
project.developers << developers(:david)
assert_equal 3, project.developers.size
project.developers << developers(:david)
project.developers << developers(:jamis)
assert_equal 3, project.developers.size
end
def test_deleting
david = Developer.find(1)
active_record = Project.find(1)

View File

@@ -14,7 +14,7 @@ require 'models/reader'
class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :comments, :author_addresses,
:people, :posts
:people, :posts, :readers
def setup
Client.destroyed_client_ids.clear

View File

@@ -76,7 +76,7 @@ class TopicWithProtectedContentAndAccessibleAuthorName < ActiveRecord::Base
end
class BasicsTest < ActiveRecord::TestCase
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories
def test_table_exists
assert !NonExistentTable.table_exists?
@@ -1361,6 +1361,12 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal(myobj, topic.content)
end
def test_serialized_time_attribute
myobj = Time.local(2008,1,1,1,0)
topic = Topic.create("content" => myobj).reload
assert_equal(myobj, topic.content)
end
def test_nil_serialized_attribute_with_class_constraint
myobj = MyObject.new('value1', 'value2')
topic = Topic.new

View File

@@ -191,6 +191,18 @@ class DirtyTest < ActiveRecord::TestCase
assert !pirate.changed?
end
def test_save_should_store_serialized_attributes_even_with_partial_updates
with_partial_updates(Topic) do
topic = Topic.create!(:content => {:a => "a"})
topic.content[:b] = "b"
#assert topic.changed? # Known bug, will fail
topic.save!
assert_equal "b", topic.content[:b]
topic.reload
assert_equal "b", topic.content[:b]
end
end
private
def with_partial_updates(klass, on = true)
old = klass.partial_updates?

View File

@@ -74,7 +74,7 @@ class MultiObserver < ActiveRecord::Observer
end
class LifecycleTest < ActiveRecord::TestCase
fixtures :topics, :developers
fixtures :topics, :developers, :minimalistics
def test_before_destroy
original_count = Topic.count

View File

@@ -6,7 +6,7 @@ require 'models/post'
require 'models/category'
class MethodScopingTest < ActiveRecord::TestCase
fixtures :developers, :projects, :comments, :posts
fixtures :developers, :projects, :comments, :posts, :developers_projects
def test_set_conditions
Developer.with_scope(:find => { :conditions => 'just a test...' }) do

View File

@@ -922,6 +922,26 @@ if ActiveRecord::Base.connection.supports_migrations?
migrations[0].name == 'innocent_jointable'
end
def test_only_loads_pending_migrations
# migrate up to 1
ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
# now unload the migrations that have been defined
PeopleHaveLastNames.unloadable
ActiveSupport::Dependencies.remove_unloadable_constants!
ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", nil)
assert !defined? PeopleHaveLastNames
%w(WeNeedReminders, InnocentJointable).each do |migration|
assert defined? migration
end
ensure
load(MIGRATIONS_ROOT + "/valid/1_people_have_last_names.rb")
end
def test_migrator_interleaved_migrations
ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_1")

View File

@@ -45,6 +45,12 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal Topic.average(:replies_count), Topic.base.average(:replies_count)
end
def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy
assert Topic.approved.respond_to?(:proxy_found)
assert Topic.approved.respond_to?(:count)
assert Topic.approved.respond_to?(:length)
end
def test_subclasses_inherit_scopes
assert Topic.scopes.include?(:base)
@@ -186,9 +192,10 @@ class NamedScopeTest < ActiveRecord::TestCase
def test_any_should_not_load_results
topics = Topic.base
assert_queries(1) do
topics.expects(:empty?).returns(true)
assert !topics.any?
assert_queries(2) do
topics.any? # use count query
topics.collect # force load
topics.any? # use loaded (no query)
end
end

View File

@@ -58,7 +58,7 @@ end
uses_mocha 'QueryCacheExpiryTest' do
class QueryCacheExpiryTest < ActiveRecord::TestCase
fixtures :tasks
fixtures :tasks, :posts, :categories, :categories_posts
def test_find
Task.connection.expects(:clear_query_cache).times(1)

View File

@@ -39,10 +39,6 @@ module ActiveSupport
class Store
cattr_accessor :logger
def threadsafe!
extend ThreadSafety
end
def silence!
@silence = true
self
@@ -115,20 +111,6 @@ module ActiveSupport
logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}") if logger && !@silence && !@logger_off
end
end
module ThreadSafety #:nodoc:
def self.extended(object) #:nodoc:
object.instance_variable_set(:@mutex, Mutex.new)
end
%w(read write delete delete_matched exist? increment decrement).each do |method|
module_eval <<-EOS, __FILE__, __LINE__
def #{method}(*args)
@mutex.synchronize { super }
end
EOS
end
end
end
end

View File

@@ -1,14 +1,14 @@
module ActiveSupport
module Cache
class CompressedMemCacheStore < MemCacheStore
def read(name, options = {})
if value = super(name, options.merge(:raw => true))
def read(name, options = nil)
if value = super(name, (options || {}).merge(:raw => true))
Marshal.load(ActiveSupport::Gzip.decompress(value))
end
end
def write(name, value, options = {})
super(name, ActiveSupport::Gzip.compress(Marshal.dump(value)), options.merge(:raw => true))
def write(name, value, options = nil)
super(name, ActiveSupport::Gzip.compress(Marshal.dump(value)), (options || {}).merge(:raw => true))
end
end
end

View File

@@ -9,13 +9,13 @@ module ActiveSupport
def read(name, options = nil)
super
File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil
File.open(real_file_path(name), 'rb') { |f| Marshal.load(f) } rescue nil
end
def write(name, value, options = nil)
super
ensure_cache_path(File.dirname(real_file_path(name)))
File.open(real_file_path(name), "wb+") { |f| f.write(value) }
File.atomic_write(real_file_path(name), cache_path) { |f| Marshal.dump(value, f) }
rescue => e
RAILS_DEFAULT_LOGGER.error "Couldn't create cache directory: #{name} (#{e.message})" if RAILS_DEFAULT_LOGGER
end

View File

@@ -3,6 +3,13 @@ module ActiveSupport
class MemoryStore < Store
def initialize
@data = {}
@mutex = Mutex.new
end
def fetch(key, options = {})
@mutex.synchronize do
super
end
end
def read(name, options = nil)
@@ -16,23 +23,32 @@ module ActiveSupport
end
def delete(name, options = nil)
super
@data.delete(name)
end
def delete_matched(matcher, options = nil)
super
@data.delete_if { |k,v| k =~ matcher }
end
def exist?(name,options = nil)
super
@data.has_key?(name)
end
def increment(key, amount = 1)
@mutex.synchronize do
super
end
end
def decrement(key, amount = 1)
@mutex.synchronize do
super
end
end
def clear
@data.clear
end
end
end
end
end

View File

@@ -1,21 +1,5 @@
require 'tempfile'
require 'active_support/core_ext/file/atomic'
# Write to a file atomically. Useful for situations where you don't
# want other processes or threads to see half-written files.
#
# File.atomic_write("important.file") do |file|
# file.write("hello")
# end
#
# If your temp directory is not on the same filesystem as the file you're
# trying to write, you can provide a different temporary directory.
#
# File.atomic_write("/data/something.important", "/data/tmp") do |f|
# file.write("hello")
# end
def File.atomic_write(file_name, temp_dir = Dir.tmpdir)
temp_file = Tempfile.new(File.basename(file_name), temp_dir)
yield temp_file
temp_file.close
File.rename(temp_file.path, file_name)
end
class File #:nodoc:
extend ActiveSupport::CoreExtensions::File::Atomic
end

View File

@@ -0,0 +1,46 @@
require 'tempfile'
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module File #:nodoc:
module Atomic
# Write to a file atomically. Useful for situations where you don't
# want other processes or threads to see half-written files.
#
# File.atomic_write("important.file") do |file|
# file.write("hello")
# end
#
# If your temp directory is not on the same filesystem as the file you're
# trying to write, you can provide a different temporary directory.
#
# File.atomic_write("/data/something.important", "/data/tmp") do |f|
# file.write("hello")
# end
def atomic_write(file_name, temp_dir = Dir.tmpdir)
temp_file = Tempfile.new(basename(file_name), temp_dir)
yield temp_file
temp_file.close
begin
# Get original file permissions
old_stat = stat(file_name)
rescue Errno::ENOENT
# No old permissions, write a temp file to determine the defaults
check_name = ".permissions_check.#{Thread.current.object_id}.#{Process.pid}.#{rand(1000000)}"
new(check_name, "w")
old_stat = stat(check_name)
unlink(check_name)
end
# Overwrite original file with temp file
rename(temp_file.path, file_name)
# Set correct permissions on new file
chown(old_stat.uid, old_stat.gid, file_name)
chmod(old_stat.mode, file_name)
end
end
end
end
end

View File

@@ -291,11 +291,14 @@ module ActiveSupport
# NameError is raised when the name is not in CamelCase or the constant is
# unknown.
def constantize(camel_cased_word)
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
end
names = camel_cased_word.split('::')
names.shift if names.empty? || names.first.empty?
Object.module_eval("::#{$1}", __FILE__, __LINE__)
constant = Object
names.each do |name|
constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
end
constant
end
# Turns a number into an ordinal string used to denote the position in an
@@ -326,4 +329,4 @@ require 'active_support/inflections'
require 'active_support/core_ext/string/inflections'
unless String.included_modules.include?(ActiveSupport::CoreExtensions::String::Inflections)
String.send :include, ActiveSupport::CoreExtensions::String::Inflections
end
end

View File

@@ -10,18 +10,37 @@ module ActiveSupport
end
def freeze_with_memoizable
methods.each do |method|
__send__($1) if method.to_s =~ /^_unmemoized_(.*)/
end unless frozen?
memoize_all unless frozen?
freeze_without_memoizable
end
def memoize_all
methods.each do |m|
if m.to_s =~ /^_unmemoized_(.*)/
if method(m).arity == 0
__send__($1)
else
ivar = :"@_memoized_#{$1}"
instance_variable_set(ivar, {})
end
end
end
end
def unmemoize_all
methods.each do |m|
if m.to_s =~ /^_unmemoized_(.*)/
ivar = :"@_memoized_#{$1}"
instance_variable_get(ivar).clear if instance_variable_defined?(ivar)
end
end
end
end
def memoize(*symbols)
symbols.each do |symbol|
original_method = "_unmemoized_#{symbol}"
memoized_ivar = "@_memoized_#{symbol}"
original_method = :"_unmemoized_#{symbol}"
memoized_ivar = :"@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}"
class_eval <<-EOS, __FILE__, __LINE__
include Freezable
@@ -29,14 +48,27 @@ module ActiveSupport
raise "Already memoized #{symbol}" if method_defined?(:#{original_method})
alias #{original_method} #{symbol}
def #{symbol}(*args)
#{memoized_ivar} ||= {}
reload = args.pop if args.last == true || args.last == :reload
if instance_method(:#{symbol}).arity == 0
def #{symbol}(reload = false)
if reload || !defined?(#{memoized_ivar}) || #{memoized_ivar}.empty?
#{memoized_ivar} = [#{original_method}.freeze]
end
#{memoized_ivar}[0]
end
else
def #{symbol}(*args)
#{memoized_ivar} ||= {} unless frozen?
reload = args.pop if args.last == true || args.last == :reload
if !reload && #{memoized_ivar} && #{memoized_ivar}.has_key?(args)
#{memoized_ivar}[args]
else
#{memoized_ivar}[args] = #{original_method}(*args).freeze
if #{memoized_ivar}
if !reload && #{memoized_ivar}.has_key?(args)
#{memoized_ivar}[args]
elsif #{memoized_ivar}
#{memoized_ivar}[args] = #{original_method}(*args).freeze
end
else
#{original_method}(*args)
end
end
end
EOS

View File

@@ -71,69 +71,29 @@ uses_mocha 'high-level cache store tests' do
end
end
class ThreadSafetyCacheStoreTest < Test::Unit::TestCase
class FileStoreTest < Test::Unit::TestCase
def setup
@cache = ActiveSupport::Cache.lookup_store(:memory_store).threadsafe!
@cache = ActiveSupport::Cache.lookup_store(:file_store, Dir.pwd)
end
def test_should_read_and_write_strings
@cache.write('foo', 'bar')
# No way to have mocha proxy to the original method
@mutex = @cache.instance_variable_get(:@mutex)
@mutex.instance_eval %(
def calls; @calls; end
def synchronize
@calls ||= 0
@calls += 1
yield
end
)
end
def test_read_is_synchronized
assert_equal 'bar', @cache.read('foo')
assert_equal 1, @mutex.calls
ensure
File.delete("foo.cache")
end
def test_write_is_synchronized
@cache.write('foo', 'baz')
assert_equal 'baz', @cache.read('foo')
assert_equal 2, @mutex.calls
def test_should_read_and_write_hash
@cache.write('foo', {:a => "b"})
assert_equal({:a => "b"}, @cache.read('foo'))
ensure
File.delete("foo.cache")
end
def test_delete_is_synchronized
assert_equal 'bar', @cache.read('foo')
@cache.delete('foo')
def test_should_read_and_write_nil
@cache.write('foo', nil)
assert_equal nil, @cache.read('foo')
assert_equal 3, @mutex.calls
end
def test_delete_matched_is_synchronized
assert_equal 'bar', @cache.read('foo')
@cache.delete_matched(/foo/)
assert_equal nil, @cache.read('foo')
assert_equal 3, @mutex.calls
end
def test_fetch_is_synchronized
assert_equal 'bar', @cache.fetch('foo') { 'baz' }
assert_equal 'fu', @cache.fetch('bar') { 'fu' }
assert_equal 3, @mutex.calls
end
def test_exist_is_synchronized
assert @cache.exist?('foo')
assert !@cache.exist?('bar')
assert_equal 2, @mutex.calls
end
def test_increment_is_synchronized
@cache.write('foo_count', 1)
assert_equal 2, @cache.increment('foo_count')
assert_equal 4, @mutex.calls
end
def test_decrement_is_synchronized
@cache.write('foo_count', 1)
assert_equal 0, @cache.decrement('foo_count')
assert_equal 4, @mutex.calls
ensure
File.delete("foo.cache")
end
end

View File

@@ -1,9 +1,8 @@
require 'abstract_unit'
class AtomicWriteTest < Test::Unit::TestCase
def test_atomic_write_without_errors
contents = "Atomic Text"
contents = "Atomic Text"
File.atomic_write(file_name, Dir.pwd) do |file|
file.write(contents)
assert !File.exist?(file_name)
@@ -13,7 +12,7 @@ class AtomicWriteTest < Test::Unit::TestCase
ensure
File.unlink(file_name) rescue nil
end
def test_atomic_write_doesnt_write_when_block_raises
File.atomic_write(file_name) do |file|
file.write("testing")
@@ -22,8 +21,47 @@ class AtomicWriteTest < Test::Unit::TestCase
rescue
assert !File.exist?(file_name)
end
def file_name
"atomic.file"
def test_atomic_write_preserves_file_permissions
contents = "Atomic Text"
File.open(file_name, "w", 0755) do |file|
file.write(contents)
assert File.exist?(file_name)
end
assert File.exist?(file_name)
assert_equal 0100755, file_mode
assert_equal contents, File.read(file_name)
File.atomic_write(file_name, Dir.pwd) do |file|
file.write(contents)
assert File.exist?(file_name)
end
assert File.exist?(file_name)
assert_equal 0100755, file_mode
assert_equal contents, File.read(file_name)
ensure
File.unlink(file_name) rescue nil
end
def test_atomic_write_preserves_default_file_permissions
contents = "Atomic Text"
File.atomic_write(file_name, Dir.pwd) do |file|
file.write(contents)
assert !File.exist?(file_name)
end
assert File.exist?(file_name)
assert_equal 0100666 ^ File.umask, file_mode
assert_equal contents, File.read(file_name)
ensure
File.unlink(file_name) rescue nil
end
private
def file_name
"atomic.file"
end
def file_mode
File.stat(file_name).mode
end
end

View File

@@ -16,6 +16,16 @@ uses_mocha 'Memoizable' do
"Josh"
end
def name?
true
end
memoize :name?
def update(name)
"Joshua"
end
memoize :update
def age
@age_calls += 1
nil
@@ -88,6 +98,10 @@ uses_mocha 'Memoizable' do
assert_equal 1, @person.name_calls
end
def test_memoization_with_punctuation
assert_equal true, @person.name?
end
def test_memoization_with_nil_value
assert_equal nil, @person.age
assert_equal 1, @person.age_calls
@@ -96,6 +110,11 @@ uses_mocha 'Memoizable' do
assert_equal 1, @person.age_calls
end
def test_memorized_results_are_immutable
assert_equal "Josh", @person.name
assert_raise(ActiveSupport::FrozenObjectError) { @person.name.gsub!("Josh", "Gosh") }
end
def test_reloadable
counter = @calculator.counter
assert_equal 1, @calculator.counter
@@ -105,6 +124,21 @@ uses_mocha 'Memoizable' do
assert_equal 3, @calculator.counter
end
def test_unmemoize_all
assert_equal 1, @calculator.counter
assert @calculator.instance_variable_get(:@_memoized_counter).any?
@calculator.unmemoize_all
assert @calculator.instance_variable_get(:@_memoized_counter).empty?
assert_equal 2, @calculator.counter
end
def test_memoize_all
@calculator.memoize_all
assert @calculator.instance_variable_defined?(:@_memoized_counter)
end
def test_memoization_cache_is_different_for_each_instance
assert_equal 1, @calculator.counter
assert_equal 2, @calculator.counter(:reload)
@@ -114,6 +148,7 @@ uses_mocha 'Memoizable' do
def test_memoized_is_not_affected_by_freeze
@person.freeze
assert_equal "Josh", @person.name
assert_equal "Joshua", @person.update("Joshua")
end
def test_memoization_with_args

View File

@@ -1,5 +1,7 @@
*Edge*
* Added config.threadsafe! to toggle allow concurrency settings and disable the dependency loader [Josh Peek]
* Turn cache_classes on by default [Josh Peek]
* Added configurable eager load paths. Defaults to app/models, app/controllers, and app/helpers [Josh Peek]

View File

@@ -4,6 +4,9 @@
# Code is not reloaded between requests
config.cache_classes = true
# Enable threaded mode
# config.threadsafe!
# Use a different logger for distributed setups
# config.logger = SyslogLogger.new

View File

@@ -42,7 +42,7 @@ if code_or_file.nil?
$stderr.puts "Run '#{$0} -h' for help."
exit 1
elsif File.exist?(code_or_file)
eval(File.read(code_or_file))
eval(File.read(code_or_file), nil, code_or_file)
else
eval(code_or_file)
end

View File

@@ -340,9 +340,11 @@ Run `rake gems:install` to install the missing gems.
end
def load_view_paths
ActionView::PathSet::Path.eager_load_templates! if configuration.cache_classes
ActionMailer::Base.template_root.load if configuration.frameworks.include?(:action_mailer)
ActionController::Base.view_paths.load if configuration.frameworks.include?(:action_controller)
if configuration.frameworks.include?(:action_view)
ActionView::PathSet::Path.eager_load_templates! if configuration.cache_classes
ActionController::Base.view_paths.load if configuration.frameworks.include?(:action_controller)
ActionMailer::Base.template_root.load if configuration.frameworks.include?(:action_mailer)
end
end
# Eager load application classes
@@ -440,9 +442,11 @@ Run `rake gems:install` to install the missing gems.
# paths have already been set, it is not changed, otherwise it is
# set to use Configuration#view_path.
def initialize_framework_views
view_path = ActionView::PathSet::Path.new(configuration.view_path, false)
ActionMailer::Base.template_root ||= view_path if configuration.frameworks.include?(:action_mailer)
ActionController::Base.view_paths = view_path if configuration.frameworks.include?(:action_controller) && ActionController::Base.view_paths.empty?
if configuration.frameworks.include?(:action_view)
view_path = ActionView::PathSet::Path.new(configuration.view_path, false)
ActionMailer::Base.template_root ||= view_path if configuration.frameworks.include?(:action_mailer)
ActionController::Base.view_paths = view_path if configuration.frameworks.include?(:action_controller) && ActionController::Base.view_paths.empty?
end
end
# If Action Controller is not one of the loaded frameworks (Configuration#frameworks)
@@ -688,13 +692,17 @@ Run `rake gems:install` to install the missing gems.
# You can add gems with the #gem method.
attr_accessor :gems
# Adds a single Gem dependency to the rails application.
# Adds a single Gem dependency to the rails application. By default, it will require
# the library with the same name as the gem. Use :lib to specify a different name.
#
# # gem 'aws-s3', '>= 0.4.0'
# # require 'aws/s3'
# config.gem 'aws-s3', :lib => 'aws/s3', :version => '>= 0.4.0', \
# :source => "http://code.whytheluckystiff.net"
#
# To require a library be installed, but not attempt to load it, pass :lib => false
#
# config.gem 'qrp', :version => '0.4.1', :lib => false
def gem(name, options = {})
@gems << Rails::GemDependency.new(name, options)
end
@@ -764,6 +772,17 @@ Run `rake gems:install` to install the missing gems.
::RAILS_ROOT.replace @root_path
end
# Enable threaded mode. Allows concurrent requests to controller actions and
# multiple database connections. Also disables automatic dependency loading
# after boot
def threadsafe!
self.cache_classes = true
self.dependency_loading = false
self.active_record.allow_concurrency = true
self.action_controller.allow_concurrency = true
self
end
# Loads and returns the contents of the #database_configuration_file. The
# contents of the file are processed via ERB before being sent through
# YAML::load.

View File

@@ -58,7 +58,7 @@ module Rails
def load
return if @loaded || @load_paths_added == false
require(@lib || @name)
require(@lib || @name) unless @lib == false
@loaded = true
rescue LoadError
puts $!.to_s

View File

@@ -182,11 +182,11 @@ namespace :db do
end
namespace :fixtures do
desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z."
desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task :load => :environment do
require 'active_record/fixtures'
ActiveRecord::Base.establish_connection(Rails.env)
base_dir = File.join(Rails.root, 'test', 'fixtures')
base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/).map {|f| File.join(fixtures_dir, f) } : Dir.glob(File.join(fixtures_dir, '*.{yml,csv}'))).each do |fixture_file|
@@ -194,7 +194,7 @@ namespace :db do
end
end
desc "Search for a fixture given a LABEL or ID."
desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task :identify => :environment do
require "active_record/fixtures"
@@ -203,7 +203,8 @@ namespace :db do
puts %Q(The fixture ID for "#{label}" is #{Fixtures.identify(label)}.) if label
Dir["#{RAILS_ROOT}/test/fixtures/**/*.yml"].each do |file|
base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
Dir["#{base_dir}/**/*.yml"].each do |file|
if data = YAML::load(ERB.new(IO.read(file)).result)
data.keys.each do |key|
key_id = Fixtures.identify(key)

View File

@@ -11,6 +11,7 @@ uses_mocha "Plugin Tests" do
@gem_with_source = Rails::GemDependency.new "hpricot", :source => "http://code.whytheluckystiff.net"
@gem_with_version = Rails::GemDependency.new "hpricot", :version => "= 0.6"
@gem_with_lib = Rails::GemDependency.new "aws-s3", :lib => "aws/s3"
@gem_without_load = Rails::GemDependency.new "hpricot", :lib => false
end
def test_configuration_adds_gem_dependency
@@ -62,5 +63,13 @@ uses_mocha "Plugin Tests" do
@gem_with_lib.add_load_paths
@gem_with_lib.load
end
def test_gem_without_lib_loading
@gem_without_load.expects(:gem).with(@gem_without_load.name)
@gem_without_load.expects(:require).with(@gem_without_load.lib).never
@gem_without_load.add_load_paths
@gem_without_load.load
end
end
end