aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPratik Naik <pratiknaik@gmail.com>2008-06-02 12:29:28 +0100
committerPratik Naik <pratiknaik@gmail.com>2008-06-02 12:29:28 +0100
commit719aa13b0781d662673dba2225bc707fc4182b5e (patch)
tree057cabf17152a1ab0ccc749dd361eeaf754c38de
parentad3c97cea6dd0714652db7d9b5b4d06b81e5c035 (diff)
parenta980eb8c7734f14109d8c2a02a88dafdf682e0dc (diff)
downloadrails-719aa13b0781d662673dba2225bc707fc4182b5e.tar.gz
rails-719aa13b0781d662673dba2225bc707fc4182b5e.tar.bz2
rails-719aa13b0781d662673dba2225bc707fc4182b5e.zip
Merge commit 'mainstream/master'
-rw-r--r--.gitignore1
-rw-r--r--actionmailer/CHANGELOG2
-rwxr-xr-xactionmailer/Rakefile2
-rw-r--r--actionmailer/lib/action_mailer/base.rb20
-rw-r--r--actionmailer/lib/action_mailer/quoting.rb2
-rw-r--r--actionmailer/lib/action_mailer/version.rb4
-rwxr-xr-xactionmailer/test/mail_service_test.rb34
-rw-r--r--actionpack/CHANGELOG7
-rw-r--r--actionpack/Rakefile2
-rwxr-xr-xactionpack/lib/action_controller.rb1
-rw-r--r--actionpack/lib/action_controller/cgi_ext/cookie.rb6
-rw-r--r--actionpack/lib/action_controller/dispatcher.rb8
-rw-r--r--actionpack/lib/action_controller/rack_process.rb321
-rwxr-xr-xactionpack/lib/action_controller/request_profiler.rb25
-rw-r--r--actionpack/lib/action_pack/version.rb4
-rw-r--r--actionpack/lib/action_view/compiled_templates.rb69
-rwxr-xr-xactionpack/test/controller/cgi_test.rb33
-rw-r--r--actionpack/test/controller/rack_test.rb150
-rw-r--r--actionpack/test/template/compiled_templates_test.rb192
-rw-r--r--activerecord/CHANGELOG13
-rwxr-xr-xactiverecord/Rakefile2
-rwxr-xr-xactiverecord/lib/active_record/associations/belongs_to_polymorphic_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb2
-rwxr-xr-xactiverecord/lib/active_record/base.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb9
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb14
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/mysql_adapter.rb3
-rw-r--r--activerecord/lib/active_record/dirty.rb2
-rw-r--r--activerecord/lib/active_record/named_scope.rb6
-rwxr-xr-xactiverecord/lib/active_record/validations.rb2
-rw-r--r--activerecord/lib/active_record/version.rb4
-rw-r--r--activerecord/test/cases/adapter_test.rb20
-rwxr-xr-xactiverecord/test/cases/associations/belongs_to_associations_test.rb29
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb6
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb1
-rw-r--r--activerecord/test/cases/defaults_test.rb2
-rwxr-xr-xactiverecord/test/cases/inheritance_test.rb18
-rw-r--r--activerecord/test/cases/method_scoping_test.rb16
-rw-r--r--activerecord/test/cases/named_scope_test.rb8
-rwxr-xr-xactiverecord/test/cases/validations_test.rb6
-rw-r--r--activeresource/CHANGELOG2
-rw-r--r--activeresource/Rakefile2
-rw-r--r--activeresource/lib/active_resource/version.rb4
-rw-r--r--activesupport/CHANGELOG4
-rw-r--r--activesupport/lib/active_support/dependencies.rb19
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb12
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb8
-rw-r--r--activesupport/lib/active_support/version.rb4
-rw-r--r--activesupport/test/dependencies_test.rb20
-rw-r--r--railties/CHANGELOG2
-rw-r--r--railties/Rakefile10
-rw-r--r--railties/environments/environment.rb5
-rw-r--r--railties/lib/commands/dbconsole.rb14
-rw-r--r--railties/lib/commands/plugin.rb2
-rw-r--r--railties/lib/initializer.rb44
-rw-r--r--railties/lib/rails/gem_dependency.rb15
-rw-r--r--railties/lib/rails/plugin.rb17
-rw-r--r--railties/lib/rails/rack.rb5
-rw-r--r--railties/lib/rails/rack/static.rb35
-rw-r--r--railties/lib/rails/version.rb4
-rw-r--r--railties/lib/rails_generator/commands.rb8
-rw-r--r--railties/lib/tasks/gems.rake12
-rwxr-xr-xrelease.rb7
64 files changed, 921 insertions, 406 deletions
diff --git a/.gitignore b/.gitignore
index e57310cdc8..8c5dfb64e1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@ actionpack/pkg
actionmailer/pkg
activesupport/pkg
railties/pkg
+*.rbc
diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG
index b2ce462f0c..bdae0d4d3d 100644
--- a/actionmailer/CHANGELOG
+++ b/actionmailer/CHANGELOG
@@ -1,4 +1,4 @@
-*2.1.0 RC1 (May 11th, 2008)*
+*2.1.0 (May 31st, 2008)*
* Fixed that a return-path header would be ignored #7572 [joost]
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index 22ed396417..c09526cbef 100755
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -55,7 +55,7 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "actionmailer"
s.homepage = "http://www.rubyonrails.org"
- s.add_dependency('actionpack', '= 2.0.991' + PKG_BUILD)
+ s.add_dependency('actionpack', '= 2.1.0' + PKG_BUILD)
s.has_rdoc = true
s.requirements << 'none'
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 030bb178da..e065132107 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -35,7 +35,8 @@ module ActionMailer #:nodoc:
# * <tt>subject</tt> - The subject of your email. Sets the <tt>Subject:</tt> header.
# * <tt>from</tt> - Who the email you are sending is from. Sets the <tt>From:</tt> header.
# * <tt>cc</tt> - Takes one or more email addresses. These addresses will receive a carbon copy of your email. Sets the <tt>Cc:</tt> header.
- # * <tt>bcc</tt> - Takes one or more email address. These addresses will receive a blind carbon copy of your email. Sets the <tt>Bcc:</tt> header.
+ # * <tt>bcc</tt> - Takes one or more email addresses. These addresses will receive a blind carbon copy of your email. Sets the <tt>Bcc:</tt> header.
+ # * <tt>reply_to</tt> - Takes one or more email addresses. These addresses will be listed as the default recipients when replying to your email. Sets the <tt>Reply-To:</tt> header.
# * <tt>sent_on</tt> - The date on which the message was sent. If not set, the header wil be set by the delivery agent.
# * <tt>content_type</tt> - Specify the content type of the message. Defaults to <tt>text/plain</tt>.
# * <tt>headers</tt> - Specify additional headers to be set for the message, e.g. <tt>headers 'X-Mail-Count' => 107370</tt>.
@@ -317,6 +318,10 @@ module ActionMailer #:nodoc:
# Specify the from address for the message.
adv_attr_accessor :from
+ # Specify the address (if different than the "from" address) to direct
+ # replies to this message.
+ adv_attr_accessor :reply_to
+
# Specify additional headers to be added to the message.
adv_attr_accessor :headers
@@ -576,13 +581,14 @@ module ActionMailer #:nodoc:
def create_mail
m = TMail::Mail.new
- m.subject, = quote_any_if_necessary(charset, subject)
- m.to, m.from = quote_any_address_if_necessary(charset, recipients, from)
- m.bcc = quote_address_if_necessary(bcc, charset) unless bcc.nil?
- m.cc = quote_address_if_necessary(cc, charset) unless cc.nil?
-
+ m.subject, = quote_any_if_necessary(charset, subject)
+ m.to, m.from = quote_any_address_if_necessary(charset, recipients, from)
+ m.bcc = quote_address_if_necessary(bcc, charset) unless bcc.nil?
+ m.cc = quote_address_if_necessary(cc, charset) unless cc.nil?
+ m.reply_to = quote_address_if_necessary(reply_to, charset) unless reply_to.nil?
m.mime_version = mime_version unless mime_version.nil?
- m.date = sent_on.to_time rescue sent_on if sent_on
+ m.date = sent_on.to_time rescue sent_on if sent_on
+
headers.each { |k, v| m[k] = v }
real_content_type, ctype_attrs = parse_content_type
diff --git a/actionmailer/lib/action_mailer/quoting.rb b/actionmailer/lib/action_mailer/quoting.rb
index b222432787..a2f2c70799 100644
--- a/actionmailer/lib/action_mailer/quoting.rb
+++ b/actionmailer/lib/action_mailer/quoting.rb
@@ -40,7 +40,7 @@ module ActionMailer
# regular email address, or it can be a phrase followed by an address in
# brackets. The phrase is the only part that will be quoted, and only if
# it needs to be. This allows extended characters to be used in the
- # "to", "from", "cc", and "bcc" headers.
+ # "to", "from", "cc", "bcc" and "reply-to" headers.
def quote_address_if_necessary(address, charset)
if Array === address
address.map { |a| quote_address_if_necessary(a, charset) }
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index 88b9a3c200..c35b648baf 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -1,8 +1,8 @@
module ActionMailer
module VERSION #:nodoc:
MAJOR = 2
- MINOR = 0
- TINY = 991
+ MINOR = 1
+ TINY = 0
STRING = [MAJOR, MINOR, TINY].join('.')
end
diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb
index 57a651ccd4..e5ecb0e254 100755
--- a/actionmailer/test/mail_service_test.rb
+++ b/actionmailer/test/mail_service_test.rb
@@ -40,6 +40,15 @@ class TestMailer < ActionMailer::Base
body "Nothing to see here."
end
+ def different_reply_to(recipient)
+ recipients recipient
+ subject "testing reply_to"
+ from "system@loudthinking.com"
+ sent_on Time.local(2008, 5, 23)
+ reply_to "atraver@gmail.com"
+ body "Nothing to see here."
+ end
+
def iso_charset(recipient)
@recipients = recipient
@subject = "testing isø charsets"
@@ -445,6 +454,31 @@ class ActionMailerTest < Test::Unit::TestCase
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
end
+ def test_reply_to
+ expected = new_mail
+
+ expected.to = @recipient
+ expected.subject = "testing reply_to"
+ expected.body = "Nothing to see here."
+ expected.from = "system@loudthinking.com"
+ expected.reply_to = "atraver@gmail.com"
+ expected.date = Time.local 2008, 5, 23
+
+ created = nil
+ assert_nothing_raised do
+ created = TestMailer.create_different_reply_to @recipient
+ end
+ assert_not_nil created
+ assert_equal expected.encoded, created.encoded
+
+ assert_nothing_raised do
+ TestMailer.deliver_different_reply_to @recipient
+ end
+
+ assert_not_nil ActionMailer::Base.deliveries.first
+ assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
+ end
+
def test_iso_charset
expected = new_mail( "iso-8859-1" )
expected.to = @recipient
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 5c4bfbf3c9..9622029362 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,6 +1,9 @@
-* InstanceTag#default_time_from_options overflows to DateTime [Geoff Buesing]
+* Added Rack processor [Ezra Zygmuntowicz, Josh Peek]
+
-*2.1.0 RC1 (May 11th, 2008)*
+*2.1.0 (May 31st, 2008)*
+
+* InstanceTag#default_time_from_options overflows to DateTime [Geoff Buesing]
* Fixed that forgery protection can be used without session tracking (Peter Jones) [#139]
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 0147a5c1e8..b37f756c1e 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -76,7 +76,7 @@ spec = Gem::Specification.new do |s|
s.has_rdoc = true
s.requirements << 'none'
- s.add_dependency('activesupport', '= 2.0.991' + PKG_BUILD)
+ s.add_dependency('activesupport', '= 2.1.0' + PKG_BUILD)
s.require_path = 'lib'
s.autorequire = 'action_controller'
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 810a5fb9b5..3c4a339d50 100755
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -53,6 +53,7 @@ require 'action_controller/streaming'
require 'action_controller/session_management'
require 'action_controller/http_authentication'
require 'action_controller/components'
+require 'action_controller/rack_process'
require 'action_controller/record_identifier'
require 'action_controller/request_forgery_protection'
require 'action_controller/headers'
diff --git a/actionpack/lib/action_controller/cgi_ext/cookie.rb b/actionpack/lib/action_controller/cgi_ext/cookie.rb
index ef033fb4f3..009ddd1c64 100644
--- a/actionpack/lib/action_controller/cgi_ext/cookie.rb
+++ b/actionpack/lib/action_controller/cgi_ext/cookie.rb
@@ -78,6 +78,12 @@ class CGI #:nodoc:
buf
end
+ # FIXME: work around broken 1.8.7 DelegateClass#respond_to?
+ def respond_to?(method, include_private = false)
+ return true if super(method)
+ return __getobj__.respond_to?(method, include_private)
+ end
+
# Parses a raw cookie string into a hash of <tt>cookie-name => cookie-object</tt>
# pairs.
#
diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb
index 6e1e7a261f..b40f1ba9be 100644
--- a/actionpack/lib/action_controller/dispatcher.rb
+++ b/actionpack/lib/action_controller/dispatcher.rb
@@ -96,7 +96,7 @@ module ActionController
include ActiveSupport::Callbacks
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
- def initialize(output, request = nil, response = nil)
+ def initialize(output = $stdout, request = nil, response = nil)
@output, @request, @response = output, request, response
end
@@ -123,6 +123,12 @@ module ActionController
failsafe_rescue exception
end
+ def call(env)
+ @request = RackRequest.new(env)
+ @response = RackResponse.new
+ dispatch
+ end
+
def reload_application
# Run prepare callbacks before every request in development mode
run_callbacks :prepare_dispatch
diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb
new file mode 100644
index 0000000000..16625519b6
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_process.rb
@@ -0,0 +1,321 @@
+require 'action_controller/cgi_ext'
+require 'action_controller/session/cookie_store'
+
+module ActionController #:nodoc:
+ class RackRequest < AbstractRequest #:nodoc:
+ attr_accessor :env, :session_options
+
+ class SessionFixationAttempt < StandardError #:nodoc:
+ end
+
+ DEFAULT_SESSION_OPTIONS = {
+ :database_manager => CGI::Session::CookieStore, # store data in cookie
+ :prefix => "ruby_sess.", # prefix session file names
+ :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
+ @env = env
+ @cgi = CGIWrapper.new(self)
+ super()
+ 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
+ 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"]
+
+ if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
+ @env["rack.request.cookie_hash"]
+ else
+ @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
+ # According to RFC 2109:
+ # If multiple cookies satisfy the criteria above, they are ordered in
+ # the Cookie header such that those with more specific Path attributes
+ # precede those with less specific. Ordering with respect to other
+ # attributes (e.g., Domain) is unspecified.
+ @env["rack.request.cookie_hash"] =
+ parse_query(@env["rack.request.cookie_string"], ';,').inject({}) { |h, (k,v)|
+ h[k] = Array === v ? v.first : v
+ h
+ }
+ end
+ 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 session
+ unless defined?(@session)
+ if @session_options == false
+ @session = Hash.new
+ else
+ stale_session_check! do
+ if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
+ raise SessionFixationAttempt
+ end
+ case value = session_options_with_string_keys['new_session']
+ when true
+ @session = new_session
+ when false
+ begin
+ @session = CGI::Session.new(@cgi, session_options_with_string_keys)
+ # CGI::Session raises ArgumentError if 'new_session' == false
+ # and no session cookie or query param is present.
+ rescue ArgumentError
+ @session = Hash.new
+ end
+ when nil
+ @session = CGI::Session.new(@cgi, session_options_with_string_keys)
+ else
+ raise ArgumentError, "Invalid new_session option: #{value}"
+ end
+ @session['__valid_session']
+ end
+ end
+ end
+ @session
+ end
+
+ def reset_session
+ @session.delete if defined?(@session) && @session.is_a?(CGI::Session)
+ @session = new_session
+ end
+
+ private
+ # Delete an old session if it exists then create a new one.
+ def new_session
+ if @session_options == false
+ Hash.new
+ else
+ CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
+ CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
+ end
+ end
+
+ def cookie_only?
+ session_options_with_string_keys['cookie_only']
+ end
+
+ def stale_session_check!
+ yield
+ rescue ArgumentError => argument_error
+ if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
+ begin
+ # Note that the regexp does not allow $1 to end with a ':'
+ $1.constantize
+ rescue LoadError, NameError => const_error
+ raise ActionController::SessionRestoreError, <<-end_msg
+Session contains objects whose class definition isn\'t available.
+Remember to require the classes for all objects kept in the session.
+(Original exception: #{const_error.message} [#{const_error.class}])
+end_msg
+ end
+
+ retry
+ else
+ raise
+ end
+ end
+
+ def session_options_with_string_keys
+ @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
+ end
+
+ # From Rack::Utils
+ def parse_query(qs, d = '&;')
+ params = {}
+ (qs || '').split(/[#{d}] */n).inject(params) { |h,p|
+ k, v = unescape(p).split('=',2)
+ if cur = params[k]
+ if cur.class == Array
+ params[k] << v
+ else
+ params[k] = [cur, v]
+ end
+ else
+ params[k] = v
+ end
+ }
+
+ return params
+ end
+
+ def unescape(s)
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
+ [$1.delete('%')].pack('H*')
+ }
+ end
+ end
+
+ class RackResponse < AbstractResponse #:nodoc:
+ attr_accessor :status
+
+ def initialize
+ @writer = lambda { |x| @body << x }
+ @block = nil
+ super()
+ end
+
+ def out(output = $stdout, &block)
+ @block = block
+ normalize_headers(@headers)
+ if [204, 304].include?(@status.to_i)
+ @headers.delete "Content-Type"
+ [status.to_i, @headers.to_hash, []]
+ else
+ [status.to_i, @headers.to_hash, self]
+ end
+ end
+ alias to_a out
+
+ def each(&callback)
+ if @body.respond_to?(:call)
+ @writer = lambda { |x| callback.call(x) }
+ @body.call(self, self)
+ else
+ @body.each(&callback)
+ end
+
+ @writer = callback
+ @block.call(self) if @block
+ end
+
+ def write(str)
+ @writer.call str.to_s
+ str
+ end
+
+ def close
+ @body.close if @body.respond_to?(:close)
+ end
+
+ def empty?
+ @block == nil && @body.empty?
+ end
+
+ private
+ def normalize_headers(options = "text/html")
+ if options.is_a?(String)
+ headers['Content-Type'] = options unless headers['Content-Type']
+ else
+ headers['Content-Length'] = options.delete('Content-Length').to_s if options['Content-Length']
+
+ headers['Content-Type'] = options.delete('type') || "text/html"
+ headers['Content-Type'] += "; charset=" + options.delete('charset') if options['charset']
+
+ headers['Content-Language'] = options.delete('language') if options['language']
+ headers['Expires'] = options.delete('expires') if options['expires']
+
+ @status = options.delete('Status') if options['Status']
+ @status ||= 200
+ # Convert 'cookie' header to 'Set-Cookie' headers.
+ # Because Set-Cookie header can appear more the once in the response body,
+ # we store it in a line break seperated string that will be translated to
+ # multiple Set-Cookie header by the handler.
+ if cookie = options.delete('cookie')
+ cookies = []
+
+ case cookie
+ when Array then cookie.each { |c| cookies << c.to_s }
+ when Hash then cookie.each { |_, c| cookies << c.to_s }
+ else cookies << cookie.to_s
+ end
+
+ @output_cookies.each { |c| cookies << c.to_s } if @output_cookies
+
+ headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].compact.join("\n")
+ end
+
+ options.each { |k,v| headers[k] = v }
+ end
+
+ ""
+ end
+ end
+
+ class CGIWrapper < ::CGI
+ def initialize(request, *args)
+ @request = request
+ @args = *args
+ @input = request.body
+
+ super *args
+ end
+
+ def params
+ @params ||= @request.params
+ end
+
+ def cookies
+ @request.cookies
+ end
+
+ def query_string
+ @request.query_string
+ end
+
+ # Used to wrap the normal args variable used inside CGI.
+ def args
+ @args
+ end
+
+ # Used to wrap the normal env_table variable used inside CGI.
+ def env_table
+ @request.env
+ end
+
+ # Used to wrap the normal stdinput variable used inside CGI.
+ def stdinput
+ @input
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/request_profiler.rb b/actionpack/lib/action_controller/request_profiler.rb
index 8a18d194bd..a6471d0c08 100755
--- a/actionpack/lib/action_controller/request_profiler.rb
+++ b/actionpack/lib/action_controller/request_profiler.rb
@@ -17,13 +17,13 @@ module ActionController
reset!
end
- def benchmark(n)
+ def benchmark(n, profiling = false)
@quiet = true
print ' '
result = Benchmark.realtime do
n.times do |i|
- run
+ run(profiling)
print_progress(i)
end
end
@@ -43,8 +43,15 @@ module ActionController
script = File.read(script_path)
source = <<-end_source
- def run
- #{script}
+ def run(profiling = false)
+ if profiling
+ RubyProf.resume do
+ #{script}
+ end
+ else
+ #{script}
+ end
+
old_request_count = request_count
reset!
self.request_count = old_request_count
@@ -91,21 +98,22 @@ module ActionController
def profile(sandbox)
load_ruby_prof
- results = RubyProf.profile { benchmark(sandbox) }
+ benchmark(sandbox, true)
+ results = RubyProf.stop
show_profile_results results
results
end
- def benchmark(sandbox)
+ def benchmark(sandbox, profiling = false)
sandbox.request_count = 0
- elapsed = sandbox.benchmark(options[:n]).to_f
+ elapsed = sandbox.benchmark(options[:n], profiling).to_f
count = sandbox.request_count.to_i
puts '%.2f sec, %d requests, %d req/sec' % [elapsed, count, count / elapsed]
end
def warmup(sandbox)
- Benchmark.realtime { sandbox.run }
+ Benchmark.realtime { sandbox.run(false) }
end
def default_options
@@ -136,6 +144,7 @@ module ActionController
protected
def load_ruby_prof
begin
+ gem 'ruby-prof', '>= 0.6.1'
require 'ruby-prof'
if mode = options[:measure]
RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index 70fc1ced8c..c67654d9a8 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -1,8 +1,8 @@
module ActionPack #:nodoc:
module VERSION #:nodoc:
MAJOR = 2
- MINOR = 0
- TINY = 991
+ MINOR = 1
+ TINY = 0
STRING = [MAJOR, MINOR, TINY].join('.')
end
diff --git a/actionpack/lib/action_view/compiled_templates.rb b/actionpack/lib/action_view/compiled_templates.rb
deleted file mode 100644
index 5a286432e3..0000000000
--- a/actionpack/lib/action_view/compiled_templates.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-module ActionView
-
- # CompiledTemplates modules hold methods that have been compiled.
- # Templates are compiled into these methods so that they do not need to be
- # read and parsed for each request.
- #
- # Each template may be compiled into one or more methods. Each method accepts a given
- # set of parameters which is used to implement local assigns passing.
- #
- # To use a compiled template module, create a new instance and include it into the class
- # in which you want the template to be rendered.
- class CompiledTemplates < Module
- attr_reader :method_names
-
- def initialize
- @method_names = Hash.new do |hash, key|
- hash[key] = "__compiled_method_#{(hash.length + 1)}"
- end
- @mtimes = {}
- end
-
- # Return the full key for the given identifier and argument names
- def full_key(identifier, arg_names)
- [identifier, arg_names]
- end
-
- # Return the selector for this method or nil if it has not been compiled
- def selector(identifier, arg_names)
- key = full_key(identifier, arg_names)
- method_names.key?(key) ? method_names[key] : nil
- end
- alias :compiled? :selector
-
- # Return the time at which the method for the given identifier and argument names was compiled.
- def mtime(identifier, arg_names)
- @mtimes[full_key(identifier, arg_names)]
- end
-
- # Compile the provided source code for the given argument names and with the given initial line number.
- # The identifier should be unique to this source.
- #
- # The file_name, if provided will appear in backtraces. If not provided, the file_name defaults
- # to the identifier.
- #
- # This method will return the selector for the compiled version of this method.
- def compile_source(identifier, arg_names, source, initial_line_number = 0, file_name = nil)
- file_name ||= identifier
- name = method_names[full_key(identifier, arg_names)]
- arg_desc = arg_names.empty? ? '' : "(#{arg_names * ', '})"
- fake_file_name = "#{file_name}#{arg_desc}" # Include the arguments for this version (for now)
-
- method_def = wrap_source(name, arg_names, source)
-
- begin
- module_eval(method_def, fake_file_name, initial_line_number)
- @mtimes[full_key(identifier, arg_names)] = Time.now
- rescue Exception => e # errors from compiled source
- e.blame_file! identifier
- raise
- end
- name
- end
-
- # Wrap the provided source in a def ... end block.
- def wrap_source(name, arg_names, source)
- "def #{name}(#{arg_names * ', '})\n#{source}\nend"
- end
- end
-end
diff --git a/actionpack/test/controller/cgi_test.rb b/actionpack/test/controller/cgi_test.rb
index 87f72fda77..f0f3a4b826 100755
--- a/actionpack/test/controller/cgi_test.rb
+++ b/actionpack/test/controller/cgi_test.rb
@@ -114,3 +114,36 @@ class CgiRequestNeedsRewoundTest < BaseCgiTest
assert_equal 0, request.body.pos
end
end
+
+class CgiResponseTest < BaseCgiTest
+ def setup
+ super
+ @fake_cgi.expects(:header).returns("HTTP/1.0 200 OK\nContent-Type: text/html\n")
+ @response = ActionController::CgiResponse.new(@fake_cgi)
+ @output = StringIO.new('')
+ end
+
+ def test_simple_output
+ @response.body = "Hello, World!"
+
+ @response.out(@output)
+ assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\nHello, World!", @output.string
+ end
+
+ def test_head_request
+ @fake_cgi.env_table['REQUEST_METHOD'] = 'HEAD'
+ @response.body = "Hello, World!"
+
+ @response.out(@output)
+ assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\n", @output.string
+ end
+
+ def test_streaming_block
+ @response.body = Proc.new do |response, output|
+ 5.times { |n| output.write(n) }
+ end
+
+ @response.out(@output)
+ assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\n01234", @output.string
+ end
+end
diff --git a/actionpack/test/controller/rack_test.rb b/actionpack/test/controller/rack_test.rb
new file mode 100644
index 0000000000..cd4151783e
--- /dev/null
+++ b/actionpack/test/controller/rack_test.rb
@@ -0,0 +1,150 @@
+require 'abstract_unit'
+require 'action_controller/rack_process'
+
+class BaseRackTest < Test::Unit::TestCase
+ def setup
+ @env = {"HTTP_MAX_FORWARDS"=>"10", "SERVER_NAME"=>"glu.ttono.us:8007", "FCGI_ROLE"=>"RESPONDER", "HTTP_X_FORWARDED_HOST"=>"glu.ttono.us", "HTTP_ACCEPT_ENCODING"=>"gzip, deflate", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", "PATH_INFO"=>"", "HTTP_ACCEPT_LANGUAGE"=>"en", "HTTP_HOST"=>"glu.ttono.us:8007", "SERVER_PROTOCOL"=>"HTTP/1.1", "REDIRECT_URI"=>"/dispatch.fcgi", "SCRIPT_NAME"=>"/dispatch.fcgi", "SERVER_ADDR"=>"207.7.108.53", "REMOTE_ADDR"=>"207.7.108.53", "SERVER_SOFTWARE"=>"lighttpd/1.4.5", "HTTP_COOKIE"=>"_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes", "HTTP_X_FORWARDED_SERVER"=>"glu.ttono.us", "REQUEST_URI"=>"/admin", "DOCUMENT_ROOT"=>"/home/kevinc/sites/typo/public", "SERVER_PORT"=>"8007", "QUERY_STRING"=>"", "REMOTE_PORT"=>"63137", "GATEWAY_INTERFACE"=>"CGI/1.1", "HTTP_X_FORWARDED_FOR"=>"65.88.180.234", "HTTP_ACCEPT"=>"*/*", "SCRIPT_FILENAME"=>"/home/kevinc/sites/typo/public/dispatch.fcgi", "REDIRECT_STATUS"=>"200", "REQUEST_METHOD"=>"GET"}
+ # some Nokia phone browsers omit the space after the semicolon separator.
+ # some developers have grown accustomed to using comma in cookie values.
+ @alt_cookie_fmt_request_hash = {"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"}
+ @request = ActionController::RackRequest.new(@env)
+ end
+
+ def default_test; end
+end
+
+
+class RackRequestTest < BaseRackTest
+ def test_proxy_request
+ assert_equal 'glu.ttono.us', @request.host_with_port
+ 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
+
+ @env['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
+ assert_equal "www.secondhost.org", @request.host
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ end
+
+ def test_cookie_syntax_resilience
+ cookies = CGI::Cookie::parse(@env["HTTP_COOKIE"]);
+ assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"], cookies.inspect
+ assert_equal ["yes"], cookies["is_admin"], cookies.inspect
+
+ alt_cookies = CGI::Cookie::parse(@alt_cookie_fmt_request_hash["HTTP_COOKIE"]);
+ assert_equal ["c84ace847,96670c052c6ceb2451fb0f2"], alt_cookies["_session_id"], alt_cookies.inspect
+ assert_equal ["yes"], alt_cookies["is_admin"], alt_cookies.inspect
+ end
+end
+
+
+class RackRequestParamsParsingTest < BaseRackTest
+ def test_doesnt_break_when_content_type_has_charset
+ data = 'flamenco=love'
+ @request.env['CONTENT_LENGTH'] = data.length
+ @request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
+ @request.env['RAW_POST_DATA'] = data
+ assert_equal({"flamenco"=> "love"}, @request.request_parameters)
+ end
+
+ def test_doesnt_interpret_request_uri_as_query_string_when_missing
+ @request.env['REQUEST_URI'] = 'foo'
+ assert_equal({}, @request.query_parameters)
+ end
+end
+
+
+class RackRequestNeedsRewoundTest < BaseRackTest
+ def test_body_should_be_rewound
+ data = 'foo'
+ @env['rack.input'] = StringIO.new(data)
+ @env['CONTENT_LENGTH'] = data.length
+ @env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
+
+ # Read the request body by parsing params.
+ request = ActionController::RackRequest.new(@env)
+ request.request_parameters
+
+ # Should have rewound the body.
+ assert_equal 0, request.body.pos
+ end
+end
+
+
+class RackResponseTest < BaseRackTest
+ def setup
+ super
+ @response = ActionController::RackResponse.new
+ @output = StringIO.new('')
+ end
+
+ def test_simple_output
+ @response.body = "Hello, World!"
+
+ status, headers, body = @response.out(@output)
+ assert_equal 200, status
+ assert_equal({"Content-Type" => "text/html", "Cache-Control" => "no-cache", "Set-Cookie" => ""}, headers)
+
+ parts = []
+ body.each { |part| parts << part }
+ assert_equal ["Hello, World!"], parts
+ end
+
+ def test_streaming_block
+ @response.body = Proc.new do |response, output|
+ 5.times { |n| output.write(n) }
+ end
+
+ status, headers, body = @response.out(@output)
+ assert_equal 200, status
+ assert_equal({"Content-Type" => "text/html", "Cache-Control" => "no-cache", "Set-Cookie" => ""}, headers)
+
+ parts = []
+ body.each { |part| parts << part }
+ assert_equal ["0", "1", "2", "3", "4"], parts
+ end
+end
diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb
deleted file mode 100644
index 73e7ec1d76..0000000000
--- a/actionpack/test/template/compiled_templates_test.rb
+++ /dev/null
@@ -1,192 +0,0 @@
-require 'abstract_unit'
-require 'action_view/helpers/date_helper'
-require 'action_view/compiled_templates'
-
-class CompiledTemplateTests < Test::Unit::TestCase
- def setup
- @ct = ActionView::CompiledTemplates.new
- @v = Class.new
- @v.send :include, @ct
- @a = './test_compile_template_a.rhtml'
- @b = './test_compile_template_b.rhtml'
- @s = './test_compile_template_link.rhtml'
- end
- def teardown
- [@a, @b, @s].each do |f|
- FileUtils.rm(f) if File.exist?(f) || File.symlink?(f)
- end
- end
- attr_reader :ct, :v
-
- def test_name_allocation
- hi_world = ct.method_names['hi world']
- hi_sexy = ct.method_names['hi sexy']
- wish_upon_a_star = ct.method_names['I love seeing decent error messages']
-
- assert_equal hi_world, ct.method_names['hi world']
- assert_equal hi_sexy, ct.method_names['hi sexy']
- assert_equal wish_upon_a_star, ct.method_names['I love seeing decent error messages']
- assert_equal 3, [hi_world, hi_sexy, wish_upon_a_star].uniq.length
- end
-
- def test_wrap_source
- assert_equal(
- "def aliased_assignment(value)\nself.value = value\nend",
- @ct.wrap_source(:aliased_assignment, [:value], 'self.value = value')
- )
-
- assert_equal(
- "def simple()\nnil\nend",
- @ct.wrap_source(:simple, [], 'nil')
- )
- end
-
- def test_compile_source_single_method
- selector = ct.compile_source('doubling method', [:a], 'a + a')
- assert_equal 2, @v.new.send(selector, 1)
- assert_equal 4, @v.new.send(selector, 2)
- assert_equal -4, @v.new.send(selector, -2)
- assert_equal 0, @v.new.send(selector, 0)
- selector
- end
-
- def test_compile_source_two_method
- sel1 = test_compile_source_single_method # compile the method in the other test
- sel2 = ct.compile_source('doubling method', [:a, :b], 'a + b + a + b')
- assert_not_equal sel1, sel2
-
- assert_equal 2, @v.new.send(sel1, 1)
- assert_equal 4, @v.new.send(sel1, 2)
-
- assert_equal 6, @v.new.send(sel2, 1, 2)
- assert_equal 32, @v.new.send(sel2, 15, 1)
- end
-
- def test_mtime
- t1 = Time.now
-
- test_compile_source_single_method
- mtime = ct.mtime('doubling method', [:a])
-
- assert mtime < Time.now
- assert mtime > t1
- end
-
- uses_mocha 'test_compile_time' do
-
- def test_compile_time
- t = Time.now
-
- File.open(@a, "w"){|f| f.puts @a}
- File.open(@b, "w"){|f| f.puts @b}
- # windows doesn't support symlinks (even under cygwin)
- windows = (RUBY_PLATFORM =~ /win32/)
- `ln -s #{@a} #{@s}` unless windows
-
- v = ActionView::Base.new
- v.base_path = '.'
- v.cache_template_loading = false
-
- ta = ActionView::Template.new(v, @a, false, {})
- tb = ActionView::Template.new(v, @b, false, {})
- ts = ActionView::Template.new(v, @s, false, {})
-
- @handler_class = ActionView::Template.handler_class_for_extension(:rhtml)
- @handler = @handler_class.new(v)
-
- # All templates were created at t+1
- File::Stat.any_instance.expects(:mtime).times(windows ? 2 : 3).returns(t + 1.second)
-
- # private methods template_changed_since? and compile_template?
- # should report true for all since they have not been compiled
- assert @handler.send(:template_changed_since?, @a, t)
- assert @handler.send(:template_changed_since?, @b, t)
- assert @handler.send(:template_changed_since?, @s, t) unless windows
-
- assert @handler.send(:compile_template?, ta)
- assert @handler.send(:compile_template?, tb)
- assert @handler.send(:compile_template?, ts) unless windows
-
- # All templates are rendered at t+2
- Time.expects(:now).times(windows ? 2 : 3).returns(t + 2.seconds)
- v.send(:render_template, ta)
- v.send(:render_template, tb)
- v.send(:render_template, ts) unless windows
- a_n = v.method_names[@a]
- b_n = v.method_names[@b]
- s_n = v.method_names[@s] unless windows
- # all of the files have changed since last compile
- assert @handler.compile_time[a_n] > t
- assert @handler.compile_time[b_n] > t
- assert @handler.compile_time[s_n] > t unless windows
-
- # private methods template_changed_since? and compile_template?
- # should report false for all since none have changed since compile
- File::Stat.any_instance.expects(:mtime).times(windows ? 6 : 12).returns(t + 1.second)
- assert !@handler.send(:template_changed_since?, @a, @handler.compile_time[a_n])
- assert !@handler.send(:template_changed_since?, @b, @handler.compile_time[b_n])
- assert !@handler.send(:template_changed_since?, @s, @handler.compile_time[s_n]) unless windows
- assert !@handler.send(:compile_template?, ta)
- assert !@handler.send(:compile_template?, tb)
- assert !@handler.send(:compile_template?, ts) unless windows
- v.send(:render_template, ta)
- v.send(:render_template, tb)
- v.send(:render_template, ts) unless windows
- # none of the files have changed since last compile
- assert @handler.compile_time[a_n] < t + 3.seconds
- assert @handler.compile_time[b_n] < t + 3.seconds
- assert @handler.compile_time[s_n] < t + 3.seconds unless windows
-
- `rm #{@s}; ln -s #{@b} #{@s}` unless windows
- # private methods template_changed_since? and compile_template?
- # should report true for symlink since it has changed since compile
-
- # t + 3.seconds is for the symlink
- File::Stat.any_instance.expects(:mtime).times(windows ? 6 : 9).returns(
- *(windows ? [ t + 1.second, t + 1.second ] :
- [ t + 1.second, t + 1.second, t + 3.second ]) * 3)
- assert !@handler.send(:template_changed_since?, @a, @handler.compile_time[a_n])
- assert !@handler.send(:template_changed_since?, @b, @handler.compile_time[b_n])
- assert @handler.send(:template_changed_since?, @s, @handler.compile_time[s_n]) unless windows
- assert !@handler.send(:compile_template?, ta)
- assert !@handler.send(:compile_template?, tb)
- assert @handler.send(:compile_template?, ts) unless windows
-
- # Only the symlink template gets rendered at t+3
- Time.stubs(:now).returns(t + 3.seconds) unless windows
- v.send(:render_template, ta)
- v.send(:render_template, tb)
- v.send(:render_template, ts) unless windows
- # the symlink has changed since last compile
- assert @handler.compile_time[a_n] < t + 3.seconds
- assert @handler.compile_time[b_n] < t + 3.seconds
- assert_equal @handler.compile_time[s_n], t + 3.seconds unless windows
-
- FileUtils.touch @b
- # private methods template_changed_since? and compile_template?
- # should report true for symlink and file at end of symlink
- # since it has changed since last compile
- #
- # t+4 is for @b and also for the file that @s points to, which is @b
- File::Stat.any_instance.expects(:mtime).times(windows ? 6 : 12).returns(
- *(windows ? [ t + 1.second, t + 4.seconds ] :
- [ t + 1.second, t + 4.seconds, t + 3.second, t + 4.seconds ]) * 3)
- assert !@handler.send(:template_changed_since?, @a, @handler.compile_time[a_n])
- assert @handler.send(:template_changed_since?, @b, @handler.compile_time[b_n])
- assert @handler.send(:template_changed_since?, @s, @handler.compile_time[s_n]) unless windows
- assert !@handler.send(:compile_template?, ta)
- assert @handler.send(:compile_template?, tb)
- assert @handler.send(:compile_template?, ts) unless windows
-
- Time.expects(:now).times(windows ? 1 : 2).returns(t + 5.seconds)
- v.send(:render_template, ta)
- v.send(:render_template, tb)
- v.send(:render_template, ts) unless windows
- # the file at the end of the symlink has changed since last compile
- # both the symlink and the file at the end of it should be recompiled
- assert @handler.compile_time[a_n] < t + 5.seconds
- assert_equal @handler.compile_time[b_n], t + 5.seconds
- assert_equal @handler.compile_time[s_n], t + 5.seconds unless windows
- end
- end
-end
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 78082665a4..a65771648e 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,6 +1,17 @@
+*Edge*
+
+* Added SQL escaping for :limit and :offset in MySQL [Jonathan Wiess]
+
+
+*2.1.0 (May 31st, 2008)*
+
+* Add ActiveRecord::Base.sti_name that checks ActiveRecord::Base#store_full_sti_class? and returns either the full or demodulized name. [rick]
+
* Add first/last methods to associations/named_scope. Resolved #226. [Ryan Bates]
-*2.1.0 RC1 (May 11th, 2008)*
+* Added SQL escaping for :limit and :offset #288 [Aaron Bedra, Steven Bristol, Jonathan Wiess]
+
+* Added first/last methods to associations/named_scope. Resolved #226. [Ryan Bates]
* Ensure hm:t preloading honours reflection options. Resolves #137. [Frederick Cheung]
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 043ab6d551..fc068b16c3 100755
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -171,7 +171,7 @@ spec = Gem::Specification.new do |s|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
- s.add_dependency('activesupport', '= 2.0.991' + PKG_BUILD)
+ s.add_dependency('activesupport', '= 2.1.0' + PKG_BUILD)
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index df4ae38f38..d8146daa54 100755
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -7,10 +7,8 @@ module ActiveRecord
else
@target = (AssociationProxy === record ? record.target : record)
- unless record.new_record?
- @owner[@reflection.primary_key_name] = record.id
- @owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
- end
+ @owner[@reflection.primary_key_name] = record.id
+ @owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
@updated = true
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 963a938485..f584a97cbb 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -9,7 +9,7 @@ module ActiveRecord
@reflection.klass.count_by_sql(@finder_sql)
else
column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
- options[:conditions] = options[:conditions].nil? ?
+ options[:conditions] = options[:conditions].blank? ?
@finder_sql :
@finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
options[:include] ||= @reflection.options[:include]
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index ebcf462f2e..52ced36d16 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -237,7 +237,7 @@ module ActiveRecord
end
def build_sti_condition
- "#{@reflection.through_reflection.quoted_table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.name.demodulize)}"
+ "#{@reflection.through_reflection.quoted_table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.sti_name)}"
end
alias_method :sql_conditions, :conditions
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index c393128621..92a24ecc5f 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1293,6 +1293,10 @@ module ActiveRecord #:nodoc:
super
end
+ def sti_name
+ store_full_sti_class ? name : name.demodulize
+ end
+
private
def find_initial(options)
options.update(:limit => 1)
@@ -1452,12 +1456,16 @@ module ActiveRecord #:nodoc:
# Nest the type name in the same module as this class.
# Bar is "MyApp::Business::Bar" relative to MyApp::Business::Foo
def type_name_with_module(type_name)
- (/^::/ =~ type_name) ? type_name : "#{parent.name}::#{type_name}"
+ if store_full_sti_class
+ type_name
+ else
+ (/^::/ =~ type_name) ? type_name : "#{parent.name}::#{type_name}"
+ end
end
def construct_finder_sql(options)
scope = scope(:find)
- sql = "SELECT #{(scope && scope[:select]) || options[:select] || (options[:joins] && quoted_table_name + '.*') || '*'} "
+ sql = "SELECT #{options[:select] || (scope && scope[:select]) || (options[:joins] && quoted_table_name + '.*') || '*'} "
sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
add_joins!(sql, options, scope)
@@ -1571,8 +1579,8 @@ module ActiveRecord #:nodoc:
def type_condition
quoted_inheritance_column = connection.quote_column_name(inheritance_column)
- type_condition = subclasses.inject("#{quoted_table_name}.#{quoted_inheritance_column} = '#{store_full_sti_class ? name : name.demodulize}' ") do |condition, subclass|
- condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{store_full_sti_class ? subclass.name : subclass.name.demodulize}' "
+ type_condition = subclasses.inject("#{quoted_table_name}.#{quoted_inheritance_column} = '#{sti_name}' ") do |condition, subclass|
+ condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{subclass.sti_name}' "
end
" (#{type_condition}) "
@@ -2508,7 +2516,7 @@ module ActiveRecord #:nodoc:
# Message class in that example.
def ensure_proper_type
unless self.class.descends_from_active_record?
- write_attribute(self.class.inheritance_column, store_full_sti_class ? self.class.name : self.class.name.demodulize)
+ write_attribute(self.class.inheritance_column, self.class.sti_name)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 16d405d3bd..5358491cde 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -106,11 +106,16 @@ module ActiveRecord
# SELECT * FROM suppliers LIMIT 10 OFFSET 50
def add_limit_offset!(sql, options)
if limit = options[:limit]
- sql << " LIMIT #{limit}"
+ sql << " LIMIT #{sanitize_limit(limit)}"
if offset = options[:offset]
- sql << " OFFSET #{offset}"
+ sql << " OFFSET #{offset.to_i}"
end
end
+ sql
+ end
+
+ def sanitize_limit(limit)
+ limit.to_s[/,/] ? limit.split(',').map{ |i| i.to_i }.join(',') : limit.to_i
end
# Appends a locking clause to an SQL statement.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 8c286f64db..f48b107a2a 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -128,15 +128,11 @@ module ActiveRecord
protected
def log(sql, name)
if block_given?
- if @logger and @logger.debug?
- result = nil
- seconds = Benchmark.realtime { result = yield }
- @runtime += seconds
- log_info(sql, name, seconds)
- result
- else
- yield
- end
+ result = nil
+ seconds = Benchmark.realtime { result = yield }
+ @runtime += seconds
+ log_info(sql, name, seconds)
+ result
else
log_info(sql, name, 0)
nil
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index f00a2c8950..653b45021d 100755
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -336,10 +336,11 @@ module ActiveRecord
def add_limit_offset!(sql, options) #:nodoc:
if limit = options[:limit]
+ limit = sanitize_limit(limit)
unless offset = options[:offset]
sql << " LIMIT #{limit}"
else
- sql << " LIMIT #{offset}, #{limit}"
+ sql << " LIMIT #{offset.to_i}, #{limit}"
end
end
end
diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb
index d8355d2143..2917f24c20 100644
--- a/activerecord/lib/active_record/dirty.rb
+++ b/activerecord/lib/active_record/dirty.rb
@@ -43,7 +43,7 @@ module ActiveRecord
base.alias_method_chain :reload, :dirty
base.superclass_delegating_accessor :partial_updates
- base.partial_updates = false
+ base.partial_updates = true
end
# Do any attributes have unsaved changes?
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index 69b7da7700..b0c8a8b815 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -102,7 +102,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)/
+ unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?)/
delegate m, :to => :proxy_found
end
end
@@ -135,6 +135,10 @@ module ActiveRecord
end
end
+ def empty?
+ @found ? @found.empty? : count.zero?
+ end
+
protected
def proxy_found
@found || load_found
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 7f04dc4c1a..b9339c2924 100755
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -692,7 +692,7 @@ module ActiveRecord
raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
validates_each(attr_names, configuration) do |record, attr_name, value|
- record.errors.add(attr_name, configuration[:message]) unless value.to_s =~ configuration[:with]
+ record.errors.add(attr_name, configuration[:message] % value) unless value.to_s =~ configuration[:with]
end
end
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index 1463e84764..aaadef9979 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -1,8 +1,8 @@
module ActiveRecord
module VERSION #:nodoc:
MAJOR = 2
- MINOR = 0
- TINY = 991
+ MINOR = 1
+ TINY = 0
STRING = [MAJOR, MINOR, TINY].join('.')
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 91504af901..11f9870534 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -104,4 +104,24 @@ class AdapterTest < ActiveRecord::TestCase
end
end
+ def test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas
+ sql_inject = "1 select * from schema"
+ assert_equal " LIMIT 1", @connection.add_limit_offset!("", :limit=>sql_inject)
+ if current_adapter?(:MysqlAdapter)
+ assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
+ else
+ assert_equal " LIMIT 1 OFFSET 7", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
+ end
+ end
+
+ def test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas
+ sql_inject = "1, 7 procedure help()"
+ if current_adapter?(:MysqlAdapter)
+ assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject)
+ assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=> '1 ; DROP TABLE USERS', :offset=>7)
+ else
+ assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject)
+ assert_equal " LIMIT 1,7 OFFSET 7", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 3073eae355..e0da8bfb7a 100755
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -12,6 +12,8 @@ require 'models/author'
require 'models/tag'
require 'models/tagging'
require 'models/comment'
+require 'models/sponsor'
+require 'models/member'
class BelongsToAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :topics,
@@ -381,5 +383,30 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_client).readonly_firm.save! }
assert companies(:first_client).readonly_firm.readonly?
end
-
+
+ def test_polymorphic_assignment_foreign_type_field_updating
+ # should update when assigning a saved record
+ sponsor = Sponsor.new
+ member = Member.create
+ sponsor.sponsorable = member
+ assert_equal "Member", sponsor.sponsorable_type
+
+ # should update when assigning a new record
+ sponsor = Sponsor.new
+ member = Member.new
+ sponsor.sponsorable = member
+ assert_equal "Member", sponsor.sponsorable_type
+ end
+
+ def test_polymorphic_assignment_updates_foreign_id_field_for_new_and_saved_records
+ sponsor = Sponsor.new
+ saved_member = Member.create
+ new_member = Member.new
+
+ sponsor.sponsorable = saved_member
+ assert_equal saved_member.id, sponsor.sponsorable_id
+
+ sponsor.sponsorable = new_member
+ assert_equal nil, sponsor.sponsorable_id
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 53b55022eb..dbfa025efb 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -32,6 +32,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, Firm.find(:first).plain_clients.count
end
+ def test_counting_with_empty_hash_conditions
+ assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => {})
+ end
+
def test_counting_with_single_conditions
assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => '1=1')
end
@@ -346,6 +350,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal "Another Client", new_client.name
assert new_client.new_record?
assert_equal new_client, company.clients_of_firm.last
+ company.name += '-changed'
assert_queries(2) { assert company.save }
assert !new_client.new_record?
assert_equal 2, company.clients_of_firm(true).size
@@ -356,6 +361,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
new_clients = assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
assert_equal 2, new_clients.size
+ company.name += '-changed'
assert_queries(3) { assert company.save }
assert_equal 3, company.clients_of_firm(true).size
end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 5561361bca..05155f6303 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -59,6 +59,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
# * 2 new records = 4
# + 1 query to save the actual post = 5
assert_queries(5) do
+ posts(:thinking).body += '-changed'
posts(:thinking).save
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index bd19ffcc29..2ea85417da 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -5,7 +5,7 @@ require 'models/entrant'
class DefaultTest < ActiveRecord::TestCase
def test_nil_defaults_for_not_null_columns
column_defaults =
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) && Mysql.client_version < 50051
{ 'id' => nil, 'name' => '', 'course_id' => nil }
else
{ 'id' => nil, 'name' => nil, 'course_id' => nil }
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 27394924a1..f09b617e6d 100755
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -5,7 +5,23 @@ require 'models/subscriber'
class InheritanceTest < ActiveRecord::TestCase
fixtures :companies, :projects, :subscribers, :accounts
-
+
+ def test_class_with_store_full_sti_class_returns_full_name
+ old = ActiveRecord::Base.store_full_sti_class
+ ActiveRecord::Base.store_full_sti_class = true
+ assert_equal 'Namespaced::Company', Namespaced::Company.sti_name
+ ensure
+ ActiveRecord::Base.store_full_sti_class = old
+ end
+
+ def test_class_without_store_full_sti_class_returns_demodulized_name
+ old = ActiveRecord::Base.store_full_sti_class
+ ActiveRecord::Base.store_full_sti_class = false
+ assert_equal 'Company', Namespaced::Company.sti_name
+ ensure
+ ActiveRecord::Base.store_full_sti_class = old
+ end
+
def test_should_store_demodulized_class_name_with_store_full_sti_class_option_disabled
old = ActiveRecord::Base.store_full_sti_class
ActiveRecord::Base.store_full_sti_class = false
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 4b5bd6c951..1a9a875730 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -50,6 +50,22 @@ class MethodScopingTest < ActiveRecord::TestCase
end
end
+ def test_scoped_find_select
+ Developer.with_scope(:find => { :select => "id, name" }) do
+ developer = Developer.find(:first, :conditions => "name = 'David'")
+ assert_equal "David", developer.name
+ assert !developer.has_attribute?(:salary)
+ end
+ end
+
+ def test_options_select_replaces_scope_select
+ Developer.with_scope(:find => { :select => "id, name" }) do
+ developer = Developer.find(:first, :select => 'id, salary', :conditions => "name = 'David'")
+ assert_equal 80000, developer.salary
+ assert !developer.has_attribute?(:name)
+ end
+ end
+
def test_scoped_count
Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
assert_equal 1, Developer.count
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 8f2fc53d67..d890cf7936 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -146,4 +146,12 @@ class NamedScopeTest < ActiveRecord::TestCase
end
end
+ def test_empty_should_not_load_results
+ topics = Topic.base
+ assert_queries(2) do
+ topics.empty? # use count query
+ topics.collect # force load
+ topics.empty? # use loaded (no query)
+ end
+ end
end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index a4d9da4806..7b71647d25 100755
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -583,6 +583,12 @@ class ValidationsTest < ActiveRecord::TestCase
assert_nil t.errors.on(:title)
end
+ def test_validate_format_with_formatted_message
+ Topic.validates_format_of(:title, :with => /^Valid Title$/, :message => "can't be %s")
+ t = Topic.create(:title => 'Invalid title')
+ assert_equal "can't be Invalid title", t.errors.on(:title)
+ end
+
def test_validates_inclusion_of
Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ) )
diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG
index 9aa7d455a2..e124e40dd1 100644
--- a/activeresource/CHANGELOG
+++ b/activeresource/CHANGELOG
@@ -1,4 +1,4 @@
-*2.1.0 RC1 (May 11th, 2008)*
+*2.1.0 (May 31st, 2008)*
* Fixed response logging to use length instead of the entire thing (seangeo) [#27]
diff --git a/activeresource/Rakefile b/activeresource/Rakefile
index 75fe52aea8..9fd0ec4921 100644
--- a/activeresource/Rakefile
+++ b/activeresource/Rakefile
@@ -64,7 +64,7 @@ spec = Gem::Specification.new do |s|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
- s.add_dependency('activesupport', '= 2.0.991' + PKG_BUILD)
+ s.add_dependency('activesupport', '= 2.1.0' + PKG_BUILD)
s.require_path = 'lib'
s.autorequire = 'active_resource'
diff --git a/activeresource/lib/active_resource/version.rb b/activeresource/lib/active_resource/version.rb
index 34fb05b703..88798ea1c1 100644
--- a/activeresource/lib/active_resource/version.rb
+++ b/activeresource/lib/active_resource/version.rb
@@ -1,8 +1,8 @@
module ActiveResource
module VERSION #:nodoc:
MAJOR = 2
- MINOR = 0
- TINY = 991
+ MINOR = 1
+ TINY = 0
STRING = [MAJOR, MINOR, TINY].join('.')
end
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 7e408840c4..690bb987dc 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,3 +1,5 @@
+*2.1.0 (May 31st, 2008)*
+
* TimeZone#to_s shows offset as GMT instead of UTC, because GMT will be more familiar to end users (see time zone selects used by Windows OS, google.com and yahoo.com.) Reverts [8370] [Geoff Buesing]
* Hash.from_xml: datetime xml types overflow to Ruby DateTime class when out of range of Time. Adding tests for utc offsets [Geoff Buesing]
@@ -6,8 +8,6 @@
* Time#to_json: don't convert to utc before encoding. References #175 [Geoff Buesing]
-*2.1.0 RC1 (May 11th, 2008)*
-
* Remove unused JSON::RESERVED_WORDS, JSON.valid_identifier? and JSON.reserved_word? methods. Resolves #164. [Cheah Chu Yeow]
* Adding Date.current, which returns Time.zone.today if config.time_zone is set; otherwise returns Date.today [Geoff Buesing]
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 25225d5615..da2ece610a 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -82,9 +82,10 @@ module Dependencies #:nodoc:
# infinite loop with mutual dependencies.
loaded << expanded
- if load?
- log "loading #{file_name}"
- begin
+ begin
+ if load?
+ log "loading #{file_name}"
+
# Enable warnings iff this file has not been loaded before and
# warnings_on_first_load is set.
load_args = ["#{file_name}.rb"]
@@ -95,13 +96,13 @@ module Dependencies #:nodoc:
else
enable_warnings { result = load_file(*load_args) }
end
- rescue Exception
- loaded.delete expanded
- raise
+ else
+ log "requiring #{file_name}"
+ result = require file_name
end
- else
- log "requiring #{file_name}"
- result = require file_name
+ rescue Exception
+ loaded.delete expanded
+ raise
end
# Record history *after* loading so first load gets warnings.
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index ee716de39e..185d03020c 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -40,13 +40,15 @@ module ActiveSupport::Multibyte #:nodoc:
# core dumps. Don't go there.
@string
end
-
+
# Make duck-typing with String possible
- def respond_to?(method)
- super || @string.respond_to?(method) || handler.respond_to?(method) ||
- (method.to_s =~ /(.*)!/ && handler.respond_to?($1)) || false
+ def respond_to?(method, include_priv = false)
+ super || @string.respond_to?(method, include_priv) ||
+ handler.respond_to?(method, include_priv) ||
+ (method.to_s =~ /(.*)!/ && handler.respond_to?($1, include_priv)) ||
+ false
end
-
+
# Create a new Chars instance.
def initialize(str)
@string = str.respond_to?(:string) ? str.string : str
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index e6f2fd4e7c..4218ed5928 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -260,14 +260,14 @@ module ActiveSupport
def marshal_load(variables)
initialize(variables[0], ::Time.send!(:get_zone, variables[1]), variables[2])
end
-
+
# Ensure proxy class responds to all methods that underlying time instance responds to.
- def respond_to?(sym)
+ def respond_to?(sym, include_priv = false)
# consistently respond false to acts_like?(:date), regardless of whether #time is a Time or DateTime
return false if sym.to_s == 'acts_like_date?'
- super || time.respond_to?(sym)
+ super || time.respond_to?(sym, include_priv)
end
-
+
# Send the missing method to +time+ instance, and wrap result in a new TimeWithZone with the existing +time_zone+.
def method_missing(sym, *args, &block)
result = time.__send__(sym, *args, &block)
diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index f3d141cf72..b346459a9b 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -1,8 +1,8 @@
module ActiveSupport
module VERSION #:nodoc:
MAJOR = 2
- MINOR = 0
- TINY = 991
+ MINOR = 1
+ TINY = 0
STRING = [MAJOR, MINOR, TINY].join('.')
end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 1e19e12da9..0309ab6858 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -673,7 +673,7 @@ class DependenciesTest < Test::Unit::TestCase
assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it should have failed!"
end
end
-
+
ensure
Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) }
end
@@ -686,11 +686,20 @@ class DependenciesTest < Test::Unit::TestCase
assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it should have failed!"
end
end
-
+
ensure
Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) }
end
+ def test_autoload_doesnt_shadow_error_when_mechanism_not_set_to_load
+ with_loading 'autoloading_fixtures' do
+ Dependencies.mechanism = :require
+ 2.times do
+ assert_raise(NameError) {"RaisesNameError".constantize}
+ end
+ end
+ end
+
def test_autoload_doesnt_shadow_name_error
with_loading 'autoloading_fixtures' do
assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it hasn't been referenced yet!"
@@ -714,7 +723,7 @@ class DependenciesTest < Test::Unit::TestCase
ensure
Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) }
end
-
+
def test_remove_constant_handles_double_colon_at_start
Object.const_set 'DeleteMe', Module.new
DeleteMe.const_set 'OrMe', Module.new
@@ -724,7 +733,7 @@ class DependenciesTest < Test::Unit::TestCase
Dependencies.remove_constant "::DeleteMe"
assert ! defined?(DeleteMe)
end
-
+
def test_load_once_constants_should_not_be_unloaded
with_loading 'autoloading_fixtures' do
Dependencies.load_once_paths = Dependencies.load_paths
@@ -737,7 +746,7 @@ class DependenciesTest < Test::Unit::TestCase
Dependencies.load_once_paths = []
Object.class_eval { remove_const :A if const_defined?(:A) }
end
-
+
def test_load_once_paths_should_behave_when_recursively_loading
with_loading 'dependencies', 'autoloading_fixtures' do
Dependencies.load_once_paths = [Dependencies.load_paths.last]
@@ -753,5 +762,4 @@ class DependenciesTest < Test::Unit::TestCase
ensure
Dependencies.load_once_paths = []
end
-
end
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 11d2926f31..fe517755ef 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,4 +1,4 @@
-*2.1.0 RC1 (May 11th, 2008)*
+*2.1.0 (May 31st, 2008)*
* script/dbconsole fires up the command-line database client. #102 [Steve Purcell]
diff --git a/railties/Rakefile b/railties/Rakefile
index 45ba394299..a1d1095c37 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -304,11 +304,11 @@ spec = Gem::Specification.new do |s|
EOF
s.add_dependency('rake', '>= 0.8.1')
- s.add_dependency('activesupport', '= 2.0.991' + PKG_BUILD)
- s.add_dependency('activerecord', '= 2.0.991' + PKG_BUILD)
- s.add_dependency('actionpack', '= 2.0.991' + PKG_BUILD)
- s.add_dependency('actionmailer', '= 2.0.991' + PKG_BUILD)
- s.add_dependency('activeresource', '= 2.0.991' + PKG_BUILD)
+ s.add_dependency('activesupport', '= 2.1.0' + PKG_BUILD)
+ s.add_dependency('activerecord', '= 2.1.0' + PKG_BUILD)
+ s.add_dependency('actionpack', '= 2.1.0' + PKG_BUILD)
+ s.add_dependency('actionmailer', '= 2.1.0' + PKG_BUILD)
+ s.add_dependency('activeresource', '= 2.1.0' + PKG_BUILD)
s.rdoc_options << '--exclude' << '.'
s.has_rdoc = false
diff --git a/railties/environments/environment.rb b/railties/environments/environment.rb
index 468fa45ef6..a85ade371b 100644
--- a/railties/environments/environment.rb
+++ b/railties/environments/environment.rb
@@ -64,7 +64,4 @@ Rails::Initializer.run do |config|
# Activate observers that should always be running
# config.active_record.observers = :cacher, :garbage_collector
-
- # Make ActiveRecord only save the attributes that have changed since the record was loaded.
- # config.active_record.partial_updates = true
-end \ No newline at end of file
+end
diff --git a/railties/lib/commands/dbconsole.rb b/railties/lib/commands/dbconsole.rb
index b81997aa59..17acb7b68f 100644
--- a/railties/lib/commands/dbconsole.rb
+++ b/railties/lib/commands/dbconsole.rb
@@ -2,8 +2,13 @@ require 'erb'
require 'yaml'
require 'optparse'
+include_password = false
+
OptionParser.new do |opt|
- opt.banner = "Usage: dbconsole [environment]"
+ opt.banner = "Usage: dbconsole [options] [environment]"
+ opt.on("-p", "--include-password", "Automatically provide the database from database.yml") do |v|
+ include_password = true
+ end
opt.parse!(ARGV)
abort opt.to_s unless (0..1).include?(ARGV.size)
end
@@ -31,10 +36,13 @@ when "mysql"
'port' => '--port',
'socket' => '--socket',
'username' => '--user',
- 'password' => '--password',
'encoding' => '--default-character-set'
}.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact
+ if config['password'] && include_password
+ args << "--password=#{config['password']}"
+ end
+
args << config['database']
exec(find_cmd('mysql5', 'mysql'), *args)
@@ -43,7 +51,7 @@ when "postgresql"
ENV['PGUSER'] = config["username"] if config["username"]
ENV['PGHOST'] = config["host"] if config["host"]
ENV['PGPORT'] = config["port"].to_s if config["port"]
- ENV['PGPASSWORD'] = config["password"].to_s if config["password"]
+ ENV['PGPASSWORD'] = config["password"].to_s if config["password"] && include_password
exec(find_cmd('psql'), config["database"])
when "sqlite"
diff --git a/railties/lib/commands/plugin.rb b/railties/lib/commands/plugin.rb
index d12d002c4e..105819ce90 100644
--- a/railties/lib/commands/plugin.rb
+++ b/railties/lib/commands/plugin.rb
@@ -163,7 +163,7 @@ class Plugin
end
def git_url?
- @uri =~ /^git:\/\// || @url =~ /\.git$/
+ @uri =~ /^git:\/\// || @uri =~ /\.git$/
end
def installed?
diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb
index 8f963cb4a5..bdadfeea8f 100644
--- a/railties/lib/initializer.rb
+++ b/railties/lib/initializer.rb
@@ -8,6 +8,7 @@ require 'rails/version'
require 'rails/plugin/locator'
require 'rails/plugin/loader'
require 'rails/gem_dependency'
+require 'rails/rack'
RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV)
@@ -140,7 +141,8 @@ module Rails
# pick up any gems that plugins depend on
add_gem_load_paths
load_gems
-
+ check_gem_dependencies
+
load_application_initializers
# the framework is now fully initialized
@@ -153,6 +155,7 @@ module Rails
initialize_routing
# Observers are loaded after plugins in case Observers or observed models are modified by plugins.
+
load_observers
end
@@ -241,7 +244,24 @@ module Rails
end
def load_gems
- @configuration.gems.each &:load
+ @configuration.gems.each(&:load)
+ end
+
+ def check_gem_dependencies
+ unloaded_gems = @configuration.gems.reject { |g| g.loaded? }
+ if unloaded_gems.size > 0
+ @gems_dependencies_loaded = false
+ # don't print if the gems rake tasks are being run
+ unless $rails_gem_installer
+ puts %{These gems that this application depends on are missing:}
+ unloaded_gems.each do |gem|
+ puts " - #{gem.name}"
+ end
+ puts %{Run "rake gems:install" to install them.}
+ end
+ else
+ @gems_dependencies_loaded = true
+ end
end
# Loads all plugins in <tt>config.plugin_paths</tt>. <tt>plugin_paths</tt>
@@ -287,12 +307,8 @@ module Rails
end
def load_observers
- if configuration.frameworks.include?(:active_record)
- if @configuration.gems.any? { |g| !g.loaded? }
- puts %{Unable to instantiate observers, some gems that this application depends on are missing. Run "rake gems:install"}
- else
- ActiveRecord::Base.instantiate_observers
- end
+ if @gems_dependencies_loaded && configuration.frameworks.include?(:active_record)
+ ActiveRecord::Base.instantiate_observers
end
end
@@ -447,14 +463,18 @@ module Rails
# Fires the user-supplied after_initialize block (Configuration#after_initialize)
def after_initialize
- configuration.after_initialize_blocks.each do |block|
- block.call
+ if @gems_dependencies_loaded
+ configuration.after_initialize_blocks.each do |block|
+ block.call
+ end
end
end
def load_application_initializers
- Dir["#{configuration.root_path}/config/initializers/**/*.rb"].sort.each do |initializer|
- load(initializer)
+ if @gems_dependencies_loaded
+ Dir["#{configuration.root_path}/config/initializers/**/*.rb"].sort.each do |initializer|
+ load(initializer)
+ end
end
end
diff --git a/railties/lib/rails/gem_dependency.rb b/railties/lib/rails/gem_dependency.rb
index 2034841cd2..9f088a18dd 100644
--- a/railties/lib/rails/gem_dependency.rb
+++ b/railties/lib/rails/gem_dependency.rb
@@ -31,12 +31,13 @@ module Rails
args << @requirement.to_s if @requirement
gem *args
else
- $LOAD_PATH << File.join(unpacked_paths.first, 'lib')
+ $LOAD_PATH.unshift File.join(unpacked_paths.first, 'lib')
+ ext = File.join(unpacked_paths.first, 'ext')
+ $LOAD_PATH.unshift(ext) if File.exist?(ext)
@frozen = true
end
@load_paths_added = true
rescue Gem::LoadError
- puts $!.to_s
end
def dependencies
@@ -73,7 +74,9 @@ module Rails
end
def install
- Gem::GemRunner.new.run(install_command)
+ cmd = "#{gem_command} #{install_command.join(' ')}"
+ puts cmd
+ puts %x(#{cmd})
end
def unpack_to(directory)
@@ -100,10 +103,14 @@ private ###################################################################
def specification
@spec ||= Gem.source_index.search(Gem::Dependency.new(@name, @requirement)).sort_by { |s| s.version }.last
end
+
+ def gem_command
+ RUBY_PLATFORM =~ /win32/ ? 'gem.bat' : 'gem'
+ end
def install_command
cmd = %w(install) << @name
- cmd << "--version" << "#{@requirement.to_s}" if @requirement
+ cmd << "--version" << %("#{@requirement.to_s}") if @requirement
cmd << "--source" << @source if @source
cmd
end
diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb
index 04f7e37a20..256f4b0132 100644
--- a/railties/lib/rails/plugin.rb
+++ b/railties/lib/rails/plugin.rb
@@ -98,23 +98,18 @@ module Rails
end
end
- # This Plugin subclass represents a Gem plugin. It behaves exactly like a
- # "traditional" Rails plugin, but doesn't expose any additional load paths,
- # since RubyGems has already taken care of things.
+ # This Plugin subclass represents a Gem plugin. Although RubyGems has already
+ # taken care of $LOAD_PATHs, it exposes its load_paths to add them
+ # to Dependencies.load_paths.
class GemPlugin < Plugin
-
# Initialize this plugin from a Gem::Specification.
def initialize(spec)
- super(File.join(spec.full_gem_path, "rails"))
+ super(File.join(spec.full_gem_path))
@name = spec.name
end
- def valid?
- true
- end
-
- def load_paths
- []
+ def init_path
+ File.join(directory, 'rails', 'init.rb')
end
end
end \ No newline at end of file
diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb
new file mode 100644
index 0000000000..abcd0741bf
--- /dev/null
+++ b/railties/lib/rails/rack.rb
@@ -0,0 +1,5 @@
+module Rails
+ module Rack
+ autoload :Static, "rails/rack/static"
+ end
+end
diff --git a/railties/lib/rails/rack/static.rb b/railties/lib/rails/rack/static.rb
new file mode 100644
index 0000000000..45eb0e5921
--- /dev/null
+++ b/railties/lib/rails/rack/static.rb
@@ -0,0 +1,35 @@
+module Rails
+ module Rack
+ class Static
+ FILE_METHODS = %w(GET HEAD).freeze
+
+ def initialize(app)
+ @app = app
+ @file_server = ::Rack::File.new(File.join(RAILS_ROOT, "public"))
+ end
+
+ def call(env)
+ path = env['PATH_INFO'].chomp('/')
+ method = env['REQUEST_METHOD']
+ cached_path = (path.empty? ? 'index' : path) + ::ActionController::Base.page_cache_extension
+
+ if FILE_METHODS.include?(method)
+ if file_exist?(path)
+ return @file_server.call(env)
+ elsif file_exist?(cached_path)
+ env['PATH_INFO'] = cached_path
+ return @file_server.call(env)
+ end
+ end
+
+ @app.call(env)
+ end
+
+ private
+ def file_exist?(path)
+ full_path = File.join(@file_server.root, ::Rack::Utils.unescape(path))
+ File.file?(full_path) && File.readable?(full_path)
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index fea63beea9..48d24da52e 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -1,8 +1,8 @@
module Rails
module VERSION #:nodoc:
MAJOR = 2
- MINOR = 0
- TINY = 991
+ MINOR = 1
+ TINY = 0
STRING = [MAJOR, MINOR, TINY].join('.')
end
diff --git a/railties/lib/rails_generator/commands.rb b/railties/lib/rails_generator/commands.rb
index 03b7d354a6..08ecbfb5cf 100644
--- a/railties/lib/rails_generator/commands.rb
+++ b/railties/lib/rails_generator/commands.rb
@@ -380,12 +380,14 @@ HELP
# Thanks to Florian Gross (flgr).
def raise_class_collision(class_name)
message = <<end_message
- The name '#{class_name}' is reserved by Ruby on Rails.
+ The name '#{class_name}' is either already used in your application or reserved by Ruby on Rails.
Please choose an alternative and run this generator again.
end_message
if suggest = find_synonyms(class_name)
- message << "\n Suggestions: \n\n"
- message << suggest.join("\n")
+ if suggest.any?
+ message << "\n Suggestions: \n\n"
+ message << suggest.join("\n")
+ end
end
raise UsageError, message
end
diff --git a/railties/lib/tasks/gems.rake b/railties/lib/tasks/gems.rake
index c18fea2d60..0321e60e0f 100644
--- a/railties/lib/tasks/gems.rake
+++ b/railties/lib/tasks/gems.rake
@@ -1,5 +1,5 @@
desc "List the gems that this rails application depends on"
-task :gems => :environment do
+task :gems => 'gems:base' do
Rails.configuration.gems.each do |gem|
code = gem.loaded? ? (gem.frozen? ? "F" : "I") : " "
puts "[#{code}] #{gem.name} #{gem.requirement.to_s}"
@@ -10,8 +10,14 @@ task :gems => :environment do
end
namespace :gems do
+ task :base do
+ $rails_gem_installer = true
+ Rake::Task[:environment].invoke
+ end
+
desc "Build any native extensions for unpacked gems"
task :build do
+ $rails_gem_installer = true
require 'rails/gem_builder'
Dir[File.join(RAILS_ROOT, 'vendor', 'gems', '*')].each do |gem_dir|
spec_file = File.join(gem_dir, '.specification')
@@ -24,14 +30,14 @@ namespace :gems do
end
desc "Installs all required gems for this application."
- task :install => :environment do
+ task :install => :base do
require 'rubygems'
require 'rubygems/gem_runner'
Rails.configuration.gems.each { |gem| gem.install unless gem.loaded? }
end
desc "Unpacks the specified gem into vendor/gems."
- task :unpack => :environment do
+ task :unpack => :base do
require 'rubygems'
require 'rubygems/gem_runner'
Rails.configuration.gems.each do |gem|
diff --git a/release.rb b/release.rb
index 94d2219525..2e1e8610b1 100755
--- a/release.rb
+++ b/release.rb
@@ -4,7 +4,7 @@ VERSION = ARGV.first
PACKAGES = %w(activesupport activerecord actionpack actionmailer activeresource)
# Checkout source
-`rm -rf release && svn export http://dev.rubyonrails.org/svn/rails/trunk release`
+# `rm -rf release && svn export http://dev.rubyonrails.org/svn/rails/trunk release`
# Create Rails packages
`cd release/railties && rake template=jamis package`
@@ -19,7 +19,4 @@ end
# Upload rails tgz/zip
`rubyforge add_release rails rails 'REL #{VERSION}' release/rails-#{VERSION}.tgz`
-`rubyforge add_release rails rails 'REL #{VERSION}' release/rails-#{VERSION}.zip`
-
-# Create SVN tag
-puts "Remember to create SVN tag"
+`rubyforge add_release rails rails 'REL #{VERSION}' release/rails-#{VERSION}.zip` \ No newline at end of file