diff options
19 files changed, 92 insertions, 117 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index e758983d7a..aab26b411c 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Ruby 1.9: fix Content-Length for multibyte send_data streaming. #2661 [Sava Chankov] + * Ruby 1.9: ERB template encoding using a magic comment at the top of the file. [Jeremy Kemper] <%# encoding: utf-8 %> diff --git a/actionpack/lib/action_controller/base/conditional_get.rb b/actionpack/lib/action_controller/base/conditional_get.rb index d287ec4994..6d35137428 100644 --- a/actionpack/lib/action_controller/base/conditional_get.rb +++ b/actionpack/lib/action_controller/base/conditional_get.rb @@ -29,11 +29,7 @@ module ActionController response.last_modified = options[:last_modified] if options[:last_modified] if options[:public] - cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip } - cache_control.delete("private") - cache_control.delete("no-cache") - cache_control << "public" - response.headers["Cache-Control"] = cache_control.join(', ') + response.cache_control[:public] = true end if request.fresh?(response) @@ -107,21 +103,10 @@ module ActionController # This method will overwrite an existing Cache-Control header. # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. def expires_in(seconds, options = {}) #:doc: - cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip } + response.cache_control.merge!(:max_age => seconds, :public => options.delete(:public)) + options.delete(:private) - cache_control << "max-age=#{seconds}" - cache_control.delete("no-cache") - if options[:public] - cache_control.delete("private") - cache_control << "public" - else - cache_control << "private" - end - - # This allows for additional headers to be passed through like 'max-stale' => 5.hours - cache_control += options.symbolize_keys.reject{|k,v| k == :public || k == :private }.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"} - - response.headers["Cache-Control"] = cache_control.join(', ') + response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"} end # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or diff --git a/actionpack/lib/action_controller/base/streaming.rb b/actionpack/lib/action_controller/base/streaming.rb index 9ff4f25f43..f0317c6e99 100644 --- a/actionpack/lib/action_controller/base/streaming.rb +++ b/actionpack/lib/action_controller/base/streaming.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/string/bytesize' + module ActionController #:nodoc: # Methods for sending arbitrary data and for streaming files to the browser, # instead of rendering. @@ -142,7 +144,7 @@ module ActionController #:nodoc: # instead. See ActionController::Base#render for more information. def send_data(data, options = {}) #:doc: logger.info "Sending data #{options[:filename]}" if logger - send_file_headers! options.merge(:length => data.size) + send_file_headers! options.merge(:length => data.bytesize) @performed_render = false render :status => options[:status], :text => data end @@ -179,7 +181,7 @@ module ActionController #:nodoc: # after it displays the "open/save" dialog, which means that if you # hit "open" the file isn't there anymore when the application that # is called for handling the download is run, so let's workaround that - headers['Cache-Control'] = 'private' if headers['Cache-Control'] == 'no-cache' + response.cache_control[:public] ||= false end end end diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb index e7c64d0942..d32d5562e8 100644 --- a/actionpack/lib/action_controller/testing/process.rb +++ b/actionpack/lib/action_controller/testing/process.rb @@ -52,7 +52,7 @@ module ActionController #:nodoc: class TestResponse < ActionDispatch::TestResponse def recycle! @status = 200 - @header = Rack::Utils::HeaderHash.new(DEFAULT_HEADERS) + @header = Rack::Utils::HeaderHash.new @writer = lambda { |x| @body << x } @block = nil @length = 0 diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index e58b4b5e19..03d1780b77 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -32,8 +32,8 @@ module ActionDispatch # :nodoc: # end # end class Response < Rack::Response - DEFAULT_HEADERS = { "Cache-Control" => "no-cache" } attr_accessor :request + attr_reader :cache_control attr_writer :header alias_method :headers=, :header= @@ -42,21 +42,26 @@ module ActionDispatch # :nodoc: def initialize super - @header = Rack::Utils::HeaderHash.new(DEFAULT_HEADERS) + @cache_control = {} + @header = Rack::Utils::HeaderHash.new + end + + def status=(status) + @status = status.to_i end # The response code of the request def response_code - status.to_s[0,3].to_i rescue 0 + @status end # Returns a String to ensure compatibility with Net::HTTPResponse def code - status.to_s.split(' ')[0] + @status.to_s end def message - status.to_s.split(' ',2)[1] || StatusCodes::STATUS_CODES[response_code] + StatusCodes::STATUS_CODES[@status] end alias_method :status_message, :message @@ -142,10 +147,7 @@ module ActionDispatch # :nodoc: def prepare! assign_default_content_type_and_charset! handle_conditional_get! - set_content_length! - convert_content_type! - convert_language! - convert_cookies! + self["Set-Cookie"] ||= "" end def each(&callback) @@ -196,22 +198,24 @@ module ActionDispatch # :nodoc: private def handle_conditional_get! - if etag? || last_modified? + if etag? || last_modified? || !cache_control.empty? set_conditional_cache_control! elsif nonempty_ok_response? self.etag = body if request && request.etag_matches?(etag) - self.status = '304 Not Modified' + self.status = 304 self.body = [] end set_conditional_cache_control! + else + headers["Cache-Control"] = "no-cache" end end def nonempty_ok_response? - ok = !status || status.to_s[0..2] == '200' + ok = !@status || @status == 200 ok && string_body? end @@ -220,40 +224,20 @@ module ActionDispatch # :nodoc: end def set_conditional_cache_control! - if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control'] - headers['Cache-Control'] = 'private, max-age=0, must-revalidate' + if cache_control.empty? + cache_control.merge!(:public => false, :max_age => 0, :must_revalidate => true) end - end - def convert_content_type! - headers['Content-Type'] ||= "text/html" - headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset'] - end + public_cache, max_age, must_revalidate, extras = + cache_control.values_at(:public, :max_age, :must_revalidate, :extras) - # Don't set the Content-Length for block-based bodies as that would mean - # reading it all into memory. Not nice for, say, a 2GB streaming file. - def set_content_length! - if status && status.to_s[0..2] == '204' - headers.delete('Content-Length') - elsif length = headers['Content-Length'] - headers['Content-Length'] = length.to_s - elsif string_body? && (!status || status.to_s[0..2] != '304') - headers["Content-Length"] = Rack::Utils.bytesize(body).to_s - end - end - - def convert_language! - headers["Content-Language"] = headers.delete("language") if headers["language"] - end + options = [] + options << "max-age=#{max_age}" if max_age + options << (public_cache ? "public" : "private") + options << "must-revalidate" if must_revalidate + options.concat(extras) if extras - def convert_cookies! - headers['Set-Cookie'] = - if header = headers['Set-Cookie'] - header = header.split("\n") if header.respond_to?(:to_str) - header.compact - else - [] - end + headers["Cache-Control"] = options.join(", ") end end end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index acb0c895e0..d0fa67c945 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -1331,7 +1331,7 @@ class EtagRenderTest < ActionController::TestCase def test_render_200_should_set_etag get :render_hello_world_from_variable assert_equal etag_for("hello david"), @response.headers['ETag'] - assert_equal "private, max-age=0, must-revalidate", @response.headers['Cache-Control'] + assert_equal "max-age=0, private, must-revalidate", @response.headers['Cache-Control'] end def test_render_against_etag_request_should_304_when_match diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index ae32ee5649..0afebac68c 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -1,9 +1,10 @@ +# encoding: utf-8 require 'abstract_unit' module TestFileUtils def file_name() File.basename(__FILE__) end def file_path() File.expand_path(__FILE__) end - def file_data() File.open(file_path, 'rb') { |f| f.read } end + def file_data() @data ||= File.open(file_path, 'rb') { |f| f.read } end end class SendFileController < ActionController::Base @@ -22,6 +23,10 @@ class SendFileController < ActionController::Base def data send_data(file_data, options) end + + def multibyte_text_data + send_data("Кирилица\n祝您好運", options) + end end class SendFileTest < ActionController::TestCase @@ -55,6 +60,7 @@ class SendFileTest < ActionController::TestCase require 'stringio' output = StringIO.new output.binmode + output.string.force_encoding(file_data.encoding) if output.string.respond_to?(:force_encoding) assert_nothing_raised { response.body_parts.each { |part| output << part.to_s } } assert_equal file_data, output.string end @@ -123,7 +129,7 @@ class SendFileTest < ActionController::TestCase # test overriding Cache-Control: no-cache header to fix IE open/save dialog @controller.headers = { 'Cache-Control' => 'no-cache' } @controller.send(:send_file_headers!, options) - h = @controller.headers + @controller.response.prepare! assert_equal 'private', h['Cache-Control'] end @@ -163,4 +169,11 @@ class SendFileTest < ActionController::TestCase assert_equal 200, @response.status end end + + def test_send_data_content_length_header + @controller.headers = {} + @controller.options = { :type => :text, :filename => 'file_with_utf8_text' } + process('multibyte_text_data') + assert_equal '29', @controller.headers['Content-Length'] + end end diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index 9e008a9ae8..301080842e 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -120,8 +120,6 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest fixture = FIXTURE_PATH + "/mona_lisa.jpg" params = { :uploaded_data => fixture_file_upload(fixture, "image/jpg") } post '/read', params - expected_length = 'File: '.length + File.size(fixture) - assert_equal expected_length, response.content_length end end diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 2ddc6cb2b5..256ed06a45 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -13,10 +13,9 @@ class ResponseTest < ActiveSupport::TestCase assert_equal 200, status assert_equal({ "Content-Type" => "text/html; charset=utf-8", - "Cache-Control" => "private, max-age=0, must-revalidate", + "Cache-Control" => "max-age=0, private, must-revalidate", "ETag" => '"65a8e27d8879283831b664bd8b7f0ad4"', - "Set-Cookie" => "", - "Content-Length" => "13" + "Set-Cookie" => "" }, headers) parts = [] @@ -32,10 +31,9 @@ class ResponseTest < ActiveSupport::TestCase assert_equal 200, status assert_equal({ "Content-Type" => "text/html; charset=utf-8", - "Cache-Control" => "private, max-age=0, must-revalidate", + "Cache-Control" => "max-age=0, private, must-revalidate", "ETag" => '"ebb5e89e8a94e9dd22abf5d915d112b2"', - "Set-Cookie" => "", - "Content-Length" => "8" + "Set-Cookie" => "" }, headers) end diff --git a/actionpack/test/new_base/base_test.rb b/actionpack/test/new_base/base_test.rb index d9d552f9e5..1b2e917ced 100644 --- a/actionpack/test/new_base/base_test.rb +++ b/actionpack/test/new_base/base_test.rb @@ -34,7 +34,6 @@ module Dispatching assert_body "success" assert_status 200 assert_content_type "text/html; charset=utf-8" - assert_header "Content-Length", "7" end # :api: plugin @@ -42,7 +41,6 @@ module Dispatching get "/dispatching/simple/modify_response_body" assert_body "success" - assert_header "Content-Length", "7" # setting the body manually sets the content length end # :api: plugin @@ -50,7 +48,6 @@ module Dispatching get "/dispatching/simple/modify_response_body_twice" assert_body "success!" - assert_header "Content-Length", "8" end test "controller path" do diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 659de99873..5515cf6b1a 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* SQLite: drop support for 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper] + * Added :primary_key option to belongs_to associations. #765 [Szymon Nowak, Philip Hallstrom, Noel Rocha] # employees.company_name references companies.name Employee.belongs_to :company, :primary_key => 'name', :foreign_key => 'company_name' diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 5cb536af1f..211b77f514 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -75,19 +75,18 @@ module ActiveRecord @@attribute_method_regexp.match(method_name) end - # Contains the names of the generated attribute methods. def generated_methods #:nodoc: - @generated_methods ||= Set.new - end - - def generated_methods? - !generated_methods.empty? + @generated_methods ||= begin + mod = Module.new + include mod + mod + end end # Generates all the attribute related methods for columns in the database # accessors, mutators and query methods. def define_attribute_methods - return if generated_methods? + return unless generated_methods.instance_methods.empty? columns_hash.keys.each do |name| attribute_method_suffixes.each do |suffix| method_name = "#{name}#{suffix}" @@ -96,7 +95,7 @@ module ActiveRecord if respond_to?(generate_method) send(generate_method, name) else - evaluate_attribute_method("def #{method_name}(*args); send(:attribute#{suffix}, '#{name}', *args); end", method_name) + generated_methods.module_eval("def #{method_name}(*args); send(:attribute#{suffix}, '#{name}', *args); end", __FILE__, __LINE__) end end end @@ -104,8 +103,9 @@ module ActiveRecord end def undefine_attribute_methods - generated_methods.each { |name| undef_method(name) } - @generated_methods = nil + generated_methods.module_eval do + instance_methods.each { |m| undef_method(m) } + end end # Checks whether the method is defined in the model or any of its subclasses @@ -129,22 +129,6 @@ module ActiveRecord def attribute_method_suffixes @@attribute_method_suffixes ||= [] end - - # Evaluate the definition for an attribute related method - def evaluate_attribute_method(method_definition, method_name) - generated_methods << method_name.to_s - - begin - class_eval(method_definition, __FILE__, __LINE__) - rescue SyntaxError => err - generated_methods.delete(method_name.to_s) - if logger - logger.warn "Exception occurred during reader method compilation." - logger.warn "Maybe #{method_name} is not a valid Ruby identifier?" - logger.warn err.message - end - end - end end # Allows access to the object attributes, which are held in the <tt>@attributes</tt> hash, as though they @@ -160,10 +144,10 @@ module ActiveRecord # If we haven't generated any methods yet, generate them, then # see if we've created the method we're looking for. - if !self.class.generated_methods? + if self.class.generated_methods.instance_methods.empty? self.class.define_attribute_methods guard_private_attribute_method!(method_name, args) - if self.class.generated_methods.include?(method_name) + if self.class.generated_methods.instance_methods.include?(method_name) return self.send(method_id, *args, &block) end end @@ -190,9 +174,9 @@ module ActiveRecord # If we're here than we haven't found among non-private methods # but found among all methods. Which means that given method is private. return false - elsif !self.class.generated_methods? + elsif self.class.generated_methods.instance_methods.empty? self.class.define_attribute_methods - if self.class.generated_methods.include?(method_name) + if self.class.generated_methods.instance_methods.include?(method_name) return true end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index bea332ef26..6c0bf072e9 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -51,7 +51,7 @@ module ActiveRecord private # Define read method for serialized attribute. def define_read_method_for_serialized_attribute(attr_name) - evaluate_attribute_method "def #{attr_name}; unserialize_attribute('#{attr_name}'); end", attr_name + generated_methods.module_eval("def #{attr_name}; unserialize_attribute('#{attr_name}'); end", __FILE__, __LINE__) end # Define an attribute reader method. Cope with nil column. @@ -66,7 +66,7 @@ module ActiveRecord if cache_attribute?(attr_name) access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})" end - evaluate_attribute_method "def #{symbol}; #{access_code}; end", symbol + generated_methods.module_eval("def #{symbol}; #{access_code}; end", __FILE__, __LINE__) end end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 9e2c6174c6..4438c7543e 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -25,7 +25,7 @@ module ActiveRecord @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time end EOV - evaluate_attribute_method method_body, attr_name + generated_methods.module_eval(method_body, __FILE__, __LINE__) else super end @@ -44,7 +44,7 @@ module ActiveRecord write_attribute(:#{attr_name}, time) end EOV - evaluate_attribute_method method_body, "#{attr_name}=" + generated_methods.module_eval(method_body, __FILE__, __LINE__) else super end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 497e72ee4a..c75745c00d 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -10,7 +10,7 @@ module ActiveRecord module ClassMethods protected def define_attribute_method=(attr_name) - evaluate_attribute_method "def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", "#{attr_name}=" + generated_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__) end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 16a976a8c7..e939701d63 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -27,7 +27,6 @@ module ActiveRecord private def parse_sqlite_config!(config) - config[:database] ||= config[:dbfile] # Require database. unless config[:database] raise ArgumentError, "No database file specified. Missing argument: database" diff --git a/activesupport/lib/active_support/core_ext/string/bytesize.rb b/activesupport/lib/active_support/core_ext/string/bytesize.rb new file mode 100644 index 0000000000..ed051b921e --- /dev/null +++ b/activesupport/lib/active_support/core_ext/string/bytesize.rb @@ -0,0 +1,5 @@ +unless '1.9'.respond_to?(:bytesize) + class String + alias :bytesize :size + end +end diff --git a/activesupport/lib/active_support/core_ext/string/interpolation.rb b/activesupport/lib/active_support/core_ext/string/interpolation.rb index d459c03d39..d9159b690a 100644 --- a/activesupport/lib/active_support/core_ext/string/interpolation.rb +++ b/activesupport/lib/active_support/core_ext/string/interpolation.rb @@ -6,6 +6,7 @@ =end if RUBY_VERSION < '1.9' + require 'active_support/core_ext/string/bytesize' # KeyError is raised by String#% when the string contains a named placeholder # that is not contained in the given arguments hash. Ruby 1.9 includes and @@ -24,8 +25,6 @@ if RUBY_VERSION < '1.9' # the meaning of the msgids using "named argument" instead of %s/%d style. class String - # For older ruby versions, such as ruby-1.8.5 - alias :bytesize :size unless instance_methods.find {|m| m.to_s == 'bytesize'} alias :interpolate_without_ruby_19_syntax :% # :nodoc: INTERPOLATION_PATTERN = Regexp.union( @@ -90,4 +89,4 @@ if RUBY_VERSION < '1.9' end end end -end
\ No newline at end of file +end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index a23d3f6fef..1005a7e7ad 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -345,3 +345,10 @@ class TestGetTextString < Test::Unit::TestCase assert_raises(ArgumentError) { "%{name} %f" % [1.0, 2.0] } end end + +class StringBytesizeTest < Test::Unit::TestCase + def test_bytesize + assert_respond_to 'foo', :bytesize + assert_equal 3, 'foo'.bytesize + end +end |