diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2008-11-02 03:52:15 +0530 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2008-11-02 03:52:15 +0530 |
commit | 1147453fce0890ea229c3af5f43c909ebe53061e (patch) | |
tree | 221d816ef0c908044fd6029950ccad064866ab8f | |
parent | a3aa0c17ef8594a0084511f4852be7b5dc66e5e2 (diff) | |
parent | 5a02f0bccf55191c2cfbcc69bd8165df6d7a2012 (diff) | |
download | rails-1147453fce0890ea229c3af5f43c909ebe53061e.tar.gz rails-1147453fce0890ea229c3af5f43c909ebe53061e.tar.bz2 rails-1147453fce0890ea229c3af5f43c909ebe53061e.zip |
Merge commit 'mainstream/master'
Conflicts:
railties/doc/guides/html/layouts_and_rendering.html
railties/doc/guides/source/active_record_basics.txt
railties/doc/guides/source/layouts_and_rendering.txt
53 files changed, 697 insertions, 338 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 3c06c87bb2..17fada156d 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,3 +1,14 @@ +*2.2.1 [RC2 or 2.2 final]* + +* Simplified the logging format for parameters (don't include controller, action, and format as duplicates) [DHH] + +* Remove the logging of the Session ID when the session store is CookieStore [DHH] + +* Fixed regex in redirect_to to fully support URI schemes #1247 [Seth Fitzsimmons] + +* Fixed bug with asset timestamping when using relative_url_root #1265 [Joe Goldwasser] + + *2.2.0 [RC1] (October 24th, 2008)* * Fix incorrect closing CDATA delimiter and that HTML::Node.parse would blow up on unclosed CDATA sections [packagethief] diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 006a3039af..cf86c5eed0 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -801,6 +801,19 @@ module ActionController #:nodoc: # # Renders "Hello from code!" # render :text => proc { |response, output| output.write("Hello from code!") } # + # === Rendering XML + # + # Rendering XML sets the content type to application/xml. + # + # # Renders '<name>David</name>' + # render :xml => {:name => "David"}.to_xml + # + # It's not necessary to call <tt>to_xml</tt> on the object you want to render, since <tt>render</tt> will + # automatically do that for you: + # + # # Also renders '<name>David</name>' + # render :xml => {:name => "David"} + # # === Rendering JSON # # Rendering JSON sets the content type to application/json and optionally wraps the JSON in a callback. It is expected @@ -846,8 +859,14 @@ module ActionController #:nodoc: # page.visual_effect :highlight, 'user_list' # end # - # === Rendering with status and location headers + # === Rendering vanilla JavaScript + # + # In addition to using RJS with render :update, you can also just render vanilla JavaScript with :js. + # + # # Renders "alert('hello')" and sets the mime type to text/javascript + # render :js => "alert('hello')" # + # === Rendering with status and location headers # All renders take the <tt>:status</tt> and <tt>:location</tt> options and turn them into headers. They can even be used together: # # render :xml => post.to_xml, :status => :created, :location => post_url(post) @@ -898,6 +917,10 @@ module ActionController #:nodoc: response.content_type ||= Mime::XML render_for_text(xml.respond_to?(:to_xml) ? xml.to_xml : xml, options[:status]) + elsif js = options[:js] + response.content_type ||= Mime::JS + render_for_text(js, options[:status]) + elsif json = options[:json] json = json.to_json unless json.is_a?(String) json = "#{options[:callback]}(#{json})" unless options[:callback].blank? @@ -933,6 +956,7 @@ module ActionController #:nodoc: def render_to_string(options = nil, &block) #:doc: render(options, &block) ensure + response.content_type = nil erase_render_results reset_variables_added_to_assigns end @@ -1056,7 +1080,10 @@ module ActionController #:nodoc: logger.info("Redirected to #{options}") if logger && logger.info? case options - when %r{^\w+://.*} + # The scheme name consist of a letter followed by any combination of + # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") + # characters; and is terminated by a colon (":"). + when %r{^\w[\w\d+.-]*:.*} redirect_to_full_url(options, status) when String redirect_to_full_url(request.protocol + request.host_with_port + options, status) @@ -1201,11 +1228,33 @@ module ActionController #:nodoc: def log_processing if logger && logger.info? - logger.info "\n\nProcessing #{self.class.name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]" - logger.info " Session ID: #{@_session.session_id}" if @_session and @_session.respond_to?(:session_id) - logger.info " Parameters: #{respond_to?(:filter_parameters) ? filter_parameters(params).inspect : params.inspect}" + log_processing_for_request_id + log_processing_for_session_id + log_processing_for_parameters end end + + def log_processing_for_request_id + request_id = "\n\nProcessing #{self.class.name}\##{action_name} " + request_id << "to #{params[:format]} " if params[:format] + request_id << "(for #{request_origin}) [#{request.method.to_s.upcase}]" + + logger.info(request_id) + end + + def log_processing_for_session_id + if @_session && @_session.respond_to?(:session_id) && @_session.respond_to?(:dbman) && + !@_session.dbman.is_a?(CGI::Session::CookieStore) + logger.info " Session ID: #{@_session.session_id}" + end + end + + def log_processing_for_parameters + parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params + parameters = parameters.except(:controller, :action, :format) + + logger.info " Parameters: #{parameters.inspect}" + end def default_render #:nodoc: render diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index 28f8ce3d53..f3e173004a 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -28,6 +28,10 @@ module ActionController end after_dispatch :flush_logger if Base.logger && Base.logger.respond_to?(:flush) + + to_prepare do + I18n.reload! + end end # Backward-compatible class method takes CGI-specific args. Deprecated diff --git a/actionpack/lib/action_controller/routing/optimisations.rb b/actionpack/lib/action_controller/routing/optimisations.rb index 894d4109e4..0a87303bda 100644 --- a/actionpack/lib/action_controller/routing/optimisations.rb +++ b/actionpack/lib/action_controller/routing/optimisations.rb @@ -20,14 +20,20 @@ module ActionController class Optimiser attr_reader :route, :kind + GLOBAL_GUARD_CONDITIONS = [ + "(!defined?(default_url_options) || default_url_options.blank?)", + "(!defined?(controller.default_url_options) || controller.default_url_options.blank?)", + "defined?(request)", + "request" + ] def initialize(route, kind) @route = route @kind = kind end - def guard_condition - 'false' + def guard_conditions + ["false"] end def generation_code @@ -36,6 +42,7 @@ module ActionController def source_code if applicable? + guard_condition = (GLOBAL_GUARD_CONDITIONS + guard_conditions).join(" && ") "return #{generation_code} if #{guard_condition}\n" else "\n" @@ -57,14 +64,14 @@ module ActionController # return a string like "/people/#{@person.to_param}" # rather than triggering the expensive logic in +url_for+. class PositionalArguments < Optimiser - def guard_condition + def guard_conditions number_of_arguments = route.segment_keys.size # if they're using foo_url(:id=>2) it's one # argument, but we don't want to generate /foos/id2 if number_of_arguments == 1 - "(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == 1 && !args.first.is_a?(Hash)" + ["args.size == 1", "!args.first.is_a?(Hash)"] else - "(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == #{number_of_arguments}" + ["args.size == #{number_of_arguments}"] end end @@ -98,8 +105,13 @@ module ActionController # above, but it supports additional query parameters as the last # argument class PositionalArgumentsWithAdditionalParams < PositionalArguments - def guard_condition - "(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == #{route.segment_keys.size + 1} && !args.last.has_key?(:anchor) && !args.last.has_key?(:port) && !args.last.has_key?(:host)" + def guard_conditions + [ + "args.size == #{route.segment_keys.size + 1}", + "!args.last.has_key?(:anchor)", + "!args.last.has_key?(:port)", + "!args.last.has_key?(:host)" + ] end # This case uses almost the same code as positional arguments, diff --git a/actionpack/lib/action_controller/routing/recognition_optimisation.rb b/actionpack/lib/action_controller/routing/recognition_optimisation.rb index 4935432d87..6c47ced6d1 100644 --- a/actionpack/lib/action_controller/routing/recognition_optimisation.rb +++ b/actionpack/lib/action_controller/routing/recognition_optimisation.rb @@ -153,13 +153,7 @@ module ActionController def clear_recognize_optimized! remove_recognize_optimized! - - class << self - def recognize_optimized(path, environment) - write_recognize_optimized! - recognize_optimized(path, environment) - end - end + write_recognize_optimized! end def remove_recognize_optimized! diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index f84c48f102..7a31f0e8d5 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -218,7 +218,7 @@ module ActionController #:nodoc: # Returns the template of the file which was used to # render this response (or nil) def rendered_template - template.send(:_first_render) + template.instance_variable_get(:@_first_render) end # A shortcut to the flash. Returns an empty hash if no session flash exists. diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index e22978fe27..945246a39a 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -222,6 +222,7 @@ module ActionView #:nodoc: def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc: @assigns = assigns_for_first_render @assigns_added = nil + @_render_stack = [] @controller = controller @helpers = ProxyModule.new(self) self.view_paths = view_paths @@ -271,9 +272,13 @@ module ActionView #:nodoc: end end - private - attr_accessor :_first_render, :_last_render + # Access the current template being rendered. + # Returns a ActionView::Template object. + def template + @_render_stack.last + end + private # Evaluates the local assigns and controller ivars, pushes them to the view. def _evaluate_assigns_and_ivars #:nodoc: unless @assigns_added @@ -312,7 +317,7 @@ module ActionView #:nodoc: template elsif template = self.view_paths[template_file_name] template - elsif _first_render && template = self.view_paths["#{template_file_name}.#{_first_render.format_and_extension}"] + elsif @_render_stack.first && template = self.view_paths["#{template_file_name}.#{@_render_stack.first.format_and_extension}"] template elsif template_format == :js && template = self.view_paths["#{template_file_name}.html"] @template_format = :html diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 93d38eb929..8bbe74b7ef 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -588,8 +588,8 @@ module ActionView source += ".#{extension}" if missing_extension?(source) unless source =~ ProtocolRegexp source = "/#{directory}/#{source}" unless source[0] == ?/ - source = prepend_relative_url_root(source) source = rewrite_asset_path(source) + source = prepend_relative_url_root(source) end source = prepend_asset_host(source) source diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index de4c1d7689..dc41ef5305 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -3,12 +3,11 @@ require 'action_view/helpers/tag_helper' module ActionView module Helpers module TranslationHelper - def translate(*args) - args << args.extract_options!.merge(:raise => true) - I18n.translate *args - + def translate(key, options = {}) + options[:raise] = true + I18n.translate(key, options) rescue I18n::MissingTranslationData => e - keys = I18n.send :normalize_translation_keys, e.locale, e.key, e.options[:scope] + keys = I18n.send(:normalize_translation_keys, e.locale, e.key, e.options[:scope]) content_tag('span', keys.join(', '), :class => 'translation_missing') end alias :t :translate diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 7ba42a3b72..2e0eb8766b 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -499,7 +499,7 @@ module ActionView # True if the current request URI was generated by the given +options+. # # ==== Examples - # Let's say we're in the <tt>/shop/checkout</tt> action. + # Let's say we're in the <tt>/shop/checkout?order=desc</tt> action. # # current_page?(:action => 'process') # # => false @@ -507,6 +507,9 @@ module ActionView # current_page?(:controller => 'shop', :action => 'checkout') # # => true # + # current_page?(:controller => 'shop', :action => 'checkout', :order => 'asc) + # # => false + # # current_page?(:action => 'checkout') # # => true # @@ -515,10 +518,18 @@ module ActionView def current_page?(options) url_string = CGI.escapeHTML(url_for(options)) request = @controller.request + # We ignore any extra parameters in the request_uri if the + # submitted url doesn't have any either. This lets the function + # work with things like ?order=asc + if url_string.index("?") + request_uri = request.request_uri + else + request_uri = request.request_uri.split('?').first + end if url_string =~ /^\w+:\/\// - url_string == "#{request.protocol}#{request.host_with_port}#{request.request_uri}" + url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}" else - url_string == request.request_uri + url_string == request_uri end end diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb index 373bb92dc4..8841099900 100644 --- a/actionpack/lib/action_view/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -181,7 +181,7 @@ module ActionView ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path) template = _pick_partial_template(_partial_path) local_assigns[template.counter_name] = index - result = template.render_partial(self, object, local_assigns, as) + result = template.render_partial(self, object, local_assigns.dup, as) index += 1 result end.join(spacer) diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index 0134bc988f..c23b8cde89 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -25,20 +25,26 @@ module ActionView def render(view, local_assigns = {}) compile(local_assigns) - view.send(:_first_render=, self) unless view.send(:_first_render) - view.send(:_last_render=, self) + stack = view.instance_variable_get(:@_render_stack) + stack.push(self) + + # This is only used for TestResponse to set rendered_template + view.instance_variable_set(:@_first_render, self) unless view.instance_variable_get(:@_first_render) view.send(:_evaluate_assigns_and_ivars) view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type) - view.send(method_name(local_assigns), local_assigns) do |*names| + result = view.send(method_name(local_assigns), local_assigns) do |*names| ivar = :@_proc_for_layout - if view.instance_variable_defined?(ivar) and proc = view.instance_variable_get(ivar) + if !view.instance_variable_defined?(:"@content_for_#{names.first}") && view.instance_variable_defined?(ivar) && (proc = view.instance_variable_get(ivar)) view.capture(*names, &proc) elsif view.instance_variable_defined?(ivar = :"@content_for_#{names.first || :layout}") view.instance_variable_get(ivar) end end + + stack.pop + result end def method_name(local_assigns) diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index 2f8bf7b6ee..c55307d645 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -73,6 +73,10 @@ class RedirectController < ActionController::Base redirect_to "http://dev.rubyonrails.org/query?status=new" end + def redirect_to_url_with_complex_scheme + redirect_to "x-test+scheme.complex:redirect" + end + def redirect_to_back redirect_to :back end @@ -198,6 +202,12 @@ class RedirectTest < Test::Unit::TestCase assert_redirected_to "http://dev.rubyonrails.org/query?status=new" end + def test_redirect_to_url_with_complex_scheme + get :redirect_to_url_with_complex_scheme + assert_response :redirect + assert_equal "x-test+scheme.complex:redirect", redirect_to_url + end + def test_redirect_to_back @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from" get :redirect_to_back diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index db2d5d885b..df9376727f 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -154,6 +154,10 @@ class TestController < ActionController::Base render :json => {:hello => 'world'}.to_json end + def render_json_with_render_to_string + render :json => {:hello => render_to_string(:partial => 'partial')} + end + def render_custom_code render :text => "hello world", :status => 404 end @@ -180,6 +184,10 @@ class TestController < ActionController::Base render("test/hello") end + def render_vanilla_js_hello + render :js => "alert('hello')" + end + def render_xml_hello @name = "David" render :template => "test/hello" @@ -772,6 +780,12 @@ class RenderTest < Test::Unit::TestCase assert_equal 'application/json', @response.content_type end + def test_render_json_with_render_to_string + get :render_json_with_render_to_string + assert_equal '{"hello": "partial html"}', @response.body + assert_equal 'application/json', @response.content_type + end + def test_render_custom_code get :render_custom_code assert_response 404 @@ -834,6 +848,12 @@ class RenderTest < Test::Unit::TestCase assert_equal "test", @response.body # name is explicitly set to 'test' inside the controller. end + def test_render_vanilla_js + get :render_vanilla_js_hello + assert_equal "alert('hello')", @response.body + assert_equal "text/javascript", @response.content_type + end + def test_render_xml get :render_xml_hello assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body diff --git a/actionpack/test/fixtures/layouts/_column.html.erb b/actionpack/test/fixtures/layouts/_column.html.erb new file mode 100644 index 0000000000..96db002b8a --- /dev/null +++ b/actionpack/test/fixtures/layouts/_column.html.erb @@ -0,0 +1,2 @@ +<div id="column"><%= yield :column %></div> +<div id="content"><%= yield %></div>
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_customer.erb b/actionpack/test/fixtures/test/_customer.erb index 872d8c44e6..d8220afeda 100644 --- a/actionpack/test/fixtures/test/_customer.erb +++ b/actionpack/test/fixtures/test/_customer.erb @@ -1 +1 @@ -Hello: <%= customer.name %>
\ No newline at end of file +Hello: <%= customer.name rescue "Anonymous" %>
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/nested_layout.erb b/actionpack/test/fixtures/test/nested_layout.erb new file mode 100644 index 0000000000..7b6dcbb6c7 --- /dev/null +++ b/actionpack/test/fixtures/test/nested_layout.erb @@ -0,0 +1,3 @@ +<% content_for :title, "title" -%> +<% content_for :column do -%>column<% end -%> +<% render :layout => 'layouts/column' do -%>content<% end -%>
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/template.erb b/actionpack/test/fixtures/test/template.erb new file mode 100644 index 0000000000..785afa8f6a --- /dev/null +++ b/actionpack/test/fixtures/test/template.erb @@ -0,0 +1 @@ +<%= template.path %>
\ No newline at end of file diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 6dc1225035..bade96fe17 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -248,6 +248,14 @@ class AssetTagHelperTest < ActionView::TestCase assert_equal %(<img alt="Rails" src="/images/rails.png?#{expected_time}" />), image_tag("rails.png") end + def test_timebased_asset_id_with_relative_url_root + ActionController::Base.relative_url_root = "/collaboration/hieraki" + expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s + assert_equal %(<img alt="Rails" src="#{ActionController::Base.relative_url_root}/images/rails.png?#{expected_time}" />), image_tag("rails.png") + ensure + ActionController::Base.relative_url_root = "" + end + def test_should_skip_asset_id_on_complete_url assert_equal %(<img alt="Rails" src="http://www.example.com/rails.png" />), image_tag("http://www.example.com/rails.png") end diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index a4ea22ddcb..476e651757 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -41,6 +41,10 @@ class ViewRenderTest < Test::Unit::TestCase assert_equal "The secret is in the sauce\n", @view.render("test/dot.directory/render_file_with_ivar") end + def test_render_has_access_current_template + assert_equal "test/template.erb", @view.render("test/template.erb") + end + def test_render_update # TODO: You should not have to stub out template because template is self! @view.instance_variable_set(:@template, @view) @@ -111,6 +115,10 @@ class ViewRenderTest < Test::Unit::TestCase assert_nil @view.render(:partial => "test/customer", :collection => nil) end + def test_render_partial_with_nil_values_in_collection + assert_equal "Hello: davidHello: Anonymous", @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), nil ]) + end + def test_render_partial_with_empty_array_should_return_nil assert_nil @view.render(:partial => []) end @@ -158,4 +166,14 @@ class ViewRenderTest < Test::Unit::TestCase ActionView::Template.register_template_handler :foo, CustomHandler assert_equal 'source: "Hello, <%= name %>!"', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo) end + + def test_render_with_layout + assert_equal %(<title></title>\nHello world!\n), + @view.render(:file => "test/hello_world.erb", :layout => "layouts/yield") + end + + def test_render_with_nested_layout + assert_equal %(<title>title</title>\n<div id="column">column</div>\n<div id="content">content</div>\n), + @view.render(:file => "test/nested_layout.erb", :layout => "layouts/yield") + end end diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb index 7b94221ec0..1b28a59288 100644 --- a/actionpack/test/template/translation_helper_test.rb +++ b/actionpack/test/template/translation_helper_test.rb @@ -10,8 +10,8 @@ class TranslationHelperTest < Test::Unit::TestCase end def test_delegates_to_i18n_setting_the_raise_option - I18n.expects(:translate).with(:foo, 'en-US', :raise => true) - translate :foo, 'en-US' + I18n.expects(:translate).with(:foo, :locale => 'en-US', :raise => true) + translate :foo, :locale => 'en-US' end def test_returns_missing_translation_message_wrapped_into_span diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 85e967ac1c..2f6fa134b5 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -258,6 +258,16 @@ class UrlHelperTest < ActionView::TestCase assert_equal "Showing", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" }) assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show") + @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc") + @controller.url = "http://www.example.com/weblog/show" + assert_equal "Showing", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" }) + assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show") + + @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc") + @controller.url = "http://www.example.com/weblog/show?order=asc" + assert_equal "<a href=\"http://www.example.com/weblog/show?order=asc\">Showing</a>", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" }) + assert_equal "<a href=\"http://www.example.com/weblog/show?order=asc\">Showing</a>", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=asc") + @controller.request = RequestMock.new("http://www.example.com/weblog/show") @controller.url = "http://www.example.com/weblog/list" assert_equal "<a href=\"http://www.example.com/weblog/list\">Listing</a>", @@ -366,6 +376,19 @@ class UrlHelperWithControllerTest < ActionView::TestCase assert_equal '/url_helper_with_controller/nil_url_for', @response.body end + def test_named_route_should_show_host_and_path_using_controller_default_url_options + class << @controller + def default_url_options(options = nil) + {:host => 'testtwo.host'} + end + end + + with_url_helper_routing do + get :show_named_route, :kind => 'url' + assert_equal 'http://testtwo.host/url_helper_with_controller/show_named_route', @response.body + end + end + protected def with_url_helper_routing with_routing do |set| diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index fec110d569..4ca062b535 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,3 +1,10 @@ +*2.2.1 [RC2 or 2.2 final]* + +* Ensure indices don't flip order in schema.rb #1266 [Jordi Bunster] + +* Fixed that serialized strings should never be type-casted (i.e. turning "Yes" to a boolean) #857 [Andreas Korth] + + *2.2.0 [RC1] (October 24th, 2008)* * Skip collection ids reader optimization if using :finder_sql [Jeremy Kemper] diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 52f6a04da1..84caca3518 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1596,16 +1596,19 @@ module ActiveRecord reflection end + mattr_accessor :valid_keys_for_has_and_belongs_to_many_association + @@valid_keys_for_has_and_belongs_to_many_association = [ + :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key, + :select, :conditions, :include, :order, :group, :limit, :offset, + :uniq, + :finder_sql, :delete_sql, :insert_sql, + :before_add, :after_add, :before_remove, :after_remove, + :extend, :readonly, + :validate + ] + def create_has_and_belongs_to_many_reflection(association_id, options, &extension) - options.assert_valid_keys( - :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key, - :select, :conditions, :include, :order, :group, :limit, :offset, - :uniq, - :finder_sql, :delete_sql, :insert_sql, - :before_add, :after_add, :before_remove, :after_remove, - :extend, :readonly, - :validate - ) + options.assert_valid_keys(valid_keys_for_has_and_belongs_to_many_association) options[:extend] = create_extension_modules(association_id, extension, options[:extend]) diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 1c753524de..177d156834 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -233,7 +233,7 @@ module ActiveRecord method_name = method_id.to_s if self.class.private_method_defined?(method_name) - raise NoMethodError("Attempt to call private method", method_name, args) + raise NoMethodError.new("Attempt to call private method", method_name, args) end # If we haven't generated any methods yet, generate them, then diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 039e14435f..a36a137f0d 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1764,7 +1764,7 @@ module ActiveRecord #:nodoc: # # Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future # attempts to use it do not run through method_missing. - def method_missing(method_id, *arguments) + def method_missing(method_id, *arguments, &block) if match = DynamicFinderMatch.match(method_id) attribute_names = match.attribute_names super unless all_attributes_exists?(attribute_names) @@ -1819,7 +1819,7 @@ module ActiveRecord #:nodoc: end end }, __FILE__, __LINE__ - send(method_id, *arguments) + send(method_id, *arguments, &block) end else super @@ -2410,10 +2410,11 @@ module ActiveRecord #:nodoc: # be made (since they can't be persisted). def destroy unless new_record? - connection.delete <<-end_sql, "#{self.class.name} Destroy" - DELETE FROM #{self.class.quoted_table_name} - WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id} - end_sql + connection.delete( + "DELETE FROM #{self.class.quoted_table_name} " + + "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id}", + "#{self.class.name} Destroy" + ) end freeze @@ -2938,7 +2939,7 @@ module ActiveRecord #:nodoc: end def object_from_yaml(string) - return string unless string.is_a?(String) + return string unless string.is_a?(String) && string =~ /^---/ YAML::load(string) rescue string end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 2fc50b9bfa..950bd72101 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -15,7 +15,7 @@ module ActiveRecord method_names.each do |method_name| base.class_eval <<-end_code, __FILE__, __LINE__ def #{method_name}_with_query_dirty(*args) - clear_query_cache if query_cache_enabled + clear_query_cache if @query_cache_enabled #{method_name}_without_query_dirty(*args) end @@ -25,38 +25,24 @@ module ActiveRecord end end - def query_cache_enabled - Thread.current['query_cache_enabled'] - end - - def query_cache_enabled=(flag) - Thread.current['query_cache_enabled'] = flag - end - - def query_cache - Thread.current['query_cache'] - end - - def query_cache=(cache) - Thread.current['query_cache'] = cache - end + attr_reader :query_cache, :query_cache_enabled # Enable the query cache within the block. def cache - old, self.query_cache_enabled = query_cache_enabled, true - self.query_cache ||= {} + old, @query_cache_enabled = @query_cache_enabled, true + @query_cache ||= {} yield ensure clear_query_cache - self.query_cache_enabled = old + @query_cache_enabled = old end # Disable the query cache within the block. def uncached - old, self.query_cache_enabled = query_cache_enabled, false + old, @query_cache_enabled = @query_cache_enabled, false yield ensure - self.query_cache_enabled = old + @query_cache_enabled = old end # Clears the query cache. @@ -66,11 +52,11 @@ module ActiveRecord # the same SQL query and repeatedly return the same result each time, silently # undermining the randomness you were expecting. def clear_query_cache - query_cache.clear if query_cache + @query_cache.clear if @query_cache end def select_all_with_query_cache(*args) - if query_cache_enabled + if @query_cache_enabled cache_sql(args.first) { select_all_without_query_cache(*args) } else select_all_without_query_cache(*args) @@ -78,8 +64,8 @@ module ActiveRecord end def columns_with_query_cache(*args) - if query_cache_enabled - query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args) + if @query_cache_enabled + @query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args) else columns_without_query_cache(*args) end @@ -88,11 +74,11 @@ module ActiveRecord private def cache_sql(sql) result = - if query_cache.has_key?(sql) + if @query_cache.has_key?(sql) log_info(sql, "CACHE", 0.0) - query_cache[sql] + @query_cache[sql] else - query_cache[sql] = yield + @query_cache[sql] = yield end if Array === result diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index bebab5d05d..60ec01b95e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -68,72 +68,6 @@ module ActiveRecord super end - # Escapes binary strings for bytea input to the database. - def self.string_to_binary(value) - if PGconn.respond_to?(:escape_bytea) - self.class.module_eval do - define_method(:string_to_binary) do |value| - PGconn.escape_bytea(value) if value - end - end - else - self.class.module_eval do - define_method(:string_to_binary) do |value| - if value - result = '' - value.each_byte { |c| result << sprintf('\\\\%03o', c) } - result - end - end - end - end - self.class.string_to_binary(value) - end - - # Unescapes bytea output from a database to the binary string it represents. - def self.binary_to_string(value) - # In each case, check if the value actually is escaped PostgreSQL bytea output - # or an unescaped Active Record attribute that was just written. - if PGconn.respond_to?(:unescape_bytea) - self.class.module_eval do - define_method(:binary_to_string) do |value| - if value =~ /\\\d{3}/ - PGconn.unescape_bytea(value) - else - value - end - end - end - else - self.class.module_eval do - define_method(:binary_to_string) do |value| - if value =~ /\\\d{3}/ - result = '' - i, max = 0, value.size - while i < max - char = value[i] - if char == ?\\ - if value[i+1] == ?\\ - char = ?\\ - i += 1 - else - char = value[i+1..i+3].oct - i += 3 - end - end - result << char - i += 1 - end - result - else - value - end - end - end - end - self.class.binary_to_string(value) - end - # Maps PostgreSQL-specific data types to logical Rails types. def simplified_type(field_type) case field_type @@ -347,10 +281,78 @@ module ActiveRecord # QUOTING ================================================== + # Escapes binary strings for bytea input to the database. + def escape_bytea(value) + if PGconn.respond_to?(:escape_bytea) + self.class.instance_eval do + define_method(:escape_bytea) do |value| + PGconn.escape_bytea(value) if value + end + end + else + self.class.instance_eval do + define_method(:escape_bytea) do |value| + if value + result = '' + value.each_byte { |c| result << sprintf('\\\\%03o', c) } + result + end + end + end + end + escape_bytea(value) + end + + # Unescapes bytea output from a database to the binary string it represents. + # NOTE: This is NOT an inverse of escape_bytea! This is only to be used + # on escaped binary output from database drive. + def unescape_bytea(value) + # In each case, check if the value actually is escaped PostgreSQL bytea output + # or an unescaped Active Record attribute that was just written. + if PGconn.respond_to?(:unescape_bytea) + self.class.instance_eval do + define_method(:unescape_bytea) do |value| + if value =~ /\\\d{3}/ + PGconn.unescape_bytea(value) + else + value + end + end + end + else + self.class.instance_eval do + define_method(:unescape_bytea) do |value| + if value =~ /\\\d{3}/ + result = '' + i, max = 0, value.size + while i < max + char = value[i] + if char == ?\\ + if value[i+1] == ?\\ + char = ?\\ + i += 1 + else + char = value[i+1..i+3].oct + i += 3 + end + end + result << char + i += 1 + end + result + else + value + end + end + end + end + unescape_bytea(value) + end + # Quotes PostgreSQL-specific data types for SQL input. def quote(value, column = nil) #:nodoc: if value.kind_of?(String) && column && column.type == :binary - "#{quoted_string_prefix}'#{column.class.string_to_binary(value)}'" + "#{quoted_string_prefix}'#{escape_bytea(value)}'" elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/ "xml '#{quote_string(value)}'" elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/ @@ -463,11 +465,20 @@ module ActiveRecord # create a 2D array representing the result set def result_as_array(res) #:nodoc: + # check if we have any binary column and if they need escaping + unescape_col = [] + for j in 0...res.nfields do + # unescape string passed BYTEA field (OID == 17) + unescape_col << ( res.ftype(j)==17 ) + end + ary = [] for i in 0...res.ntuples do ary << [] for j in 0...res.nfields do - ary[i] << res.getvalue(i,j) + data = res.getvalue(i,j) + data = unescape_bytea(data) if unescape_col[j] and data.is_a?(String) + ary[i] << data end end return ary diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 4f96e225c1..2181bdf2dd 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -159,13 +159,19 @@ HEADER end def indexes(table, stream) - indexes = @connection.indexes(table) - indexes.each do |index| - stream.print " add_index #{index.table.inspect}, #{index.columns.inspect}, :name => #{index.name.inspect}" - stream.print ", :unique => true" if index.unique + if (indexes = @connection.indexes(table)).any? + add_index_statements = indexes.map do |index| + statment_parts = [ ('add_index ' + index.table.inspect) ] + statment_parts << index.columns.inspect + statment_parts << (':name => ' + index.name.inspect) + statment_parts << ':unique => true' if index.unique + + ' ' + statment_parts.join(', ') + end + + stream.puts add_index_statements.sort.join("\n") stream.puts end - stream.puts unless indexes.empty? end end -end +end
\ No newline at end of file diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 160716f944..77ee8d8fc4 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -233,8 +233,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new(:title => "The pros and cons of programming naked.") assert !topic.respond_to?(:title) - assert_raise(NoMethodError) { topic.title } - topic.send(:title) + exception = assert_raise(NoMethodError) { topic.title } + assert_equal "Attempt to call private method", exception.message + assert_equal "I'm private", topic.send(:title) end def test_write_attributes_respect_access_control @@ -242,7 +243,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new assert !topic.respond_to?(:title=) - assert_raise(NoMethodError) { topic.title = "Pants"} + exception = assert_raise(NoMethodError) { topic.title = "Pants"} + assert_equal "Attempt to call private method", exception.message topic.send(:title=, "Very large pants") end @@ -251,7 +253,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new(:title => "Isaac Newton's pants") assert !topic.respond_to?(:title?) - assert_raise(NoMethodError) { topic.title? } + exception = assert_raise(NoMethodError) { topic.title? } + assert_equal "Attempt to call private method", exception.message assert topic.send(:title?) end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index d512834237..da9f2742d8 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1429,6 +1429,12 @@ class BasicsTest < ActiveRecord::TestCase topic = Topic.create("content" => myobj).reload assert_equal(myobj, topic.content) end + + def test_serialized_string_attribute + myobj = "Yes" + topic = Topic.create("content" => myobj).reload + assert_equal(myobj, topic.content) + end def test_nil_serialized_attribute_with_class_constraint myobj = MyObject.new('value1', 'value2') diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 853474916c..153880afbd 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -846,6 +846,17 @@ class FinderTest < ActiveRecord::TestCase assert !c.new_record? end + def test_find_or_create_should_work_with_block_on_first_call + class << Company + undef_method(:find_or_create_by_name) if method_defined?(:find_or_create_by_name) + end + c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 } + assert_equal "Fortune 1000", c.name + assert_equal 1000.to_f, c.rating.to_f + assert c.valid? + assert !c.new_record? + end + def test_dynamic_find_or_initialize_from_one_attribute_caches_method class << Company; self; end.send(:remove_method, :find_or_initialize_by_name) if Company.public_methods.any? { |m| m.to_s == 'find_or_initialize_by_name' } assert !Company.public_methods.any? { |m| m.to_s == 'find_or_initialize_by_name' } diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG index 74ca71f65a..114a63c415 100644 --- a/activeresource/CHANGELOG +++ b/activeresource/CHANGELOG @@ -1,3 +1,8 @@ +*2.2.1 [RC2 or 2.2 final]* + +* Fixed that ActiveResource#post would post an empty string when it shouldn't be posting anything #525 [Paolo Angelini] + + *2.2.0 [RC1] (October 24th, 2008)* * Add ActiveResource::Base#to_xml and ActiveResource::Base#to_json. #1011 [Rasik Pandey, Cody Fauser] diff --git a/activeresource/lib/active_resource/custom_methods.rb b/activeresource/lib/active_resource/custom_methods.rb index 24306f251d..4647e8342c 100644 --- a/activeresource/lib/active_resource/custom_methods.rb +++ b/activeresource/lib/active_resource/custom_methods.rb @@ -90,7 +90,7 @@ module ActiveResource end def post(method_name, options = {}, body = nil) - request_body = body.nil? ? encode : body + request_body = body.blank? ? encode : body if new? connection.post(custom_method_new_element_url(method_name, options), request_body, self.class.headers) else diff --git a/activeresource/test/base/custom_methods_test.rb b/activeresource/test/base/custom_methods_test.rb index ba5799edfb..61887f4ec7 100644 --- a/activeresource/test/base/custom_methods_test.rb +++ b/activeresource/test/base/custom_methods_test.rb @@ -81,6 +81,8 @@ class CustomMethodsTest < Test::Unit::TestCase # Test POST against a new element URL ryan = Person.new(:name => 'Ryan') assert_equal ActiveResource::Response.new(@ryan, 201, {'Location' => '/people/5.xml'}), ryan.post(:register) + expected_request = ActiveResource::Request.new(:post, '/people/new/register.xml', @ryan) + assert_equal expected_request.body, ActiveResource::HttpMock.requests.first.body # Test POST against a nested collection URL addy = StreetAddress.new(:street => '123 Test Dr.', :person_id => 1) diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 819a67adfa..e77affc315 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,3 +1,12 @@ +*2.2.1 [RC2 or 2.2 final]* + +* Added render :js for people who want to render inline JavaScript replies without using RJS [DHH] + +* Fixed the option merging in Array#to_xml #1126 [Rudolf Gavlas] + +* Make I18n::Backend::Simple reload its translations in development mode [DHH/Sven Fuchs] + + *2.2.0 [RC1] (October 24th, 2008)* * TimeWithZone#freeze: preload instance variables so that we can actually freeze [Geoff Buesing] diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 11c128da22..cf3e03f62c 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -172,7 +172,7 @@ module ActiveSupport #:nodoc: else xml.tag!(root, options[:skip_types] ? {} : {:type => "array"}) { yield xml if block_given? - each { |e| e.to_xml(opts.merge!({ :skip_instruct => true })) } + each { |e| e.to_xml(opts.merge({ :skip_instruct => true })) } } end end diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index 950bca60a6..b4d8f61b8c 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -51,8 +51,8 @@ module ActiveSupport private def deprecation_message(callstack, message = nil) - message ||= "You are using deprecated behavior which will be removed from Rails 2.0." - "DEPRECATION WARNING: #{message} See http://www.rubyonrails.org/deprecation for details. #{deprecation_caller_message(callstack)}" + message ||= "You are using deprecated behavior which will be removed from the next major or minor release." + "DEPRECATION WARNING: #{message}. #{deprecation_caller_message(callstack)}" end def deprecation_caller_message(callstack) diff --git a/activesupport/lib/active_support/string_inquirer.rb b/activesupport/lib/active_support/string_inquirer.rb index cd722a3cfb..e6b1f39225 100644 --- a/activesupport/lib/active_support/string_inquirer.rb +++ b/activesupport/lib/active_support/string_inquirer.rb @@ -11,7 +11,7 @@ module ActiveSupport # class StringInquirer < String def method_missing(method_name, *arguments) - if method_name.to_s.ends_with?("?") + if method_name.to_s[-1,1] == "?" self == method_name.to_s[0..-2] else super diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb index 344c77aecf..40e82d8225 100755 --- a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb +++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb @@ -67,6 +67,13 @@ module I18n def load_path=(load_path) @@load_path = load_path end + + # Tells the backend to reload translations. Used in situations like the + # Rails development environment. Backends can implement whatever strategy + # is useful. + def reload! + backend.reload! + end # Translates, pluralizes and interpolates a given key using a given locale, # scope, and default, as well as interpolation values. @@ -150,10 +157,10 @@ module I18n # I18n.t [:foo, :bar], :scope => :baz def translate(key, options = {}) locale = options.delete(:locale) || I18n.locale - backend.translate locale, key, options + backend.translate(locale, key, options) rescue I18n::ArgumentError => e raise e if options[:raise] - send @@exception_handler, e, locale, key, options + send(@@exception_handler, e, locale, key, options) end alias :t :translate @@ -180,8 +187,8 @@ module I18n # keys are Symbols. def normalize_translation_keys(locale, key, scope) keys = [locale] + Array(scope) + [key] - keys = keys.map{|k| k.to_s.split(/\./) } - keys.flatten.map{|k| k.to_sym} + keys = keys.map { |k| k.to_s.split(/\./) } + keys.flatten.map { |k| k.to_sym } end end end
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb index 30e3655b7b..bdda55d3fe 100644 --- a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb +++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb @@ -10,7 +10,7 @@ module I18n # plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml # for details. def load_translations(*filenames) - filenames.each {|filename| load_file filename } + filenames.each { |filename| load_file(filename) } end # Stores translations for the given locale in memory. @@ -23,12 +23,12 @@ module I18n def translate(locale, key, options = {}) raise InvalidLocale.new(locale) if locale.nil? - return key.map{|k| translate locale, k, options } if key.is_a? Array + return key.map { |k| translate(locale, k, options) } if key.is_a? Array reserved = :scope, :default count, scope, default = options.values_at(:count, *reserved) options.delete(:default) - values = options.reject{|name, value| reserved.include? name } + values = options.reject { |name, value| reserved.include?(name) } entry = lookup(locale, key, scope) if entry.nil? @@ -37,8 +37,8 @@ module I18n raise(I18n::MissingTranslationData.new(locale, key, options)) end end - entry = pluralize locale, entry, count - entry = interpolate locale, entry, values + entry = pluralize(locale, entry, count) + entry = interpolate(locale, entry, values) entry end @@ -69,8 +69,12 @@ module I18n @initialized ||= false end - protected + def reload! + @initialized = false + @translations = nil + end + protected def init_translations load_translations(*I18n.load_path) @initialized = true @@ -88,7 +92,7 @@ module I18n def lookup(locale, key, scope = []) return unless key init_translations unless initialized? - keys = I18n.send :normalize_translation_keys, locale, key, scope + keys = I18n.send(:normalize_translation_keys, locale, key, scope) keys.inject(translations) do |result, k| if (x = result[k.to_sym]).nil? return nil @@ -170,21 +174,21 @@ module I18n # for all other file extensions. def load_file(filename) type = File.extname(filename).tr('.', '').downcase - raise UnknownFileType.new(type, filename) unless respond_to? :"load_#{type}" + raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}") data = send :"load_#{type}", filename # TODO raise a meaningful exception if this does not yield a Hash - data.each{|locale, d| merge_translations locale, d } + data.each { |locale, d| merge_translations(locale, d) } end # Loads a plain Ruby translations file. eval'ing the file must yield # a Hash containing translation data with locales as toplevel keys. def load_rb(filename) - eval IO.read(filename), binding, filename + eval(IO.read(filename), binding, filename) end # Loads a YAML translations file. The data must have locales as # toplevel keys. def load_yml(filename) - YAML::load IO.read(filename) + YAML::load(IO.read(filename)) end # Deep merges the given translations hash with the existing translations @@ -192,16 +196,16 @@ module I18n def merge_translations(locale, data) locale = locale.to_sym translations[locale] ||= {} - data = deep_symbolize_keys data + data = deep_symbolize_keys(data) # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809 - merger = proc{|key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 } - translations[locale].merge! data, &merger + merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 } + translations[locale].merge!(data, &merger) end # Return a new hash with all keys and nested keys converted to symbols. def deep_symbolize_keys(hash) - hash.inject({}){|result, (key, value)| + hash.inject({}) { |result, (key, value)| value = deep_symbolize_keys(value) if value.is_a? Hash result[(key.to_sym rescue key) || key] = value result @@ -209,4 +213,4 @@ module I18n end end end -end +end
\ No newline at end of file diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 390da3b890..058afddbde 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,3 +1,10 @@ +*2.2.1 [RC2 or 2.2 final]* + +* Fixed plugin generator so that generated unit tests would subclass ActiveSupport::TestCase, also introduced a helper script to reduce the needed require statements #1137 [Mathias Meyer] + +* Update Prototype to 1.6.0.3 [sam] + + *2.2.0 [RC1] (October 24th, 2008)* * Fixed that sqlite would report "db/development.sqlite3 already exists" whether true or not on db:create #614 [Antonio Cangiano] diff --git a/railties/Rakefile b/railties/Rakefile index adb6db0b64..872ea83ec2 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -192,7 +192,7 @@ task :copy_configs do app_name = "rails" socket = nil require 'erb' - File.open("#{PKG_DESTINATION}/config/database.yml", 'w') {|f| f.write ERB.new(IO.read("configs/databases/mysql.yml"), nil, '-').result(binding)} + File.open("#{PKG_DESTINATION}/config/database.yml", 'w') {|f| f.write ERB.new(IO.read("configs/databases/sqlite3.yml"), nil, '-').result(binding)} cp "configs/routes.rb", "#{PKG_DESTINATION}/config/routes.rb" diff --git a/railties/doc/guides/source/active_record_basics.txt b/railties/doc/guides/source/active_record_basics.txt index 11a7bdb302..15fc544f25 100644 --- a/railties/doc/guides/source/active_record_basics.txt +++ b/railties/doc/guides/source/active_record_basics.txt @@ -178,4 +178,4 @@ Rails has a reputation of being a zero-config framework which means that it aim * (6) before_create * (-) create * (7) after_create - * (8) after_save
\ No newline at end of file + * (8) after_save diff --git a/railties/html/javascripts/prototype.js b/railties/html/javascripts/prototype.js index 2c70b8a7e8..dfe8ab4e13 100644 --- a/railties/html/javascripts/prototype.js +++ b/railties/html/javascripts/prototype.js @@ -1,4 +1,4 @@ -/* Prototype JavaScript framework, version 1.6.0.2 +/* Prototype JavaScript framework, version 1.6.0.3 * (c) 2005-2008 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. @@ -7,23 +7,26 @@ *--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.6.0.2', + Version: '1.6.0.3', Browser: { - IE: !!(window.attachEvent && !window.opera), - Opera: !!window.opera, + IE: !!(window.attachEvent && + navigator.userAgent.indexOf('Opera') === -1), + Opera: navigator.userAgent.indexOf('Opera') > -1, WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, - Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && + navigator.userAgent.indexOf('KHTML') === -1, MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) }, BrowserFeatures: { XPath: !!document.evaluate, + SelectorsAPI: !!document.querySelector, ElementExtensions: !!window.HTMLElement, SpecificElementExtensions: - document.createElement('div').__proto__ && - document.createElement('div').__proto__ !== - document.createElement('form').__proto__ + document.createElement('div')['__proto__'] && + document.createElement('div')['__proto__'] !== + document.createElement('form')['__proto__'] }, ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>', @@ -83,12 +86,13 @@ Class.Methods = { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && value.argumentNames().first() == "$super") { - var method = value, value = Object.extend((function(m) { + var method = value; + value = (function(m) { return function() { return ancestor[m].apply(this, arguments) }; - })(property).wrap(method), { - valueOf: function() { return method }, - toString: function() { return method.toString() } - }); + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); } this.prototype[property] = value; } @@ -167,7 +171,7 @@ Object.extend(Object, { }, isElement: function(object) { - return object && object.nodeType == 1; + return !!(object && object.nodeType == 1); }, isArray: function(object) { @@ -198,7 +202,8 @@ Object.extend(Object, { Object.extend(Function.prototype, { argumentNames: function() { - var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1] + .replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; }, @@ -232,6 +237,11 @@ Object.extend(Function.prototype, { }, timeout); }, + defer: function() { + var args = [0.01].concat($A(arguments)); + return this.delay.apply(this, args); + }, + wrap: function(wrapper) { var __method = this; return function() { @@ -248,8 +258,6 @@ Object.extend(Function.prototype, { } }); -Function.prototype.defer = Function.prototype.delay.curry(0.01); - Date.prototype.toJSON = function() { return '"' + this.getUTCFullYear() + '-' + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + @@ -530,7 +538,7 @@ if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.proto return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }, unescapeHTML: function() { - return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); } }); @@ -547,7 +555,7 @@ Object.extend(String.prototype.escapeHTML, { text: document.createTextNode('') }); -with (String.prototype.escapeHTML) div.appendChild(text); +String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text); var Template = Class.create({ initialize: function(template, pattern) { @@ -589,10 +597,9 @@ var $break = { }; var Enumerable = { each: function(iterator, context) { var index = 0; - iterator = iterator.bind(context); try { this._each(function(value) { - iterator(value, index++); + iterator.call(context, value, index++); }); } catch (e) { if (e != $break) throw e; @@ -601,47 +608,46 @@ var Enumerable = { }, eachSlice: function(number, iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; while ((index += number) < array.length) slices.push(array.slice(index, index+number)); return slices.collect(iterator, context); }, all: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result = true; this.each(function(value, index) { - result = result && !!iterator(value, index); + result = result && !!iterator.call(context, value, index); if (!result) throw $break; }); return result; }, any: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result = false; this.each(function(value, index) { - if (result = !!iterator(value, index)) + if (result = !!iterator.call(context, value, index)) throw $break; }); return result; }, collect: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var results = []; this.each(function(value, index) { - results.push(iterator(value, index)); + results.push(iterator.call(context, value, index)); }); return results; }, detect: function(iterator, context) { - iterator = iterator.bind(context); var result; this.each(function(value, index) { - if (iterator(value, index)) { + if (iterator.call(context, value, index)) { result = value; throw $break; } @@ -650,17 +656,16 @@ var Enumerable = { }, findAll: function(iterator, context) { - iterator = iterator.bind(context); var results = []; this.each(function(value, index) { - if (iterator(value, index)) + if (iterator.call(context, value, index)) results.push(value); }); return results; }, grep: function(filter, iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var results = []; if (Object.isString(filter)) @@ -668,7 +673,7 @@ var Enumerable = { this.each(function(value, index) { if (filter.match(value)) - results.push(iterator(value, index)); + results.push(iterator.call(context, value, index)); }); return results; }, @@ -696,9 +701,8 @@ var Enumerable = { }, inject: function(memo, iterator, context) { - iterator = iterator.bind(context); this.each(function(value, index) { - memo = iterator(memo, value, index); + memo = iterator.call(context, memo, value, index); }); return memo; }, @@ -711,10 +715,10 @@ var Enumerable = { }, max: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result; this.each(function(value, index) { - value = iterator(value, index); + value = iterator.call(context, value, index); if (result == null || value >= result) result = value; }); @@ -722,10 +726,10 @@ var Enumerable = { }, min: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result; this.each(function(value, index) { - value = iterator(value, index); + value = iterator.call(context, value, index); if (result == null || value < result) result = value; }); @@ -733,10 +737,10 @@ var Enumerable = { }, partition: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var trues = [], falses = []; this.each(function(value, index) { - (iterator(value, index) ? + (iterator.call(context, value, index) ? trues : falses).push(value); }); return [trues, falses]; @@ -751,19 +755,20 @@ var Enumerable = { }, reject: function(iterator, context) { - iterator = iterator.bind(context); var results = []; this.each(function(value, index) { - if (!iterator(value, index)) + if (!iterator.call(context, value, index)) results.push(value); }); return results; }, sortBy: function(iterator, context) { - iterator = iterator.bind(context); return this.map(function(value, index) { - return {value: value, criteria: iterator(value, index)}; + return { + value: value, + criteria: iterator.call(context, value, index) + }; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; @@ -815,8 +820,12 @@ function $A(iterable) { if (Prototype.Browser.WebKit) { $A = function(iterable) { if (!iterable) return []; - if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && - iterable.toArray) return iterable.toArray(); + // In Safari, only use the `toArray` method if it's not a NodeList. + // A NodeList is a function, has an function `item` property, and a numeric + // `length` property. Adapted from Google Doctype. + if (!(typeof iterable === 'function' && typeof iterable.length === + 'number' && typeof iterable.item === 'function') && iterable.toArray) + return iterable.toArray(); var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; @@ -963,8 +972,8 @@ Object.extend(Number.prototype, { return this + 1; }, - times: function(iterator) { - $R(0, this, true).each(iterator); + times: function(iterator, context) { + $R(0, this, true).each(iterator, context); return this; }, @@ -1011,7 +1020,9 @@ var Hash = Class.create(Enumerable, (function() { }, get: function(key) { - return this._object[key]; + // simulating poorly supported hasOwnProperty + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; }, unset: function(key) { @@ -1051,14 +1062,14 @@ var Hash = Class.create(Enumerable, (function() { }, toQueryString: function() { - return this.map(function(pair) { + return this.inject([], function(results, pair) { var key = encodeURIComponent(pair.key), values = pair.value; if (values && typeof values == 'object') { if (Object.isArray(values)) - return values.map(toQueryPair.curry(key)).join('&'); - } - return toQueryPair(key, values); + return results.concat(values.map(toQueryPair.curry(key))); + } else results.push(toQueryPair(key, values)); + return results; }).join('&'); }, @@ -1558,6 +1569,7 @@ if (!Node.ELEMENT_NODE) { return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); }; Object.extend(this.Element, element || { }); + if (element) this.Element.prototype = element.prototype; }).call(window); Element.cache = { }; @@ -1574,12 +1586,14 @@ Element.Methods = { }, hide: function(element) { - $(element).style.display = 'none'; + element = $(element); + element.style.display = 'none'; return element; }, show: function(element) { - $(element).style.display = ''; + element = $(element); + element.style.display = ''; return element; }, @@ -1733,7 +1747,7 @@ Element.Methods = { element = $(element); if (arguments.length == 1) return element.firstDescendant(); return Object.isNumber(expression) ? element.descendants()[expression] : - element.select(expression)[index || 0]; + Element.select(element, expression)[index || 0]; }, previous: function(element, expression, index) { @@ -1863,24 +1877,16 @@ Element.Methods = { descendantOf: function(element, ancestor) { element = $(element), ancestor = $(ancestor); - var originalAncestor = ancestor; if (element.compareDocumentPosition) return (element.compareDocumentPosition(ancestor) & 8) === 8; - if (element.sourceIndex && !Prototype.Browser.Opera) { - var e = element.sourceIndex, a = ancestor.sourceIndex, - nextAncestor = ancestor.nextSibling; - if (!nextAncestor) { - do { ancestor = ancestor.parentNode; } - while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); - } - if (nextAncestor && nextAncestor.sourceIndex) - return (e > a && e < nextAncestor.sourceIndex); - } + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; while (element = element.parentNode) - if (element == originalAncestor) return true; + if (element == ancestor) return true; + return false; }, @@ -1895,7 +1901,7 @@ Element.Methods = { element = $(element); style = style == 'float' ? 'cssFloat' : style.camelize(); var value = element.style[style]; - if (!value) { + if (!value || value == 'auto') { var css = document.defaultView.getComputedStyle(element, null); value = css ? css[style] : null; } @@ -1934,7 +1940,7 @@ Element.Methods = { getDimensions: function(element) { element = $(element); - var display = $(element).getStyle('display'); + var display = element.getStyle('display'); if (display != 'none' && display != null) // Safari bug return {width: element.offsetWidth, height: element.offsetHeight}; @@ -1963,7 +1969,7 @@ Element.Methods = { element.style.position = 'relative'; // Opera returns the offset relative to the positioning context, when an // element is position relative but top and left have not been defined - if (window.opera) { + if (Prototype.Browser.Opera) { element.style.top = 0; element.style.left = 0; } @@ -2018,7 +2024,7 @@ Element.Methods = { valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { - if (element.tagName == 'BODY') break; + if (element.tagName.toUpperCase() == 'BODY') break; var p = Element.getStyle(element, 'position'); if (p !== 'static') break; } @@ -2028,7 +2034,7 @@ Element.Methods = { absolutize: function(element) { element = $(element); - if (element.getStyle('position') == 'absolute') return; + if (element.getStyle('position') == 'absolute') return element; // Position.prepare(); // To be done manually by Scripty when it needs it. var offsets = element.positionedOffset(); @@ -2052,7 +2058,7 @@ Element.Methods = { relativize: function(element) { element = $(element); - if (element.getStyle('position') == 'relative') return; + if (element.getStyle('position') == 'relative') return element; // Position.prepare(); // To be done manually by Scripty when it needs it. element.style.position = 'relative'; @@ -2103,7 +2109,7 @@ Element.Methods = { element = forElement; do { - if (!Prototype.Browser.Opera || element.tagName == 'BODY') { + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } @@ -2218,6 +2224,9 @@ else if (Prototype.Browser.IE) { Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( function(proceed, element) { element = $(element); + // IE throws an error if element is not in document + try { element.offsetParent } + catch(e) { return $(document.body) } var position = element.getStyle('position'); if (position !== 'static') return proceed(element); element.setStyle({ position: 'relative' }); @@ -2231,6 +2240,8 @@ else if (Prototype.Browser.IE) { Element.Methods[method] = Element.Methods[method].wrap( function(proceed, element) { element = $(element); + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } var position = element.getStyle('position'); if (position !== 'static') return proceed(element); // Trigger hasLayout on the offset parent so that IE6 reports @@ -2246,6 +2257,14 @@ else if (Prototype.Browser.IE) { ); }); + Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( + function(proceed, element) { + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + return proceed(element); + } + ); + Element.Methods.getStyle = function(element, style) { element = $(element); style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); @@ -2337,7 +2356,7 @@ else if (Prototype.Browser.IE) { Element._attributeTranslations.has = {}; $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + - 'encType maxLength readOnly longDesc').each(function(attr) { + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; Element._attributeTranslations.has[attr.toLowerCase()] = attr; }); @@ -2390,7 +2409,7 @@ else if (Prototype.Browser.WebKit) { (value < 0.00001) ? 0 : value; if (value == 1) - if(element.tagName == 'IMG' && element.width) { + if(element.tagName.toUpperCase() == 'IMG' && element.width) { element.width++; element.width--; } else try { var n = document.createTextNode(' '); @@ -2521,7 +2540,7 @@ Element.Methods.Simulated = { hasAttribute: function(element, attribute) { attribute = Element._attributeTranslations.has[attribute] || attribute; var node = $(element).getAttributeNode(attribute); - return node && node.specified; + return !!(node && node.specified); } }; @@ -2530,9 +2549,9 @@ Element.Methods.ByTag = { }; Object.extend(Element, Element.Methods); if (!Prototype.BrowserFeatures.ElementExtensions && - document.createElement('div').__proto__) { + document.createElement('div')['__proto__']) { window.HTMLElement = { }; - window.HTMLElement.prototype = document.createElement('div').__proto__; + window.HTMLElement.prototype = document.createElement('div')['__proto__']; Prototype.BrowserFeatures.ElementExtensions = true; } @@ -2547,7 +2566,7 @@ Element.extend = (function() { element.nodeType != 1 || element == window) return element; var methods = Object.clone(Methods), - tagName = element.tagName, property, value; + tagName = element.tagName.toUpperCase(), property, value; // extend methods for specific tags if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); @@ -2643,7 +2662,7 @@ Element.addMethods = function(methods) { if (window[klass]) return window[klass]; window[klass] = { }; - window[klass].prototype = document.createElement(tagName).__proto__; + window[klass].prototype = document.createElement(tagName)['__proto__']; return window[klass]; } @@ -2669,12 +2688,18 @@ Element.addMethods = function(methods) { document.viewport = { getDimensions: function() { - var dimensions = { }; - var B = Prototype.Browser; + var dimensions = { }, B = Prototype.Browser; $w('width height').each(function(d) { var D = d.capitalize(); - dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] : - (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D]; + if (B.WebKit && !document.evaluate) { + // Safari <3.0 needs self.innerWidth/Height + dimensions[d] = self['inner' + D]; + } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) { + // Opera <9.5 needs document.body.clientWidth/Height + dimensions[d] = document.body['client' + D] + } else { + dimensions[d] = document.documentElement['client' + D]; + } }); return dimensions; }, @@ -2693,14 +2718,24 @@ document.viewport = { window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); } }; -/* Portions of the Selector class are derived from Jack Slocum’s DomQuery, +/* Portions of the Selector class are derived from Jack Slocum's DomQuery, * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style * license. Please see http://www.yui-ext.com/ for more information. */ var Selector = Class.create({ initialize: function(expression) { this.expression = expression.strip(); - this.compileMatcher(); + + if (this.shouldUseSelectorsAPI()) { + this.mode = 'selectorsAPI'; + } else if (this.shouldUseXPath()) { + this.mode = 'xpath'; + this.compileXPathMatcher(); + } else { + this.mode = "normal"; + this.compileMatcher(); + } + }, shouldUseXPath: function() { @@ -2715,16 +2750,29 @@ var Selector = Class.create({ // XPath can't do namespaced attributes, nor can it read // the "checked" property from DOM nodes - if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) + if ((/(\[[\w-]*?:|:checked)/).test(e)) return false; return true; }, - compileMatcher: function() { - if (this.shouldUseXPath()) - return this.compileXPathMatcher(); + shouldUseSelectorsAPI: function() { + if (!Prototype.BrowserFeatures.SelectorsAPI) return false; + + if (!Selector._div) Selector._div = new Element('div'); + + // Make sure the browser treats the selector as valid. Test on an + // isolated element to minimize cost of this check. + try { + Selector._div.querySelector(this.expression); + } catch(e) { + return false; + } + + return true; + }, + compileMatcher: function() { var e = this.expression, ps = Selector.patterns, h = Selector.handlers, c = Selector.criteria, le, p, m; @@ -2742,7 +2790,7 @@ var Selector = Class.create({ p = ps[i]; if (m = e.match(p)) { this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : - new Template(c[i]).evaluate(m)); + new Template(c[i]).evaluate(m)); e = e.replace(m[0], ''); break; } @@ -2781,8 +2829,27 @@ var Selector = Class.create({ findElements: function(root) { root = root || document; - if (this.xpath) return document._getElementsByXPath(this.xpath, root); - return this.matcher(root); + var e = this.expression, results; + + switch (this.mode) { + case 'selectorsAPI': + // querySelectorAll queries document-wide, then filters to descendants + // of the context element. That's not what we want. + // Add an explicit context to the selector if necessary. + if (root !== document) { + var oldId = root.id, id = $(root).identify(); + e = "#" + id + " " + e; + } + + results = $A(root.querySelectorAll(e)).map(Element.extend); + root.id = oldId; + + return results; + case 'xpath': + return document._getElementsByXPath(this.xpath, root); + default: + return this.matcher(root); + } }, match: function(element) { @@ -2873,10 +2940,10 @@ Object.extend(Selector, { 'first-child': '[not(preceding-sibling::*)]', 'last-child': '[not(following-sibling::*)]', 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', - 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", + 'empty': "[count(*) = 0 and (count(text()) = 0)]", 'checked': "[@checked]", - 'disabled': "[@disabled]", - 'enabled': "[not(@disabled)]", + 'disabled': "[(@disabled) and (@type!='hidden')]", + 'enabled': "[not(@disabled) and (@type!='hidden')]", 'not': function(m) { var e = m[6], p = Selector.patterns, x = Selector.xpath, le, v; @@ -2968,7 +3035,7 @@ Object.extend(Selector, { className: /^\.([\w\-\*]+)(\b|$)/, pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, - attrPresence: /^\[([\w]+)\]/, + attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/, attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }, @@ -3081,7 +3148,7 @@ Object.extend(Selector, { nextElementSibling: function(node) { while (node = node.nextSibling) - if (node.nodeType == 1) return node; + if (node.nodeType == 1) return node; return null; }, @@ -3270,7 +3337,7 @@ Object.extend(Selector, { 'empty': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { // IE treats comments as element nodes - if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; + if (node.tagName == '!' || node.firstChild) continue; results.push(node); } return results; @@ -3288,7 +3355,8 @@ Object.extend(Selector, { 'enabled': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node.disabled) results.push(node); + if (!node.disabled && (!node.type || node.type !== 'hidden')) + results.push(node); return results; }, @@ -3308,11 +3376,14 @@ Object.extend(Selector, { operators: { '=': function(nv, v) { return nv == v; }, '!=': function(nv, v) { return nv != v; }, - '^=': function(nv, v) { return nv.startsWith(v); }, + '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, + '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, + '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, '$=': function(nv, v) { return nv.endsWith(v); }, '*=': function(nv, v) { return nv.include(v); }, '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, - '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } + '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + + '-').include('-' + (v || "").toUpperCase() + '-'); } }, split: function(expression) { @@ -3386,7 +3457,7 @@ var Form = { var data = elements.inject({ }, function(result, element) { if (!element.disabled && element.name) { key = element.name; value = $(element).getValue(); - if (value != null && (element.type != 'submit' || (!submitted && + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && submit !== false && (!submit || key == submit) && (submitted = true)))) { if (key in result) { // a key is already present; construct an array of values @@ -3547,7 +3618,6 @@ Form.Element.Methods = { disable: function(element) { element = $(element); - element.blur(); element.disabled = true; return element; }, @@ -3587,22 +3657,22 @@ Form.Element.Serializers = { else element.value = value; }, - select: function(element, index) { - if (Object.isUndefined(index)) + select: function(element, value) { + if (Object.isUndefined(value)) return this[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element); else { - var opt, value, single = !Object.isArray(index); + var opt, currentValue, single = !Object.isArray(value); for (var i = 0, length = element.length; i < length; i++) { opt = element.options[i]; - value = this.optionValue(opt); + currentValue = this.optionValue(opt); if (single) { - if (value == index) { + if (currentValue == value) { opt.selected = true; return; } } - else opt.selected = index.include(value); + else opt.selected = value.include(currentValue); } } }, @@ -3773,8 +3843,23 @@ Event.Methods = (function() { isRightClick: function(event) { return isButton(event, 2) }, element: function(event) { - var node = Event.extend(event).target; - return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); + event = Event.extend(event); + + var node = event.target, + type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + // Firefox screws up the "click" event when moving between radio buttons + // via arrow keys. It also screws up the "load" and "error" events on images, + // reporting the document as the target instead of the original image. + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + if (node.nodeType == Node.TEXT_NODE) node = node.parentNode; + return Element.extend(node); }, findElement: function(event, expression) { @@ -3785,11 +3870,15 @@ Event.Methods = (function() { }, pointer: function(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0, scrollTop: 0 }; return { x: event.pageX || (event.clientX + - (document.documentElement.scrollLeft || document.body.scrollLeft)), + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)), y: event.pageY || (event.clientY + - (document.documentElement.scrollTop || document.body.scrollTop)) + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)) }; }, @@ -3834,7 +3923,7 @@ Event.extend = (function() { }; } else { - Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; + Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__']; Object.extend(Event.prototype, methods); return Prototype.K; } @@ -3899,10 +3988,20 @@ Object.extend(Event, (function() { cache[id][eventName] = null; } + + // Internet Explorer needs to remove event handlers on page unload + // in order to avoid memory leaks. if (window.attachEvent) { window.attachEvent("onunload", destroyCache); } + // Safari has a dummy event handler on page unload so that it won't + // use its bfcache. Safari <= 3.1 has an issue with restoring the "document" + // object when page is returned to via the back button using its bfcache. + if (Prototype.Browser.WebKit) { + window.addEventListener('unload', Prototype.emptyFunction, false); + } + return { observe: function(element, eventName, handler) { element = $(element); diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb index 6500b2d309..e3a0e3bad1 100644 --- a/railties/lib/initializer.rb +++ b/railties/lib/initializer.rb @@ -267,6 +267,7 @@ module Rails end def add_gem_load_paths + Rails::GemDependency.add_frozen_gem_path unless @configuration.gems.empty? require "rubygems" @configuration.gems.each { |gem| gem.add_load_paths } diff --git a/railties/lib/rails/gem_dependency.rb b/railties/lib/rails/gem_dependency.rb index 46d5fd3a47..cd280ac023 100644 --- a/railties/lib/rails/gem_dependency.rb +++ b/railties/lib/rails/gem_dependency.rb @@ -18,11 +18,14 @@ module Rails def self.add_frozen_gem_path @@paths_loaded ||= begin - Gem.source_index = Rails::VendorGemSourceIndex.new(Gem.source_index) + source_index = Rails::VendorGemSourceIndex.new(Gem.source_index) + Gem.clear_paths + Gem.source_index = source_index # loaded before us - we can't change them, so mark them Gem.loaded_specs.each do |name, spec| @@framework_gems[name] = spec end + true end end @@ -170,19 +173,27 @@ module Rails exact_dep = Gem::Dependency.new(name, "= #{specification.version}") matches = real_gems.search(exact_dep) installed_spec = matches.first - if installed_spec - # we have a real copy - # get a fresh spec - matches should only have one element - # note that there is no reliable method to check that the loaded - # spec is the same as the copy from real_gems - Gem.activate changes - # some of the fields - real_spec = Gem::Specification.load(matches.first.loaded_from) - write_spec(directory, real_spec) - puts "Reloaded specification for #{name} from installed gems." + if File.exist?(File.dirname(spec_filename(directory))) + if installed_spec + # we have a real copy + # get a fresh spec - matches should only have one element + # note that there is no reliable method to check that the loaded + # spec is the same as the copy from real_gems - Gem.activate changes + # some of the fields + real_spec = Gem::Specification.load(matches.first.loaded_from) + write_spec(directory, real_spec) + puts "Reloaded specification for #{name} from installed gems." + else + # the gem isn't installed locally - write out our current specs + write_spec(directory, specification) + puts "Gem #{name} not loaded locally - writing out current spec." + end else - # the gem isn't installed locally - write out our current specs - write_spec(directory, specification) - puts "Gem #{name} not loaded locally - writing out current spec." + if framework_gem? + puts "Gem directory for #{name} not found - check if it's loading before rails." + else + puts "Something bad is going on - gem directory not found for #{name}." + end end end diff --git a/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb b/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb index 615c575e6e..6826998252 100644 --- a/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb +++ b/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb @@ -16,16 +16,16 @@ class PluginGenerator < Rails::Generator::NamedBase m.directory "#{plugin_path}/tasks" m.directory "#{plugin_path}/test" - m.template 'README', "#{plugin_path}/README" - m.template 'MIT-LICENSE', "#{plugin_path}/MIT-LICENSE" - m.template 'Rakefile', "#{plugin_path}/Rakefile" - m.template 'init.rb', "#{plugin_path}/init.rb" - m.template 'install.rb', "#{plugin_path}/install.rb" - m.template 'uninstall.rb', "#{plugin_path}/uninstall.rb" - m.template 'plugin.rb', "#{plugin_path}/lib/#{file_name}.rb" - m.template 'tasks.rake', "#{plugin_path}/tasks/#{file_name}_tasks.rake" - m.template 'unit_test.rb', "#{plugin_path}/test/#{file_name}_test.rb" - + m.template 'README', "#{plugin_path}/README" + m.template 'MIT-LICENSE', "#{plugin_path}/MIT-LICENSE" + m.template 'Rakefile', "#{plugin_path}/Rakefile" + m.template 'init.rb', "#{plugin_path}/init.rb" + m.template 'install.rb', "#{plugin_path}/install.rb" + m.template 'uninstall.rb', "#{plugin_path}/uninstall.rb" + m.template 'plugin.rb', "#{plugin_path}/lib/#{file_name}.rb" + m.template 'tasks.rake', "#{plugin_path}/tasks/#{file_name}_tasks.rake" + m.template 'unit_test.rb', "#{plugin_path}/test/#{file_name}_test.rb" + m.template 'test_helper.rb', "#{plugin_path}/test/test_helper.rb" if @with_generator m.directory "#{plugin_path}/generators" m.directory "#{plugin_path}/generators/#{file_name}" diff --git a/railties/lib/rails_generator/generators/components/plugin/templates/Rakefile b/railties/lib/rails_generator/generators/components/plugin/templates/Rakefile index 1824fb10fe..85e8ff1834 100644 --- a/railties/lib/rails_generator/generators/components/plugin/templates/Rakefile +++ b/railties/lib/rails_generator/generators/components/plugin/templates/Rakefile @@ -8,6 +8,7 @@ task :default => :test desc 'Test the <%= file_name %> plugin.' Rake::TestTask.new(:test) do |t| t.libs << 'lib' + t.libs << 'test' t.pattern = 'test/**/*_test.rb' t.verbose = true end diff --git a/railties/lib/rails_generator/generators/components/plugin/templates/test_helper.rb b/railties/lib/rails_generator/generators/components/plugin/templates/test_helper.rb new file mode 100644 index 0000000000..cf148b8b47 --- /dev/null +++ b/railties/lib/rails_generator/generators/components/plugin/templates/test_helper.rb @@ -0,0 +1,3 @@ +require 'rubygems' +require 'active_support' +require 'active_support/test_case'
\ No newline at end of file diff --git a/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb b/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb index 6ede6ef1d2..3e0bc29d3a 100644 --- a/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb +++ b/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb @@ -1,6 +1,6 @@ -require 'test/unit' +require 'test_helper' -class <%= class_name %>Test < Test::Unit::TestCase +class <%= class_name %>Test < ActiveSupport::TestCase # Replace this with your real tests. test "the truth" do assert true diff --git a/railties/lib/tasks/gems.rake b/railties/lib/tasks/gems.rake index e2cb4b9577..754e3ba5c9 100644 --- a/railties/lib/tasks/gems.rake +++ b/railties/lib/tasks/gems.rake @@ -6,10 +6,11 @@ task :gems => 'gems:base' do puts puts "I = Installed" puts "F = Frozen" + puts "R = Framework (loaded before rails starts)" end def print_gem_status(gem, indent=1) - code = gem.loaded? ? (gem.frozen? ? "F" : "I") : " " + code = gem.loaded? ? (gem.frozen? ? (gem.framework_gem? ? "R" : "F") : "I") : " " puts " "*(indent-1)+" - [#{code}] #{gem.name} #{gem.requirement.to_s}" gem.dependencies.each { |g| print_gem_status(g, indent+1)} if gem.loaded? end diff --git a/railties/test/gem_dependency_test.rb b/railties/test/gem_dependency_test.rb index 5f026b2616..4f9e824c9f 100644 --- a/railties/test/gem_dependency_test.rb +++ b/railties/test/gem_dependency_test.rb @@ -1,12 +1,11 @@ -require 'lib/rails/vendor_gem_source_index' -Rails::VendorGemSourceIndex.silence_spec_warnings = true - require 'plugin_test_helper' class Rails::GemDependency public :install_command, :unpack_command end +Rails::VendorGemSourceIndex.silence_spec_warnings = true + uses_mocha "Plugin Tests" do class GemDependencyTest < Test::Unit::TestCase def setup |