diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2008-06-02 12:29:28 +0100 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2008-06-02 12:29:28 +0100 |
commit | 719aa13b0781d662673dba2225bc707fc4182b5e (patch) | |
tree | 057cabf17152a1ab0ccc749dd361eeaf754c38de | |
parent | ad3c97cea6dd0714652db7d9b5b4d06b81e5c035 (diff) | |
parent | a980eb8c7734f14109d8c2a02a88dafdf682e0dc (diff) | |
download | rails-719aa13b0781d662673dba2225bc707fc4182b5e.tar.gz rails-719aa13b0781d662673dba2225bc707fc4182b5e.tar.bz2 rails-719aa13b0781d662673dba2225bc707fc4182b5e.zip |
Merge commit 'mainstream/master'
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 |