From c358389f25c12c9cddc824eeeab15f086e34582b Mon Sep 17 00:00:00 2001
From: Jacques Distler <%= post.title %> Good seeing you! Parameters: <%=h request_dump == "{}" ? "None" : request_dump %>
\nline #2"
end
@@ -273,8 +245,6 @@ class TestMailer < ActionMailer::Base
end
end
-TestMailer.template_root = File.dirname(__FILE__) + "/fixtures"
-
class ActionMailerTest < Test::Unit::TestCase
include ActionMailer::Quoting
@@ -284,6 +254,7 @@ class ActionMailerTest < Test::Unit::TestCase
def new_mail( charset="utf-8" )
mail = TMail::Mail.new
+ mail.mime_version = "1.0"
if charset
mail.set_content_type "text", "plain", { "charset" => charset }
end
@@ -306,6 +277,7 @@ class ActionMailerTest < Test::Unit::TestCase
assert_equal "multipart/mixed", created.content_type
assert_equal "multipart/alternative", created.parts.first.content_type
+ assert_equal "bar", created.parts.first.header['foo'].to_s
assert_equal "text/plain", created.parts.first.parts.first.content_type
assert_equal "text/html", created.parts.first.parts[1].content_type
assert_equal "application/octet-stream", created.parts[1].content_type
@@ -324,7 +296,6 @@ class ActionMailerTest < Test::Unit::TestCase
expected.body = "Hello there, \n\nMr. #{@recipient}"
expected.from = "system@loudthinking.com"
expected.date = Time.local(2004, 12, 12)
- expected.mime_version = nil
created = nil
assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) }
@@ -816,17 +787,30 @@ EOF
assert_match %r{format=flowed}, mail['content-type'].to_s
assert_match %r{charset=utf-8}, mail['content-type'].to_s
end
+
+ def test_deprecated_server_settings
+ old_smtp_settings = ActionMailer::Base.smtp_settings
+ assert_deprecated do
+ ActionMailer::Base.server_settings
+ end
+ assert_deprecated do
+ ActionMailer::Base.server_settings={}
+ assert_equal Hash.new, ActionMailer::Base.smtp_settings
+ end
+ ensure
+ ActionMailer::Base.smtp_settings=old_smtp_settings
+ end
end
-class InheritableTemplateRootTest < Test::Unit::TestCase
- def test_attr
- expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
- assert_equal expected, FunkyPathMailer.template_root
-
- sub = Class.new(FunkyPathMailer)
- sub.template_root = 'test/path'
-
- assert_equal 'test/path', sub.template_root
- assert_equal expected, FunkyPathMailer.template_root
- end
-end
+class InheritableTemplateRootTest < Test::Unit::TestCase
+ def test_attr
+ expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
+ assert_equal expected, FunkyPathMailer.template_root
+
+ sub = Class.new(FunkyPathMailer)
+ sub.template_root = 'test/path'
+
+ assert_equal 'test/path', sub.template_root
+ assert_equal expected, FunkyPathMailer.template_root
+ end
+end
diff --git a/vendor/rails/actionmailer/test/quoting_test.rb b/vendor/rails/actionmailer/test/quoting_test.rb
index 6291cd3d..77bd769b 100644
--- a/vendor/rails/actionmailer/test/quoting_test.rb
+++ b/vendor/rails/actionmailer/test/quoting_test.rb
@@ -1,7 +1,4 @@
-$:.unshift(File.dirname(__FILE__) + "/../lib/")
-$:.unshift(File.dirname(__FILE__) + "/../lib/action_mailer/vendor")
-
-require 'test/unit'
+require "#{File.dirname(__FILE__)}/abstract_unit"
require 'tmail'
require 'tempfile'
@@ -22,6 +19,18 @@ class QuotingTest < Test::Unit::TestCase
assert_equal unquoted, original
end
+ # test an email that has been created using \r\n newlines, instead of
+ # \n newlines.
+ def test_email_quoted_with_0d0a
+ mail = TMail::Mail.parse(IO.read("#{File.dirname(__FILE__)}/fixtures/raw_email_quoted_with_0d0a"))
+ assert_match %r{Elapsed time}, mail.body
+ end
+
+ def test_email_with_partially_quoted_subject
+ mail = TMail::Mail.parse(IO.read("#{File.dirname(__FILE__)}/fixtures/raw_email_with_partially_quoted_subject"))
+ assert_equal "Re: Test: \"\346\274\242\345\255\227\" mid \"\346\274\242\345\255\227\" tail", mail.subject
+ end
+
private
# This whole thing *could* be much simpler, but I don't think Tempfile,
@@ -40,7 +49,7 @@ class QuotingTest < Test::Unit::TestCase
end
system("ruby #{test_name} > #{res_name}") or raise "could not run test in sandbox"
- File.read(res_name)
+ File.read(res_name).chomp
ensure
File.delete(test_name) rescue nil
File.delete(res_name) rescue nil
diff --git a/vendor/rails/actionmailer/test/tmail_test.rb b/vendor/rails/actionmailer/test/tmail_test.rb
index 3930c7d3..7d83a68a 100644
--- a/vendor/rails/actionmailer/test/tmail_test.rb
+++ b/vendor/rails/actionmailer/test/tmail_test.rb
@@ -1,8 +1,4 @@
-$:.unshift(File.dirname(__FILE__) + "/../lib/")
-$:.unshift File.dirname(__FILE__) + "/fixtures/helpers"
-
-require 'test/unit'
-require 'action_mailer'
+require "#{File.dirname(__FILE__)}/abstract_unit"
class TMailMailTest < Test::Unit::TestCase
def test_body
diff --git a/vendor/rails/actionpack/CHANGELOG b/vendor/rails/actionpack/CHANGELOG
index be0d4064..9480aecd 100644
--- a/vendor/rails/actionpack/CHANGELOG
+++ b/vendor/rails/actionpack/CHANGELOG
@@ -1,3 +1,557 @@
+*1.13.2* (February 5th, 2007)
+
+* Add much-needed html-scanner tests. Fixed CDATA parsing bug. [Rick]
+
+* improve error message for Routing for named routes. [Rob Sanheim]
+
+* Added enhanced docs to routing assertions. [Rob Sanheim]
+
+* fix form_for example in ActionController::Resources documentation. [gnarg]
+
+* Add singleton resources from trunk [Rick Olson]
+
+* TestSession supports indifferent access so session['foo'] == session[:foo] in your tests. #7372 [julik, jean.helou]
+
+* select :multiple => true suffixes the attribute name with [] unless already suffixed. #6977 [nik.kakelin, ben, julik]
+
+* Improve routes documentation. #7095 [zackchandler]
+
+* Resource member routes require :id, eliminating the ambiguous overlap with collection routes. #7229 [dkubb]
+
+* Fixed NumberHelper#number_with_delimiter to use "." always for splitting the original number, not the delimiter parameter #7389 [ceefour]
+
+* Autolinking recognizes trailing and embedded . , : ; #7354 [Jarkko Laine]
+
+* Make TextHelper::auto_link recognize URLs with colons in path correctly, fixes #7268. [imajes]
+
+* Improved auto_link to match more valid urls correctly [Tobias Luetke]
+
+
+*1.13.1* (January 18th, 2007)
+
+* Fixed content-type bug in Prototype [sam]
+
+
+*1.13.0* (January 16th, 2007)
+
+* Modernize cookie testing code, and increase coverage (Heckle++) #7101 [Kevin Clark]
+
+* Heckling ActionController::Resources::Resource revealed that set_prefixes didn't break when :name_prefix was munged. #7081 [Kevin Clark]
+
+* Update to Prototype 1.5.0. [Sam Stephenson]
+
+* Allow exempt_from_layout :rhtml. #6742, #7026 [dcmanges, Squeegy]
+
+* Fix parsing of array[] CGI parameters so extra empty values aren't included. #6252 [Nicholas Seckar, aiwilliams, brentrowland]
+
+* link_to_unless_current works with full URLs as well as paths. #6891 [Jarkko Laine, manfred, idrifter]
+
+* Fix HTML::Node to output double quotes instead of single quotes. Closes #6845 [mitreandy]
+
+* Fix no method error with error_messages_on. Closes #6935 [nik.wakelin Koz]
+
+* Slight doc tweak to the ActionView::Helpers::PrototypeHelper#replace docs. Closes #6922 [Steven Bristol]
+
+* Slight doc tweak to #prepend_filter. Closes #6493 [Jeremy Voorhis]
+
+* Add more extensive documentation to the AssetTagHelper. Closes #6452 [Bob Silva]
+
+* Clean up multiple calls to #stringify_keys in TagHelper, add better documentation and testing for TagHelper. Closes #6394 [Bob Silva]
+
+* [DOCS] fix reference to ActionController::Macros::AutoComplete for #text_field_with_auto_complete. Closes #2578 [Jan Prill]
+
+* Make sure html_document is reset between integration test requests. [ctm]
+
+* Set session to an empty hash if :new_session => false and no session cookie or param is present. CGI::Session was raising an unrescued ArgumentError. [Josh Susser]
+
+* Fix assert_redirected_to bug where redirecting from a nested to to a top-level controller incorrectly added the current controller's nesting. Closes #6128. [Rick Olson]
+
+* Ensure render :json => ... skips the layout. #6808 [Josh Peek]
+
+* Silence log_error deprecation warnings from inspecting deprecated instance variables. [Nate Wiger]
+
+* Only cache GET requests with a 200 OK response. #6514, #6743 [RSL, anamba]
+
+* Correctly report which filter halted the chain. #6699 [Martin Emde]
+
+* respond_to recognizes JSON. render :json => @person.to_json automatically sets the content type and takes a :callback option to specify a client-side function to call using the rendered JSON as an argument. #4185 [Scott Raymond, eventualbuddha]
+ # application/json response with body 'Element.show({:name: "David"})'
+ respond_to do |format|
+ format.json { render :json => { :name => "David" }.to_json, :callback => 'Element.show' }
+ end
+
+* Makes :discard_year work without breaking multi-attribute parsing in AR. #1260, #3800 [sean@ardismg.com, jmartin@desertflood.com, stephen@touset.org, Bob Silva]
+
+* Adds html id attribute to date helper elements. #1050, #1382 [mortonda@dgrmm.net, David North, Bob Silva]
+
+* Add :index and @auto_index capability to model driven date/time selects. #847, #2655 [moriq, Doug Fales, Bob Silva]
+
+* Add :order to datetime_select, select_datetime, and select_date. #1427 [Timothee Peignier, patrick@lenz.sh, Bob Silva]
+
+* Added time_select to work with time values in models. Update scaffolding. #2489, #2833 [Justin Palmer, Andre Caum, Bob Silva]
+
+* Added :include_seconds to select_datetime, datetime_select and time_select. #2998 [csn, Bob Silva]
+
+* All date/datetime selects can now accept an array of month names with :use_month_names. Allows for localization. #363 [tomasj, Bob Silva]
+
+* Adds :time_separator to select_time and :date_separator to select_datetime. Preserves BC. #3811 [Bob Silva]
+
+* @response.redirect_url works with 201 Created responses: just return headers['Location'] rather than checking the response status. [Jeremy Kemper]
+
+* Fixed that HEAD should return the proper Content-Length header (that is, actually use @body.size, not just 0) [DHH]
+
+* Added GET-masquarading for HEAD, so request.method will return :get even for HEADs. This will help anyone relying on case request.method to automatically work with HEAD and map.resources will also allow HEADs to all GET actions. Rails automatically throws away the response content in a reply to HEAD, so you don't even need to worry about that. If you, for whatever reason, still need to distinguish between GET and HEAD in some edge case, you can use Request#head? and even Request.headers["REQUEST_METHOD"] for get the "real" answer. Closes #6694 [DHH]
+
+
+*1.13.0 RC1* (r5619, November 22nd, 2006)
+
+* Update Routing to complain when :controller is not specified by a route. Closes #6669. [Nicholas Seckar]
+
+* Ensure render_to_string cleans up after itself when an exception is raised. #6658 [rsanheim]
+
+* Update to Prototype and script.aculo.us [5579]. [Sam Stephenson, Thomas Fuchs]
+
+* simple_format helper doesn't choke on nil. #6644 [jerry426]
+
+* Reuse named route helper module between Routing reloads. Use remove_method to delete named route methods after each load. Since the module is never collected, this fixes a significant memory leak. [Nicholas Seckar]
+
+* Deprecate standalone components. [Jeremy Kemper]
+
+* Always clear model associations from session. #4795 [sd@notso.net, andylien@gmail.com]
+
+* Remove JavaScriptLiteral in favor of ActiveSupport::JSON::Variable. [Sam Stephenson]
+
+* Sync ActionController::StatusCodes::STATUS_CODES with http://www.iana.org/assignments/http-status-codes. #6586 [dkubb]
+
+* Multipart form values may have a content type without being treated as uploaded files if they do not provide a filename. #6401 [Andreas Schwarz, Jeremy Kemper]
+
+* assert_response supports symbolic status codes. #6569 [Kevin Clark]
+ assert_response :ok
+ assert_response :not_found
+ assert_response :forbidden
+
+* Cache parsed query parameters. #6559 [Stefan Kaes]
+
+* Deprecate JavaScriptHelper#update_element_function, which is superseeded by RJS [Thomas Fuchs]
+
+* Fix invalid test fixture exposed by stricter Ruby 1.8.5 multipart parsing. #6524 [Bob Silva]
+
+* Set ActionView::Base.default_form_builder once rather than passing the :builder option to every form or overriding the form helper methods. [Jeremy Kemper]
+
+* Deprecate expire_matched_fragments. Use expire_fragment instead. #6535 [Bob Silva]
+
+* Deprecate start_form_tag and end_form_tag. Use form_tag / '' from now on. [Rick]
+
+* Added block-usage to PrototypeHelper#form_remote_tag, document block-usage of FormTagHelper#form_tag [Rick]
+
+* Add a 0 margin/padding div around the hidden _method input tag that form_tag outputs. [Rick]
+
+* Added block-usage to TagHelper#content_tag [DHH]. Example:
+
+ <% content_tag :div, :class => "strong" %>
+ Hello world!
+ <% end %>
+
+ Will output:
+ . [Rick Olson]
+
+* Add routing tests to assert that RoutingError is raised when conditions aren't met. Closes #6016 [Nathan Witmer]
+
+* Make auto_link parse a greater subset of valid url formats. [Jamis Buck]
+
+* Integration tests: headers beginning with X aren't excluded from the HTTP_ prefix, so X-Requested-With becomes HTTP_X_REQUESTED_WITH as expected. [Mike Clark]
+
+* Switch to using FormEncodedPairParser for parsing request parameters. [Nicholas Seckar, DHH]
+
+* respond_to .html now always renders #{action_name}.rhtml so that registered custom template handlers do not override it in priority. Custom mime types require a block and throw proper error now. [Tobias Luetke]
+
+* Deprecation: test deprecated instance vars in partials. [Jeremy Kemper]
+
+* Add UrlWriter to allow writing urls from Mailers and scripts. [Nicholas Seckar]
+
+* Relax Routing's anchor pattern warning; it was preventing use of [^/] inside restrictions. [Nicholas Seckar]
+
+* Add controller_paths variable to Routing. [Nicholas Seckar]
+
+* Fix assert_redirected_to issue with named routes for module controllers. [Rick Olson]
+
+* Tweak RoutingError message to show option diffs, not just missing named route significant keys. [Rick Olson]
+
+* Invoke method_missing directly on hidden actions. Closes #3030. [Nicholas Seckar]
+
+* Add RoutingError exception when RouteSet fails to generate a path from a Named Route. [Rick Olson]
+
+* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
+
+* Deprecation: check whether instance variables have been monkeyed with before assigning them to deprecation proxies. Raises a RuntimeError if so. [Jeremy Kemper]
+
+* Add support for the param_name parameter to the auto_complete_field helper. #5026 [david.a.williams@gmail.com]
+
+* Deprecation! @params, @session, @flash will be removed after 1.2. Use the corresponding instance methods instead. You'll get printed warnings during tests and logged warnings in dev mode when you access either instance variable directly. [Jeremy Kemper]
+
+* Make Routing noisy when an anchor regexp is assigned to a segment. #5674 [francois.beausoleil@gmail.com]
+
+* Added months and years to the resolution of DateHelper#distance_of_time_in_words, such that "60 days ago" becomes "2 months ago" #5611 [pjhyett@gmail.com]
+
+* Make controller_path available as an instance method. #5724 [jmckible@gmail.com]
+
+* Update query parser to support adjacent hashes. [Nicholas Seckar]
+
+* Make action caching aware of different formats for the same action so that, e.g. foo.xml is cached separately from foo.html. Implicitly set content type when reading in cached content with mime revealing extensions so the entire onous isn't on the webserver. [Marcel Molina Jr.]
+
+* Restrict Request Method hacking with ?_method to POST requests. [Rick Olson]
+
+* Fixed the new_#{resource}_url route and added named route tests for Simply Restful. [Rick Olson]
+
+* Added map.resources from the Simply Restful plugin [DHH]. Examples (the API has changed to use plurals!):
+
+ map.resources :messages
+ map.resources :messages, :comments
+ map.resources :messages, :new => { :preview => :post }
+
+* Fixed that integration simulation of XHRs should set Accept header as well [Edward Frederick]
+
+* TestRequest#reset_session should restore a TestSession, not a hash [Koz]
+
+* Don't search a load-path of '.' for controller files [Jamis Buck]
+
+* Update integration.rb to require test_process explicitly instead of via Dependencies. [Nicholas Seckar]
+
+* Fixed that you can still access the flash after the flash has been reset in reset_session. Closes #5584 [lmarlow@yahoo.com]
+
+* Allow form_for and fields_for to work with indexed form inputs. [Jeremy Kemper, Matt Lyon]
+
+ <% form_for 'post[]', @post do |f| -%>
+ <% end -%>
+
+* Remove leak in development mode by replacing define_method with module_eval. [Nicholas Seckar]
+
+* Provide support for decimal columns to form helpers. Closes #5672. [dave@pragprog.com]
+
+* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com, sebastien@goetzilla.info]
+
+* Reset @html_document between requests so assert_tag works. #4810 [jarkko@jlaine.net, easleydp@gmail.com]
+
+* Integration tests behave well with render_component. #4632 [edward.frederick@revolution.com, dev.rubyonrails@maxdunn.com]
+
+* Added exception handling of missing layouts #5373 [chris@ozmm.org]
+
+* Fixed that real files and symlinks should be treated the same when compiling templates #5438 [zachary@panandscan.com]
+
+* Fixed that the flash should be reset when reset_session is called #5584 [shugo@ruby-lang.org]
+
+* Added special case for "1 Byte" in NumberHelper#number_to_human_size #5593 [murpyh@rubychan.de]
+
+* Fixed proper form-encoded parameter parsing for requests with "Content-Type: application/x-www-form-urlencoded; charset=utf-8" (note the presence of a charset directive) [DHH]
+
+* Add route_name_path method to generate only the path for a named routes. For example, map.person will add person_path. [Nicholas Seckar]
+
+* Avoid naming collision among compiled view methods. [Jeremy Kemper]
+
+* Fix CGI extensions when they expect string but get nil in Windows. Closes #5276 [mislav@nippur.irb.hr]
+
+* Determine the correct template_root for deeply nested components. #2841 [s.brink@web.de]
+
+* Fix that routes with *path segments in the recall can generate URLs. [Rick]
+
+* Fix strip_links so that it doesn't hang on multiline tags [Jamis Buck]
+
+* Remove problematic control chars in rescue template. #5316 [Stefan Kaes]
+
+* Make sure passed routing options are not mutated by routing code. #5314 [Blair Zajac]
+
+* Make sure changing the controller from foo/bar to bing/bang does not change relative to foo. [Jamis Buck]
+
+* Escape the path before routing recognition. #3671
+
+* Make sure :id and friends are unescaped properly. #5275 [me@julik.nl]
+
+* Rewind readable CGI params so others may reread them (such as CGI::Session when passing the session id in a multipart form). #210 [mklame@atxeu.com, matthew@walker.wattle.id.au]
+
+* Added Mime::TEXT (text/plain) and Mime::ICS (text/calendar) as new default types [DHH]
+
+* Added Mime::Type.register(string, symbol, synonyms = []) for adding new custom mime types [DHH]. Example: Mime::Type.register("image/gif", :gif)
+
+* Added support for Mime objects in render :content_type option [DHH]. Example: render :text => some_atom, :content_type => Mime::ATOM
+
+* Add :status option to send_data and send_file. Defaults to '200 OK'. #5243 [Manfred Stienstra
Hello world
@@ -155,7 +155,7 @@ A short rundown of the major features:
map.connect 'clients/:client_name/:project_name/:controller/:action'
Accessing /clients/37signals/basecamp/project/dash calls ProjectController#dash with
- { "client_name" => "37signals", "project_name" => "basecamp" } in @params["params"]
+ { "client_name" => "37signals", "project_name" => "basecamp" } in params[:params]
From that URL, you can rewrite the redirect in a number of ways:
@@ -296,9 +296,8 @@ A short rundown of the major features:
{Learn more}[link:classes/ActionController/Rescue.html]
-* Scaffolding for Action Record model objects
+* Scaffolding for Active Record model objects
- require 'account' # must be an Active Record class
class AccountController < ActionController::Base
scaffold :account
end
@@ -306,7 +305,7 @@ A short rundown of the major features:
The AccountController now has the full CRUD range of actions and default
templates: list, show, destroy, new, create, edit, update
- {Learn more}link:classes/ActionController/Scaffolding/ClassMethods.html
+ {Learn more}[link:classes/ActionController/Scaffolding/ClassMethods.html]
* Form building for Active Record model objects
@@ -338,10 +337,10 @@ A short rundown of the major features:
- This form generates a @params["post"] array that can be used directly in a save action:
+ This form generates a params[:post] array that can be used directly in a save action:
class WeblogController < ActionController::Base
- def save
+ def create
post = Post.create(params[:post])
redirect_to :action => "display", :id => post.id
end
@@ -350,10 +349,10 @@ A short rundown of the major features:
{Learn more}[link:classes/ActionView/Helpers/ActiveRecordHelper.html]
-* Runs on top of WEBrick, CGI, FCGI, and mod_ruby
+* Runs on top of WEBrick, Mongrel, CGI, FCGI, and mod_ruby
-== Simple example
+== Simple example (from outside of Rails)
This example will implement a simple weblog system using inline templates and
an Active Record model. So let's build that WeblogController with just a few
@@ -366,11 +365,11 @@ methods:
layout "weblog/layout"
def index
- @posts = Post.find_all
+ @posts = Post.find(:all)
end
def display
- @post = Post.find(:params[:id])
+ @post = Post.find(params[:id])
end
def new
@@ -394,7 +393,7 @@ And the templates look like this:
weblog/layout.rhtml:
- <%= @content_for_layout %>
+ <%= yield %>
weblog/index.rhtml:
@@ -431,6 +430,8 @@ template casing from content.
Please note that you might need to change the "shebang" line to
#!/usr/local/env ruby, if your Ruby is not placed in /usr/local/bin/ruby
+Also note that these examples are all for demonstrating using Action Pack on
+its own. Not for when it's used inside of Rails.
== Download
@@ -440,7 +441,7 @@ The latest version of Action Pack can be found at
Documentation can be found at
-* http://ap.rubyonrails.com
+* http://api.rubyonrails.com
== Installation
@@ -459,13 +460,10 @@ Action Pack is released under the MIT license.
== Support
-The Action Pack homepage is http://www.rubyonrails.com. You can find
+The Action Pack homepage is http://www.rubyonrails.org. You can find
the Action Pack RubyForge page at http://rubyforge.org/projects/actionpack.
And as Jim from Rake says:
Feel free to submit commits or feature requests. If you send a patch,
remember to update the corresponding unit tests. If fact, I prefer
- new feature to be submitted in the form of new unit tests.
-
-For other information, feel free to ask on the ruby-talk mailing list (which
-is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com.
+ new feature to be submitted in the form of new unit tests.
\ No newline at end of file
diff --git a/vendor/rails/actionpack/Rakefile b/vendor/rails/actionpack/Rakefile
index 56aacf26..4f5b47b6 100755
--- a/vendor/rails/actionpack/Rakefile
+++ b/vendor/rails/actionpack/Rakefile
@@ -22,11 +22,14 @@ task :default => [ :test ]
# Run the unit tests
-Rake::TestTask.new { |t|
+desc "Run all unit tests"
+task :test => [:test_action_pack, :test_active_record_integration]
+
+Rake::TestTask.new(:test_action_pack) { |t|
t.libs << "test"
# make sure we include the controller tests (c*) first as on some systems
# this will not happen automatically and the tests (as a whole) will error
- t.test_files=Dir.glob( "test/c*/*_test.rb" ) + Dir.glob( "test/[ft]*/*_test.rb" )
+ t.test_files=Dir.glob( "test/c*/**/*_test.rb" ) + Dir.glob( "test/[ft]*/*_test.rb" )
# t.pattern = 'test/*/*_test.rb'
t.verbose = true
}
@@ -72,12 +75,12 @@ spec = Gem::Specification.new do |s|
s.has_rdoc = true
s.requirements << 'none'
- s.add_dependency('activesupport', '= 1.3.1' + PKG_BUILD)
+ s.add_dependency('activesupport', '= 1.4.1' + PKG_BUILD)
s.require_path = 'lib'
s.autorequire = 'action_controller'
- s.files = [ "Rakefile", "install.rb", "filler.txt", "README", "RUNNING_UNIT_TESTS", "CHANGELOG", "MIT-LICENSE", "examples/.htaccess" ]
+ s.files = [ "Rakefile", "install.rb", "README", "RUNNING_UNIT_TESTS", "CHANGELOG", "MIT-LICENSE", "examples/.htaccess" ]
dist_dirs.each do |dir|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
diff --git a/vendor/rails/actionpack/examples/address_book_controller.rb b/vendor/rails/actionpack/examples/address_book_controller.rb
index 01d498e1..9ca66125 100644
--- a/vendor/rails/actionpack/examples/address_book_controller.rb
+++ b/vendor/rails/actionpack/examples/address_book_controller.rb
@@ -28,11 +28,11 @@ class AddressBookController < ActionController::Base
end
def person
- @person = @address_book.find_person(@params["id"])
+ @person = @address_book.find_person(params[:id])
end
def create_person
- @address_book.create_person(@params["person"])
+ @address_book.create_person(params[:person])
redirect_to :action => "index"
end
@@ -49,4 +49,4 @@ begin
AddressBookController.process_cgi(CGI.new) if $0 == __FILE__
rescue => e
CGI.new.out { "#{e.class}: #{e.message}" }
-end
\ No newline at end of file
+end
diff --git a/vendor/rails/actionpack/examples/blog_controller.cgi b/vendor/rails/actionpack/examples/blog_controller.cgi
index e64fe85f..3868c306 100755
--- a/vendor/rails/actionpack/examples/blog_controller.cgi
+++ b/vendor/rails/actionpack/examples/blog_controller.cgi
@@ -14,7 +14,7 @@ class BlogController < ActionController::Base
render_template <<-"EOF"
- <%= @flash["alert"] %>
+ <%= flash["alert"] %>
Posts
<% @posts.each do |post| %>
<%= post.body %>Application error (Rails)
"
+ else
+ render_text(IO.read(File.join(RAILS_ROOT, 'public', '500.html')), "500 Internal Error")
end
end
@@ -60,19 +61,19 @@ module ActionController #:nodoc:
# the remote IP being 127.0.0.1. For example, this could include the IP of the developer machine when debugging
# remotely.
def local_request? #:doc:
- [@request.remote_addr, @request.remote_ip] == ["127.0.0.1"] * 2
+ [request.remote_addr, request.remote_ip] == ["127.0.0.1"] * 2
end
# Renders a detailed diagnostics screen on action exceptions.
def rescue_action_locally(exception)
add_variables_to_assigns
@template.instance_variable_set("@exception", exception)
- @template.instance_variable_set("@rescues_path", File.dirname(__FILE__) + "/templates/rescues/")
+ @template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
@template.send(:assign_variables_from_controller)
@template.instance_variable_set("@contents", @template.render_file(template_path_for_local_rescue(exception), false))
- @headers["Content-Type"] = "text/html"
+ response.content_type = Mime::HTML
render_file(rescues_path("layout"), response_code_for_rescue(exception))
end
@@ -80,8 +81,8 @@ module ActionController #:nodoc:
def perform_action_with_rescue #:nodoc:
begin
perform_action_without_rescue
- rescue Object => exception
- if defined?(Breakpoint) && @params["BP-RETRY"]
+ rescue Exception => exception # errors from action performed
+ if defined?(Breakpoint) && params["BP-RETRY"]
msg = exception.backtrace.first
if md = /^(.+?):(\d+)(?::in `(.+)')?$/.match(msg) then
origin_file, origin_line = md[1], md[2].to_i
@@ -89,7 +90,7 @@ module ActionController #:nodoc:
set_trace_func(lambda do |type, file, line, method, context, klass|
if file == origin_file and line == origin_line then
set_trace_func(nil)
- @params["BP-RETRY"] = false
+ params["BP-RETRY"] = false
callstack = caller
callstack.slice!(0) if callstack.first["rescue.rb"]
@@ -127,8 +128,10 @@ module ActionController #:nodoc:
def response_code_for_rescue(exception)
case exception
- when UnknownAction, RoutingError then "404 Page Not Found"
- else "500 Internal Error"
+ when UnknownAction, RoutingError
+ "404 Page Not Found"
+ else
+ "500 Internal Error"
end
end
diff --git a/vendor/rails/actionpack/lib/action_controller/response.rb b/vendor/rails/actionpack/lib/action_controller/response.rb
index 746d6097..a6438a13 100755
--- a/vendor/rails/actionpack/lib/action_controller/response.rb
+++ b/vendor/rails/actionpack/lib/action_controller/response.rb
@@ -1,15 +1,33 @@
module ActionController
class AbstractResponse #:nodoc:
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
- attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params
+ attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout
def initialize
@body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
end
+ def content_type=(mime_type)
+ @headers["Content-Type"] = charset ? "#{mime_type}; charset=#{charset}" : mime_type
+ end
+
+ def content_type
+ content_type = String(@headers["Content-Type"]).split(";")[0]
+ content_type.blank? ? nil : content_type
+ end
+
+ def charset=(encoding)
+ @headers["Content-Type"] = "#{content_type || "text/html"}; charset=#{encoding}"
+ end
+
+ def charset
+ charset = String(@headers["Content-Type"]).split(";")[1]
+ charset.blank? ? nil : charset.strip.split("=")[1]
+ end
+
def redirect(to_url, permanently = false)
@headers["Status"] = "302 Found" unless @headers["Status"] == "301 Moved Permanently"
- @headers["location"] = to_url
+ @headers["Location"] = to_url
@body = "You are being redirected."
end
diff --git a/vendor/rails/actionpack/lib/action_controller/routing.rb b/vendor/rails/actionpack/lib/action_controller/routing.rb
index e0b9b845..dee2e66e 100644
--- a/vendor/rails/actionpack/lib/action_controller/routing.rb
+++ b/vendor/rails/actionpack/lib/action_controller/routing.rb
@@ -1,716 +1,1340 @@
+require 'cgi'
+
+class Object
+ def to_param
+ to_s
+ end
+end
+
+class TrueClass
+ def to_param
+ self
+ end
+end
+
+class FalseClass
+ def to_param
+ self
+ end
+end
+
+class NilClass
+ def to_param
+ self
+ end
+end
+
+class Regexp #:nodoc:
+ def number_of_captures
+ Regexp.new("|#{source}").match('').captures.length
+ end
+
+ class << self
+ def optionalize(pattern)
+ case unoptionalize(pattern)
+ when /\A(.|\(.*\))\Z/ then "#{pattern}?"
+ else "(?:#{pattern})?"
+ end
+ end
+
+ def unoptionalize(pattern)
+ [/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp|
+ return $1 if regexp =~ pattern
+ end
+ return pattern
+ end
+ end
+end
+
module ActionController
- module Routing #:nodoc:
+ # == Routing
+ #
+ # The routing module provides URL rewriting in native Ruby. It's a way to
+ # redirect incoming requests to controllers and actions. This replaces
+ # mod_rewrite rules. Best of all Rails' Routing works with any web server.
+ # Routes are defined in routes.rb in your RAILS_ROOT/config directory.
+ #
+ # Consider the following route, installed by Rails when you generate your
+ # application:
+ #
+ # map.connect ':controller/:action/:id'
+ #
+ # This route states that it expects requests to consist of a
+ # :controller followed by an :action that in turns is fed by some :id
+ #
+ # Suppose you get an incoming request for /blog/edit/22, you'll end up
+ # with:
+ #
+ # params = { :controller => 'blog',
+ # :action => 'edit'
+ # :id => '22'
+ # }
+ #
+ # Think of creating routes as drawing a map for your requests. The map tells
+ # them where to go based on some predefined pattern:
+ #
+ # ActionController::Routing::Routes.draw do |map|
+ # Pattern 1 tells some request to go to one place
+ # Pattern 2 tell them to go to another
+ # ...
+ # end
+ #
+ # The following symbols are special:
+ #
+ # :controller maps to your controller name
+ # :action maps to an action with your controllers
+ #
+ # Other names simply map to a parameter as in the case of +:id+.
+ #
+ # == Route priority
+ #
+ # Not all routes are created equally. Routes have priority defined by the
+ # order of appearance of the routes in the routes.rb file. The priority goes
+ # from top to bottom. The last route in that file is at the lowest priority
+ # will be applied last. If no route matches, 404 is returned.
+ #
+ # Within blocks, the empty pattern goes first i.e. is at the highest priority.
+ # In practice this works out nicely:
+ #
+ # ActionController::Routing::Routes.draw do |map|
+ # map.with_options :controller => 'blog' do |blog|
+ # blog.show '', :action => 'list'
+ # end
+ # map.connect ':controller/:action/:view
+ # end
+ #
+ # In this case, invoking blog controller (with an URL like '/blog/')
+ # without parameters will activate the 'list' action by default.
+ #
+ # == Defaults routes and default parameters
+ #
+ # Setting a default route is straightforward in Rails because by appending a
+ # Hash to the end of your mapping you can set default parameters.
+ #
+ # Example:
+ # ActionController::Routing:Routes.draw do |map|
+ # map.connect ':controller/:action/:id', :controller => 'blog'
+ # end
+ #
+ # This sets up +blog+ as the default controller if no other is specified.
+ # This means visiting '/' would invoke the blog controller.
+ #
+ # More formally, you can define defaults in a route with the +:defaults+ key.
+ #
+ # map.connect ':controller/:id/:action', :action => 'show', :defaults => { :page => 'Dashboard' }
+ #
+ # == Named routes
+ #
+ # Routes can be named with the syntax map.name_of_route options,
+ # allowing for easy reference within your source as +name_of_route_url+
+ # for the full URL and +name_of_route_path+ for the URI path.
+ #
+ # Example:
+ # # In routes.rb
+ # map.login 'login', :controller => 'accounts', :action => 'login'
+ #
+ # # With render, redirect_to, tests, etc.
+ # redirect_to login_url
+ #
+ # Arguments can be passed as well.
+ #
+ # redirect_to show_item_path(:id => 25)
+ #
+ # Use map.root as a shorthand to name a route for the root path ""
+ #
+ # # In routes.rb
+ # map.root :controller => 'blogs'
+ #
+ # # would recognize http://www.example.com/ as
+ # params = { :controller => 'blogs', :action => 'index' }
+ #
+ # # and provide these named routes
+ # root_url # => 'http://www.example.com/'
+ # root_path # => ''
+ #
+ # Note: when using +with_options+, the route is simply named after the
+ # method you call on the block parameter rather than map.
+ #
+ # # In routes.rb
+ # map.with_options :controller => 'blog' do |blog|
+ # blog.show '', :action => 'list'
+ # blog.delete 'delete/:id', :action => 'delete',
+ # blog.edit 'edit/:id', :action => 'edit'
+ # end
+ #
+ # # provides named routes for show, delete, and edit
+ # link_to @article.title, show_path(:id => @article.id)
+ #
+ # == Pretty URLs
+ #
+ # Routes can generate pretty URLs. For example:
+ #
+ # map.connect 'articles/:year/:month/:day',
+ # :controller => 'articles',
+ # :action => 'find_by_date',
+ # :year => /\d{4}/,
+ # :month => /\d{1,2}/,
+ # :day => /\d{1,2}/
+ #
+ # # Using the route above, the url below maps to:
+ # # params = {:year => '2005', :month => '11', :day => '06'}
+ # # http://localhost:3000/articles/2005/11/06
+ #
+ # == Regular Expressions and parameters
+ # You can specify a reqular expression to define a format for a parameter.
+ #
+ # map.geocode 'geocode/:postalcode', :controller => 'geocode',
+ # :action => 'show', :postalcode => /\d{5}(-\d{4})?/
+ #
+ # or more formally:
+ #
+ # map.geocode 'geocode/:postalcode', :controller => 'geocode',
+ # :action => 'show',
+ # :requirements { :postalcode => /\d{5}(-\d{4})?/ }
+ #
+ # == Route globbing
+ #
+ # Specifying *[string] as part of a rule like :
+ #
+ # map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
+ #
+ # will glob all remaining parts of the route that were not recognized earlier. This idiom must appear at the end of the path. The globbed values are in params[:path] in this case.
+ #
+ # == Reloading routes
+ #
+ # You can reload routes if you feel you must:
+ #
+ # Action::Controller::Routes.reload
+ #
+ # This will clear all named routes and reload routes.rb
+ #
+ # == Testing Routes
+ #
+ # The two main methods for testing your routes:
+ #
+ # === +assert_routing+
+ #
+ # def test_movie_route_properly_splits
+ # opts = {:controller => "plugin", :action => "checkout", :id => "2"}
+ # assert_routing "plugin/checkout/2", opts
+ # end
+ #
+ # +assert_routing+ lets you test whether or not the route properly resolves into options.
+ #
+ # === +assert_recognizes+
+ #
+ # def test_route_has_options
+ # opts = {:controller => "plugin", :action => "show", :id => "12"}
+ # assert_recognizes opts, "/plugins/show/12"
+ # end
+ #
+ # Note the subtle difference between the two: +assert_routing+ tests that
+ # an URL fits options while +assert_recognizes+ tests that an URL
+ # breaks into parameters properly.
+ #
+ # In tests you can simply pass the URL or named route to +get+ or +post+.
+ #
+ # def send_to_jail
+ # get '/jail'
+ # assert_response :success
+ # assert_template "jail/front"
+ # end
+ #
+ # def goes_to_login
+ # get login_url
+ # #...
+ # end
+ #
+ module Routing
+ SEPARATORS = %w( / ; . , ? )
+
+ # The root paths which may contain controller files
+ mattr_accessor :controller_paths
+ self.controller_paths = []
+
class << self
- def expiry_hash(options, recall)
- k = v = nil
- expire_on = {}
- options.each {|k, v| expire_on[k] = ((rcv = recall[k]) && (rcv != v))}
- expire_on
+ def with_controllers(names)
+ prior_controllers = @possible_controllers
+ use_controllers! names
+ yield
+ ensure
+ use_controllers! prior_controllers
end
- def extract_parameter_value(parameter) #:nodoc:
- CGI.escape((parameter.respond_to?(:to_param) ? parameter.to_param : parameter).to_s)
+ def normalize_paths(paths)
+ # do the hokey-pokey of path normalization...
+ paths = paths.collect do |path|
+ path = path.
+ gsub("//", "/"). # replace double / chars with a single
+ gsub("\\\\", "\\"). # replace double \ chars with a single
+ gsub(%r{(.)[\\/]$}, '\1') # drop final / or \ if path ends with it
+
+ # eliminate .. paths where possible
+ re = %r{\w+[/\\]\.\.[/\\]}
+ path.gsub!(%r{\w+[/\\]\.\.[/\\]}, "") while path.match(re)
+ path
+ end
+
+ # start with longest path, first
+ paths = paths.uniq.sort_by { |path| - path.length }
end
+
+ def possible_controllers
+ unless @possible_controllers
+ @possible_controllers = []
+
+ paths = controller_paths.select { |path| File.directory?(path) && path != "." }
+
+ seen_paths = Hash.new {|h, k| h[k] = true; false}
+ normalize_paths(paths).each do |load_path|
+ Dir["#{load_path}/**/*_controller.rb"].collect do |path|
+ next if seen_paths[path.gsub(%r{^\.[/\\]}, "")]
+
+ controller_name = path[(load_path.length + 1)..-1]
+
+ controller_name.gsub!(/_controller\.rb\Z/, '')
+ @possible_controllers << controller_name
+ end
+ end
+
+ # remove duplicates
+ @possible_controllers.uniq!
+ end
+ @possible_controllers
+ end
+
+ def use_controllers!(controller_names)
+ @possible_controllers = controller_names
+ end
+
def controller_relative_to(controller, previous)
if controller.nil? then previous
elsif controller[0] == ?/ then controller[1..-1]
elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
else controller
- end
+ end
+ end
+ end
+
+ class Route #:nodoc:
+ attr_accessor :segments, :requirements, :conditions
+
+ def initialize
+ @segments = []
+ @requirements = {}
+ @conditions = {}
end
+
+ # Write and compile a +generate+ method for this Route.
+ def write_generation
+ # Build the main body of the generation
+ body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
+
+ # If we have conditions that must be tested first, nest the body inside an if
+ body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
+ args = "options, hash, expire_on = {}"
- def treat_hash(hash, keys_to_delete = [])
- k = v = nil
- hash.each do |k, v|
- if v then hash[k] = (v.respond_to? :to_param) ? v.to_param.to_s : v.to_s
+ # Nest the body inside of a def block, and then compile it.
+ raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
+
+ # expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
+ # are the same as the keys that were recalled from the previous request. Thus,
+ # we can use the expire_on.keys to determine which keys ought to be used to build
+ # the query string. (Never use keys from the recalled request when building the
+ # query string.)
+
+ method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(hash, expire_on))\nend"
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
+
+ method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(hash, expire_on)]\nend"
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
+ raw_method
+ end
+
+ # Build several lines of code that extract values from the options hash. If any
+ # of the values are missing or rejected then a return will be executed.
+ def generation_extraction
+ segments.collect do |segment|
+ segment.extraction_code
+ end.compact * "\n"
+ end
+
+ # Produce a condition expression that will check the requirements of this route
+ # upon generation.
+ def generation_requirements
+ requirement_conditions = requirements.collect do |key, req|
+ if req.is_a? Regexp
+ value_regexp = Regexp.new "\\A#{req.source}\\Z"
+ "hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
else
- hash.delete k
- keys_to_delete << k
+ "hash[:#{key}] == #{req.inspect}"
end
end
- hash
+ requirement_conditions * ' && ' unless requirement_conditions.empty?
+ end
+ def generation_structure
+ segments.last.string_structure segments[0..-2]
+ end
+
+ # Write and compile a +recognize+ method for this Route.
+ def write_recognition
+ # Create an if structure to extract the params from a match if it occurs.
+ body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
+ body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
+
+ # Build the method declaration and compile it
+ method_decl = "def recognize(path, env={})\n#{body}\nend"
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
+ method_decl
+ end
+
+ # Plugins may override this method to add other conditions, like checks on
+ # host, subdomain, and so forth. Note that changes here only affect route
+ # recognition, not generation.
+ def recognition_conditions
+ result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
+ result << "conditions[:method] === env[:method]" if conditions[:method]
+ result
+ end
+
+ # Build the regular expression pattern that will match this route.
+ def recognition_pattern(wrap = true)
+ pattern = ''
+ segments.reverse_each do |segment|
+ pattern = segment.build_pattern pattern
+ end
+ wrap ? ("\\A" + pattern + "\\Z") : pattern
end
- def test_condition(expression, condition)
- case condition
- when String then "(#{expression} == #{condition.inspect})"
- when Regexp then
- condition = Regexp.new("^#{condition.source}$") unless /^\^.*\$$/ =~ condition.source
- "(#{condition.inspect} =~ #{expression})"
- when Array then
- conds = condition.collect do |condition|
- cond = test_condition(expression, condition)
- (cond[0, 1] == '(' && cond[-1, 1] == ')') ? cond : "(#{cond})"
- end
- "(#{conds.join(' || ')})"
- when true then expression
- when nil then "! #{expression}"
- else
- raise ArgumentError, "Valid criteria are strings, regular expressions, true, or nil"
+ # Write the code to extract the parameters from a matched route.
+ def recognition_extraction
+ next_capture = 1
+ extraction = segments.collect do |segment|
+ x = segment.match_extraction next_capture
+ next_capture += Regexp.new(segment.regexp_chunk).number_of_captures
+ x
end
+ extraction.compact
end
- end
-
- class Component #:nodoc:
- def dynamic?() false end
- def optional?() false end
-
- def key() nil end
- def self.new(string, *args)
- return super(string, *args) unless self == Component
- case string
- when ':controller' then ControllerComponent.new(:controller, *args)
- when /^:(\w+)$/ then DynamicComponent.new($1, *args)
- when /^\*(\w+)$/ then PathComponent.new($1, *args)
- else StaticComponent.new(string, *args)
- end
- end
- end
-
- class StaticComponent < Component #:nodoc:
- attr_reader :value
-
- def initialize(value)
- @value = value
+ # Write the real generation implementation and then resend the message.
+ def generate(options, hash, expire_on = {})
+ write_generation
+ generate options, hash, expire_on
end
- def write_recognition(g)
- g.if_next_matches(value) do |gp|
- gp.move_forward {|gpp| gpp.continue}
+ def generate_extras(options, hash, expire_on = {})
+ write_generation
+ generate_extras options, hash, expire_on
+ end
+
+ # Generate the query string with any extra keys in the hash and append
+ # it to the given path, returning the new path.
+ def append_query_string(path, hash, query_keys=nil)
+ return nil unless path
+ query_keys ||= extra_keys(hash)
+ "#{path}#{build_query_string(hash, query_keys)}"
+ end
+
+ # Determine which keys in the given hash are "extra". Extra keys are
+ # those that were not used to generate a particular route. The extra
+ # keys also do not include those recalled from the prior request, nor
+ # do they include any keys that were implied in the route (like a
+ # :controller that is required, but not explicitly used in the text of
+ # the route.)
+ def extra_keys(hash, recall={})
+ (hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
+ end
+
+ # Build a query string from the keys of the given hash. If +only_keys+
+ # is given (as an array), only the keys indicated will be used to build
+ # the query string. The query string will correctly build array parameter
+ # values.
+ def build_query_string(hash, only_keys=nil)
+ elements = []
+
+ only_keys ||= hash.keys
+
+ only_keys.each do |key|
+ value = hash[key] or next
+ key = CGI.escape key.to_s
+ if value.class == Array
+ key << '[]'
+ else
+ value = [ value ]
+ end
+ value.each { |val| elements << "#{key}=#{CGI.escape(val.to_param.to_s)}" }
+ end
+
+ query_string = "?#{elements.join("&")}" unless elements.empty?
+ query_string || ""
+ end
+
+ # Write the real recognition implementation and then resend the message.
+ def recognize(path, environment={})
+ write_recognition
+ recognize path, environment
+ end
+
+ # A route's parameter shell contains parameter values that are not in the
+ # route's path, but should be placed in the recognized hash.
+ #
+ # For example, +{:controller => 'pages', :action => 'show'} is the shell for the route:
+ #
+ # map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
+ #
+ def parameter_shell
+ @parameter_shell ||= returning({}) do |shell|
+ requirements.each do |key, requirement|
+ shell[key] = requirement unless requirement.is_a? Regexp
+ end
+ end
+ end
+
+ # Return an array containing all the keys that are used in this route. This
+ # includes keys that appear inside the path, and keys that have requirements
+ # placed upon them.
+ def significant_keys
+ @significant_keys ||= returning [] do |sk|
+ segments.each { |segment| sk << segment.key if segment.respond_to? :key }
+ sk.concat requirements.keys
+ sk.uniq!
end
end
- def write_generation(g)
- g.add_segment(value) {|gp| gp.continue }
+ # Return a hash of key/value pairs representing the keys in the route that
+ # have defaults, or which are specified by non-regexp requirements.
+ def defaults
+ @defaults ||= returning({}) do |hash|
+ segments.each do |segment|
+ next unless segment.respond_to? :default
+ hash[segment.key] = segment.default unless segment.default.nil?
+ end
+ requirements.each do |key,req|
+ next if Regexp === req || req.nil?
+ hash[key] = req
+ end
+ end
end
+
+ def matches_controller_and_action?(controller, action)
+ unless @matching_prepared
+ @controller_requirement = requirement_for(:controller)
+ @action_requirement = requirement_for(:action)
+ @matching_prepared = true
+ end
+
+ (@controller_requirement.nil? || @controller_requirement === controller) &&
+ (@action_requirement.nil? || @action_requirement === action)
+ end
+
+ def to_s
+ @to_s ||= begin
+ segs = segments.inject("") { |str,s| str << s.to_s }
+ "%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect]
+ end
+ end
+
+ protected
+ def requirement_for(key)
+ return requirements[key] if requirements.key? key
+ segments.each do |segment|
+ return segment.regexp if segment.respond_to?(:key) && segment.key == key
+ end
+ nil
+ end
+
end
- class DynamicComponent < Component #:nodoc:
- attr_reader :key, :default
- attr_accessor :condition
+ class Segment #:nodoc:
+ attr_accessor :is_optional
+ alias_method :optional?, :is_optional
+
+ def initialize
+ self.is_optional = false
+ end
+
+ def extraction_code
+ nil
+ end
- def dynamic?() true end
- def optional?() @optional end
-
- def default=(default)
- @optional = true
- @default = default
- end
-
- def initialize(key, options = {})
- @key = key.to_sym
- @optional = false
- default, @condition = options[:default], options[:condition]
- self.default = default if options.key?(:default)
- end
-
- def default_check(g)
- presence = "#{g.hash_value(key, !! default)}"
- if default
- "!(#{presence} && #{g.hash_value(key, false)} != #{default.to_s.inspect})"
+ # Continue generating string for the prior segments.
+ def continue_string_structure(prior_segments)
+ if prior_segments.empty?
+ interpolation_statement(prior_segments)
else
- "! #{presence}"
+ new_priors = prior_segments[0..-2]
+ prior_segments.last.string_structure(new_priors)
end
end
- def write_generation(g)
- wrote_dropout = write_dropout_generation(g)
- write_continue_generation(g, wrote_dropout)
- end
-
- def write_dropout_generation(g)
- return false unless optional? && g.after.all? {|c| c.optional?}
-
- check = [default_check(g)]
- gp = g.dup # Use another generator to write the conditions after the first &&
- # We do this to ensure that the generator will not assume x_value is set. It will
- # not be set if it follows a false condition -- for example, false && (x = 2)
-
- check += gp.after.map {|c| c.default_check gp}
- gp.if(check.join(' && ')) { gp.finish } # If this condition is met, we stop here
- true
- end
-
- def write_continue_generation(g, use_else)
- test = Routing.test_condition(g.hash_value(key, true, default), condition || true)
- check = (use_else && condition.nil? && default) ? [:else] : [use_else ? :elsif : :if, test]
-
- g.send(*check) do |gp|
- gp.expire_for_keys(key) unless gp.after.empty?
- add_segments_to(gp) {|gpp| gpp.continue}
- end
- end
-
- def add_segments_to(g)
- g.add_segment(%(\#{CGI.escape(#{g.hash_value(key, true, default)})})) {|gp| yield gp}
+ # Return a string interpolation statement for this segment and those before it.
+ def interpolation_statement(prior_segments)
+ chunks = prior_segments.collect { |s| s.interpolation_chunk }
+ chunks << interpolation_chunk
+ "\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}"
end
- def recognition_check(g)
- test_type = [true, nil].include?(condition) ? :presence : :constraint
-
- prefix = condition.is_a?(Regexp) ? "#{g.next_segment(true)} && " : ''
- check = prefix + Routing.test_condition(g.next_segment(true), condition || true)
-
- g.if(check) {|gp| yield gp, test_type}
+ def string_structure(prior_segments)
+ optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments)
end
- def write_recognition(g)
- test_type = nil
- recognition_check(g) do |gp, test_type|
- assign_result(gp) {|gpp| gpp.continue}
- end
-
- if optional? && g.after.all? {|c| c.optional?}
- call = (test_type == :presence) ? [:else] : [:elsif, "! #{g.next_segment(true)}"]
-
- g.send(*call) do |gp|
- assign_default(gp)
- gp.after.each {|c| c.assign_default(gp)}
- gp.finish(false)
- end
- end
+ # Return an if condition that is true if all the prior segments can be generated.
+ # If there are no optional segments before this one, then nil is returned.
+ def all_optionals_available_condition(prior_segments)
+ optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact
+ optional_locals.empty? ? nil : " if #{optional_locals * ' && '}"
end
-
- def assign_result(g, with_default = false)
- g.result key, "CGI.unescape(#{g.next_segment(true, with_default ? default : nil)})"
- g.move_forward {|gp| yield gp}
+
+ # Recognition
+
+ def match_extraction(next_capture)
+ nil
end
-
- def assign_default(g)
- g.constant_result key, default unless default.nil?
+
+ # Warning
+
+ # Returns true if this segment is optional? because of a default. If so, then
+ # no warning will be emitted regarding this segment.
+ def optionality_implied?
+ false
end
end
- class ControllerComponent < DynamicComponent #:nodoc:
- def key() :controller end
-
- def add_segments_to(g)
- g.add_segment(%(\#{#{g.hash_value(key, true, default)}})) {|gp| yield gp}
- end
-
- def recognition_check(g)
- g << "controller_result = ::ActionController::Routing::ControllerComponent.traverse_to_controller(#{g.path_name}, #{g.index_name})"
- g.if('controller_result') do |gp|
- gp << 'controller_value, segments_to_controller = controller_result'
- if condition
- gp << "controller_path = #{gp.path_name}[#{gp.index_name},segments_to_controller].join('/')"
- gp.if(Routing.test_condition("controller_path", condition)) do |gpp|
- gpp.move_forward('segments_to_controller') {|gppp| yield gppp, :constraint}
- end
- else
- gp.move_forward('segments_to_controller') {|gpp| yield gpp, :constraint}
- end
- end
- end
-
- def assign_result(g)
- g.result key, 'controller_value'
- yield g
- end
-
- def assign_default(g)
- ControllerComponent.assign_controller(g, default)
+ class StaticSegment < Segment #:nodoc:
+ attr_accessor :value, :raw
+ alias_method :raw?, :raw
+
+ def initialize(value = nil)
+ super()
+ self.value = value
end
- class << self
- def assign_controller(g, controller)
- expr = "::#{controller.split('/').collect {|c| c.camelize}.join('::')}Controller"
- g.result :controller, expr, true
+ def interpolation_chunk
+ raw? ? value : CGI.escape(value)
+ end
+
+ def regexp_chunk
+ chunk = Regexp.escape value
+ optional? ? Regexp.optionalize(chunk) : chunk
+ end
+
+ def build_pattern(pattern)
+ escaped = Regexp.escape(value)
+ if optional? && ! pattern.empty?
+ "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})"
+ elsif optional?
+ Regexp.optionalize escaped
+ else
+ escaped + pattern
end
+ end
+
+ def to_s
+ value
+ end
+ end
- def traverse_to_controller(segments, start_at = 0)
- mod = ::Object
- length = segments.length
- index = start_at
- mod_name = controller_name = segment = nil
- while index < length
- return nil unless /\A[A-Za-z][A-Za-z\d_]*\Z/ =~ (segment = segments[index])
- index += 1
-
- mod_name = segment.camelize
- controller_name = "#{mod_name}Controller"
- path_suffix = File.join(segments[start_at..(index - 1)])
- next_mod = nil
-
- # If the controller is already present, or if we load it, return it.
- if mod.const_defined?(controller_name) || attempt_load(mod, controller_name, path_suffix + "_controller") == :defined
- controller = mod.const_get(controller_name)
- return nil unless controller.is_a?(Class) && controller.ancestors.include?(ActionController::Base) # it's not really a controller?
- return [controller, (index - start_at)]
- end
-
- # No controller? Look for the module
- if mod.const_defined? mod_name
- next_mod = mod.send(:const_get, mod_name)
- next_mod = nil unless next_mod.is_a?(Module)
- else
- # Try to load a file that defines the module we want.
- case attempt_load(mod, mod_name, path_suffix)
- when :defined then next_mod = mod.const_get mod_name
- when :dir then # We didn't find a file, but there's a dir.
- next_mod = Module.new # So create a module for the directory
- mod.send :const_set, mod_name, next_mod
- else
- return nil
- end
- end
- mod = next_mod
-
- return nil unless mod && mod.is_a?(Module)
- end
- nil
+ class DividerSegment < StaticSegment #:nodoc:
+ def initialize(value = nil)
+ super(value)
+ self.raw = true
+ self.is_optional = true
+ end
+
+ def optionality_implied?
+ true
+ end
+ end
+
+ class DynamicSegment < Segment #:nodoc:
+ attr_accessor :key, :default, :regexp
+
+ def initialize(key = nil, options = {})
+ super()
+ self.key = key
+ self.default = options[:default] if options.key? :default
+ self.is_optional = true if options[:optional] || options.key?(:default)
+ end
+
+ def to_s
+ ":#{key}"
+ end
+
+ # The local variable name that the value of this segment will be extracted to.
+ def local_name
+ "#{key}_value"
+ end
+
+ def extract_value
+ "#{local_name} = hash[:#{key}] #{"|| #{default.inspect}" if default}"
+ end
+ def value_check
+ if default # Then we know it won't be nil
+ "#{value_regexp.inspect} =~ #{local_name}" if regexp
+ elsif optional?
+ # If we have a regexp check that the value is not given, or that it matches.
+ # If we have no regexp, return nil since we do not require a condition.
+ "#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp
+ else # Then it must be present, and if we have a regexp, it must match too.
+ "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"
end
-
- protected
- def safe_load_paths #:nodoc:
- if defined?(RAILS_ROOT)
- $LOAD_PATH.select do |base|
- base = File.expand_path(base)
- extended_root = File.expand_path(RAILS_ROOT)
- # Exclude all paths that are not nested within app, lib, or components.
- base.match(/\A#{Regexp.escape(extended_root)}\/*(app|lib|components)\/[a-z]/) || base =~ %r{rails-[\d.]+/builtin}
- end
- else
- $LOAD_PATH
- end
+ end
+ def expiry_statement
+ "expired, hash = true, options if !expired && expire_on[:#{key}]"
+ end
+
+ def extraction_code
+ s = extract_value
+ vc = value_check
+ s << "\nreturn [nil,nil] unless #{vc}" if vc
+ s << "\n#{expiry_statement}"
+ end
+
+ def interpolation_chunk
+ "\#{CGI.escape(#{local_name}.to_s)}"
+ end
+
+ def string_structure(prior_segments)
+ if optional? # We have a conditional to do...
+ # If we should not appear in the url, just write the code for the prior
+ # segments. This occurs if our value is the default value, or, if we are
+ # optional, if we have nil as our value.
+ "if #{local_name} == #{default.inspect}\n" +
+ continue_string_structure(prior_segments) +
+ "\nelse\n" + # Otherwise, write the code up to here
+ "#{interpolation_statement(prior_segments)}\nend"
+ else
+ interpolation_statement(prior_segments)
end
+ end
+
+ def value_regexp
+ Regexp.new "\\A#{regexp.source}\\Z" if regexp
+ end
+ def regexp_chunk
+ regexp ? "(#{regexp.source})" : "([^#{Routing::SEPARATORS.join}]+)"
+ end
+
+ def build_pattern(pattern)
+ chunk = regexp_chunk
+ chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0
+ pattern = "#{chunk}#{pattern}"
+ optional? ? Regexp.optionalize(pattern) : pattern
+ end
+ def match_extraction(next_capture)
+ hangon = (default ? "|| #{default.inspect}" : "if match[#{next_capture}]")
- def attempt_load(mod, const_name, path)
- has_dir = false
- safe_load_paths.each do |load_path|
- full_path = File.join(load_path, path)
- file_path = full_path + '.rb'
- if File.file?(file_path) # Found a .rb file? Load it up
- require_dependency(file_path)
- return :defined if mod.const_defined? const_name
- else
- has_dir ||= File.directory?(full_path)
- end
- end
- return (has_dir ? :dir : nil)
+ # All non code-related keys (such as :id, :slug) have to be unescaped as other CGI params
+ "params[:#{key}] = match[#{next_capture}] #{hangon}"
+ end
+
+ def optionality_implied?
+ [:action, :id].include? key
+ end
+
+ end
+
+ class ControllerSegment < DynamicSegment #:nodoc:
+ def regexp_chunk
+ possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name }
+ "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
+ end
+
+ # Don't CGI.escape the controller name, since it may have slashes in it,
+ # like admin/foo.
+ def interpolation_chunk
+ "\#{#{local_name}.to_s}"
+ end
+
+ # Make sure controller names like Admin/Content are correctly normalized to
+ # admin/content
+ def extract_value
+ "#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase"
+ end
+
+ def match_extraction(next_capture)
+ if default
+ "params[:#{key}] = match[#{next_capture}] ? match[#{next_capture}].downcase : '#{default}'"
+ else
+ "params[:#{key}] = match[#{next_capture}].downcase if match[#{next_capture}]"
end
end
end
- class PathComponent < DynamicComponent #:nodoc:
- def optional?() true end
- def default() [] end
- def condition() nil end
+ class PathSegment < DynamicSegment #:nodoc:
+ EscapedSlash = CGI.escape("/")
+ def interpolation_chunk
+ "\#{CGI.escape(#{local_name}.to_s).gsub(#{EscapedSlash.inspect}, '/')}"
+ end
- def default=(value)
- raise RoutingError, "All path components have an implicit default of []" unless value == []
+ def default
+ ''
end
-
- def write_generation(g)
- raise RoutingError, 'Path components must occur last' unless g.after.empty?
- g.if("#{g.hash_value(key, true)} && ! #{g.hash_value(key, true)}.empty?") do
- g << "#{g.hash_value(key, true)} = #{g.hash_value(key, true)}.join('/') unless #{g.hash_value(key, true)}.is_a?(String)"
- g.add_segment("\#{CGI.escape_skipping_slashes(#{g.hash_value(key, true)})}") {|gp| gp.finish }
- end
- g.else { g.finish }
+
+ def default=(path)
+ raise RoutingError, "paths cannot have non-empty default values" unless path.blank?
end
-
- def write_recognition(g)
- raise RoutingError, "Path components must occur last" unless g.after.empty?
-
- start = g.index_name
- start = "(#{start})" unless /^\w+$/ =~ start
-
- value_expr = "#{g.path_name}[#{start}..-1] || []"
- g.result key, "ActionController::Routing::PathComponent::Result.new_escaped(#{value_expr})"
- g.finish(false)
+
+ def match_extraction(next_capture)
+ "params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}"
end
-
+
+ def regexp_chunk
+ regexp || "(.*)"
+ end
+
class Result < ::Array #:nodoc:
- def to_s() join '/' end
+ def to_s() join '/' end
def self.new_escaped(strings)
new strings.collect {|str| CGI.unescape str}
- end
- end
+ end
+ end
end
- class Route #:nodoc:
- attr_accessor :components, :known
- attr_reader :path, :options, :keys, :defaults
+ class RouteBuilder #:nodoc:
+ attr_accessor :separators, :optional_separators
- def initialize(path, options = {})
- @path, @options = path, options
+ def initialize
+ self.separators = Routing::SEPARATORS
+ self.optional_separators = %w( / )
+ end
+
+ def separator_pattern(inverted = false)
+ "[#{'^' if inverted}#{Regexp.escape(separators.join)}]"
+ end
+
+ def interval_regexp
+ Regexp.new "(.*?)(#{separators.source}|$)"
+ end
+
+ # Accepts a "route path" (a string defining a route), and returns the array
+ # of segments that corresponds to it. Note that the segment array is only
+ # partially initialized--the defaults and requirements, for instance, need
+ # to be set separately, via the #assign_route_options method, and the
+ # #optional? method for each segment will not be reliable until after
+ # #assign_route_options is called, as well.
+ def segments_for_route_path(path)
+ rest, segments = path, []
- initialize_components path
- defaults, conditions = initialize_hashes options.dup
- @defaults = defaults.dup
- configure_components(defaults, conditions)
- add_default_requirements
- initialize_keys
+ until rest.empty?
+ segment, rest = segment_for rest
+ segments << segment
+ end
+ segments
end
-
- def inspect
- "<#{self.class} #{path.inspect}, #{options.inspect[1..-1]}>"
- end
-
- def write_generation(generator = CodeGeneration::GenerationGenerator.new)
- generator.before, generator.current, generator.after = [], components.first, (components[1..-1] || [])
- if known.empty? then generator.go
- else
- # Alter the conditions to allow :action => 'index' to also catch :action => nil
- altered_known = known.collect do |k, v|
- if k == :action && v== 'index' then [k, [nil, 'index']]
- else [k, v]
+ # A factory method that returns a new segment instance appropriate for the
+ # format of the given string.
+ def segment_for(string)
+ segment = case string
+ when /\A:(\w+)/
+ key = $1.to_sym
+ case key
+ when :controller then ControllerSegment.new(key)
+ else DynamicSegment.new key
+ end
+ when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true)
+ when /\A\?(.*?)\?/
+ returning segment = StaticSegment.new($1) do
+ segment.is_optional = true
+ end
+ when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1)
+ when Regexp.new(separator_pattern) then
+ returning segment = DividerSegment.new($&) do
+ segment.is_optional = (optional_separators.include? $&)
end
- end
- generator.if(generator.check_conditions(altered_known)) {|gp| gp.go }
end
-
- generator
+ [segment, $~.post_match]
end
- def write_recognition(generator = CodeGeneration::RecognitionGenerator.new)
- g = generator.dup
- g.share_locals_with generator
- g.before, g.current, g.after = [], components.first, (components[1..-1] || [])
-
- known.each do |key, value|
- if key == :controller then ControllerComponent.assign_controller(g, value)
- else g.constant_result(key, value)
- end
- end
-
- g.go
-
- generator
- end
+ # Split the given hash of options into requirement and default hashes. The
+ # segments are passed alongside in order to distinguish between default values
+ # and requirements.
+ def divide_route_options(segments, options)
+ options = options.dup
+ requirements = (options.delete(:requirements) || {}).dup
+ defaults = (options.delete(:defaults) || {}).dup
+ conditions = (options.delete(:conditions) || {}).dup
- def initialize_keys
- @keys = (components.collect {|c| c.key} + known.keys).compact
- @keys.freeze
- end
-
- def extra_keys(options)
- options.keys - @keys
- end
-
- def matches_controller?(controller)
- if known[:controller] then known[:controller] == controller
- else
- c = components.find {|c| c.key == :controller}
- return false unless c
- return c.condition.nil? || eval(Routing.test_condition('controller', c.condition))
- end
- end
-
- protected
- def initialize_components(path)
- path = path.split('/') if path.is_a? String
- path.shift if path.first.blank?
- self.components = path.collect {|str| Component.new str}
+ path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
+ options.each do |key, value|
+ hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
+ hash[key] = value
end
- def initialize_hashes(options)
- path_keys = components.collect {|c| c.key }.compact
- self.known = {}
- defaults = options.delete(:defaults) || {}
- conditions = options.delete(:require) || {}
- conditions.update(options.delete(:requirements) || {})
+ [defaults, requirements, conditions]
+ end
- options.each do |k, v|
- if path_keys.include?(k) then (v.is_a?(Regexp) ? conditions : defaults)[k] = v
- else known[k] = v
- end
- end
- [defaults, conditions]
+ # Takes a hash of defaults and a hash of requirements, and assigns them to
+ # the segments. Any unused requirements (which do not correspond to a segment)
+ # are returned as a hash.
+ def assign_route_options(segments, defaults, requirements)
+ route_requirements = {} # Requirements that do not belong to a segment
+
+ segment_named = Proc.new do |key|
+ segments.detect { |segment| segment.key == key if segment.respond_to?(:key) }
end
+
+ requirements.each do |key, requirement|
+ segment = segment_named[key]
+ if segment
+ raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp)
+ if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
+ raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
+ end
+ segment.regexp = requirement
+ else
+ route_requirements[key] = requirement
+ end
+ end
+
+ defaults.each do |key, default|
+ segment = segment_named[key]
+ raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment
+ segment.is_optional = true
+ segment.default = default.to_param if default
+ end
+
+ assign_default_route_options(segments)
+ ensure_required_segments(segments)
+ route_requirements
+ end
+
+ # Assign default options, such as 'index' as a default for :action. This
+ # method must be run *after* user supplied requirements and defaults have
+ # been applied to the segments.
+ def assign_default_route_options(segments)
+ segments.each do |segment|
+ next unless segment.is_a? DynamicSegment
+ case segment.key
+ when :action
+ if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index'
+ segment.default ||= 'index'
+ segment.is_optional = true
+ end
+ when :id
+ if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ ''
+ segment.is_optional = true
+ end
+ end
+ end
+ end
+
+ # Makes sure that there are no optional segments that precede a required
+ # segment. If any are found that precede a required segment, they are
+ # made required.
+ def ensure_required_segments(segments)
+ allow_optional = true
+ segments.reverse_each do |segment|
+ allow_optional &&= segment.optional?
+ if !allow_optional && segment.optional?
+ unless segment.optionality_implied?
+ warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required."
+ end
+ segment.is_optional = false
+ elsif allow_optional & segment.respond_to?(:default) && segment.default
+ # if a segment has a default, then it is optional
+ segment.is_optional = true
+ end
+ end
+ end
+
+ # Construct and return a route with the given path and options.
+ def build(path, options)
+ # Wrap the path with slashes
+ path = "/#{path}" unless path[0] == ?/
+ path = "#{path}/" unless path[-1] == ?/
- def configure_components(defaults, conditions)
- components.each do |component|
- if defaults.key?(component.key) then component.default = defaults[component.key]
- elsif component.key == :action then component.default = 'index'
- elsif component.key == :id then component.default = nil
- end
-
- component.condition = conditions[component.key] if conditions.key?(component.key)
- end
+ segments = segments_for_route_path(path)
+ defaults, requirements, conditions = divide_route_options(segments, options)
+ requirements = assign_route_options(segments, defaults, requirements)
+
+ route = Route.new
+ route.segments = segments
+ route.requirements = requirements
+ route.conditions = conditions
+
+ if !route.significant_keys.include?(:action) && !route.requirements[:action]
+ route.requirements[:action] = "index"
+ route.significant_keys << :action
end
-
- def add_default_requirements
- component_keys = components.collect {|c| c.key}
- known[:action] ||= 'index' unless component_keys.include? :action
+
+ if !route.significant_keys.include?(:controller)
+ raise ArgumentError, "Illegal route: the :controller must be specified!"
end
+
+ route
+ end
end
class RouteSet #:nodoc:
- attr_reader :routes, :categories, :controller_to_selector
- def initialize
- @routes = []
- @generation_methods = Hash.new(:generate_default_path)
- end
-
- def generate(options, request_or_recall_hash = {})
- recall = request_or_recall_hash.is_a?(Hash) ? request_or_recall_hash : request_or_recall_hash.symbolized_path_parameters
- use_recall = true
-
- controller = options[:controller]
- options[:action] ||= 'index' if controller
- recall_controller = recall[:controller]
- if (recall_controller && recall_controller.include?(?/)) || (controller && controller.include?(?/))
- recall = {} if controller && controller[0] == ?/
- options[:controller] = Routing.controller_relative_to(controller, recall_controller)
+ # Mapper instances are used to build routes. The object passed to the draw
+ # block in config/routes.rb is a Mapper instance.
+ #
+ # Mapper instances have relatively few instance methods, in order to avoid
+ # clashes with named routes.
+ class Mapper #:nodoc:
+ def initialize(set)
+ @set = set
end
- options = recall.dup if options.empty? # XXX move to url_rewriter?
-
- keys_to_delete = []
- Routing.treat_hash(options, keys_to_delete)
-
- merged = recall.merge(options)
- keys_to_delete.each {|key| merged.delete key}
- expire_on = Routing.expiry_hash(options, recall)
- generate_path(merged, options, expire_on)
+ # Create an unnamed route with the provided +path+ and +options+. See
+ # SomeHelpfulUrl for an introduction to routes.
+ def connect(path, options = {})
+ @set.add_route(path, options)
+ end
+
+ def named_route(name, path, options = {})
+ @set.add_named_route(name, path, options)
+ end
+
+ # Added deprecation notice for anyone who already added a named route called "root".
+ # It'll be used as a shortcut for map.connect '' in Rails 2.0.
+ def root(*args, &proc)
+ super unless args.length >= 1 && proc.nil?
+ @set.add_named_route("root", *args)
+ end
+ deprecate :root => "(as the the label for a named route) will become a shortcut for map.connect '', so find another name"
+
+ def method_missing(route_name, *args, &proc)
+ super unless args.length >= 1 && proc.nil?
+ @set.add_named_route(route_name, *args)
+ end
end
-
- def generate_path(merged, options, expire_on)
- send @generation_methods[merged[:controller]], merged, options, expire_on
- end
- def generate_default_path(*args)
- write_generation
- generate_default_path(*args)
- end
-
- def write_generation
- method_sources = []
- @generation_methods = Hash.new(:generate_default_path)
- categorize_routes.each do |controller, routes|
- next unless routes.length < @routes.length
-
- ivar = controller.gsub('/', '__')
- method_name = "generate_path_for_#{ivar}".to_sym
- instance_variable_set "@#{ivar}", routes
- code = generation_code_for(ivar, method_name).to_s
- method_sources << code
+
+ # A NamedRouteCollection instance is a collection of named routes, and also
+ # maintains an anonymous module that can be used to install helpers for the
+ # named routes.
+ class NamedRouteCollection #:nodoc:
+ include Enumerable
+
+ attr_reader :routes, :helpers
+
+ def initialize
+ clear!
+ end
+
+ def clear!
+ @routes = {}
+ @helpers = []
- filename = "generated_code/routing/generation_for_controller_#{controller}.rb"
- eval(code, nil, filename)
-
- @generation_methods[controller.to_s] = method_name
- @generation_methods[controller.to_sym] = method_name
- end
-
- code = generation_code_for('routes', 'generate_default_path').to_s
- eval(code, nil, 'generated_code/routing/generation.rb')
-
- return (method_sources << code)
- end
-
- def recognize(request)
- string_path = request.path
- string_path.chomp! if string_path[0] == ?/
- path = string_path.split '/'
- path.shift
-
- hash = recognize_path(path)
- return recognition_failed(request) unless hash && hash['controller']
-
- controller = hash['controller']
- hash['controller'] = controller.controller_path
- request.path_parameters = hash
- controller.new
- end
- alias :recognize! :recognize
-
- def recognition_failed(request)
- raise ActionController::RoutingError, "Recognition failed for #{request.path.inspect}"
- end
-
- def write_recognition
- g = generator = CodeGeneration::RecognitionGenerator.new
- g.finish_statement = Proc.new {|hash_expr| "return #{hash_expr}"}
-
- g.def "self.recognize_path(path)" do
- each do |route|
- g << 'index = 0'
- route.write_recognition(g)
+ @module ||= Module.new
+ @module.instance_methods.each do |selector|
+ @module.send :remove_method, selector
end
end
-
- eval g.to_s, nil, 'generated/routing/recognition.rb'
- return g.to_s
- end
-
- def generation_code_for(ivar = 'routes', method_name = nil)
- routes = instance_variable_get('@' + ivar)
- key_ivar = "@keys_for_#{ivar}"
- instance_variable_set(key_ivar, routes.collect {|route| route.keys})
-
- g = generator = CodeGeneration::GenerationGenerator.new
- g.def "self.#{method_name}(merged, options, expire_on)" do
- g << 'unused_count = options.length + 1'
- g << "unused_keys = keys = options.keys"
- g << 'path = nil'
-
- routes.each_with_index do |route, index|
- g << "new_unused_keys = keys - #{key_ivar}[#{index}]"
- g << 'new_path = ('
- g.source.indent do
- if index.zero?
- g << "new_unused_count = new_unused_keys.length"
- g << "hash = merged; not_expired = true"
- route.write_generation(g.dup)
- else
- g.if "(new_unused_count = new_unused_keys.length) < unused_count" do |gp|
- gp << "hash = merged; not_expired = true"
- route.write_generation(gp)
- end
+
+ def add(name, route)
+ routes[name.to_sym] = route
+ define_named_route_methods(name, route)
+ end
+
+ def get(name)
+ routes[name.to_sym]
+ end
+
+ alias []= add
+ alias [] get
+ alias clear clear!
+
+ def each
+ routes.each { |name, route| yield name, route }
+ self
+ end
+
+ def names
+ routes.keys
+ end
+
+ def length
+ routes.length
+ end
+
+ def install(destinations = [ActionController::Base, ActionView::Base])
+ Array(destinations).each { |dest| dest.send :include, @module }
+ end
+
+ private
+ def url_helper_name(name, kind = :url)
+ :"#{name}_#{kind}"
+ end
+
+ def hash_access_name(name, kind = :url)
+ :"hash_for_#{name}_#{kind}"
+ end
+
+ def define_named_route_methods(name, route)
+ {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
+ hash = route.defaults.merge(:use_route => name).merge(opts)
+ define_hash_access route, name, kind, hash
+ define_url_helper route, name, kind, hash
+ end
+ end
+
+ def define_hash_access(route, name, kind, options)
+ selector = hash_access_name(name, kind)
+ @module.send :module_eval, <<-end_eval # We use module_eval to avoid leaks
+ def #{selector}(options = nil)
+ options ? #{options.inspect}.merge(options) : #{options.inspect}
end
- end
- g.source.lines.last << ' )' # Add the closing brace to the end line
- g.if 'new_path' do
- g << 'return new_path, [] if new_unused_count.zero?'
- g << 'path = new_path; unused_keys = new_unused_keys; unused_count = new_unused_count'
- end
+ end_eval
+ @module.send(:protected, selector)
+ helpers << selector
end
-
- g << "raise RoutingError, \"No url can be generated for the hash \#{options.inspect}\" unless path"
- g << "return path, unused_keys"
- end
-
- return g
+
+ def define_url_helper(route, name, kind, options)
+ selector = url_helper_name(name, kind)
+
+ # The segment keys used for positional paramters
+ segment_keys = route.segments.collect do |segment|
+ segment.key if segment.respond_to? :key
+ end.compact
+ hash_access_method = hash_access_name(name, kind)
+
+ @module.send :module_eval, <<-end_eval # We use module_eval to avoid leaks
+ def #{selector}(*args)
+ opts = if args.empty? || Hash === args.first
+ args.first || {}
+ else
+ # allow ordered parameters to be associated with corresponding
+ # dynamic segments, so you can do
+ #
+ # foo_url(bar, baz, bang)
+ #
+ # instead of
+ #
+ # foo_url(:bar => bar, :baz => baz, :bang => bang)
+ args.zip(#{segment_keys.inspect}).inject({}) do |h, (v, k)|
+ h[k] = v
+ h
+ end
+ end
+
+ url_for(#{hash_access_method}(opts))
+ end
+ end_eval
+ @module.send(:protected, selector)
+ helpers << selector
+ end
+
end
-
- def categorize_routes
- @categorized_routes = by_controller = Hash.new(self)
-
- known_controllers.each do |name|
- set = by_controller[name] = []
- each do |route|
- set << route if route.matches_controller? name
- end
- end
-
- @categorized_routes
- end
-
- def known_controllers
- @routes.inject([]) do |known, route|
- if (controller = route.known[:controller])
- if controller.is_a?(Regexp)
- known << controller.source.scan(%r{[\w\d/]+}).select {|word| controller =~ word}
- else known << controller
- end
- end
- known
- end.uniq
+
+ attr_accessor :routes, :named_routes
+
+ def initialize
+ self.routes = []
+ self.named_routes = NamedRouteCollection.new
end
- def reload
- NamedRoutes.clear
-
- if defined?(RAILS_ROOT) then load(File.join(RAILS_ROOT, 'config', 'routes.rb'))
- else connect(':controller/:action/:id', :action => 'index', :id => nil)
- end
-
- NamedRoutes.install
- end
-
- def connect(*args)
- new_route = Route.new(*args)
- @routes << new_route
- return new_route
+ # Subclasses and plugins may override this method to specify a different
+ # RouteBuilder instance, so that other route DSL's can be created.
+ def builder
+ @builder ||= RouteBuilder.new
end
def draw
- old_routes = @routes
- @routes = []
-
- begin yield self
- rescue
- @routes = old_routes
- raise
- end
- write_generation
- write_recognition
+ clear!
+ yield Mapper.new(self)
+ named_routes.install
end
-
- def empty?() @routes.empty? end
- def each(&block) @routes.each(&block) end
-
- # Defines a new named route with the provided name and arguments.
- # This method need only be used when you wish to use a name that a RouteSet instance
- # method exists for, such as categories.
- #
- # For example, map.categories '/categories', :controller => 'categories' will not work
- # due to RouteSet#categories.
- def named_route(name, path, hash = {})
- route = connect(path, hash)
- NamedRoutes.name_route(route, name)
+ def clear!
+ routes.clear
+ named_routes.clear
+ @combined_regexp = nil
+ @routes_by_controller = nil
+ end
+
+ def empty?
+ routes.empty?
+ end
+
+ def load!
+ Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
+ clear!
+ load_routes!
+ named_routes.install
+ end
+
+ alias reload load!
+
+ def load_routes!
+ if defined?(RAILS_ROOT) && defined?(::ActionController::Routing::Routes) && self == ::ActionController::Routing::Routes
+ load File.join("#{RAILS_ROOT}/config/routes.rb")
+ else
+ add_route ":controller/:action/:id"
+ end
+ end
+
+ def add_route(path, options = {})
+ route = builder.build(path, options)
+ routes << route
route
end
-
- def method_missing(name, *args)
- (1..2).include?(args.length) ? named_route(name, *args) : super(name, *args)
- end
-
- def extra_keys(options, recall = {})
- generate(options.dup, recall).last
- end
- end
-
- module NamedRoutes #:nodoc:
- Helpers = []
- class << self
- def clear() Helpers.clear end
- def hash_access_name(name)
- "hash_for_#{name}_url"
+ def add_named_route(name, path, options = {})
+ named_routes[name] = add_route(path, options)
+ end
+
+ def options_as_params(options)
+ # If an explicit :controller was given, always make :action explicit
+ # too, so that action expiry works as expected for things like
+ #
+ # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
+ #
+ # (the above is from the unit tests). In the above case, because the
+ # controller was explicitly given, but no action, the action is implied to
+ # be "index", not the recalled action of "show".
+ #
+ # great fun, eh?
+
+ options_as_params = options[:controller] ? { :action => "index" } : {}
+ options.each do |k, value|
+ options_as_params[k] = value.to_param
+ end
+ options_as_params
+ end
+
+ def build_expiry(options, recall)
+ recall.inject({}) do |expiry, (key, recalled_value)|
+ expiry[key] = (options.key?(key) && options[key] != recalled_value)
+ expiry
+ end
+ end
+
+ # Generate the path indicated by the arguments, and return an array of
+ # the keys that were not used to generate it.
+ def extra_keys(options, recall={})
+ generate_extras(options, recall).last
+ end
+
+ def generate_extras(options, recall={})
+ generate(options, recall, :generate_extras)
+ end
+
+ def generate(options, recall = {}, method=:generate)
+ named_route_name = options.delete(:use_route)
+ if named_route_name
+ named_route = named_routes[named_route_name]
+ options = named_route.parameter_shell.merge(options)
end
- def url_helper_name(name)
- "#{name}_url"
+ options = options_as_params(options)
+ expire_on = build_expiry(options, recall)
+
+ # if the controller has changed, make sure it changes relative to the
+ # current controller module, if any. In other words, if we're currently
+ # on admin/get, and the new controller is 'set', the new controller
+ # should really be admin/set.
+ if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
+ old_parts = recall[:controller].split('/')
+ new_parts = options[:controller].split('/')
+ parts = old_parts[0..-(new_parts.length + 1)] + new_parts
+ options[:controller] = parts.join('/')
end
-
- def known_hash_for_route(route)
- hash = route.known.symbolize_keys
- route.defaults.each do |key, value|
- hash[key.to_sym] ||= value if value
+
+ # drop the leading '/' on the controller name
+ options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
+ merged = recall.merge(options)
+
+ if named_route
+ path = named_route.generate(options, merged, expire_on)
+ if path.nil?
+ raise_named_route_error(options, named_route, named_route_name)
+ else
+ return path
end
- hash[:controller] = "/#{hash[:controller]}"
-
- hash
- end
-
- def define_hash_access_method(route, name)
- hash = known_hash_for_route(route)
- define_method(hash_access_name(name)) do |*args|
- args.first ? hash.merge(args.first) : hash
+ else
+ merged[:action] ||= 'index'
+ options[:action] ||= 'index'
+
+ controller = merged[:controller]
+ action = merged[:action]
+
+ raise RoutingError, "Need controller and action!" unless controller && action
+ # don't use the recalled keys when determining which routes to check
+ routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }]
+
+ routes.each do |route|
+ results = route.send(method, options, merged, expire_on)
+ return results if results && (!results.is_a?(Array) || results.first)
end
end
-
- def name_route(route, name)
- define_hash_access_method(route, name)
-
- module_eval(%{def #{url_helper_name name}(options = {})
- url_for(#{hash_access_name(name)}.merge(options))
- end}, "generated/routing/named_routes/#{name}.rb")
-
- protected url_helper_name(name), hash_access_name(name)
-
- Helpers << url_helper_name(name).to_sym
- Helpers << hash_access_name(name).to_sym
- Helpers.uniq!
- end
- def install(cls = ActionController::Base)
- cls.send :include, self
- if cls.respond_to? :helper_method
- Helpers.each do |helper_name|
- cls.send :helper_method, helper_name
+ raise RoutingError, "No route matches #{options.inspect}"
+ end
+
+ # try to give a helpful error message when named route generation fails
+ def raise_named_route_error(options, named_route, named_route_name)
+ diff = named_route.requirements.diff(options)
+ unless diff.empty?
+ raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}"
+ else
+ required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
+ required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
+ raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisifed?"
+ end
+ end
+
+ def recognize(request)
+ params = recognize_path(request.path, extract_request_environment(request))
+ request.path_parameters = params.with_indifferent_access
+ "#{params[:controller].camelize}Controller".constantize
+ end
+
+ def recognize_path(path, environment={})
+ path = CGI.unescape(path)
+ routes.each do |route|
+ result = route.recognize(path, environment) and return result
+ end
+ raise RoutingError, "no route found to match #{path.inspect} with #{environment.inspect}"
+ end
+
+ def routes_by_controller
+ @routes_by_controller ||= Hash.new do |controller_hash, controller|
+ controller_hash[controller] = Hash.new do |action_hash, action|
+ action_hash[action] = Hash.new do |key_hash, keys|
+ key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys)
end
end
end
end
+
+ def routes_for(options, merged, expire_on)
+ raise "Need controller and action!" unless controller && action
+ controller = merged[:controller]
+ merged = options if expire_on[:controller]
+ action = merged[:action] || 'index'
+
+ routes_by_controller[controller][action][merged.keys]
+ end
+
+ def routes_for_controller_and_action(controller, action)
+ selected = routes.select do |route|
+ route.matches_controller_and_action? controller, action
+ end
+ (selected.length == routes.length) ? routes : selected
+ end
+
+ def routes_for_controller_and_action_and_keys(controller, action, keys)
+ selected = routes.select do |route|
+ route.matches_controller_and_action? controller, action
+ end
+ selected.sort_by do |route|
+ (keys - route.significant_keys).length
+ end
+ end
+
+ # Subclasses and plugins may override this method to extract further attributes
+ # from the request, for use by route conditions and such.
+ def extract_request_environment(request)
+ { :method => request.method }
+ end
end
Routes = RouteSet.new
end
end
+
diff --git a/vendor/rails/actionpack/lib/action_controller/scaffolding.rb b/vendor/rails/actionpack/lib/action_controller/scaffolding.rb
index 2481107a..d02a31e6 100644
--- a/vendor/rails/actionpack/lib/action_controller/scaffolding.rb
+++ b/vendor/rails/actionpack/lib/action_controller/scaffolding.rb
@@ -1,7 +1,6 @@
module ActionController
module Scaffolding # :nodoc:
- def self.append_features(base)
- super
+ def self.included(base)
base.extend(ClassMethods)
end
@@ -25,25 +24,25 @@ module ActionController
# end
#
# def list
- # @entries = Entry.find_all
+ # @entries = Entry.find(:all)
# render_scaffold "list"
# end
- #
+ #
# def show
# @entry = Entry.find(params[:id])
# render_scaffold
# end
- #
+ #
# def destroy
# Entry.find(params[:id]).destroy
# redirect_to :action => "list"
# end
- #
+ #
# def new
# @entry = Entry.new
# render_scaffold
# end
- #
+ #
# def create
# @entry = Entry.new(params[:entry])
# if @entry.save
@@ -53,16 +52,16 @@ module ActionController
# render_scaffold('new')
# end
# end
- #
+ #
# def edit
# @entry = Entry.find(params[:id])
# render_scaffold
# end
- #
+ #
# def update
# @entry = Entry.find(params[:id])
# @entry.attributes = params[:entry]
- #
+ #
# if @entry.save
# flash[:notice] = "Entry was successfully updated"
# redirect_to :action => "show", :id => @entry
@@ -72,17 +71,17 @@ module ActionController
# end
# end
#
- # The render_scaffold method will first check to see if you've made your own template (like "weblog/show.rhtml" for
- # the show action) and if not, then render the generic template for that action. This gives you the possibility of using the
- # scaffold while you're building your specific application. Start out with a totally generic setup, then replace one template
+ # The render_scaffold method will first check to see if you've made your own template (like "weblog/show.rhtml" for
+ # the show action) and if not, then render the generic template for that action. This gives you the possibility of using the
+ # scaffold while you're building your specific application. Start out with a totally generic setup, then replace one template
# and one action at a time while relying on the rest of the scaffolded templates and actions.
module ClassMethods
# Adds a swath of generic CRUD actions to the controller. The +model_id+ is automatically converted into a class name unless
# one is specifically provide through options[:class_name]. So scaffold :post would use Post as the class
# and @post/@posts for the instance variables.
- #
+ #
# It's possible to use more than one scaffold in a single controller by specifying options[:suffix] = true. This will
- # make scaffold :post, :suffix => true use method names like list_post, show_post, and create_post
+ # make scaffold :post, :suffix => true use method names like list_post, show_post, and create_post
# instead of just list, show, and post. If suffix is used, then no index method is added.
def scaffold(model_id, options = {})
options.assert_valid_keys(:class_name, :suffix)
@@ -99,13 +98,13 @@ module ActionController
end
end_eval
end
-
+
module_eval <<-"end_eval", __FILE__, __LINE__
-
+
verify :method => :post, :only => [ :destroy#{suffix}, :create#{suffix}, :update#{suffix} ],
:redirect_to => { :action => :list#{suffix} }
-
-
+
+
def list#{suffix}
@#{singular_name}_pages, @#{plural_name} = paginate :#{plural_name}, :per_page => 10
render#{suffix}_scaffold "list#{suffix}"
@@ -115,17 +114,17 @@ module ActionController
@#{singular_name} = #{class_name}.find(params[:id])
render#{suffix}_scaffold
end
-
+
def destroy#{suffix}
#{class_name}.find(params[:id]).destroy
redirect_to :action => "list#{suffix}"
end
-
+
def new#{suffix}
@#{singular_name} = #{class_name}.new
render#{suffix}_scaffold
end
-
+
def create#{suffix}
@#{singular_name} = #{class_name}.new(params[:#{singular_name}])
if @#{singular_name}.save
@@ -135,12 +134,12 @@ module ActionController
render#{suffix}_scaffold('new')
end
end
-
+
def edit#{suffix}
@#{singular_name} = #{class_name}.find(params[:id])
render#{suffix}_scaffold
end
-
+
def update#{suffix}
@#{singular_name} = #{class_name}.find(params[:id])
@#{singular_name}.attributes = params[:#{singular_name}]
@@ -152,14 +151,14 @@ module ActionController
render#{suffix}_scaffold('edit')
end
end
-
+
private
def render#{suffix}_scaffold(action=nil)
action ||= caller_method_name(caller)
# logger.info ("testing template:" + "\#{self.class.controller_path}/\#{action}") if logger
-
+
if template_exists?("\#{self.class.controller_path}/\#{action}")
- render_action(action)
+ render :action => action
else
@scaffold_class = #{class_name}
@scaffold_singular_name, @scaffold_plural_name = "#{singular_name}", "#{plural_name}"
@@ -169,9 +168,9 @@ module ActionController
@template.instance_variable_set("@content_for_layout", @template.render_file(scaffold_path(action.sub(/#{suffix}$/, "")), false))
if !active_layout.nil?
- render_file(active_layout, nil, true)
+ render :file => active_layout, :use_full_path => true
else
- render_file(scaffold_path("layout"))
+ render :file => scaffold_path('layout')
end
end
end
@@ -179,12 +178,12 @@ module ActionController
def scaffold_path(template_name)
File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
end
-
+
def caller_method_name(caller)
caller.first.scan(/`(.*)'/).first.first # ' ruby-mode
end
end_eval
- end
+ end
end
end
end
diff --git a/vendor/rails/actionpack/lib/action_controller/session/active_record_store.rb b/vendor/rails/actionpack/lib/action_controller/session/active_record_store.rb
index 5f3b960b..cbd5422a 100644
--- a/vendor/rails/actionpack/lib/action_controller/session/active_record_store.rb
+++ b/vendor/rails/actionpack/lib/action_controller/session/active_record_store.rb
@@ -5,6 +5,8 @@ require 'base64'
class CGI
class Session
+ attr_reader :data
+
# Return this session's underlying Session instance. Useful for the DB-backed session stores.
def model
@dbman.model if @dbman
diff --git a/vendor/rails/actionpack/lib/action_controller/session/drb_store.rb b/vendor/rails/actionpack/lib/action_controller/session/drb_store.rb
index 8ea23e8f..4feb2636 100644
--- a/vendor/rails/actionpack/lib/action_controller/session/drb_store.rb
+++ b/vendor/rails/actionpack/lib/action_controller/session/drb_store.rb
@@ -26,6 +26,10 @@ class CGI #:nodoc:all
def delete
@@session_data.delete(@session_id)
end
+
+ def data
+ @@session_data[@session_id]
+ end
end
end
end
diff --git a/vendor/rails/actionpack/lib/action_controller/session/mem_cache_store.rb b/vendor/rails/actionpack/lib/action_controller/session/mem_cache_store.rb
index a7076fcd..e62c0ef9 100644
--- a/vendor/rails/actionpack/lib/action_controller/session/mem_cache_store.rb
+++ b/vendor/rails/actionpack/lib/action_controller/session/mem_cache_store.rb
@@ -93,6 +93,10 @@ begin
end
@session_data = {}
end
+
+ def data
+ @session_data
+ end
end
end
end
diff --git a/vendor/rails/actionpack/lib/action_controller/session_management.rb b/vendor/rails/actionpack/lib/action_controller/session_management.rb
index 408ef279..60b0cd5f 100644
--- a/vendor/rails/actionpack/lib/action_controller/session_management.rb
+++ b/vendor/rails/actionpack/lib/action_controller/session_management.rb
@@ -8,12 +8,9 @@ module ActionController #:nodoc:
module SessionManagement #:nodoc:
def self.included(base)
base.extend(ClassMethods)
-
- base.send :alias_method, :process_without_session_management_support, :process
- base.send :alias_method, :process, :process_with_session_management_support
-
- base.send :alias_method, :process_cleanup_without_session_management_support, :process_cleanup
- base.send :alias_method, :process_cleanup, :process_cleanup_with_session_management_support
+
+ base.send :alias_method_chain, :process, :session_management_support
+ base.send :alias_method_chain, :process_cleanup, :session_management_support
end
module ClassMethods
@@ -123,16 +120,16 @@ module ActionController #:nodoc:
end
def process_cleanup_with_session_management_support
- process_cleanup_without_session_management_support
clear_persistent_model_associations
+ process_cleanup_without_session_management_support
end
# Clear cached associations in session data so they don't overflow
# the database field. Only applies to ActiveRecordStore since there
# is not a standard way to iterate over session data.
def clear_persistent_model_associations #:doc:
- if defined?(@session) && @session.instance_variables.include?('@data')
- session_data = @session.instance_variable_get('@data')
+ if defined?(@_session) && @_session.respond_to?(:data)
+ session_data = @_session.data
if session_data && session_data.respond_to?(:each_value)
session_data.each_value do |obj|
diff --git a/vendor/rails/actionpack/lib/action_controller/streaming.rb b/vendor/rails/actionpack/lib/action_controller/streaming.rb
index 618888d0..484913c7 100644
--- a/vendor/rails/actionpack/lib/action_controller/streaming.rb
+++ b/vendor/rails/actionpack/lib/action_controller/streaming.rb
@@ -69,17 +69,8 @@ module ActionController #:nodoc:
logger.info "Streaming file #{path}" unless logger.nil?
len = options[:buffer_size] || 4096
File.open(path, 'rb') do |file|
- if output.respond_to?(:syswrite)
- begin
- while true
- output.syswrite(file.sysread(len))
- end
- rescue EOFError
- end
- else
- while buf = file.read(len)
- output.write(buf)
- end
+ while buf = file.read(len)
+ output.write(buf)
end
end
}
@@ -97,8 +88,8 @@ module ActionController #:nodoc:
# * :type - specifies an HTTP content type.
# Defaults to 'application/octet-stream'.
# * :disposition - specifies whether the file will be shown inline or downloaded.
- # * :status - specifies the status code to send with the response. Defaults to '200 OK'.
# Valid values are 'inline' and 'attachment' (default).
+ # * :status - specifies the status code to send with the response. Defaults to '200 OK'.
#
# Generic data download:
# send_data buffer
@@ -125,10 +116,10 @@ module ActionController #:nodoc:
end
disposition = options[:disposition].dup || 'attachment'
-
+
disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
- @headers.update(
+ headers.update(
'Content-Length' => options[:length],
'Content-Type' => options[:type].strip, # fixes a problem with extra '\r' with some browsers
'Content-Disposition' => disposition,
@@ -141,7 +132,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'
+ headers['Cache-Control'] = 'private' if headers['Cache-Control'] == 'no-cache'
end
end
end
diff --git a/vendor/rails/actionpack/lib/action_controller/templates/rescues/_request_and_response.rhtml b/vendor/rails/actionpack/lib/action_controller/templates/rescues/_request_and_response.rhtml
index 81995320..f2f5732e 100644
--- a/vendor/rails/actionpack/lib/action_controller/templates/rescues/_request_and_response.rhtml
+++ b/vendor/rails/actionpack/lib/action_controller/templates/rescues/_request_and_response.rhtml
@@ -8,10 +8,10 @@
<% if false %>
<% begin %>
- <%= form_tag(@request.request_uri, "method" => @request.method) %>
+ <%= form_tag(request.request_uri, "method" => request.method) %>
- <% for key, values in @params %>
+ <% for key, values in params %>
<% next if key == "BP-RETRY" %>
<% for value in Array(values) %>
@@ -26,7 +26,7 @@
<% end %>
<%
- request_parameters_without_action = @request.parameters.clone
+ request_parameters_without_action = request.parameters.clone
request_parameters_without_action.delete("action")
request_parameters_without_action.delete("controller")
@@ -37,8 +37,8 @@
Response
-Headers: <%=h @response.headers.inspect.gsub(/,/, ",\n") %>
+Headers: <%=h response ? response.headers.inspect.gsub(/,/, ",\n") : "None" %>
diff --git a/vendor/rails/actionpack/lib/action_controller/templates/rescues/diagnostics.rhtml b/vendor/rails/actionpack/lib/action_controller/templates/rescues/diagnostics.rhtml
index fa48b62f..e9faacef 100644
--- a/vendor/rails/actionpack/lib/action_controller/templates/rescues/diagnostics.rhtml
+++ b/vendor/rails/actionpack/lib/action_controller/templates/rescues/diagnostics.rhtml
@@ -1,7 +1,7 @@
<%=h @exception.class.to_s %>
- <% if @request.parameters['controller'] %>
- in <%=h @request.parameters['controller'].humanize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
+ <% if request.parameters['controller'] %>
+ in <%=h request.parameters['controller'].humanize %>Controller<% if request.parameters['action'] %>#<%=h request.parameters['action'] %><% end %>
<% end %>
<%=h @exception.clean_message %>
diff --git a/vendor/rails/actionpack/lib/action_controller/templates/rescues/routing_error.rhtml b/vendor/rails/actionpack/lib/action_controller/templates/rescues/routing_error.rhtml
index bf7dcb23..ccfa858c 100644
--- a/vendor/rails/actionpack/lib/action_controller/templates/rescues/routing_error.rhtml
+++ b/vendor/rails/actionpack/lib/action_controller/templates/rescues/routing_error.rhtml
@@ -1,10 +1,10 @@
Routing Error
<%=h @exception.message %>
-
<%=h route.inspect.gsub('\\', '') %>
failed because <%=h reason.downcase %><%=h route.inspect.gsub('\\', '') %>
failed because <%=h reason.downcase %>diff --git a/vendor/rails/actionpack/lib/action_controller/templates/scaffolds/list.rhtml b/vendor/rails/actionpack/lib/action_controller/templates/scaffolds/list.rhtml index fad2aab7..fea23dc6 100644 --- a/vendor/rails/actionpack/lib/action_controller/templates/scaffolds/list.rhtml +++ b/vendor/rails/actionpack/lib/action_controller/templates/scaffolds/list.rhtml @@ -14,7 +14,7 @@ <% end %>
#{h(object.to_yaml).gsub(" ", " ")}" - rescue Object => e + rescue Exception => e # errors from Marshal or YAML # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback "
#{h(object.inspect)}
"
end
diff --git a/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb
index 7c8748d6..772753b7 100644
--- a/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb
@@ -142,11 +142,13 @@ module ActionView
#
# Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base.
# Like collection_select and datetime_select.
- def fields_for(object_name, *args, &proc)
+ def fields_for(object_name, *args, &block)
raise ArgumentError, "Missing block" unless block_given?
options = args.last.is_a?(Hash) ? args.pop : {}
object = args.first
- yield((options[:builder] || FormBuilder).new(object_name, object, self, options, proc))
+
+ builder = options[:builder] || ActionView::Base.default_form_builder
+ yield builder.new(object_name, object, self, options, block)
end
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -238,7 +240,11 @@ module ActionView
@template_object, @local_binding = template_object, local_binding
@object = object
if @object_name.sub!(/\[\]$/,"")
- @auto_index = @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}").id_before_type_cast
+ if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:id_before_type_cast)
+ @auto_index = object.id_before_type_cast
+ else
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to id_before_type_cast: #{object.inspect}"
+ end
end
end
@@ -250,7 +256,7 @@ module ActionView
options.delete("size")
end
options["type"] = field_type
- options["value"] ||= value_before_type_cast unless field_type == "file"
+ options["value"] ||= value_before_type_cast(object) unless field_type == "file"
add_default_name_and_id(options)
tag("input", options)
end
@@ -259,9 +265,15 @@ module ActionView
options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
options["type"] = "radio"
options["value"] = tag_value
- options["checked"] = "checked" if value.to_s == tag_value.to_s
+ if options.has_key?("checked")
+ cv = options.delete "checked"
+ checked = cv == true || cv == "checked"
+ else
+ checked = self.class.radio_button_checked?(value(object), tag_value)
+ end
+ options["checked"] = "checked" if checked
pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
- options["id"] = @auto_index ?
+ options["id"] ||= defined?(@auto_index) ?
"#{@object_name}_#{@auto_index}_#{@method_name}_#{pretty_tag_value}" :
"#{@object_name}_#{@method_name}_#{pretty_tag_value}"
add_default_name_and_id(options)
@@ -271,14 +283,82 @@ module ActionView
def to_text_area_tag(options = {})
options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
add_default_name_and_id(options)
- content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast), options)
+
+ if size = options.delete("size")
+ options["cols"], options["rows"] = size.split("x")
+ end
+
+ content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast(object)), options)
end
def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
options = options.stringify_keys
options["type"] = "checkbox"
options["value"] = checked_value
- checked = case value
+ if options.has_key?("checked")
+ cv = options.delete "checked"
+ checked = cv == true || cv == "checked"
+ else
+ checked = self.class.check_box_checked?(value(object), checked_value)
+ end
+ options["checked"] = "checked" if checked
+ add_default_name_and_id(options)
+ tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value)
+ end
+
+ def to_date_tag()
+ defaults = DEFAULT_DATE_OPTIONS.dup
+ date = value(object) || Date.today
+ options = Proc.new { |position| defaults.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
+ html_day_select(date, options.call(3)) +
+ html_month_select(date, options.call(2)) +
+ html_year_select(date, options.call(1))
+ end
+
+ def to_boolean_select_tag(options = {})
+ options = options.stringify_keys
+ add_default_name_and_id(options)
+ value = value(object)
+ tag_text = ""
+ end
+
+ def to_content_tag(tag_name, options = {})
+ content_tag(tag_name, value(object), options)
+ end
+
+ def object
+ @object || @template_object.instance_variable_get("@#{@object_name}")
+ end
+
+ def value(object)
+ self.class.value(object, @method_name)
+ end
+
+ def value_before_type_cast(object)
+ self.class.value_before_type_cast(object, @method_name)
+ end
+
+ class << self
+ def value(object, method_name)
+ object.send method_name unless object.nil?
+ end
+
+ def value_before_type_cast(object, method_name)
+ unless object.nil?
+ object.respond_to?(method_name + "_before_type_cast") ?
+ object.send(method_name + "_before_type_cast") :
+ object.send(method_name)
+ end
+ end
+
+ def check_box_checked?(value, checked_value)
+ case value
when TrueClass, FalseClass
value
when NilClass
@@ -290,55 +370,10 @@ module ActionView
else
value.to_i != 0
end
- if checked || options["checked"] == "checked"
- options["checked"] = "checked"
- else
- options.delete("checked")
end
- add_default_name_and_id(options)
- tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value)
- end
-
- def to_date_tag()
- defaults = DEFAULT_DATE_OPTIONS.dup
- date = value || Date.today
- options = Proc.new { |position| defaults.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
- html_day_select(date, options.call(3)) +
- html_month_select(date, options.call(2)) +
- html_year_select(date, options.call(1))
- end
-
- def to_boolean_select_tag(options = {})
- options = options.stringify_keys
- add_default_name_and_id(options)
- tag_text = ""
- end
-
- def to_content_tag(tag_name, options = {})
- content_tag(tag_name, value, options)
- end
-
- def object
- @object || @template_object.instance_variable_get("@#{@object_name}")
- end
-
- def value
- unless object.nil?
- object.send(@method_name)
- end
- end
-
- def value_before_type_cast
- unless object.nil?
- object.respond_to?(@method_name + "_before_type_cast") ?
- object.send(@method_name + "_before_type_cast") :
- object.send(@method_name)
+
+ def radio_button_checked?(value, checked_value)
+ value.to_s == checked_value.to_s
end
end
@@ -348,11 +383,11 @@ module ActionView
options["name"] ||= tag_name_with_index(options["index"])
options["id"] ||= tag_id_with_index(options["index"])
options.delete("index")
- elsif @auto_index
+ elsif defined?(@auto_index)
options["name"] ||= tag_name_with_index(@auto_index)
options["id"] ||= tag_id_with_index(@auto_index)
else
- options["name"] ||= tag_name
+ options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
options["id"] ||= tag_id
end
end
@@ -379,7 +414,7 @@ module ActionView
class_inheritable_accessor :field_helpers
self.field_helpers = (FormHelper.instance_methods - ['form_for'])
- attr_accessor :object_name, :object
+ attr_accessor :object_name, :object, :options
def initialize(object_name, object, template, options, proc)
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
@@ -403,4 +438,9 @@ module ActionView
end
end
end
+
+ class Base
+ cattr_accessor :default_form_builder
+ self.default_form_builder = ::ActionView::Helpers::FormBuilder
+ end
end
diff --git a/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb
index 53b39305..6b3da64f 100644
--- a/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -26,7 +26,7 @@ module ActionView
#
# Another common case is a select tag for an belongs_to-associated object. For example,
#
- # select("post", "person_id", Person.find_all.collect {|p| [ p.name, p.id ] })
+ # select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] })
#
# could become:
#
@@ -43,7 +43,7 @@ module ActionView
# See options_for_select for the required format of the choices parameter.
#
# Example with @post.person_id => 1:
- # select("post", "person_id", Person.find_all.collect {|p| [ p.name, p.id ] }, { :include_blank => true })
+ # select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, { :include_blank => true })
#
# could become:
#
@@ -113,7 +113,6 @@ module ActionView
options_for_select = container.inject([]) do |options, element|
if !element.is_a?(String) and element.respond_to?(:first) and element.respond_to?(:last)
- is_selected = ( (selected.respond_to?(:include?) ? selected.include?(element.last) : element.last == selected) )
is_selected = ( (selected.respond_to?(:include?) && !selected.is_a?(String) ? selected.include?(element.last) : element.last == selected) )
if is_selected
options << ""
@@ -121,7 +120,6 @@ module ActionView
options << ""
end
else
- is_selected = ( (selected.respond_to?(:include?) ? selected.include?(element) : element == selected) )
is_selected = ( (selected.respond_to?(:include?) && !selected.is_a?(String) ? selected.include?(element) : element == selected) )
options << ((is_selected) ? "" : "")
end
@@ -299,13 +297,15 @@ module ActionView
def to_select_tag(choices, options, html_options)
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
+ value = value(object)
selected_value = options.has_key?(:selected) ? options[:selected] : value
- content_tag("select", add_options(options_for_select(choices, selected_value), options, value), html_options)
+ content_tag("select", add_options(options_for_select(choices, selected_value), options, selected_value), html_options)
end
def to_collection_select_tag(collection, value_method, text_method, options, html_options)
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
+ value = value(object)
content_tag(
"select", add_options(options_from_collection_for_select(collection, value_method, text_method, value), options, value), html_options
)
@@ -314,12 +314,14 @@ module ActionView
def to_country_select_tag(priority_countries, options, html_options)
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
+ value = value(object)
content_tag("select", add_options(country_options_for_select(value, priority_countries), options, value), html_options)
end
def to_time_zone_select_tag(priority_zones, options, html_options)
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
+ value = value(object)
content_tag("select",
add_options(
time_zone_options_for_select(value, priority_zones, options[:model] || TimeZone),
diff --git a/vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb
index c7a5d1bb..e2e796e7 100644
--- a/vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -12,14 +12,49 @@ module ActionView
# Starts a form tag that points the action to an url configured with url_for_options just like
# ActionController::Base#url_for. The method for the form defaults to POST.
#
+ # Examples:
+ # * form_tag('/posts') =>
+ # * form_tag('/posts/1', :method => :put) =>
+ # * form_tag('/upload', :multipart => true) =>
+ #
+ # ERb example:
+ # <% form_tag '/posts' do -%>
+ # ' + html.stripScripts() + ' |
New product!
")) %> - # - # <% replacement_function = update_element_function("products") do %> - #Product 1
- #Product 2
- # <% end %> - # <%= javascript_tag(replacement_function) %> - # - # This method can also be used in combination with remote method call - # where the result is evaluated afterwards to cause multiple updates on - # a page. Example: - # - # # Calling view - # <%= form_remote_tag :url => { :action => "buy" }, - # :complete => evaluate_remote_response %> - # all the inputs here... - # - # # Controller action - # def buy - # @product = Product.find(1) - # end - # - # # Returning view - # <%= update_element_function( - # "cart", :action => :update, :position => :bottom, - # :content => "New Product: #{@product.name}
")) %> - # <% update_element_function("status", :binding => binding) do %> - # You've bought a new product! - # <% end %> - # - # Notice how the second call doesn't need to be in an ERb output block - # since it uses a block and passes in the binding to render directly. - # This trick will however only work in ERb (not Builder or other - # template forms). - # - # See also JavaScriptGenerator and update_page. - def update_element_function(element_id, options = {}, &block) - content = escape_javascript(options[:content] || '') - content = escape_javascript(capture(&block)) if block - - javascript_function = case (options[:action] || :update) - when :update - if options[:position] - "new Insertion.#{options[:position].to_s.camelize}('#{element_id}','#{content}')" - else - "$('#{element_id}').innerHTML = '#{content}'" - end - - when :empty - "$('#{element_id}').innerHTML = ''" - - when :remove - "Element.remove('#{element_id}')" - - else - raise ArgumentError, "Invalid action, choose one of :update, :remove, :empty" - end - - javascript_function << ";\n" - options[:binding] ? concat(javascript_function, options[:binding]) : javascript_function - end - # Returns 'eval(request.responseText)' which is the JavaScript function # that form_remote_tag can call in :complete to evaluate a multiple # update return document using update_element_function calls. @@ -289,7 +224,7 @@ module ActionView javascript_options = options_for_ajax(options) update = '' - if options[:update] and options[:update].is_a?Hash + if options[:update] && options[:update].is_a?(Hash) update = [] update << "success:'#{options[:update][:success]}'" if options[:update][:success] update << "failure:'#{options[:update][:failure]}'" if options[:update][:failure] @@ -303,7 +238,7 @@ module ActionView "new Ajax.Updater(#{update}, " url_options = options[:url] - url_options = url_options.merge(:escape => false) if url_options.is_a? Hash + url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash) function << "'#{url_for(url_options)}'" function << ", #{javascript_options})" @@ -438,7 +373,7 @@ module ActionView if ActionView::Base.debug_rjs source = javascript.dup javascript.replace "try {\n#{source}\n} catch (e) " - javascript << "{ alert('RJS error:\\n\\n' + e.toString()); alert('#{source.gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}'); throw e }" + javascript << "{ alert('RJS error:\\n\\n' + e.toString()); alert('#{source.gsub('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}'); throw e }" end end end @@ -453,6 +388,12 @@ module ActionView JavaScriptElementProxy.new(self, id) end + # Returns an object whose #to_json evaluates to +code+. Use this to pass a literal JavaScript + # expression as an argument to another JavaScriptGenerator method. + def literal(code) + ActiveSupport::JSON::Variable.new(code.to_s) + end + # Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be # used for further method calls. Examples: # @@ -526,7 +467,7 @@ module ActionView # # # Replace the DOM element having ID 'person-45' with the # # 'person' partial for the appropriate object. - # replace_html 'person-45', :partial => 'person', :object => @person + # replace 'person-45', :partial => 'person', :object => @person # # This allows the same partial that is used for the +insert_html+ to # be also used for the input to +replace+ without resorting to @@ -550,22 +491,22 @@ module ActionView # Removes the DOM elements with the given +ids+ from the page. def remove(*ids) - record "#{javascript_object_for(ids)}.each(Element.remove)" + loop_on_multiple_args 'Element.remove', ids end # Shows hidden DOM elements with the given +ids+. def show(*ids) - call 'Element.show', *ids + loop_on_multiple_args 'Element.show', ids end # Hides the visible DOM elements with the given +ids+. def hide(*ids) - call 'Element.hide', *ids + loop_on_multiple_args 'Element.hide', ids end # Toggles the visibility of the DOM elements with the given +ids+. def toggle(*ids) - call 'Element.toggle', *ids + loop_on_multiple_args 'Element.toggle', ids end # Displays an alert dialog with the given +message+. @@ -573,16 +514,18 @@ module ActionView call 'alert', message end - # Redirects the browser to the given +location+, in the same form as - # +url_for+. + # Redirects the browser to the given +location+, in the same form as +url_for+. def redirect_to(location) assign 'window.location.href', @context.url_for(location) end - # Calls the JavaScript +function+, optionally with the given - # +arguments+. - def call(function, *arguments) - record "#{function}(#{arguments_for_call(arguments)})" + # Calls the JavaScript +function+, optionally with the given +arguments+. + # + # If a block is given, the block will be passed to a new JavaScriptGenerator; + # the resulting JavaScript code will then be wrapped inside function() { ... } + # and passed as the called function's final argument. + def call(function, *arguments, &block) + record "#{function}(#{arguments_for_call(arguments, block)})" end # Assigns the JavaScript +variable+ the given +value+. @@ -633,12 +576,18 @@ module ActionView end private + def loop_on_multiple_args(method, ids) + record(ids.size>1 ? + "#{javascript_object_for(ids)}.each(#{method})" : + "#{method}(#{ids.first.to_json})") + end + def page self end def record(line) - returning line = "#{line.to_s.chomp.gsub /\;$/, ''};" do + returning line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" do self << line end end @@ -653,10 +602,16 @@ module ActionView object.respond_to?(:to_json) ? object.to_json : object.inspect end - def arguments_for_call(arguments) + def arguments_for_call(arguments, block = nil) + arguments << block_to_function(block) if block arguments.map { |argument| javascript_object_for(argument) }.join ', ' end + def block_to_function(block) + generator = self.class.new(@context, &block) + literal("function() { #{generator.to_s} }") + end + def method_missing(method, *arguments) JavaScriptProxy.new(self, method.to_s.camelize) end @@ -673,8 +628,11 @@ module ActionView # Works like update_page but wraps the generated JavaScript in a ') + # => <script> do_nasty_stuff() </script> + # sanitize('Click here for $100') + # => Click here for $100 def sanitize(html) # only do this if absolutely necessary if html.index("<") @@ -192,8 +233,8 @@ module ActionView else if node.closing != :close node.attributes.delete_if { |attr,v| attr =~ VERBOTEN_ATTRS } - if node.attributes["href"] =~ /^javascript:/i - node.attributes.delete "href" + %w(href src).each do |attr| + node.attributes.delete attr if node.attributes[attr] =~ /^javascript:/i end end node.to_s @@ -209,11 +250,11 @@ module ActionView html end - # Strips all HTML tags from the input, including comments. This uses the html-scanner - # tokenizer and so it's HTML parsing ability is limited by that of html-scanner. - # - # Returns the tag free text. - def strip_tags(html) + # Strips all HTML tags from the +html+, including comments. This uses the + # html-scanner tokenizer and so its HTML parsing ability is limited by + # that of html-scanner. + def strip_tags(html) + return html if html.blank? if html.index("<") text = "" tokenizer = HTML::Tokenizer.new(html) @@ -231,32 +272,33 @@ module ActionView end end - # Returns a Cycle object whose to_s value cycles through items of an - # array every time it is called. This can be used to alternate classes - # for table rows: + # Creates a Cycle object whose _to_s_ method cycles through elements of an + # array every time it is called. This can be used for example, to alternate + # classes for table rows: # - # <%- for item in @items do -%> - #