aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/rails-2.0.2/actionpack
diff options
context:
space:
mode:
authorfrancis <francis>2008-01-23 01:48:14 +0000
committerfrancis <francis>2008-01-23 01:48:14 +0000
commit60eaae4f7df1f1dae91defb87d3707451c359cf4 (patch)
treee74835c37779a2f094e810960cda07b99a75330e /vendor/rails-2.0.2/actionpack
parent71d22c740302e1f83bbbd89b229734ea9c67493c (diff)
Freeze in rails 2.0.2 (Am I going to regret having this beast in CVS?)
Diffstat (limited to 'vendor/rails-2.0.2/actionpack')
-rw-r--r--vendor/rails-2.0.2/actionpack/CHANGELOG4606
-rw-r--r--vendor/rails-2.0.2/actionpack/MIT-LICENSE21
-rw-r--r--vendor/rails-2.0.2/actionpack/README469
-rw-r--r--vendor/rails-2.0.2/actionpack/RUNNING_UNIT_TESTS24
-rw-r--r--vendor/rails-2.0.2/actionpack/Rakefile153
-rw-r--r--vendor/rails-2.0.2/actionpack/install.rb30
-rwxr-xr-xvendor/rails-2.0.2/actionpack/lib/action_controller.rb79
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/assertions.rb69
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/dom_assertions.rb39
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/model_assertions.rb19
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/response_assertions.rb166
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/routing_assertions.rb143
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/selector_assertions.rb640
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/tag_assertions.rb130
-rwxr-xr-xvendor/rails-2.0.2/actionpack/lib/action_controller/base.rb1295
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/benchmarking.rb94
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/caching.rb683
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext.rb16
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/cookie.rb106
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/query_extension.rb22
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/session.rb73
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/stdinput.rb23
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_process.rb221
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/components.rb165
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/cookies.rb84
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/dispatcher.rb195
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/filters.rb767
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/flash.rb177
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/helpers.rb204
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/http_authentication.rb126
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/integration.rb581
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/layout.rb326
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/mime_responds.rb170
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/mime_type.rb163
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/mime_types.rb20
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/polymorphic_routes.rb88
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/record_identifier.rb91
-rwxr-xr-xvendor/rails-2.0.2/actionpack/lib/action_controller/request.rb730
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/request_forgery_protection.rb132
-rwxr-xr-xvendor/rails-2.0.2/actionpack/lib/action_controller/request_profiler.rb138
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/rescue.rb258
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/resources.rb529
-rwxr-xr-xvendor/rails-2.0.2/actionpack/lib/action_controller/response.rb76
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/routing.rb1499
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/routing_optimisation.rb119
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/session/active_record_store.rb336
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/session/cookie_store.rb164
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/session/drb_server.rb32
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/session/drb_store.rb35
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/session/mem_cache_store.rb98
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/session_management.rb151
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/status_codes.rb88
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/streaming.rb141
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/_request_and_response.erb24
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/_trace.erb26
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/diagnostics.erb11
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/layout.erb29
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/missing_template.erb2
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/routing_error.erb10
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/template_error.erb21
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/unknown_action.erb2
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/test_case.rb53
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/test_process.rb520
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/url_rewriter.rb135
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb68
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb530
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb173
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb828
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb105
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/version.rb11
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_controller/verification.rb114
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_pack.rb24
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_pack/version.rb9
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view.rb37
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/base.rb642
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/compiled_templates.rb69
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/active_record_helper.rb255
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/asset_tag_helper.rb548
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/atom_feed_helper.rb111
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/benchmark_helper.rb31
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/cache_helper.rb39
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/capture_helper.rb162
-rwxr-xr-xvendor/rails-2.0.2/actionpack/lib/action_view/helpers/date_helper.rb689
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/debug_helper.rb31
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/form_helper.rb689
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/form_options_helper.rb425
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/form_tag_helper.rb432
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascript_helper.rb217
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/controls.js963
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/dragdrop.js972
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/effects.js1120
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/prototype.js4225
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/number_helper.rb179
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/prototype_helper.rb1268
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/record_identification_helper.rb20
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/record_tag_helper.rb59
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/sanitize_helper.rb223
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/scriptaculous_helper.rb199
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/tag_helper.rb133
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/text_helper.rb479
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/helpers/url_helper.rb524
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/partials.rb200
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/template_error.rb102
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/template_handler.rb17
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/template_handlers/builder.rb19
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/template_handlers/erb.rb21
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/action_view/template_handlers/rjs.rb14
-rw-r--r--vendor/rails-2.0.2/actionpack/lib/actionpack.rb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/abstract_unit.rb36
-rw-r--r--vendor/rails-2.0.2/actionpack/test/action_view_test.rb44
-rw-r--r--vendor/rails-2.0.2/actionpack/test/active_record_unit.rb108
-rw-r--r--vendor/rails-2.0.2/actionpack/test/activerecord/active_record_store_test.rb142
-rw-r--r--vendor/rails-2.0.2/actionpack/test/activerecord/render_partial_with_record_identification_test.rb74
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/action_pack_assertions_test.rb492
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/addresses_render_test.rb43
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/assert_select_test.rb682
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/base_test.rb134
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/benchmark_test.rb33
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/caching_test.rb349
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/capture_test.rb89
-rwxr-xr-xvendor/rails-2.0.2/actionpack/test/controller/cgi_test.rb115
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/components_test.rb140
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/content_type_test.rb139
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb0
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb0
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb0
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/cookie_test.rb135
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/custom_handler_test.rb41
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb37
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/dispatcher_test.rb123
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/fake_controllers.rb16
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/fake_models.rb5
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/filter_params_test.rb43
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/filters_test.rb856
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/flash_test.rb146
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/fragment_store_setting_test.rb47
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/helper_test.rb206
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/html-scanner/document_test.rb124
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/html-scanner/node_test.rb69
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/html-scanner/sanitizer_test.rb250
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/html-scanner/tag_node_test.rb239
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/html-scanner/text_node_test.rb51
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/html-scanner/tokenizer_test.rb125
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/http_authentication_test.rb54
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/integration_test.rb255
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/layout_test.rb239
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/mime_responds_test.rb506
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/mime_type_test.rb55
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/new_render_test.rb832
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/polymorphic_routes_test.rb98
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/record_identifier_test.rb103
-rwxr-xr-xvendor/rails-2.0.2/actionpack/test/controller/redirect_test.rb258
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/render_test.rb464
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/request_forgery_protection_test.rb217
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/request_test.rb836
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/rescue_test.rb501
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/resources_test.rb787
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/routing_test.rb2203
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/selector_test.rb628
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/send_file_test.rb127
-rwxr-xr-xvendor/rails-2.0.2/actionpack/test/controller/session/cookie_store_test.rb246
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/session/mem_cache_store_test.rb182
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/session_fixation_test.rb89
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/session_management_test.rb156
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/test_test.rb623
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/url_rewriter_test.rb246
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/verification_test.rb253
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/view_paths_test.rb137
-rw-r--r--vendor/rails-2.0.2/actionpack/test/controller/webservice_test.rb184
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/addresses/list.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/companies.yml24
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/company.rb9
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_for_rhtml.rhtml1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_for_rjs.rjs1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_for_rxml.rxml1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/db_definitions/sqlite.sql43
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/developer.rb9
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/developers.yml21
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/developers_projects.yml13
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/fun/games/hello_world.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/helpers/abc_helper.rb5
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/helpers/fun/games_helper.rb3
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/helpers/fun/pdf_helper.rb3
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/alt/hello.rhtml1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/item.rhtml1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/layout_test.rhtml1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/third_party_template_library.mab1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/views/hello.rhtml1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/layouts/builder.builder3
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/layouts/standard.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/layouts/talk_from_action.erb2
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/layouts/yield.erb2
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/multipart/binary_filebin0 -> 19820 bytes
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/multipart/bracketed_param5
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/multipart/large_text_file10
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/multipart/mixed_filesbin0 -> 19937 bytes
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/multipart/mona_lisa.jpgbin0 -> 159528 bytes
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/multipart/single_parameter5
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/multipart/text_file10
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/override/test/hello_world.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/override2/layouts/test/sub.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/post_test/layouts/post.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/post_test/layouts/super_post.iphone.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/post_test/post/index.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/post_test/post/index.iphone.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/post_test/super_post/index.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/post_test/super_post/index.iphone.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/project.rb3
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/projects.yml7
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/public/404.html1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/public/500.html1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/public/images/rails.pngbin0 -> 1787 bytes
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/public/javascripts/application.js0
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/public/javascripts/bank.js1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/public/javascripts/robber.js1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/public/stylesheets/bank.css1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/public/stylesheets/robber.css1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/replies.yml15
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/reply.rb6
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/all_types_with_layout.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/all_types_with_layout.js.rjs1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/iphone_with_html_response_type.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/layouts/missing.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/layouts/standard.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/layouts/standard.iphone.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults.js.rjs1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults.xml.builder1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/scope/test/modgreet.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/_customer.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/_customer_greeting.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/_hash_greeting.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/_hash_object.erb2
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/_hello.builder1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/_layout_for_partial.html.erb3
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial.js.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial_for_use_in_layout.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial_only.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/_person.erb2
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/action_talk_to_layout.erb2
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/block_content_for.erb2
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/calling_partial_with_layout.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/capturing.erb4
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/content_for.erb2
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/content_for_concatenated.erb3
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/content_for_with_parameter.erb2
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/delete_with_js.rjs2
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/dot.directory/render_file_with_ivar.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/enum_rjs_test.rjs6
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/erb_content_for.erb2
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_html_erb.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_xml_erb.builder1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_xml_erb.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_xml_erb.xml.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/greeting.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/hello.builder4
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world_container.builder3
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world_from_rxml.builder4
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world_with_layout_false.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_xml_world.builder11
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/list.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/non_erb_block_content_for.builder4
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/potential_conflicts.erb4
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/render_file_with_ivar.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/render_file_with_locals.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/render_to_string_test.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/update_element_with_capture.erb9
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/test/using_layout_around_block.html.erb1
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/topic.rb3
-rw-r--r--vendor/rails-2.0.2/actionpack/test/fixtures/topics.yml22
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/active_record_helper_test.rb251
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/asset_tag_helper_test.rb438
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/atom_feed_helper_test.rb101
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/benchmark_helper_test.rb72
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/compiled_templates_test.rb193
-rwxr-xr-xvendor/rails-2.0.2/actionpack/test/template/date_helper_test.rb1438
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/erb_util_test.rb56
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/form_helper_test.rb792
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/form_options_helper_test.rb1296
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/form_tag_helper_test.rb238
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/javascript_helper_test.rb115
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/number_helper_test.rb93
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/prototype_helper_test.rb627
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/sanitize_helper_test.rb49
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/scriptaculous_helper_test.rb96
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/tag_helper_test.rb80
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/text_helper_test.rb335
-rw-r--r--vendor/rails-2.0.2/actionpack/test/template/url_helper_test.rb536
-rw-r--r--vendor/rails-2.0.2/actionpack/test/testing_sandbox.rb11
300 files changed, 57203 insertions, 0 deletions
diff --git a/vendor/rails-2.0.2/actionpack/CHANGELOG b/vendor/rails-2.0.2/actionpack/CHANGELOG
new file mode 100644
index 000000000..d8a1cc752
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/CHANGELOG
@@ -0,0 +1,4606 @@
+*2.0.2* (December 16th, 2007)
+
+* Added delete_via_redirect and put_via_redirect to integration testing #10497 [philodespotos]
+
+* Allow headers['Accept'] to be set by hand when calling xml_http_request #10461 [BMorearty]
+
+* Added OPTIONS to list of default accepted HTTP methods #10449 [holoway]
+
+* Added option to pass proc to ActionController::Base.asset_host for maximum configurability #10521 [chuyeow]. Example:
+
+ ActionController::Base.asset_host = Proc.new { |source|
+ if source.starts_with?('/images')
+ "http://images.example.com"
+ else
+ "http://assets.example.com"
+ end
+ }
+
+* Fixed that ActionView#file_exists? would be incorrect if @first_render is set #10569 [dbussink]
+
+* Added that Array#to_param calls to_param on all it's elements #10473 [brandon]
+
+* Ensure asset cache directories are automatically created. #10337 [Josh Peek, Cheah Chu Yeow]
+
+* render :xml and :json preserve custom content types. #10388 [jmettraux, Cheah Chu Yeow]
+
+* Refactor Action View template handlers. #10437, #10455 [Josh Peek]
+
+* Fix DoubleRenderError message and leave out mention of returning false from filters. Closes #10380 [Frederick Cheung]
+
+* Clean up some cruft around ActionController::Base#head. Closes #10417 [ssoroka]
+
+
+*2.0.1* (December 7th, 2007)
+
+* Fixed send_file/binary_content for testing #8044 [tolsen]
+
+* When a NonInferrableControllerError is raised, make the proposed fix clearer in the error message. Closes #10199 [danger]
+
+* Update Prototype to 1.6.0.1. [sam]
+
+* Update script.aculo.us to 1.8.0.1. [madrobby]
+
+* Add 'disabled' attribute to <OPTION> separators used in time zone and country selects. Closes #10354 [hasmanyjosh]
+
+* Added the same record identification guessing rules to fields_for as form_for has [DHH]
+
+* Fixed that verification violations with no specified action didn't halt the chain (now they do with a 400 Bad Request) [DHH]
+
+* Raise UnknownHttpMethod exception for unknown HTTP methods. Closes #10303 [Tarmo Tänav]
+
+* Update to Prototype -r8232. [sam]
+
+* Make sure the optimisation code for routes doesn't get used if :host, :anchor or :port are provided in the hash arguments. [pager, Koz] #10292
+
+* Added protection from trailing slashes on page caching #10229 [devrieda]
+
+* Asset timestamps are appended, not prepended. Closes #10276 [mnaberez]
+
+* Minor inconsistency in description of render example. Closes #10029 [ScottSchram]
+
+* Add #prepend_view_path and #append_view_path instance methods on ActionController::Base for consistency with the class methods. [rick]
+
+* Refactor sanitizer helpers into HTML classes and make it easy to swap them out with custom implementations. Closes #10129. [rick]
+
+* Add deprecation for old subtemplate syntax for ActionMailer templates, use render :partial [rick]
+
+* Fix TemplateError so it doesn't bomb on exceptions while running tests [rick]
+
+* Fixed that named routes living under resources shouldn't have double slashes #10198 [isaacfeliu]
+
+* Make sure that cookie sessions use a secret that is at least 30 chars in length. [Koz]
+
+* Fixed that partial rendering should look at the type of the first render to determine its own type if no other clues are available (like when using text.plain.erb as the extension in AM) #10130 [java]
+
+* Fixed that has_many :through associations should render as collections too #9051 [mathie/danger]
+
+* Added :mouseover short-cut to AssetTagHelper#image_tag for doing easy image swaps #6893 [joost]
+
+* Fixed handling of non-domain hosts #9479 [purp]
+
+* Fix syntax error in documentation example for cycle method. Closes #8735 [foca]
+
+* Document :with option for link_to_remote. Closes #8765 [ryanb]
+
+* Document :minute_step option for time_select. Closes #8814 [brupm]
+
+* Explain how to use the :href option for link_to_remote to degrade gracefully in the absence of JavaScript. Closes #8911 [vlad]
+
+* Disambiguate :size option for text area tag. Closes #8955 [redbeard]
+
+* Fix broken tag in assert_tag documentation. Closes #9037 [mfazekas]
+
+* Add documentation for route conditions. Closes #9041 [innu, manfred]
+
+* Fix typo left over from previous typo fix in url helper. Closes #9414 [Henrik N]
+
+* Fixed that ActionController::CgiRequest#host_with_port() should handle standard port #10082 [moro]
+
+* Update Prototype to 1.6.0 and script.aculo.us to 1.8.0. [sam, madrobby]
+
+* Expose the cookie jar as a helper method (before the view would just get the raw cookie hash) [DHH]
+
+* Integration tests: get_ and post_via_redirect take a headers hash. #9130 [simonjefford]
+
+* Simplfy #view_paths implementation. ActionView templates get the exact object, not a dup. [Rick]
+
+* Update tests for ActiveSupport's JSON escaping change. [rick]
+
+* FormHelper's auto_index should use #to_param instead of #id_before_type_cast. Closes #9994 [mattly]
+
+* Doc typo fixes for ActiveRecordHelper. Closes #9973 [mikong]
+
+* Make example parameters in restful routing docs idiomatic. Closes #9993 [danger]
+
+* Make documentation comment for mime responders match documentation example. Closes #9357 [yon]
+
+* Introduce a new test case class for functional tests. ActionController::TestCase. [Koz]
+
+* Fix incorrect path in helper rdoc. Closes #9926 [viktor tron]
+
+* Partials also set 'object' to the default partial variable. #8823 [Nick Retallack, Jeremy Kemper]
+
+* Request profiler. [Jeremy Kemper]
+ $ cat login_session.rb
+ get_with_redirect '/'
+ say "GET / => #{path}"
+ post_with_redirect '/sessions', :username => 'john', :password => 'doe'
+ say "POST /sessions => #{path}"
+ $ ./script/performance/request -n 10 login_session.rb
+
+* Disabled checkboxes don't submit a form value. #9301 [vladr, robinjfisher]
+
+* Added tests for options to ActiveRecordHelper#form. Closes #7213 [richcollins, mikong, mislav]
+
+* Changed before_filter halting to happen automatically on render or redirect but no longer on simply returning false [DHH]
+
+* Ensure that cookies handle array values correctly. Closes #9937 [queso]
+
+* Make sure resource routes don't clash with internal helpers like javascript_path, image_path etc. #9928 [gbuesing]
+
+* caches_page uses a single after_filter instead of one per action. #9891 [Pratik Naik]
+
+* Update Prototype to 1.6.0_rc1 and script.aculo.us to 1.8.0 preview 0. [sam, madrobby]
+
+* Dispatcher: fix that to_prepare should only run once in production. #9889 [Nathaniel Talbott]
+
+* Memcached sessions: add session data on initialization; don't silently discard exceptions; add unit tests. #9823 [kamk]
+
+* error_messages_for also takes :message and :header_message options which defaults to the old "There were problems with the following fields:" and "<count> errors prohibited this <object_name> from being saved". #8270 [rmm5t, zach-inglis-lt3]
+
+* Make sure that custom inflections are picked up by map.resources. #9815 [mislav]
+
+* Changed SanitizeHelper#sanitize to only allow the custom attributes and tags when specified in the call [DHH]
+
+* Extracted sanitization methods from TextHelper to SanitizeHelper [DHH]
+
+* rescue_from accepts :with => lambda { |exception| ... } or a normal block. #9827 [Pratik Naik]
+
+* Add :status to redirect_to allowing users to choose their own response code without manually setting headers. #8297 [codahale, chasgrundy]
+
+* Add link_to :back which uses your referrer with a fallback to a javascript link. #7366 [eventualbuddha, Tarmo Tänav]
+
+* error_messages_for and friends also work with local variables. #9699 [Frederick Cheung]
+
+* Fix url_for, redirect_to, etc. with :controller => :symbol instead of 'string'. #8562, #9525 [Justin Lynn, Tarmo Tänav, shoe]
+
+* Use #require_library_or_gem to load the memcache library for the MemCache session and fragment cache stores. Closes #8662. [Rick]
+
+* Move ActionController::Routing.optimise_named_routes to ActionController::Base.optimise_named_routes. Now you can set it in the config. [Rick]
+
+ config.action_controller.optimise_named_routes = false
+
+* ActionController::Routing::DynamicSegment#interpolation_chunk should call #to_s on all values before calling URI.escape. [Rick]
+
+* Only accept session ids from cookies, prevents session fixation attacks. [bradediger]
+
+
+*2.0.0 [Preview Release]* (September 29th, 2007) [Includes duplicates of changes from 1.12.2 - 1.13.3]
+
+* Fixed that render template did not honor exempt_from_layout #9698 [pezra]
+
+* Better error messages if you leave out the :secret option for request forgery protection. Closes #9670 [rick]
+
+* Allow ability to disable request forgery protection, disable it in test mode by default. Closes #9693 [Pratik Naik]
+
+* Avoid calling is_missing on LoadErrors. Closes #7460. [ntalbott]
+
+* Move Railties' Dispatcher to ActionController::Dispatcher, introduce before_ and after_dispatch callbacks, and warm up to non-CGI requests. [Jeremy Kemper]
+
+* The tag helper may bypass escaping. [Jeremy Kemper]
+
+* Cache asset ids. [Jeremy Kemper]
+
+* Optimized named routes respect AbstractRequest.relative_url_root. #9612 [danielmorrison, Jeremy Kemper]
+
+* Introduce ActionController::Base.rescue_from to declare exception-handling methods. Cleaner style than the case-heavy rescue_action_in_public. #9449 [Norbert Crombach]
+
+* Rename some RequestForgeryProtection methods. The class method is now #protect_from_forgery, and the default parameter is now 'authenticity_token'. [Rick]
+
+* Merge csrf_killer plugin into rails. Adds RequestForgeryProtection model that verifies session-specific _tokens for non-GET requests. [Rick]
+
+* Secure #sanitize, #strip_tags, and #strip_links helpers against xss attacks. Closes #8877. [Rick, Pratik Naik, Jacques Distler]
+
+ This merges and renames the popular white_list helper (along with some css sanitizing from Jacques Distler version of the same plugin).
+ Also applied updated versions of #strip_tags and #strip_links from #8877.
+
+* Remove use of & logic operator. Closes #8114. [watson]
+
+* Fixed JavaScriptHelper#escape_javascript to also escape closing tags #8023 [rubyruy]
+
+* Fixed TextHelper#word_wrap for multiline strings with extra carrier returns #8663 [seth]
+
+* Fixed that setting the :host option in url_for would automatically turn off :only_path (since :host would otherwise not be shown) #9586 [Bounga]
+
+* Added FormHelper#label. #8641, #9850 [jcoglan, jarkko]
+
+* Added AtomFeedHelper (slightly improved from the atom_feed_helper plugin) [DHH]
+
+* Prevent errors when generating routes for uncountable resources, (i.e. sheep where plural == singluar). map.resources :sheep now creates sheep_index_url for the collection and sheep_url for the specific item. [Koz]
+
+* Added support for HTTP Only cookies (works in IE6+ and FF 2.0.5+) as an improvement for XSS attacks #8895 [Pratik Naik, Spakman]
+
+* Don't warn when a path segment precedes a required segment. Closes #9615. [Nicholas Seckar]
+
+* Fixed CaptureHelper#content_for to work with the optional content parameter instead of just the block #9434 [sandofsky/wildchild].
+
+* Added Mime::Type.register_alias for dealing with different formats using the same mime type [DHH]. Example:
+
+ class PostsController < ApplicationController
+ before_filter :adjust_format_for_iphone
+
+ def index
+ @posts = Post.find(:all)
+
+ respond_to do |format|
+ format.html # => renders index.html.erb and uses "text/html" as the content type
+ format.iphone # => renders index.iphone.erb and uses "text/html" as the content type
+ end
+ end
+
+
+ private
+ def adjust_format_for_iphone
+ if request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/iPhone/]
+ request.format = :iphone
+ end
+ end
+ end
+
+* Added that render :json will automatically call .to_json unless it's being passed a string [DHH].
+
+* Autolink behaves well with emails embedded in URLs. #7313 [Jeremy McAnally, Tarmo Tänav]
+
+* Fixed that default layouts did not take the format into account #9564 [Pratik Naik]
+
+* Fixed optimized route segment escaping. #9562 [wildchild, Jeremy Kemper]
+
+* Added block acceptance to JavaScriptHelper#javascript_tag. #7527 [Bob Silva, Tarmo Tänav, rmm5t]
+
+* root_path returns '/' not ''. #9563 [Pratik Naik]
+
+* Fixed that setting request.format should also affect respond_to blocks [DHH]
+
+* Add option to force binary mode on tempfile used for fixture_file_upload. #6380 [Jonathan Viney]
+
+* Fixed that resource namespaces wouldn't stick to all nested resources #9399 [pixeltrix]
+
+* Moved ActionController::Macros::AutoComplete into the auto_complete plugin on the official Rails svn. #9512 [Pratik Naik]
+
+* Moved ActionController::Macros::InPlaceEditing into the in_place_editor plugin on the official Rails svn. #9513 [Pratik Naik]
+
+* Removed deprecated form of calling xml_http_request/xhr without the first argument being the http verb [DHH]
+
+* Removed deprecated methods [DHH]:
+
+ - ActionController::Base#keep_flash (use flash.keep instead)
+ - ActionController::Base#expire_matched_fragments (just call expire_fragment with a regular expression)
+ - ActionController::Base.template_root/= methods (use ActionController#Base.view_paths/= instead)
+ - ActionController::Base.cookie (use ActionController#Base.cookies[]= instead)
+
+* Removed the deprecated behavior of appending ".png" to image_tag/image_path calls without an existing extension [DHH]
+
+* Removed ActionController::Base.scaffold -- it went through the whole idea of scaffolding (card board walls you remove and tweak one by one). Use the scaffold generator instead (it does resources too now!) [DHH]
+
+* Optimise named route generation when using positional arguments. [Koz]
+
+ This change delivers significant performance benefits for the most
+ common usage scenarios for modern rails applications by avoiding the
+ costly trip through url_for. Initial benchmarks indicate this is
+ between 6 and 20 times as fast.
+
+* Explicitly require active_record/query_cache before using it. [Jeremy Kemper]
+
+* Fix layout overriding response status. #9476 [lotswholetime]
+
+* Add field_set_tag for generating field_sets, closes #9477. [djanowski]
+
+* Allow additional parameters to be passed to named route helpers when using positional arguments. Closes #8930 [ian.w.white@gmail.com]
+
+* Make render :partial work with a :collection of Hashes, previously this wasn't possible due to backwards compatibility restrictions. [Pratik Naik]
+
+* request.host works with IPv6 addresses. #9458 [yuya]
+
+* Fix bug where action caching sets the content type to the ActionCachePath object. Closes #9282 [mindforge]
+
+* Find layouts even if they're not in the first view_paths directory. Closes #9258 [caio]
+
+* Major improvement to the documentation for the options / select form helpers. Closes #9038 [kampers, jardeon, wesg]
+
+* Fix number_to_human_size when using different precisions. Closes #7536. [RichardStrand, mpalmer]
+
+* Added partial layouts (see example in action_view/lib/partials.rb) [DHH]
+
+* Allow you to set custom :conditions on resource routes. [Rick]
+
+* Fixed that file.content_type for uploaded files would include a trailing \r #9053 [bgreenlee]
+
+* url_for now accepts a series of symbols representing the namespace of the record [Josh Knowles]
+
+* Make :trailing_slash work with query parameters for url_for. Closes #4004 [nov]
+
+* Make sure missing template exceptions actually say which template they were looking for. Closes #8683 [dasil003]
+
+* Fix errors with around_filters which do not yield, restore 1.1 behaviour with after filters. Closes #8891 [skaes]
+
+ After filters will *no longer* be run if an around_filter fails to yield, users relying on
+ this behaviour are advised to put the code in question after a yield statement in an around filter.
+
+
+* Allow you to delete cookies with options. Closes #3685 [Josh Peek, Chris Wanstrath]
+
+* Allow you to render views with periods in the name. Closes #8076 [Norbert Crombach]
+
+ render :partial => 'show.html.erb'
+
+* Improve capture helper documentation. #8796 [kampers]
+
+* Prefix nested resource named routes with their action name, e.g. new_group_user_path(@group) instead of group_new_user_path(@group). The old nested action named route is deprecated in Rails 1.2.4. #8558 [David Chelimsky]
+
+* Allow sweepers to be created solely for expiring after controller actions, not model changes [DHH]
+
+* Added assigns method to ActionController::Caching::Sweeper to easily access instance variables on the controller [DHH]
+
+* Give the legacy X-POST_DATA_FORMAT header greater precedence during params parsing for backward compatibility. [Jeremy Kemper]
+
+* Fixed that link_to with an href of # when using :method will not allow for click-through without JavaScript #7037 [Steven Bristol, Josh Peek]
+
+* Fixed that radio_button_tag should generate unique ids #3353 [Bob Silva, Rebecca, Josh Peek]
+
+* Fixed that HTTP authentication should work if the header is called REDIRECT_X_HTTP_AUTHORIZATION as well #6754 [mislaw]
+
+* Don't mistakenly interpret the request uri as the query string. #8731 [Pratik Naik, Jeremy Kemper]
+
+* Make ActionView#view_paths an attr_accessor for real this time. Also, don't perform an unnecessary #compact on the @view_paths array in #initialize. Closes #8582 [dasil003, julik, rick]
+
+* Tolerate missing content type on multipart file uploads. Fix for Safari 3. [Jeremy Kemper]
+
+* Deprecation: remove pagination. Install the classic_pagination plugin for forward compatibility, or move to the superior will_paginate plugin. #8157 [Josh Peek]
+
+* Action caching is limited to GET requests returning 200 OK status. #3335 [tom@craz8.com, halfbyte, Dan Kubb, Josh Peek]
+
+* Improve Text Helper test coverage. #7274 [Rob Sanheim, Josh Peek]
+
+* Improve helper test coverage. #7208, #7212, #7215, #7233, #7234, #7235, #7236, #7237, #7238, #7241, #7243, #7244 [Rich Collins, Josh Peek]
+
+* Improve UrlRewriter tests. #7207 [Rich Collins]
+
+* Resources: url_for([parent, child]) generates /parents/1/children/2 for the nested resource. Likewise with the other simply helpful methods like form_for and link_to. #6432 [mhw, Jonathan Vaught, lotswholetime]
+
+* Assume html format when rendering partials in RJS. #8076 [Rick]
+
+* Don't double-escape url_for in views. #8144 [Rich Collins, Josh Peek]
+
+* Allow JSON-style values for the :with option of observe_field. Closes #8557 [kommen]
+
+* Remove RAILS_ROOT from backtrace paths. #8540 [Tim Pope]
+
+* Routing: map.resource :logo routes to LogosController so the controller may be reused for multiple nestings or namespaces. [Jeremy Kemper]
+
+* render :partial recognizes Active Record associations as Arrays. #8538 [Kamal Fariz Mahyuddin]
+
+* Routing: drop semicolon and comma as route separators. [Jeremy Kemper]
+
+* request.remote_ip understands X-Forwarded-For addresses with nonstandard whitespace. #7386 [moses]
+
+* Don't prepare response when rendering a component. #8493 [jsierles]
+
+* Reduce file stat calls when checking for template changes. #7736 [alex]
+
+* Added custom path cache_page/expire_page parameters in addition to the options hashes [DHH]. Example:
+
+ def index
+ caches_page(response.body, "/index.html")
+ end
+
+* Action Caching speedup. #8231 [skaes]
+
+* Wordsmith resources documentation. #8484 [marclove]
+
+* Fix syntax error in code example for routing documentation. #8377. [Norbert Crombach]
+
+* Routing: respond with 405 Method Not Allowed status when the route path matches but the HTTP method does not. #6953 [Josh Peek, defeated, Dan Kubb, Coda Hale]
+
+* Add support for assert_select_rjs with :show and :hide. #7780 [dchelimsky]
+
+* Make assert_select's failure messages clearer about what failed. #7779 [dchelimsky]
+
+* Introduce a default respond_to block for custom types. #8174 [Josh Peek]
+
+* auto_complete_field takes a :method option so you can GET or POST. #8120 [zapnap]
+
+* Added option to suppress :size when using :maxlength for FormTagHelper#text_field #3112 [Tim Pope]
+
+* catch possible WSOD when trying to render a missing partial. Closes #8454 [Catfish]
+
+* Rewind request body after reading it, if possible. #8438 [s450r1]
+
+* Resource namespaces are inherited by their has_many subresources. #8280 [marclove, ggarside]
+
+* Fix filtered parameter logging with nil parameter values. #8422 [choonkeat]
+
+* Integration tests: alias xhr to xml_http_request and add a request_method argument instead of always using POST. #7124 [Nik Wakelin, Francois Beausoleil, Wizard]
+
+* Document caches_action. #5419 [Jarkko Laine]
+
+* Update to Prototype 1.5.1. [Sam Stephenson]
+
+* Allow routes to be decalred under namespaces [Tobias Luetke]:
+
+ map.namespace :admin do |admin|
+ admin.root :controller => "products"
+ admin.feed 'feed.xml', :controller => 'products', :action => 'feed', :format => 'xml'
+ end
+
+* Update to script.aculo.us 1.7.1_beta3. [Thomas Fuchs]
+
+* observe_form always sends the serialized form. #5271 [manfred, normelton@gmail.com]
+
+* Parse url-encoded and multipart requests ourselves instead of delegating to CGI. [Jeremy Kemper]
+
+* select :include_blank option can be set to a string instead of true, which just uses an empty string. #7664 [Wizard]
+
+* Added url_for usage on render :location, which allows for record identification [DHH]. Example:
+
+ render :xml => person, :status => :created, :location => person
+
+ ...expands the location to person_url(person).
+
+* Introduce the request.body stream. Lazy-read to parse parameters rather than always setting RAW_POST_DATA. Reduces the memory footprint of large binary PUT requests. [Jeremy Kemper]
+
+* Add some performance enhancements to ActionView.
+
+ * Cache base_paths in @@cached_base_paths
+ * Cache template extensions in @@cached_template_extension
+ * Remove unnecessary rescues
+
+* Assume that rendered partials go by the HTML format by default
+
+ def my_partial
+ render :update do |page|
+ # in this order
+ # _foo.html.erb
+ # _foo.erb
+ # _foo.rhtml
+ page.replace :foo, :partial => 'foo'
+ end
+ end
+
+* Added record identifications to FormHelper#form_for and PrototypeHelper#remote_form_for [DHH]. Examples:
+
+ <% form_for(@post) do |f| %>
+ ...
+ <% end %>
+
+ This will expand to be the same as:
+
+ <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
+ ...
+ <% end %>
+
+ And for new records:
+
+ <% form_for(Post.new) do |f| %>
+ ...
+ <% end %>
+
+ This will expand to be the same as:
+
+ <% form_for :post, @post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
+ ...
+ <% end %>
+
+* Rationalize route path escaping according to RFC 2396 section 3.3. #7544, #8307. [Jeremy Kemper, chrisroos, begemot, jugend]
+
+* Added record identification with polymorphic routes for ActionController::Base#url_for and ActionView::Base#url_for [DHH]. Examples:
+
+ redirect_to(post) # => redirect_to(posts_url(post)) => Location: http://example.com/posts/1
+ link_to(post.title, post) # => link_to(post.title, posts_url(post)) => <a href="/posts/1">Hello world</a>
+
+ Any method that calls url_for on its parameters will automatically benefit from this.
+
+* Removed deprecated parameters_for_method_reference concept (legacy from before named routes) [DHH]
+
+* Add ActionController::Routing::Helpers, a module to contain common URL helpers such as polymorphic_url. [Nicholas Seckar]
+
+* Included the HttpAuthentication plugin as part of core (ActionController::HttpAuthentication::Basic) [DHH]
+
+* Modernize documentation for form helpers. [jeremymcanally]
+
+* Add brief introduction to REST to the resources documentation. [fearoffish]
+
+* Fix various documentation typos throughout ActionPack. [Henrik N]
+
+* Enhance documentation and add examples for url_for. [jeremymcanally]
+
+* Fix documentation typo in routes. [Norbert Crombach, pam]
+
+* Sweep flash when filter chain is halted. [Caio Chassot <lists@v2studio.com>]
+
+* Fixed that content_tag with a block will just return the result instead of concate it if not used in a ERb view #7857, #7432 [michael.niessner]
+
+* Replace the current block/continuation filter chain handling by an implementation based on a simple loop. #8226 [Stefan Kaes]
+
+* Update UrlWriter to accept :anchor parameter. Closes #6771. [octopod]
+
+* Added RecordTagHelper for using RecordIdentifier conventions on divs and other container elements [DHH]. Example:
+
+ <% div_for(post) do %> <div id="post_45" class="post">
+ <%= post.body %> What a wonderful world!
+ <% end %> </div>
+
+* Added page[record] accessor to JavaScriptGenerator that relies on RecordIdentifier to find the right dom id [DHH]. Example:
+
+ format.js do
+ # Calls: new Effect.fade('post_45');
+ render(:update) { |page| page[post].visual_effect(:fade) }
+ end
+
+* Added RecordIdentifier to enforce view conventions on records for dom ids, classes, and partial paths [DHH]
+
+* Added map.namespace to deal with the common situation of admin sections and the like [DHH]
+
+ Before:
+
+ map.resources :products, :path_prefix => "admin", :controller => "admin/products", :collection => { :inventory => :get }, :member => { :duplicate => :post }
+ map.resources :tags, :name_prefix => 'admin_product_', :path_prefix => "admin/products/:product_id", :controller => "admin/product_tags"
+ map.resources :images, :name_prefix => 'admin_product_', :path_prefix => "admin/products/:product_id", :controller => "admin/product_images"
+ map.resources :variants, :name_prefix => 'admin_product_', :path_prefix => "admin/products/:product_id", :controller => "admin/product_variants"
+
+ After:
+
+ map.namespace(:admin) do |admin|
+ admin.resources :products,
+ :collection => { :inventory => :get },
+ :member => { :duplicate => :post },
+ :has_many => [ :tags, :images, :variants ]
+ end
+
+* Added :name_prefix as standard for nested resources [DHH]. WARNING: May be backwards incompatible with your app
+
+ Before:
+
+ map.resources :emails do |emails|
+ emails.resources :comments, :name_prefix => "email_"
+ emails.resources :attachments, :name_prefix => "email_"
+ end
+
+ After:
+
+ map.resources :emails do |emails|
+ emails.resources :comments
+ emails.resources :attachments
+ end
+
+ This does mean that if you intended to have comments_url go to /emails/5/comments, then you'll have to set :name_prefix to nil explicitly.
+
+* Added :has_many and :has_one for declaring plural and singular resources beneath the current [DHH]
+
+ Before:
+
+ map.resources :notes do |notes|
+ notes.resources :comments
+ notes.resources :attachments
+ notes.resource :author
+ end
+
+ After:
+
+ map.resources :notes, :has_many => [ :comments, :attachments ], :has_one => :author
+
+* Added that render :xml will try to call to_xml if it can [DHH]. Makes these work:
+
+ render :xml => post
+ render :xml => comments
+
+* Added :location option to render so that the common pattern of rendering a response after creating a new resource is now a 1-liner [DHH]
+
+ render :xml => post.to_xml, :status => :created, :location => post_url(post)
+
+* Ensure that render_text only adds string content to the body of the response [DHH]
+
+* Return the string representation from an Xml Builder when rendering a partial. Closes #5044 [Tim Pope]
+
+* Fixed that parameters from XML should also be presented in a hash with indifferent access [DHH]
+
+* Tweak template format rules so that the ACCEPT header is only used if it's text/javascript. This is so ajax actions without a :format param get recognized as Mime::JS. [Rick]
+
+* The default respond_to blocks don't set a specific extension anymore, so that both 'show.rjs' and 'show.js.rjs' will work. [Rick]
+
+* Allow layouts with extension of .html.erb. Closes #8032 [Josh Knowles]
+
+* Change default respond_to templates for xml and rjs formats. [Rick]
+
+ * Default xml template goes from #{action_name}.rxml => #{action_name}.xml.builder.
+ * Default rjs template goes from #{action_name}.rjs => #{action_name}.js.rjs.
+
+ You can still specify your old templates:
+
+ respond_to do |format|
+ format.xml do
+ render :action => "#{action_name}.rxml"
+ end
+ end
+
+* Fix WSOD due to modification of a formatted template extension so that requests to templates like 'foo.html.erb' fail on the second hit. [Rick]
+
+* Fix WSOD when template compilation fails [Rick]
+
+* Change ActionView template defaults. Look for templates using the request format first, such as "show.html.erb" or "show.xml.builder", before looking for the old defaults like "show.erb" or "show.builder" [Rick]
+
+* Highlight helper highlights one or many terms in a single pass. [Jeremy Kemper]
+
+* Dropped the use of ; as a separator of non-crud actions on resources and went back to the vanilla slash. It was a neat idea, but lots of the non-crud actions turned out not to be RPC (as the ; was primarily intended to discourage), but legitimate sub-resources, like /parties/recent, which didn't deserve the uglification of /parties;recent. Further more, the semicolon caused issues with caching and HTTP authentication in Safari. Just Not Worth It [DHH]
+
+* Added that FormTagHelper#submit_tag will return to its original state if the submit fails and you're using :disable_with [DHH]
+
+* Cleaned up, corrected, and mildly expanded ActionPack documentation. Closes #7190 [jeremymcanally]
+
+* Small collection of ActionController documentation cleanups. Closes #7319 [jeremymcanally]
+
+* Make sure the route expiry hash is constructed by comparing the to_param-ized values of each hash. [Jamis Buck]
+
+* Allow configuration of the default action cache path for #caches_action calls. [Rick Olson]
+
+ class ListsController < ApplicationController
+ caches_action :index, :cache_path => Proc.new { |controller|
+ controller.params[:user_id] ?
+ controller.send(:user_lists_url, c.params[:user_id]) :
+ controller.send(:lists_url) }
+ end
+
+* Performance: patch cgi/session/pstore to require digest/md5 once rather than per #initialize. #7583 [Stefan Kaes]
+
+* Cookie session store: ensure that new sessions doesn't reuse data from a deleted session in the same request. [Jeremy Kemper]
+
+* Deprecation: verification with :redirect_to => :named_route shouldn't be deprecated. #7525 [Justin French]
+
+* Cookie session store: raise ArgumentError when :session_key is blank. [Jeremy Kemper]
+
+* Deprecation: remove deprecated request, redirect, and dependency methods. Remove deprecated instance variables. Remove deprecated url_for(:symbol, *args) and redirect_to(:symbol, *args) in favor of named routes. Remove uses_component_template_root for toplevel components directory. Privatize deprecated render_partial and render_partial_collection view methods. Remove deprecated link_to_image, link_image_to, update_element_function, start_form_tag, and end_form_tag helper methods. Remove deprecated human_size helper alias. [Jeremy Kemper]
+
+* Consistent public/protected/private visibility for chained methods. #7813 [Dan Manges]
+
+* Prefer MIME constants to strings. #7707 [Dan Kubb]
+
+* Allow array and hash query parameters. Array route parameters are converted/to/a/path as before. #6765, #7047, #7462 [bgipsy, Jeremy McAnally, Dan Kubb, brendan]
+
+# Add a #dbman attr_reader for CGI::Session and make CGI::Session::CookieStore#generate_digest public so it's easy to generate digests
+using the cookie store's secret. [Rick]
+
+* Added Request#url that returns the complete URL used for the request [DHH]
+
+* Extract dynamic scaffolding into a plugin. #7700 [Josh Peek]
+
+* Added user/password options for url_for to add http authentication in a URL [DHH]
+
+* Fixed that FormTagHelper#text_area_tag should disregard :size option if it's not a string [Brendon Davidson]
+
+* Set the original button value in an attribute of the button when using the :disable_with key with submit_tag, so that the original can be restored later. [Jamis Buck]
+
+* session_enabled? works with session :off. #6680 [Catfish]
+
+* Added :port and :host handling to UrlRewriter (which unified url_for usage, regardless of whether it's called in view or controller) #7616 [alancfrancis]
+
+* Allow send_file/send_data to use a registered mime type as the :type parameter #7620 [jonathan]
+
+* Allow routing requirements on map.resource(s) #7633 [quixoten]. Example:
+
+ map.resources :network_interfaces, :requirements => { :id => /^\d+\.\d+\.\d+\.\d+$/ }
+
+* Cookie session store: empty and unchanged sessions don't write a cookie. [Jeremy Kemper]
+
+* Added helper(:all) as a way to include all helpers from app/helpers/**/*.rb in ApplicationController [DHH]
+
+* Integration tests: introduce methods for other HTTP methods. #6353 [caboose]
+
+* Routing: better support for escaped values in route segments. #7544 [Chris
+Roos]
+
+* Introduce a cookie-based session store as the Rails default. Sessions typically contain at most a user_id and flash message; both fit within the 4K cookie size limit. A secure message digest is included with the cookie to ensure data integrity (a user cannot alter his user_id without knowing the secret key included in the digest). If you have more than 4K of session data or don't want your data to be visible to the user, pick another session store. Cookie-based sessions are dramatically faster than the alternatives. [Jeremy Kemper]
+
+ Example config/environment.rb:
+ # Use an application-wide secret key and the default SHA1 message digest.
+ config.action_controller.session = { :secret => "can't touch this" }
+
+ # Store a secret key per user and employ a stronger message digest.
+ config.action_controller.session = {
+ :digest => 'SHA512',
+ :secret => Proc.new { User.current.secret_key }
+ }
+
+* Added .erb and .builder as preferred aliases to the now deprecated .rhtml and .rxml extensions [Chad Fowler]. This is done to separate the renderer from the mime type. .erb templates are often used to render emails, atom, csv, whatever. So labeling them .rhtml doesn't make too much sense. The same goes for .rxml, which can be used to build everything from HTML to Atom to whatever. .rhtml and .rxml will continue to work until Rails 3.0, though. So this is a slow phasing out. All generators and examples will start using the new aliases, though.
+
+* Added caching option to AssetTagHelper#stylesheet_link_tag and AssetTagHelper#javascript_include_tag [DHH]. Examples:
+
+ stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
+ <link href="/stylesheets/style1.css" media="screen" rel="Stylesheet" type="text/css" />
+ <link href="/stylesheets/styleB.css" media="screen" rel="Stylesheet" type="text/css" />
+ <link href="/stylesheets/styleX2.css" media="screen" rel="Stylesheet" type="text/css" />
+
+ stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is true =>
+ <link href="/stylesheets/all.css" media="screen" rel="Stylesheet" type="text/css" />
+
+ ...when caching is on, all.css is the concatenation of style1.css, styleB.css, and styleX2.css.
+ Same deal for JavaScripts.
+
+* Work around the two connection per host browser limit: use asset%d.myapp.com to distribute asset requests among asset[0123].myapp.com. Use a DNS wildcard or CNAMEs to map these hosts to your asset server. See http://www.die.net/musings/page_load_time/ for background. [Jeremy Kemper]
+
+* Added default mime type for CSS (Mime::CSS) [DHH]
+
+* Added that rendering will automatically insert the etag header on 200 OK responses. The etag is calculated using MD5 of the response body. If a request comes in that has a matching etag, the response will be changed to a 304 Not Modified and the response body will be set to an empty string. [DHH]
+
+* Added X-Runtime to all responses with the request run time [DHH]
+
+* Add Mime::Type convenience methods to check the current mime type. [Rick]
+
+ request.format.html? # => true if Mime::HTML
+ request.format.jpg? # => true if Mime::JPG
+
+ # ActionController sample usage:
+ # the session will be disabled for non html/ajax requests
+ session :off, :if => Proc.new { |req| !(req.format.html? || req.format.js?) }
+
+* Performance: patch cgi/session to require digest/md5 once rather than per #create_new_id. [Stefan Kaes]
+
+* Add a :url_based_filename => true option to ActionController::Streaming::send_file, which allows URL-based filenames. [Thomas Fuchs]
+
+* Fix that FormTagHelper#submit_tag using :disable_with should trigger the onsubmit handler of its form if available [DHH]
+
+* Fix #render_file so that TemplateError is called with the correct params and you don't get the WSOD. [Rick]
+
+* Fix issue with deprecation messing up #template_root= usage. Add #prepend_view_path and #append_view_path to allow modification of a copy of the
+superclass' view_paths. [Rick]
+
+* Allow Controllers to have multiple view_paths instead of a single template_root. Closes #2754 [John Long]
+
+* Add much-needed html-scanner tests. Fixed CDATA parsing bug. [Rick]
+
+* improve error message for Routing for named routes. Closes #7346 [Rob Sanheim]
+
+* Added enhanced docs to routing assertions. Closes #7359 [Rob Sanheim]
+
+* fix form_for example in ActionController::Resources documentation. Closes #7362 [gnarg]
+
+* Make sure that the string returned by TextHelper#truncate is actually a string, not a char proxy -- that should only be used internally while working on a multibyte-safe way of truncating [DHH]
+
+* Added FormBuilder#submit as a delegate for FormTagHelper#submit_tag [DHH]
+
+* Allow Routes to generate all urls for a set of options by specifying :generate_all => true. Allows caching to properly set or expire all paths for a resource. References #1739. [Nicholas Seckar]
+
+* Change the query parser to map empty GET params to "" rather than nil. Closes #5694. [Nicholas Seckar]
+
+* date_select and datetime_select take a :default option. #7052 [nik.wakelin]
+ date_select "post", "written_on", :default => 3.days.from_now
+ date_select "credit_card", "bill_due", :default => { :day => 20 }
+
+* select :multiple => true suffixes the attribute name with [] unless already suffixed. #6977 [nik.kakelin, ben, julik]
+
+* Improve routes documentation. #7095 [zackchandler]
+
+* mail_to :encode => 'hex' also encodes the mailto: part of the href attribute as well as the linked email when no name is given. #2061 [Jarkko Laine, pfc.pille@gmx.net]
+
+* Resource member routes require :id, eliminating the ambiguous overlap with collection routes. #7229 [dkubb]
+
+* Remove deprecated assertions. [Jeremy Kemper]
+
+* Change session restoration to allow namespaced models to be autoloaded. Closes #6348. [Nicholas Seckar]
+
+* Fix doubly appearing parameters due to string and symbol mixups. Closes #2551. [aeden]
+
+* Fix overly greedy rescues when loading helpers. Fixes #6268. [Nicholas Seckar]
+
+* 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]
+
+* Update to script.aculo.us 1.7.0. [Thomas Fuchs]
+
+* Modernize cookie testing code, and increase coverage (Heckle++) #7101 [Kevin Clark]
+
+* Improve Test Coverage for ActionController::Routing::Route#matches_controller_and_action? (Heckle++) #7115 [Kevin Clark]
+
+* Heckling ActionController::Resources::Resource revealed that set_prefixes didn't break when :name_prefix was munged. #7081 [Kevin Clark]
+
+* Fix #distance_of_time_in_words to report accurately against the Duration class. #7114 [eventualbuddha]
+
+* Refactor #form_tag to allow easy extending. [Rick]
+
+* Update to Prototype 1.5.0. [Sam Stephenson]
+
+* RecordInvalid, RecordNotSaved => 422 Unprocessable Entity, StaleObjectError => 409 Conflict. #7097 [dkubb]
+
+* Allow fields_for to be nested inside form_for, so that the name and id get properly constructed [Jamis Buck]
+
+* Allow inGroupsOf and eachSlice to be called through rjs. #7046 [Cody Fauser]
+
+* Allow exempt_from_layout :rhtml. #6742, #7026 [Dan Manges, Squeegy]
+
+* Recognize the .txt extension as Mime::TEXT [Rick]
+
+* 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]
+
+* Lookup the mime type for #auto_discovery_link_tag in the Mime::Type class. Closes #6941 [Josh Peek]
+
+* Fix bug where nested resources ignore a parent singleton parent's path prefix. Closes #6940 [Dan Kubb]
+
+* 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]
+
+* Routing uses URI escaping for path components and CGI escaping for query parameters. [darix, Jeremy Kemper]
+
+* 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]
+
+* Singleton resources: POST /singleton => create, GET /singleton/new => new. [Jeremy Kemper]
+
+* Use 400 Bad Request status for unrescued ActiveRecord::RecordInvalid exceptions. [Jeremy Kemper]
+
+* 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]
+
+* Add a 'referer' attribute to TestRequest. [Jamis Buck]
+
+* Ensure render :json => ... skips the layout. Closes #6808 [Josh Peek]
+
+* Fix HTML::Node to output double quotes instead of single quotes. Closes #6845 [mitreandy]
+
+* Correctly report which filter halted the chain. #6699 [Martin Emde]
+
+* Fix a bug in Routing where a parameter taken from the path of the current request could not be used as a query parameter for the next. Closes #6752. [Nicholas Seckar]
+
+* Unrescued ActiveRecord::RecordNotFound responds with 404 instead of 500. [Jeremy Kemper]
+
+* Improved auto_link to match more valid urls correctly [Tobias Luetke]
+
+* Add singleton resources. [Rick Olson]
+
+ map.resource :account
+
+ GET /account
+ GET /account;edit
+ UPDATE /account
+ DELETE /account
+
+* 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]
+
+* Added map.root as an alias for map.connect '' [DHH]
+
+* Added Request#format to return the format used for the request as a mime type. If no format is specified, the first Request#accepts type is used. This means you can stop using respond_to for anything else than responses [DHH]. Examples:
+
+ GET /posts/5.xml | request.format => Mime::XML
+ GET /posts/5.xhtml | request.format => Mime::HTML
+ GET /posts/5 | request.format => request.accepts.first (usually Mime::HTML for browsers)
+
+* Added the option for extension aliases to mime type registration [DHH]. Example (already in the default routes):
+
+ Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
+
+ ...will respond on both .html and .xhtml.
+
+* @response.redirect_url works with 201 Created responses: just return headers['Location'] rather than checking the response status. [Jeremy Kemper]
+
+* Added CSV to Mime::SET so that respond_to csv will work [Cody Fauser]
+
+* 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]
+
+* 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 [Rob Sanheim]
+
+* Extract template_changed_since? from compile_template? so plugins may override its behavior for non-file-based templates. #6651 [Jeff Barczewski]
+
+* Update to Prototype and script.aculo.us [5579]. [Thomas Fuchs]
+
+* simple_format helper doesn't choke on nil. #6644 [jerry426]
+
+* Update to Prototype 1.5.0_rc2 [5550] which makes it work in Opera again [Thomas Fuchs]
+
+* 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]
+
+* ActionView::Base.erb_variable accessor names the buffer variable used to render templates. Defaults to _erbout; use _buf for erubis. [Rick Olson]
+
+* assert_select_rjs :remove. [Dylan Egan]
+
+* Always clear model associations from session. #4795 [sd@notso.net, andylien@gmail.com]
+
+* Update to Prototype 1.5.0_rc2. [Sam Stephenson]
+
+* 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]
+
+* pluralize helper interprets nil as zero. #6474 [Tim Pope]
+
+* 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]
+
+* Update to latest Prototype, which doesn't serialize disabled form elements, adds clone() to arrays, empty/non-string Element.update() and adds a fixes excessive error reporting in WebKit beta versions [Thomas Fuchs]
+
+* Deprecate start_form_tag and end_form_tag. Use form_tag / '</form>' 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:
+ <div class="strong">Hello world!</div>
+
+* Deprecated UrlHelper#link_to_image and UrlHelper#link_to :post => true #6409 [BobSilva]
+
+* Upgraded NumberHelper with number_to_phone support international formats to comply with ITU E.123 by supporting area codes with less than 3 digits, added precision argument to number_to_human_size (defaults to 1) #6421 [BobSilva]
+
+* Fixed that setting RAILS_ASSET_ID to "" should not add a trailing slash after assets #6454 [BobSilva/chrismear]
+
+* Force *_url named routes to show the host in ActionView [Rick]
+
+ <%= url_for ... %> # no host
+ <%= foo_path %> # no host
+ <%= foo_url %> # host!
+
+* Add support for converting blocks into function arguments to JavaScriptGenerator#call and JavaScriptProxy#call. [Sam Stephenson]
+
+* Add JavaScriptGenerator#literal for wrapping a string in an object whose #to_json is the string itself. [Sam Stephenson]
+
+* Add <%= escape_once html %> to escape html while leaving any currently escaped entities alone. Fix button_to double-escaping issue. [Rick]
+
+* Fix double-escaped entities, such as &amp;amp;, &amp;#123;, etc. [Rick]
+
+* Fix deprecation warnings when rendering the template error template. [Nicholas Seckar]
+
+* Fix routing to correctly determine when generation fails. Closes #6300. [psross].
+
+* Fix broken assert_generates when extra keys are being checked. [Jamis Buck]
+
+* Replace KCODE checks with String#chars for truncate. Closes #6385 [Manfred Stienstra]
+
+* Make page caching respect the format of the resource that is being requested even if the current route is the default route so that, e.g. posts.rss is not transformed by url_for to '/' and subsequently cached as '/index.html' when it should be cached as '/posts.rss'. [Marcel Molina Jr.]
+
+* Use String#chars in TextHelper::excerpt. Closes #6386 [Manfred Stienstra]
+
+* Install named routes into ActionView::Base instead of proxying them to the view via helper_method. Closes #5932. [Nicholas Seckar]
+
+* Update to latest Prototype and script.aculo.us trunk versions [Thomas Fuchs]
+
+* Fix relative URL root matching problems. [Mark Imbriaco]
+
+* Fix filter skipping in controller subclasses. #5949, #6297, #6299 [Martin Emde]
+
+* render_text may optionally append to the response body. render_javascript appends by default. This allows you to chain multiple render :update calls by setting @performed_render = false between them (awaiting a better public API). [Jeremy Kemper]
+
+* Rename test assertion to prevent shadowing. Closes #6306. [psross]
+
+* Fixed that NumberHelper#number_to_delimiter should respect precision of higher than two digits #6231 [phallstrom]
+
+* Fixed that FormHelper#radio_button didn't respect an :id being passed in #6266 [evansj]
+
+* Added an html_options hash parameter to javascript_tag() and update_page_tag() helpers #6311 [tzaharia]. Example:
+
+ update_page_tag :defer => 'true' { |page| ... }
+
+ Gives:
+
+ <script defer="true" type="text/javascript">...</script>
+
+ Which is needed for dealing with the IE6 DOM when it's not yet fully loaded.
+
+* Fixed that rescue template path shouldn't be hardcoded, then it's easier to hook in your own #6295 [mnaberez]
+
+* Fixed escaping of backslashes in JavaScriptHelper#escape_javascript #6302 [sven@c3d2.de]
+
+* Fixed that some 500 rescues would cause 500's themselves because the response had not yet been generated #6329 [cmselmer]
+
+* respond_to :html doesn't assume .rhtml. #6281 [Hampton Catlin]
+
+* Fixed some deprecation warnings in ActionPack [Rick Olson]
+
+* assert_select_rjs decodes escaped unicode chars since the Javascript generators encode them. #6240 [japgolly]
+
+* Deprecation: @cookies, @headers, @request, @response will be removed after 1.2. Use the corresponding method instead. [Jeremy Kemper]
+
+* Make the :status parameter expand to the default message for that status code if it is an integer. Also support symbol statuses. [Jamis Buck]. Examples:
+
+ head :status => 404 # expands to "404 Not Found"
+ head :status => :not_found # expands to "404 Not Found"
+ head :status => :created # expands to "201 Created"
+
+* Add head(options = {}) for responses that have no body. [Jamis Buck]. Examples:
+
+ head :status => 404 # return an empty response with a 404 status
+ head :location => person_path(@person), :status => 201
+
+* Fix bug that kept any before_filter except the first one from being able to halt the before_filter chain. [Rick Olson]
+
+* strip_links is case-insensitive. #6285 [tagoh, Bob Silva]
+
+* Clear the cache of possible controllers whenever Routes are reloaded. [Nicholas Seckar]
+
+* Filters overhaul including meantime filter support using around filters + blocks. #5949 [Martin Emde, Roman Le Negrate, Stefan Kaes, Jeremy Kemper]
+
+* Update RJS render tests. [sam]
+
+* Update CGI process to allow sessions to contain namespaced models. Closes #4638. [dfelstead@site5.com]
+
+* Fix routing to respect user provided requirements and defaults when assigning default routing options (such as :action => 'index'). Closes #5950. [Nicholas Seckar]
+
+* Rescue Errno::ECONNRESET to handle an unexpectedly closed socket connection. Improves SCGI reliability. #3368, #6226 [sdsykes, fhanshaw@vesaria.com]
+
+* Added that respond_to blocks will automatically set the content type to be the same as is requested [DHH]. Examples:
+
+ respond_to do |format|
+ format.html { render :text => "I'm being sent as text/html" }
+ format.rss { render :text => "I'm being sent as application/rss+xml" }
+ format.atom { render :text => "I'm being sent as application/xml", :content_type => Mime::XML }
+ end
+
+* Added utf-8 as the default charset for all renders. You can change this default using ActionController::Base.default_charset=(encoding) [DHH]
+
+* Added proper getters and setters for content type and charset [DHH]. Example of what we used to do:
+
+ response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
+
+ ...now:
+
+ response.content_type = Mime::ATOM
+ response.charset = "utf-8"
+
+* Updated prototype.js to 1.5.0_rc1 with latest fixes. [Rick Olson]
+
+ - XPATH support
+ - Make Form.getElements() return elements in the correct order
+ - fix broken Form.serialize return
+
+* Declare file extensions exempt from layouts. #6219 [brandon]
+ Example: ActionController::Base.exempt_from_layout 'rpdf'
+
+* Add chained replace/update support for assert_select_rjs [Rick Olson]
+
+ Given RJS like...
+
+ page['test1'].replace "<div id=\"1\">foo</div>"
+ page['test2'].replace_html "<div id=\"2\">foo</div>"
+
+ Test it with...
+
+ assert_select_rjs :chained_replace
+ assert_select_rjs :chained_replace, "test1"
+
+ assert_select_rjs :chained_replace_html
+ assert_select_rjs :chained_replace_html, "test2"
+
+* Load helpers in alphabetical order for consistency. Resolve cyclic javascript_helper dependency. #6132, #6178 [choonkeat@gmail.com]
+
+* Skip params with empty names, such as the &=Save query string from <input type="submit"/>. #2569 [manfred, raphinou@yahoo.com]
+
+* Fix assert_tag so that :content => "foo" does not match substrings, but only exact strings. Use :content => /foo/ to match substrings. #2799 [Eric Hodel]
+
+* Add descriptive messages to the exceptions thrown by cgi_methods. #6091, #6103 [Nicholas Seckar, Bob Silva]
+
+* Update JavaScriptGenerator#show/hide/toggle/remove to new Prototype syntax for multiple ids, #6068 [petermichaux@gmail.com]
+
+* Update UrlWriter to support :only_path. [Nicholas Seckar, Dave Thomas]
+
+* Fixed JavaScriptHelper#link_to_function and JavaScriptHelper#button_to_function to have the script argument be optional [DHH]. So what used to require a nil, like this:
+
+ link_to("Hider", nil, :class => "hider_link") { |p| p[:something].hide }
+
+ ...can be written like this:
+
+ link_to("Hider", :class => "hider_link") { |p| p[:something].hide }
+
+* Update to script.aculo.us 1.6.3 [Thomas Fuchs]
+
+* Update to Prototype 1.5.0_rc1 [sam]
+
+* Added access to nested attributes in RJS #4548 [richcollins@gmail.com]. Examples:
+
+ page['foo']['style'] # => $('foo').style;
+ page['foo']['style']['color'] # => $('blank_slate').style.color;
+ page['foo']['style']['color'] = 'red' # => $('blank_slate').style.color = 'red';
+ page['foo']['style'].color = 'red' # => $('blank_slate').style.color = 'red';
+
+* Fixed that AssetTagHelper#image_tag and others using compute_public_path should not modify the incoming source argument (closes #5102) [eule@space.ch]
+
+* Deprecated the auto-appending of .png to AssetTagHelper#image_tag calls that doesn't have an extension [DHH]
+
+* Fixed FormOptionsHelper#select to respect :selected value #5813
+
+* Fixed TextHelper#simple_format to deal with multiple single returns within a single paragraph #5835 [moriq@moriq.com]
+
+* Fixed TextHelper#pluralize to handle 1 as a string #5909 [rails@bencurtis.com]
+
+* Improved resolution of DateHelper#distance_of_time_in_words for better precision #5994 [Bob Silva]
+
+* Changed that uncaught exceptions raised any where in the application will cause RAILS_ROOT/public/500.html to be read and shown instead of just the static "Application error (Rails)" [DHH]
+
+* Integration tests: thoroughly test ActionController::Integration::Session. #6022 [Kevin Clark]
+ (tests skipped unless you `gem install mocha`)
+
+* Added deprecation language for pagination which will become a plugin by Rails 2.0 [DHH]
+
+* Added deprecation language for in_place_editor and auto_complete_field that both pieces will become plugins by Rails 2.0 [DHH]
+
+* Deprecated all of ActionController::Dependencies. All dependency loading is now handled from Active Support [DHH]
+
+* Added assert_select* for CSS selector-based testing (deprecates assert_tag) #5936 [assaf.arkin@gmail.com]
+
+* radio_button_tag generates unique id attributes. #3353 [Bob Silva, somekool@gmail.com]
+
+* strip_tags passes through blank args such as nil or "". #2229, #6702 [duncan@whomwah.com, dharana]
+
+* Cleanup assert_tag :children counting. #2181 [jamie@bravenet.com]
+
+* button_to accepts :method so you can PUT and DELETE with it. #6005 [Dan Webb]
+
+* Update sanitize text helper to strip plaintext tags, and <img src="javascript:bang">. [Rick Olson]
+
+* Update routing documentation. Closes #6017 [Nathan Witmer]
+
+* Add routing tests to assert that RoutingError is raised when conditions aren't met. Closes #6016 [Nathan Witmer]
+
+* Deprecation: update docs. #5998 [jakob@mentalized.net, Kevin Clark]
+
+* 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]
+
+* Tighten rescue clauses. #5985 [james@grayproductions.net]
+
+* Fix send_data documentation typo. #5982 [brad@madriska.com]
+
+* 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]
+
+* Clean up and run the Active Record integration tests by default. #5854 [kevin.clark@gmail.com, Jeremy Kemper]
+
+* Correct example in cookies docs. #5832 [jessemerriman@warpmail.net]
+
+* Updated to script.aculo.us 1.6.2 [Thomas Fuchs]
+
+* 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]
+
+* Require Tempfile explicitly for TestUploadedFile due to changes in class auto loading. [Rick Olson]
+
+* 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]
+
+* Short documentation to mention use of Mime::Type.register. #5710 [choonkeat@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]
+
+* Fix bug when passing multiple options to SimplyRestful, like :new => { :preview => :get, :draft => :get }. [Rick Olson, Josh Susser, Lars Pind]
+
+* Dup the options passed to map.resources so that multiple resources get the same options. [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]
+
+* Update documentation for erb trim syntax. #5651 [matt@mattmargolis.net]
+
+* 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]
+
+* Update render :partial documentation. #5646 [matt@mattmargolis.net]
+
+* 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 <acronym> 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]
+
+* Fix documentation for with_routing to reflect new reality. #5281 [rramdas@gmail.com]
+
+* 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 <m.stienstra@fngtps.com>]
+
+* Routing rewrite. Simpler, faster, easier to understand. The published API for config/routes.rb is unchanged, but nearly everything else is different, so expect breakage in plugins and libs that try to fiddle with routes. [Nicholas Seckar, Jamis Buck]
+
+ map.connect '/foo/:id', :controller => '...', :action => '...'
+ map.connect '/foo/:id.:format', :controller => '...', :action => '...'
+ map.connect '/foo/:id', ..., :conditions => { :method => :get }
+
+* Cope with missing content type and length headers. Parse parameters from multipart and urlencoded request bodies only. [Jeremy Kemper]
+
+* Accept multipart PUT parameters. #5235 [guy.naor@famundo.com]
+
+* Added interrogation of params[:format] to determine Accept type. If :format is specified and matches a declared extension, like "rss" or "xml", that mime type will be put in front of the accept handler. This means you can link to the same action from different extensions and use that fact to determine output [DHH]. Example:
+
+ class WeblogController < ActionController::Base
+ def index
+ @posts = Post.find :all
+
+ respond_to do |format|
+ format.html
+ format.xml { render :xml => @posts.to_xml }
+ format.rss { render :action => "feed.rxml" }
+ end
+ end
+ end
+
+ # returns HTML when requested by a browser, since the browser
+ # has the HTML mimetype at the top of its priority list
+ Accept: text/html
+ GET /weblog
+
+ # returns the XML
+ Accept: application/xml
+ GET /weblog
+
+ # returns the HTML
+ Accept: application/xml
+ GET /weblog.html
+
+ # returns the XML
+ Accept: text/html
+ GET /weblog.xml
+
+ All this relies on the fact that you have a route that includes .:format.
+
+* Expanded :method option in FormTagHelper#form_tag, FormHelper#form_for, PrototypeHelper#remote_form_for, PrototypeHelper#remote_form_tag, and PrototypeHelper#link_to_remote to allow for verbs other than GET and POST by automatically creating a hidden form field named _method, which will simulate the other verbs over post [DHH]
+
+* Added :method option to UrlHelper#link_to, which allows for using other verbs than GET for the link. This replaces the :post option, which is now deprecated. Example: link_to "Destroy", person_url(:id => person), :method => :delete [DHH]
+
+* follow_redirect doesn't complain about being redirected to the same controller. #5153 [dymo@mk.ukrtelecom.ua]
+
+* Add layout attribute to response object with the name of the layout that was rendered, or nil if none rendered. [Kevin Clark kevin.clark@gmail.com]
+
+* Fix NoMethodError when parsing params like &&. [Adam Greenfield]
+
+* Fix flip flopped logic in docs for url_for's :only_path option. Closes #4998. [esad@esse.at]
+
+* form.text_area handles the :size option just like the original text_area (:size => '60x10' becomes cols="60" rows="10"). [Jeremy Kemper]
+
+* Excise ingrown code from FormOptionsHelper#options_for_select. #5008 [anonymous]
+
+* Small fix in routing to allow dynamic routes (broken after [4242]) [Rick]
+
+ map.connect '*path', :controller => 'files', :action => 'show'
+
+* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.]
+
+* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
+
+* Use #flush between switching from #write to #syswrite. Closes #4907. [Blair Zajac <blair@orcaware.com>]
+
+* Documentation fix: integration test scripts don't require integration_test. Closes #4914. [Frederick Ros <sl33p3r@free.fr>]
+
+* ActionController::Base Summary documentation rewrite. Closes #4900. [kevin.clark@gmail.com]
+
+* Fix text_helper.rb documentation rendering. Closes #4725. [Frederick Ros]
+
+* Fixes bad rendering of JavaScriptMacrosHelper rdoc (closes #4910) [Frederick Ros]
+
+* Allow error_messages_for to report errors for multiple objects, as well as support for customizing the name of the object in the error summary header. Closes #4186. [andrew@redlinesoftware.com, Marcel Molina Jr.]
+
+ error_messages_for :account, :user, :subscription, :object_name => :account
+
+* Enhance documentation for setting headers in integration tests. Skip auto HTTP prepending when its already there. Closes #4079. [Rick Olson]
+
+* Documentation for AbstractRequest. Closes #4895. [kevin.clark@gmail.com]
+
+* Refactor various InstanceTag instance method to class methods. Closes #4800. [skaes@web.de]
+
+* Remove all remaining references to @params in the documentation. [Marcel Molina Jr.]
+
+* Add documentation for redirect_to :back's RedirectBackError exception. [Marcel Molina Jr.]
+
+* Update layout and content_for documentation to use yield rather than magic @content_for instance variables. [Marcel Molina Jr.]
+
+* Fix assert_redirected_to tests according to real-world usage. Also, don't fail if you add an extra :controller option: [Rick]
+
+ redirect_to :action => 'new'
+ assert_redirected_to :controller => 'monkeys', :action => 'new'
+
+* Cache CgiRequest#request_parameters so that multiple calls don't re-parse multipart data. [Rick]
+
+* Diff compared routing options. Allow #assert_recognizes to take a second arg as a hash to specify optional request method [Rick]
+
+ assert_recognizes({:controller => 'users', :action => 'index'}, 'users')
+ assert_recognizes({:controller => 'users', :action => 'create'}, {:path => 'users', :method => :post})
+
+* Diff compared options with #assert_redirected_to [Rick]
+
+* Add support in routes for semicolon delimited "subpaths", like /books/:id;:action [Jamis Buck]
+
+* Change link_to_function and button_to_function to (optionally) take an update_page block instead of a JavaScript string. Closes #4804. [zraii@comcast.net, Sam Stephenson]
+
+* Fixed that remote_form_for can leave out the object parameter and default to the instance variable of the object_name, just like form_for [DHH]
+
+* Modify routing so that you can say :require => { :method => :post } for a route, and the route will never be selected unless the request method is POST. Only works for route recognition, not for route generation. [Jamis Buck]
+
+* Added :add_headers option to verify which merges a hash of name/value pairs into the response's headers hash if the prerequisites cannot be satisfied. [Sam Stephenson]
+ ex. verify :only => :speak, :method => :post,
+ :render => { :status => 405, :text => "Must be post" },
+ :add_headers => { "Allow" => "POST" }
+
+* Added ActionController.filter_parameter_logging that makes it easy to remove passwords, credit card numbers, and other sensitive information from being logged when a request is handled #1897 [jeremye@bsa.ca.gov]
+
+
+*1.13.3* (March 12th, 2007)
+
+* Apply [5709] to stable.
+
+* session_enabled? works with session :off. #6680 [Catfish]
+
+* Performance: patch cgi/session to require digest/md5 once rather than per #create_new_id. [Stefan Kaes]
+
+
+*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]
+
+* 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 / '</form>' 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:
+ <div class="strong">Hello world!</div>
+
+* Deprecated UrlHelper#link_to_image and UrlHelper#link_to :post => true #6409 [BobSilva]
+
+* Upgraded NumberHelper with number_to_phone support international formats to comply with ITU E.123 by supporting area codes with less than 3 digits, added precision argument to number_to_human_size (defaults to 1) #6421 [BobSilva]
+
+* Fixed that setting RAILS_ASSET_ID to "" should not add a trailing slash after assets #6454 [BobSilva/chrismear]
+
+* Force *_url named routes to show the host in ActionView [Rick]
+
+ <%= url_for ... %> # no host
+ <%= foo_path %> # no host
+ <%= foo_url %> # host!
+
+* Add support for converting blocks into function arguments to JavaScriptGenerator#call and JavaScriptProxy#call. [Sam Stephenson]
+
+* Add JavaScriptGenerator#literal for wrapping a string in an object whose #to_json is the string itself. [Sam Stephenson]
+
+* Add <%= escape_once html %> to escape html while leaving any currently escaped entities alone. Fix button_to double-escaping issue. [Rick]
+
+* Fix double-escaped entities, such as &amp;amp;, &amp;#123;, etc. [Rick]
+
+* Fix routing to correctly determine when generation fails. Closes #6300. [psross].
+
+* Fix broken assert_generates when extra keys are being checked. [Jamis Buck]
+
+* Replace KCODE checks with String#chars for truncate. Closes #6385 [Manfred Stienstra]
+
+* Make page caching respect the format of the resource that is being requested even if the current route is the default route so that, e.g. posts.rss is not transformed by url_for to '/' and subsequently cached as '/index.html' when it should be cached as '/posts.rss'. [Marcel Molina Jr.]
+
+* Use String#chars in TextHelper::excerpt. Closes #6386 [Manfred Stienstra]
+
+* Fix relative URL root matching problems. [Mark Imbriaco]
+
+* Fix filter skipping in controller subclasses. #5949, #6297, #6299 [Martin Emde]
+
+* render_text may optionally append to the response body. render_javascript appends by default. This allows you to chain multiple render :update calls by setting @performed_render = false between them (awaiting a better public API). [Jeremy Kemper]
+
+* Rename test assertion to prevent shadowing. Closes #6306. [psross]
+
+* Fixed that NumberHelper#number_to_delimiter should respect precision of higher than two digits #6231 [phallstrom]
+
+* Fixed that FormHelper#radio_button didn't respect an :id being passed in #6266 [evansj]
+
+* Added an html_options hash parameter to javascript_tag() and update_page_tag() helpers #6311 [tzaharia]. Example:
+
+ update_page_tag :defer => 'true' { |page| ... }
+
+ Gives:
+
+ <script defer="true" type="text/javascript">...</script>
+
+ Which is needed for dealing with the IE6 DOM when it's not yet fully loaded.
+
+* Fixed that rescue template path shouldn't be hardcoded, then it's easier to hook in your own #6295 [mnaberez]
+
+* Fixed escaping of backslashes in JavaScriptHelper#escape_javascript #6302 [sven@c3d2.de]
+
+* Fixed that some 500 rescues would cause 500's themselves because the response had not yet been generated #6329 [cmselmer]
+
+* respond_to :html doesn't assume .rhtml. #6281 [Hampton Catlin]
+
+* Fixed some deprecation warnings in ActionPack [Rick Olson]
+
+* assert_select_rjs decodes escaped unicode chars since the Javascript generators encode them. #6240 [japgolly]
+
+* Deprecation: @cookies, @headers, @request, @response will be removed after 1.2. Use the corresponding method instead. [Jeremy Kemper]
+
+* Make the :status parameter expand to the default message for that status code if it is an integer. Also support symbol statuses. [Jamis Buck]. Examples:
+
+ head :status => 404 # expands to "404 Not Found"
+ head :status => :not_found # expands to "404 Not Found"
+ head :status => :created # expands to "201 Created"
+
+* Add head(options = {}) for responses that have no body. [Jamis Buck]. Examples:
+
+ head :status => 404 # return an empty response with a 404 status
+ head :location => person_path(@person), :status => 201
+
+* Fix bug that kept any before_filter except the first one from being able to halt the before_filter chain. [Rick Olson]
+
+* strip_links is case-insensitive. #6285 [tagoh, Bob Silva]
+
+* Clear the cache of possible controllers whenever Routes are reloaded. [Nicholas Seckar]
+
+* Filters overhaul including meantime filter support using around filters + blocks. #5949 [Martin Emde, Roman Le Negrate, Stefan Kaes, Jeremy Kemper]
+
+* Update CGI process to allow sessions to contain namespaced models. Closes #4638. [dfelstead@site5.com]
+
+* Fix routing to respect user provided requirements and defaults when assigning default routing options (such as :action => 'index'). Closes #5950. [Nicholas Seckar]
+
+* Rescue Errno::ECONNRESET to handle an unexpectedly closed socket connection. Improves SCGI reliability. #3368, #6226 [sdsykes, fhanshaw@vesaria.com]
+
+* Added that respond_to blocks will automatically set the content type to be the same as is requested [DHH]. Examples:
+
+ respond_to do |format|
+ format.html { render :text => "I'm being sent as text/html" }
+ format.rss { render :text => "I'm being sent as application/rss+xml" }
+ format.atom { render :text => "I'm being sent as application/xml", :content_type => Mime::XML }
+ end
+
+* Added utf-8 as the default charset for all renders. You can change this default using ActionController::Base.default_charset=(encoding) [DHH]
+
+* Added proper getters and setters for content type and charset [DHH]. Example of what we used to do:
+
+ response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
+
+ ...now:
+
+ response.content_type = Mime::ATOM
+ response.charset = "utf-8"
+
+* Declare file extensions exempt from layouts. #6219 [brandon]
+ Example: ActionController::Base.exempt_from_layout 'rpdf'
+
+* Add chained replace/update support for assert_select_rjs [Rick Olson]
+
+ Given RJS like...
+
+ page['test1'].replace "<div id=\"1\">foo</div>"
+ page['test2'].replace_html "<div id=\"2\">foo</div>"
+
+ Test it with...
+
+ assert_select_rjs :chained_replace
+ assert_select_rjs :chained_replace, "test1"
+
+ assert_select_rjs :chained_replace_html
+ assert_select_rjs :chained_replace_html, "test2"
+
+* Load helpers in alphabetical order for consistency. Resolve cyclic javascript_helper dependency. #6132, #6178 [choonkeat@gmail.com]
+
+* Skip params with empty names, such as the &=Save query string from <input type="submit"/>. #2569 [manfred, raphinou@yahoo.com]
+
+* Fix assert_tag so that :content => "foo" does not match substrings, but only exact strings. Use :content => /foo/ to match substrings. #2799 [Eric Hodel]
+
+* Update JavaScriptGenerator#show/hide/toggle/remove to new Prototype syntax for multiple ids, #6068 [petermichaux@gmail.com]
+
+* Update UrlWriter to support :only_path. [Nicholas Seckar, Dave Thomas]
+
+* Fixed JavaScriptHelper#link_to_function and JavaScriptHelper#button_to_function to have the script argument be optional [DHH]. So what used to require a nil, like this:
+
+ link_to("Hider", nil, :class => "hider_link") { |p| p[:something].hide }
+
+ ...can be written like this:
+
+ link_to("Hider", :class => "hider_link") { |p| p[:something].hide }
+
+* Added access to nested attributes in RJS #4548 [richcollins@gmail.com]. Examples:
+
+ page['foo']['style'] # => $('foo').style;
+ page['foo']['style']['color'] # => $('blank_slate').style.color;
+ page['foo']['style']['color'] = 'red' # => $('blank_slate').style.color = 'red';
+ page['foo']['style'].color = 'red' # => $('blank_slate').style.color = 'red';
+
+* Fixed that AssetTagHelper#image_tag and others using compute_public_path should not modify the incoming source argument (closes #5102) [eule@space.ch]
+
+* Deprecated the auto-appending of .png to AssetTagHelper#image_tag calls that doesn't have an extension [DHH]
+
+* Fixed FormOptionsHelper#select to respect :selected value #5813
+
+* Fixed TextHelper#simple_format to deal with multiple single returns within a single paragraph #5835 [moriq@moriq.com]
+
+* Fixed TextHelper#pluralize to handle 1 as a string #5909 [rails@bencurtis.com]
+
+* Improved resolution of DateHelper#distance_of_time_in_words for better precision #5994 [Bob Silva]
+
+* Changed that uncaught exceptions raised any where in the application will cause RAILS_ROOT/public/500.html to be read and shown instead of just the static "Application error (Rails)" [DHH]
+
+* Added deprecation language for pagination which will become a plugin by Rails 2.0 [DHH]
+
+* Added deprecation language for in_place_editor and auto_complete_field that both pieces will become plugins by Rails 2.0 [DHH]
+
+* Deprecated all of ActionController::Dependencies. All dependency loading is now handled from Active Support [DHH]
+
+* Added assert_select* for CSS selector-based testing (deprecates assert_tag) #5936 [assaf.arkin@gmail.com]
+
+* radio_button_tag generates unique id attributes. #3353 [Bob Silva, somekool@gmail.com]
+
+* strip_tags passes through blank args such as nil or "". #2229, #6702 [duncan@whomwah.com, dharana]
+
+* Cleanup assert_tag :children counting. #2181 [jamie@bravenet.com]
+
+* button_to accepts :method so you can PUT and DELETE with it. #6005 [Dan Webb]
+
+* Update sanitize text helper to strip plaintext tags, and <img src="javascript:bang">. [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 <acronym> 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 <m.stienstra@fngtps.com>]
+
+* Routing rewrite. Simpler, faster, easier to understand. The published API for config/routes.rb is unchanged, but nearly everything else is different, so expect breakage in plugins and libs that try to fiddle with routes. [Nicholas Seckar, Jamis Buck]
+
+ map.connect '/foo/:id', :controller => '...', :action => '...'
+ map.connect '/foo/:id.:format', :controller => '...', :action => '...'
+ map.connect '/foo/:id', ..., :conditions => { :method => :get }
+
+* Cope with missing content type and length headers. Parse parameters from multipart and urlencoded request bodies only. [Jeremy Kemper]
+
+* Accept multipart PUT parameters. #5235 [guy.naor@famundo.com]
+
+* Added interrogation of params[:format] to determine Accept type. If :format is specified and matches a declared extension, like "rss" or "xml", that mime type will be put in front of the accept handler. This means you can link to the same action from different extensions and use that fact to determine output [DHH]. Example:
+
+ class WeblogController < ActionController::Base
+ def index
+ @posts = Post.find :all
+
+ respond_to do |format|
+ format.html
+ format.xml { render :xml => @posts.to_xml }
+ format.rss { render :action => "feed.rxml" }
+ end
+ end
+ end
+
+ # returns HTML when requested by a browser, since the browser
+ # has the HTML mimetype at the top of its priority list
+ Accept: text/html
+ GET /weblog
+
+ # returns the XML
+ Accept: application/xml
+ GET /weblog
+
+ # returns the HTML
+ Accept: application/xml
+ GET /weblog.html
+
+ # returns the XML
+ Accept: text/html
+ GET /weblog.xml
+
+ All this relies on the fact that you have a route that includes .:format.
+
+* Expanded :method option in FormTagHelper#form_tag, FormHelper#form_for, PrototypeHelper#remote_form_for, PrototypeHelper#remote_form_tag, and PrototypeHelper#link_to_remote to allow for verbs other than GET and POST by automatically creating a hidden form field named _method, which will simulate the other verbs over post [DHH]
+
+* Added :method option to UrlHelper#link_to, which allows for using other verbs than GET for the link. This replaces the :post option, which is now deprecated. Example: link_to "Destroy", person_url(:id => person), :method => :delete [DHH]
+
+* follow_redirect doesn't complain about being redirected to the same controller. #5153 [dymo@mk.ukrtelecom.ua]
+
+* Add layout attribute to response object with the name of the layout that was rendered, or nil if none rendered. [Kevin Clark kevin.clark@gmail.com]
+
+* Fix NoMethodError when parsing params like &&. [Adam Greenfield]
+
+* form.text_area handles the :size option just like the original text_area (:size => '60x10' becomes cols="60" rows="10"). [Jeremy Kemper]
+
+* Excise ingrown code from FormOptionsHelper#options_for_select. #5008 [anonymous]
+
+* Small fix in routing to allow dynamic routes (broken after [4242]) [Rick]
+
+ map.connect '*path', :controller => 'files', :action => 'show'
+
+* Use #flush between switching from #write to #syswrite. Closes #4907. [Blair Zajac <blair@orcaware.com>]
+
+* Allow error_messages_for to report errors for multiple objects, as well as support for customizing the name of the object in the error summary header. Closes #4186. [andrew@redlinesoftware.com, Marcel Molina Jr.]
+
+ error_messages_for :account, :user, :subscription, :object_name => :account
+
+* Fix assert_redirected_to tests according to real-world usage. Also, don't fail if you add an extra :controller option: [Rick]
+
+ redirect_to :action => 'new'
+ assert_redirected_to :controller => 'monkeys', :action => 'new'
+
+* Diff compared routing options. Allow #assert_recognizes to take a second arg as a hash to specify optional request method [Rick]
+
+ assert_recognizes({:controller => 'users', :action => 'index'}, 'users')
+ assert_recognizes({:controller => 'users', :action => 'create'}, {:path => 'users', :method => :post})
+
+* Diff compared options with #assert_redirected_to [Rick]
+
+* Add support in routes for semicolon delimited "subpaths", like /books/:id;:action [Jamis Buck]
+
+* Change link_to_function and button_to_function to (optionally) take an update_page block instead of a JavaScript string. Closes #4804. [zraii@comcast.net, Sam Stephenson]
+
+* Modify routing so that you can say :require => { :method => :post } for a route, and the route will never be selected unless the request method is POST. Only works for route recognition, not for route generation. [Jamis Buck]
+
+* Added :add_headers option to verify which merges a hash of name/value pairs into the response's headers hash if the prerequisites cannot be satisfied. [Sam Stephenson]
+ ex. verify :only => :speak, :method => :post,
+ :render => { :status => 405, :text => "Must be post" },
+ :add_headers => { "Allow" => "POST" }
+
+
+*1.12.5* (August 10th, 2006)
+
+* Updated security fix
+
+
+*1.12.4* (August 8th, 2006)
+
+* Cache CgiRequest#request_parameters so that multiple calls don't re-parse multipart data. [Rick]
+
+* Fixed that remote_form_for can leave out the object parameter and default to the instance variable of the object_name, just like form_for [DHH]
+
+* Added ActionController.filter_parameter_logging that makes it easy to remove passwords, credit card numbers, and other sensitive information from being logged when a request is handled. #1897 [jeremye@bsa.ca.gov]
+
+* Fixed that real files and symlinks should be treated the same when compiling templates. #5438 [zachary@panandscan.com]
+
+* Add :status option to send_data and send_file. Defaults to '200 OK'. #5243 [Manfred Stienstra <m.stienstra@fngtps.com>]
+
+* Update documentation for erb trim syntax. #5651 [matt@mattmargolis.net]
+
+* Short documentation to mention use of Mime::Type.register. #5710 [choonkeat@gmail.com]
+
+
+*1.12.3* (June 28th, 2006)
+
+* Fix broken traverse_to_controller. We now:
+ Look for a _controller.rb file under RAILS_ROOT to load.
+ If we find it, we require_dependency it and return the controller it defined. (If none was defined we stop looking.)
+ If we don't find it, we look for a .rb file under RAILS_ROOT to load. If we find it, and it loads a constant we keep looking.
+ Otherwise we check to see if a directory of the same name exists, and if it does we create a module for it.
+
+
+*1.12.2* (June 27th, 2006)
+
+* Refinement to avoid exceptions in traverse_to_controller.
+
+* (Hackish) Fix loading of arbitrary files in Ruby's load path by traverse_to_controller. [Nicholas Seckar]
+
+
+*1.12.1* (April 6th, 2006)
+
+* Fixed that template extensions would be cached development mode #4624 [Stefan Kaes]
+
+* Update to Prototype 1.5.0_rc0 [Sam Stephenson]
+
+* Honor skipping filters conditionally for only certain actions even when the parent class sets that filter to conditionally be executed only for the same actions. #4522 [Marcel Molina Jr.]
+
+* Delegate xml_http_request in integration tests to the session instance. [Jamis Buck]
+
+* Update the diagnostics template skip the useless '<controller not set>' text. [Nicholas Seckar]
+
+* CHANGED DEFAULT: Don't parse YAML input by default, but keep it available as an easy option [DHH]
+
+* Add additional autocompleter options [aballai, Thomas Fuchs]
+
+* Fixed fragment caching of binary data on Windows #4493 [bellis@deepthought.org]
+
+* Applied Prototype $() performance patches (#4465, #4477) and updated script.aculo.us [Sam Stephenson, Thomas Fuchs]
+
+* Added automated timestamping to AssetTagHelper methods for stylesheets, javascripts, and images when Action Controller is run under Rails [DHH]. Example:
+
+ image_tag("rails.png") # => '<img alt="Rails" src="/images/rails.png?1143664135" />'
+
+ ...to avoid frequent stats (not a problem for most people), you can set RAILS_ASSET_ID in the ENV to avoid stats:
+
+ ENV["RAILS_ASSET_ID"] = "2345"
+ image_tag("rails.png") # => '<img alt="Rails" src="/images/rails.png?2345" />'
+
+ This can be used by deployment managers to set the asset id by application revision
+
+
+*1.12.0* (March 27th, 2006)
+
+* Add documentation for respond_to. [Jamis Buck]
+
+* Fixed require of bluecloth and redcloth when gems haven't been loaded #4446 [murphy@cYcnus.de]
+
+* Update to Prototype 1.5.0_pre1 [Sam Stephenson]
+
+* Change #form_for and #fields_for so that the second argument is not required [Dave Thomas]
+
+ <% form_for :post, @post, :url => { :action => 'create' } do |f| -%>
+
+ becomes...
+
+ <% form_for :post, :url => { :action => 'create' } do |f| -%>
+
+* Update to script.aculo.us 1.6 [Thomas Fuchs]
+
+* Enable application/x-yaml processing by default [Jamis Buck]
+
+* Fix double url escaping of remote_function. Add :escape => false option to ActionView's url_for. [Nicholas Seckar]
+
+* Add :script option to in_place_editor to support evalScripts (closes #4194) [codyfauser@gmail.com]
+
+* Fix mixed case enumerable methods in the JavaScript Collection Proxy (closes #4314) [codyfauser@gmail.com]
+
+* Undo accidental escaping for mail_to; add regression test. [Nicholas Seckar]
+
+* Added nicer message for assert_redirected_to (closes #4294) [court3nay]
+
+ assert_redirected_to :action => 'other_host', :only_path => false
+
+ when it was expecting...
+
+ redirected_to :action => 'other_host', :only_path => true, :host => 'other.test.host'
+
+ gives the error message...
+
+ response is not a redirection to all of the options supplied (redirection is <{:only_path=>false, :host=>"other.test.host", :action=>"other_host"}>), difference: <{:only_path=>"true", :host=>"other.test.host"}>
+
+* Change url_for to escape the resulting URLs when called from a view. [Nicholas Seckar, coffee2code]
+
+* Added easy support for testing file uploads with fixture_file_upload #4105 [turnip@turnipspatch.com]. Example:
+
+ # Looks in Test::Unit::TestCase.fixture_path + '/files/spongebob.png'
+ post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
+
+* Fixed UrlHelper#current_page? to behave even when url-escaped entities are present #3929 [jeremy@planetargon.com]
+
+* Add ability for relative_url_root to be specified via an environment variable RAILS_RELATIVE_URL_ROOT. [isaac@reuben.com, Nicholas Seckar]
+
+* Fixed link_to "somewhere", :post => true to produce valid XHTML by using the parentnode instead of document.body for the instant form #3007 [Bob Silva]
+
+* Added :function option to PrototypeHelper#observe_field/observe_form that allows you to call a function instead of submitting an ajax call as the trigger #4268 [jonathan@daikini.com]
+
+* Make Mime::Type.parse consider q values (if any) [Jamis Buck]
+
+* XML-formatted requests are typecast according to "type" attributes for :xml_simple [Jamis Buck]
+
+* Added protection against proxy setups treating requests as local even when they're not #3898 [stephen_purcell@yahoo.com]
+
+* Added TestRequest#raw_post that simulate raw_post from CgiRequest #3042 [francois.beausoleil@gmail.com]
+
+* Underscore dasherized keys in formatted requests [Jamis Buck]
+
+* Add MimeResponds::Responder#any for managing multiple types with identical responses [Jamis Buck]
+
+* Make the xml_http_request testing method set the HTTP_ACCEPT header [Jamis Buck]
+
+* Add Verification to scaffolds. Prevent destructive actions using GET [Michael Koziarski]
+
+* Avoid hitting the filesystem when using layouts by using a File.directory? cache. [Stefan Kaes, Nicholas Seckar]
+
+* Simplify ActionController::Base#controller_path [Nicholas Seckar]
+
+* Added simple alert() notifications for RJS exceptions when config.action_view.debug_rjs = true. [Sam Stephenson]
+
+* Added :content_type option to render, so you can change the content type on the fly [DHH]. Example: render :action => "atom.rxml", :content_type => "application/atom+xml"
+
+* CHANGED DEFAULT: The default content type for .rxml is now application/xml instead of type/xml, see http://www.xml.com/pub/a/2004/07/21/dive.html for reason [DHH]
+
+* Added option to render action/template/file of a specific extension (and here by template type). This means you can have multiple templates with the same name but a different extension [DHH]. Example:
+
+ class WeblogController < ActionController::Base
+ def index
+ @posts = Post.find :all
+
+ respond_to do |type|
+ type.html # using defaults, which will render weblog/index.rhtml
+ type.xml { render :action => "index.rxml" }
+ type.js { render :action => "index.rjs" }
+ end
+ end
+ end
+
+* Added better support for using the same actions to output for different sources depending on the Accept header [DHH]. Example:
+
+ class WeblogController < ActionController::Base
+ def create
+ @post = Post.create(params[:post])
+
+ respond_to do |type|
+ type.js { render } # renders create.rjs
+ type.html { redirect_to :action => "index" }
+ type.xml do
+ headers["Location"] = url_for(:action => "show", :id => @post)
+ render(:nothing, :status => "201 Created")
+ end
+ end
+ end
+ end
+
+* Added Base#render(:xml => xml) that works just like Base#render(:text => text), but sets the content-type to text/xml and the charset to UTF-8 [DHH]
+
+* Integration test's url_for now runs in the context of the last request (if any) so after post /products/show/1 url_for :action => 'new' will yield /product/new [Tobias Luetke]
+
+* Re-added mixed-in helper methods for the JavascriptGenerator. Moved JavascriptGenerators methods to a module that is mixed in after the helpers are added. Also fixed that variables set in the enumeration methods like #collect are set correctly. Documentation added for the enumeration methods [Rick Olson]. Examples:
+
+ page.select('#items li').collect('items') do |element|
+ element.hide
+ end
+ # => var items = $$('#items li').collect(function(value, index) { return value.hide(); });
+
+* Added plugin support for parameter parsers, which allows for better support for REST web services. By default, posts submitted with the application/xml content type is handled by creating a XmlSimple hash with the same name as the root element of the submitted xml. More handlers can easily be registered like this:
+
+ # Assign a new param parser to a new content type
+ ActionController::Base.param_parsers['application/atom+xml'] = Proc.new do |data|
+ node = REXML::Document.new(post)
+ { node.root.name => node.root }
+ end
+
+ # Assign the default XmlSimple to a new content type
+ ActionController::Base.param_parsers['application/backpack+xml'] = :xml_simple
+
+Default YAML web services were retired, ActionController::Base.param_parsers carries an example which shows how to get this functionality back. As part of this new plugin support, request.[formatted_post?, xml_post?, yaml_post? and post_format] were all deprecated in favor of request.content_type [Tobias Luetke]
+
+* Fixed Effect.Appear in effects.js to work with floats in Safari #3524, #3813, #3044 [Thomas Fuchs]
+
+* Fixed that default image extension was not appended when using a full URL with AssetTagHelper#image_tag #4032, #3728 [rubyonrails@beautifulpixel.com]
+
+* Added that page caching will only happen if the response code is less than 400 #4033 [g.bucher@teti.ch]
+
+* Add ActionController::IntegrationTest to allow high-level testing of the way the controllers and routes all work together [Jamis Buck]
+
+* Added support to AssetTagHelper#javascript_include_tag for having :defaults appear anywhere in the list, so you can now make one call ala javascript_include_tag(:defaults, "my_scripts") or javascript_include_tag("my_scripts", :defaults) depending on how you want the load order #3506 [Bob Silva]
+
+* Added support for visual effects scoped queues to the visual_effect helper #3530 [Abdur-Rahman Advany]
+
+* Added .rxml (and any non-rhtml template, really) supportfor CaptureHelper#content_for and CaptureHelper#capture #3287 [Brian Takita]
+
+* Added script.aculo.us drag and drop helpers to RJS [Thomas Fuchs]. Examples:
+
+ page.draggable 'product-1'
+ page.drop_receiving 'wastebasket', :url => { :action => 'delete' }
+ page.sortable 'todolist', :url => { action => 'change_order' }
+
+* Fixed that form elements would strip the trailing [] from the first parameter #3545 [ruby@bobsilva.com]
+
+* During controller resolution, update the NameError suppression to check for the expected constant. [Nicholas Seckar]
+
+* Update script.aculo.us to V1.5.3 [Thomas Fuchs]
+
+* Added various InPlaceEditor options, #3746, #3891, #3896, #3906 [Bill Burcham, ruairi, sl33p3r]
+
+* Added :count option to pagination that'll make it possible for the ActiveRecord::Base.count call to using something else than * for the count. Especially important for count queries using DISTINCT #3839 [skaes]
+
+* Update script.aculo.us to V1.5.2 [Thomas Fuchs]
+
+* Added element and collection proxies to RJS [DHH]. Examples:
+
+ page['blank_slate'] # => $('blank_slate');
+ page['blank_slate'].show # => $('blank_slate').show();
+ page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
+
+ page.select('p') # => $$('p');
+ page.select('p.welcome b').first # => $$('p.welcome b').first();
+ page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
+
+* Add JavaScriptGenerator#replace for replacing an element's "outer HTML". #3246 [tom@craz8.com, Sam Stephenson]
+
+* Remove over-engineered form_for code for a leaner implementation. [Nicholas Seckar]
+
+* Document form_for's :html option. [Nicholas Seckar]
+
+* Major components cleanup and speedup. #3527 [Stefan Kaes]
+
+* Fix problems with pagination and :include. [Kevin Clark]
+
+* Add ActiveRecordTestCase for testing AR integration. [Kevin Clark]
+
+* Add Unit Tests for pagination [Kevin Clark]
+
+* Add :html option for specifying form tag options in form_for. [Sam Stephenson]
+
+* Replace dubious controller parent class in filter docs. #3655, #3722 [info@rhalff.com, eigentone@gmail.com]
+
+* Don't interpret the :value option on text_area as an html attribute. Set the text_area's value. #3752 [gabriel@gironda.org]
+
+* Fix remote_form_for creates a non-ajax form. [Rick Olson]
+
+* Don't let arbitrary classes match as controllers -- a potentially dangerous bug. [Nicholas Seckar]
+
+* Fix Routing tests. Fix routing where failing to match a controller would prevent the rest of routes from being attempted. [Nicholas Seckar]
+
+* Add :builder => option to form_for and friends. [Nicholas Seckar, Rick Olson]
+
+* Fix controller resolution to avoid accidentally inheriting a controller from a parent module. [Nicholas Seckar]
+
+* Set sweeper's @controller to nil after a request so that the controller may be collected between requests. [Nicholas Seckar]
+
+* Subclasses of ActionController::Caching::Sweeper should be Reloadable. [Rick Olson]
+
+* Document the :xhr option for verifications. #3666 [leeo]
+
+* Added :only and :except controls to skip_before/after_filter just like for when you add filters [DHH]
+
+* Ensure that the instance variables are copied to the template when performing render :update. [Nicholas Seckar]
+
+* Add the ability to call JavaScriptGenerator methods from helpers called in update blocks. [Sam Stephenson] Example:
+ module ApplicationHelper
+ def update_time
+ page.replace_html 'time', Time.now.to_s(:db)
+ page.visual_effect :highlight, 'time'
+ end
+ end
+
+ class UserController < ApplicationController
+ def poll
+ render :update { |page| page.update_time }
+ end
+ end
+
+* Add render(:update) to ActionView::Base. [Sam Stephenson]
+
+* Fix render(:update) to not render layouts. [Sam Stephenson]
+
+* Fixed that SSL would not correctly be detected when running lighttpd/fcgi behind lighttpd w/mod_proxy #3548 [stephen_purcell@yahoo.com]
+
+* Added the possibility to specify atomatic expiration for the memcachd session container #3571 [Stefan Kaes]
+
+* Change layout discovery to take into account the change in semantics with File.join and nil arguments. [Marcel Molina Jr.]
+
+* Raise a RedirectBackError if redirect_to :back is called when there's no HTTP_REFERER defined #3049 [kevin.clark@gmail.com]
+
+* Treat timestamps like datetimes for scaffolding purposes #3388 [Maik Schmidt]
+
+* Fix IE bug with link_to "something", :post => true #3443 [Justin Palmer]
+
+* Extract Test::Unit::TestCase test process behavior into an ActionController::TestProcess module. [Sam Stephenson]
+
+* Pass along blocks from render_to_string to render. [Sam Stephenson]
+
+* Add render :update for inline RJS. [Sam Stephenson] Example:
+ class UserController < ApplicationController
+ def refresh
+ render :update do |page|
+ page.replace_html 'user_list', :partial => 'user', :collection => @users
+ page.visual_effect :highlight, 'user_list'
+ end
+ end
+ end
+
+* allow nil objects for error_messages_for [Michael Koziarski]
+
+* Refactor human_size to exclude decimal place if it is zero. [Marcel Molina Jr.]
+
+* Update to Prototype 1.5.0_pre0 [Sam Stephenson]
+
+* Automatically discover layouts when a controller is namespaced. #2199, #3424 [me@jonnii.com rails@jeffcole.net Marcel Molina Jr.]
+
+* Add support for multiple proxy servers to CgiRequest#host [gaetanot@comcast.net]
+
+* Documentation typo fix. #2367 [Blair Zajac]
+
+* Remove Upload Progress. #2871 [Sean Treadway]
+
+* Fix typo in function name mapping in auto_complete_field. #2929 #3446 [doppler@gmail.com phil.ross@gmail.com]
+
+* Allow auto-discovery of third party template library layouts. [Marcel Molina Jr.]
+
+* Have the form builder output radio button, not check box, when calling the radio button helper. #3331 [LouisStAmour@gmail.com]
+
+* Added assignment of the Autocompleter object created by JavaScriptMacroHelper#auto_complete_field to a local javascript variables [DHH]
+
+* Added :on option for PrototypeHelper#observe_field that allows you to specify a different callback hook to have the observer trigger on [DHH]
+
+* Added JavaScriptHelper#button_to_function that works just like JavaScriptHelper#link_to_function but uses a button instead of a href [DHH]
+
+* Added that JavaScriptHelper#link_to_function will honor existing :onclick definitions when adding the function call [DHH]
+
+* Added :disable_with option to FormTagHelper#submit_tag to allow for easily disabled submit buttons with different text [DHH]
+
+* Make auto_link handle nil by returning quickly if blank? [Scott Barron]
+
+* Make auto_link match urls with a port number specified. [Marcel Molina Jr.]
+
+* Added support for toggling visual effects to ScriptaculousHelper::visual_effect, #3323. [Thomas Fuchs]
+
+* Update to script.aculo.us to 1.5.0 rev. 3343 [Thomas Fuchs]
+
+* Added :select option for JavaScriptMacroHelper#auto_complete_field that makes it easier to only use part of the auto-complete suggestion as the value for insertion [Thomas Fuchs]
+
+* Added delayed execution of Javascript from within RJS #3264 [devslashnull@gmail.com]. Example:
+
+ page.delay(20) do
+ page.visual_effect :fade, 'notice'
+ end
+
+* Add session ID to default logging, but remove the verbose description of every step [DHH]
+
+* Add the following RJS methods: [Sam Stephenson]
+
+ * alert - Displays an alert() dialog
+ * redirect_to - Changes window.location.href to simulate a browser redirect
+ * call - Calls a JavaScript function
+ * assign - Assigns to a JavaScript variable
+ * << - Inserts an arbitrary JavaScript string
+
+* Fix incorrect documentation for form_for [Nicholas Seckar]
+
+* Don't include a layout when rendering an rjs template using render's :template option. [Marcel Molina Jr.]
+
+*1.1.2* (December 13th, 2005)
+
+* Become part of Rails 1.0
+
+* Update to script.aculo.us 1.5.0 final (equals 1.5.0_rc6) [Thomas Fuchs]
+
+* Update to Prototype 1.4.0 final [Sam Stephenson]
+
+* Added form_remote_for (form_for meets form_remote_tag) [DHH]
+
+* Update to script.aculo.us 1.5.0_rc6
+
+* More robust relative url root discovery for SCGI compatibility. This solves the 'SCGI routes problem' -- you no longer need to prefix all your routes with the name of the SCGI mountpoint. #3070 [Dave Ringoen]
+
+* Fix docs for text_area_tag. #3083. [Christopher Cotton]
+
+* Change form_for and fields_for method signatures to take object name and object as separate arguments rather than as a Hash. [DHH]
+
+* Introduce :selected option to the select helper. Allows you to specify a selection other than the current value of object.method. Specify :selected => nil to leave all options unselected. #2991 [Jonathan Viney <jonathan@bluewire.net.nz>]
+
+* Initialize @optional in routing code to avoid warnings about uninitialized access to an instance variable. [Nicholas Seckar]
+
+* Make ActionController's render honor the :locals option when rendering a :file. #1665. [Emanuel Borsboom, Marcel Molina Jr.]
+
+* Allow assert_tag(:conditions) to match the empty string when a tag has no children. Closes #2959. [Jamis Buck]
+
+* Update html-scanner to handle CDATA sections better. Closes #2970. [Jamis Buck]
+
+* Don't put flash in session if sessions are disabled. [Jeremy Kemper]
+
+* Strip out trailing &_= for raw post bodies. Closes #2868. [Sam Stephenson]
+
+* Pass multiple arguments to Element.show and Element.hide in JavaScriptGenerator instead of using iterators. [Sam Stephenson]
+
+* Improve expire_fragment documentation. #2966 [court3nay@gmail.com]
+
+* Correct docs for automatic layout assignment. #2610. [Charles M. Gerungan]
+
+* Always create new AR sessions rather than trying too hard to avoid database traffic. #2731 [Jeremy Kemper]
+
+* Update to Prototype 1.4.0_rc4. Closes #2943 (old Array.prototype.reverse behavior can be obtained by passing false as an argument). [Sam Stephenson]
+
+* Use Element.update('id', 'html') instead of $('id').innerHTML = 'html' in JavaScriptGenerator#replace_html so that script tags are evaluated. [Sam Stephenson]
+
+* Make rjs templates always implicitly skip out on layouts. [Marcel Molina Jr.]
+
+* Correct length for the truncate text helper. #2913 [Stefan Kaes]
+
+* Update to Prototype 1.4.0_rc3. Closes #1893, #2505, #2550, #2748, #2783. [Sam Stephenson]
+
+* Add support for new rjs templates which wrap an update_page block. [Marcel Molina Jr.]
+
+* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.]
+
+* Correct time_zone_options_for_select docs. #2892 [pudeyo@rpi.com]
+
+* Remove the unused, slow response_dump and session_dump variables from error pages. #1222 [lmarlow@yahoo.com]
+
+* Performance tweaks: use Set instead of Array to speed up prototype helper include? calls. Avoid logging code if logger is nil. Inline commonly-called template presence checks. #2880, #2881, #2882, #2883 [Stefan Kaes]
+
+* MemCache store may be given multiple addresses. #2869 [Ryan Carver <ryan@fivesevensix.com>]
+
+* Handle cookie parsing irregularity for certain Nokia phones. #2530 [zaitzow@gmail.com]
+
+* Added PrototypeHelper::JavaScriptGenerator and PrototypeHelper#update_page for easily modifying multiple elements in an Ajax response. [Sam Stephenson] Example:
+
+ update_page do |page|
+ page.insert_html :bottom, 'list', '<li>Last item</li>'
+ page.visual_effect :highlight, 'list'
+ page.hide 'status-indicator', 'cancel-link'
+ end
+
+ generates the following JavaScript:
+
+ new Insertion.Bottom("list", "<li>Last item</li>");
+ new Effect.Highlight("list");
+ ["status-indicator", "cancel-link"].each(Element.hide);
+
+* Refactored JavaScriptHelper into PrototypeHelper and ScriptaculousHelper [Sam Stephenson]
+
+* Update to latest script.aculo.us version (as of [3031])
+
+* Updated docs for in_place_editor, fixes a couple bugs and offers extended support for external controls [Justin Palmer]
+
+* Update documentation for render :file. #2858 [Tom Werner]
+
+* Only include builtin filters whose filenames match /^[a-z][a-z_]*_helper.rb$/ to avoid including operating system metadata such as ._foo_helper.rb. #2855 [court3nay@gmail.com]
+
+* Added FormHelper#form_for and FormHelper#fields_for that makes it easier to work with forms for single objects also if they don't reside in instance variables [DHH]. Examples:
+
+ <% form_for :person, @person, :url => { :action => "update" } do |f| %>
+ First name: <%= f.text_field :first_name %>
+ Last name : <%= f.text_field :last_name %>
+ Biography : <%= f.text_area :biography %>
+ Admin? : <%= f.check_box :admin %>
+ <% end %>
+
+ <% form_for :person, person, :url => { :action => "update" } do |person_form| %>
+ First name: <%= person_form.text_field :first_name %>
+ Last name : <%= person_form.text_field :last_name %>
+
+ <% fields_for :permission => person.permission do |permission_fields| %>
+ Admin? : <%= permission_fields.check_box :admin %>
+ <% end %>
+ <% end %>
+
+* options_for_select allows any objects which respond_to? :first and :last rather than restricting to Array and Range. #2824 [Jacob Robbins <jrobbins@cmj.com>, Jeremy Kemper]
+
+* The auto_link text helper accepts an optional block to format the link text for each url and email address. Example: auto_link(post.body) { |text| truncate(text, 10) } [Jeremy Kemper]
+
+* assert_tag uses exact matches for string conditions, instead of partial matches. Use regex to do partial matches. #2799 [Jamis Buck]
+
+* CGI::Session::ActiveRecordStore.data_column_name = 'foobar' to use a different session data column than the 'data' default. [nbpwie102@sneakemail.com]
+
+* Do not raise an exception when default helper is missing; log a debug message instead. It's nice to delete empty helpers. [Jeremy Kemper]
+
+* Controllers with acronyms in their names (e.g. PDFController) require the correct default helper (PDFHelper in file pdf_helper.rb). #2262 [jeff@opendbms.com]
+
+
+*1.11.0* (November 7th, 2005)
+
+* Added request as instance method to views, so you can do <%= request.env["HTTP_REFERER"] %>, just like you can already access response, session, and the likes [DHH]
+
+* Fix conflict with assert_tag and Glue gem #2255 [david.felstead@gmail.com]
+
+* Add documentation to assert_tag indicating that it only works with well-formed XHTML #1937, #2570 [Jamis Buck]
+
+* Added action_pack.rb stub so that ActionPack::Version loads properly [Sam Stephenson]
+
+* Added short-hand to assert_tag so assert_tag :tag => "span" can be written as assert_tag "span" [DHH]
+
+* Added skip_before_filter/skip_after_filter for easier control of the filter chain in inheritance hierachies [DHH]. Example:
+
+ class ApplicationController < ActionController::Base
+ before_filter :authenticate
+ end
+
+ class WeblogController < ApplicationController
+ # will run the :authenticate filter
+ end
+
+ class SignupController < ActionController::Base
+ # will not run the :authenticate filter
+ skip_before_filter :authenticate
+ end
+
+* Added redirect_to :back as a short-hand for redirect_to(request.env["HTTP_REFERER"]) [DHH]
+
+* Change javascript_include_tag :defaults to not use script.aculo.us loader, which facilitates the use of plugins for future script.aculo.us and third party javascript extensions, and provide register_javascript_include_default for plugins to specify additional JavaScript files to load. Removed slider.js and builder.js from actionpack. [Thomas Fuchs]
+
+* Fix problem where redirecting components can cause an infinite loop [Rick Olson]
+
+* Added support for the queue option on visual_effect [Thomas Fuchs]
+
+* Update script.aculo.us to V1.5_rc4 [Thomas Fuchs]
+
+* Fix that render :text didn't interpolate instance variables #2629, #2626 [skaes]
+
+* Fix line number detection and escape RAILS_ROOT in backtrace Regexp [Nicholas Seckar]
+
+* Fixed document.getElementsByClassName from Prototype to be speedy again [Sam Stephenson]
+
+* Recognize ./#{RAILS_ROOT} as RAILS_ROOT in error traces [Nicholas Seckar]
+
+* Remove ARStore session fingerprinting [Nicholas Seckar]
+
+* Fix obscure bug in ARStore [Nicholas Seckar]
+
+* Added TextHelper#strip_tags for removing HTML tags from a string (using HTMLTokenizer) #2229 [marcin@junkheap.net]
+
+* Added a reader for flash.now, so it's possible to do stuff like flash.now[:alert] ||= 'New if not set' #2422 [Caio Chassot]
+
+
+*1.10.2* (October 26th, 2005)
+
+* Reset template variables after using render_to_string [skaes@web.de]
+
+* Expose the session model backing CGI::Session
+
+* Abbreviate RAILS_ROOT in traces
+
+
+*1.10.1* (October 19th, 2005)
+
+* Update error trace templates [Nicholas Seckar]
+
+* Stop showing generated routing code in application traces [Nicholas Seckar]
+
+
+*1.10.0* (October 16th, 2005)
+
+* Make string-keys locals assigns optional. Add documentation describing depreciated state [skaes@web.de]
+
+* Improve line number detection for template errors [Nicholas Seckar]
+
+* Update/clean up documentation (rdoc)
+
+* Upgrade to Prototype 1.4.0_rc0 [Sam Stephenson]
+
+* Added assert_vaild. Reports the proper AR error messages as fail message when the passed record is invalid [Tobias Luetke]
+
+* Add temporary support for passing locals to render using string keys [Nicholas Seckar]
+
+* Clean up error pages by providing better backtraces [Nicholas Seckar]
+
+* Raise an exception if an attempt is made to insert more session data into the ActiveRecordStore data column than the column can hold. #2234. [justin@textdrive.com]
+
+* Removed references to assertions.rb from actionpack assert's backtraces. Makes error reports in functional unit tests much less noisy. [Tobias Luetke]
+
+* Updated and clarified documentation for JavaScriptHelper to be more concise about the various options for including the JavaScript libs. [Thomas Fuchs]
+
+* Hide "Retry with Breakpoint" button on error pages until feature is functional. [DHH]
+
+* Fix Request#host_with_port to use the standard port when Rails is behind a proxy. [Nicholas Seckar]
+
+* Escape query strings in the href attribute of URLs created by url_helper. #2333 [Michael Schuerig <michael@schuerig.de>]
+
+* Improved line number reporting for template errors [Nicholas Seckar]
+
+* Added :locals support for render :inline #2463 [mdabney@cavoksolutions.com]
+
+* Unset the X-Requested-With header when using the xhr wrapper in functional tests so that future requests aren't accidentally xhr'ed #2352 [me@julik.nl, Sam Stephenson]
+
+* Unescape paths before writing cache to file system. #1877. [Damien Pollet]
+
+* Wrap javascript_tag contents in a CDATA section and add a cdata_section method to TagHelper #1691 [Michael Schuerig, Sam Stephenson]
+
+* Misc doc fixes (typos/grammar/etc). #2445. [coffee2code]
+
+* Speed improvement for session_options. #2287. [skaes@web.de]
+
+* Make cacheing binary files friendly with Windows. #1975. [Rich Olson]
+
+* Convert boolean form options form the tag_helper. #809. [Michael Schuerig <michael@schuerig.de>]
+
+* Fixed that an instance variable with the same name as a partial should be implicitly passed as the partial :object #2269 [court3nay]
+
+* Update Prototype to V1.4.0_pre11, script.aculo.us to [2502] [Thomas Fuchs]
+
+* Make assert_tag :children count appropriately. Closes #2181. [jamie@bravenet.com]
+
+* Forced newer versions of RedCloth to use hard breaks [DHH]
+
+* Added new scriptaculous options for auto_complete_field #2343 [m.stienstra@fngtps.com]
+
+* Don't prepend the asset host if the string is already a fully-qualified URL
+
+* Updated to script.aculo.us V1.5.0_rc2 and Prototype to V1.4.0_pre7 [Thomas Fuchs]
+
+* Undo condition change made in [2345] to prevent normal parameters arriving as StringIO.
+
+* Tolerate consecutive delimiters in query parameters. #2295 [darashi@gmail.com]
+
+* Streamline render process, code cleaning. Closes #2294. [skae]
+
+* Keep flash after components are rendered. #2291 [Rick Olson, Scott]
+
+* Shorten IE file upload path to filename only to match other browsers. #1507 [court3nay@gmail.com]
+
+* Fix open/save dialog in IE not opening files send with send_file/send_data, #2279 [Thomas Fuchs]
+
+* Fixed that auto_discovery_link_tag couldn't take a string as the URL [DHH]
+
+* Fixed problem with send_file and WEBrick using stdout #1812 [DHH]
+
+* Optimized tag_options to not sort keys, which is no longer necessary when assert_dom_equal and friend is available #1995 [skae]
+
+* Added assert_dom_equal and assert_dom_not_equal to compare tags generated by the helpers in an order-indifferent manner #1995 [skae]
+
+* Fixed that Request#domain caused an exception if the domain header wasn't set in the original http request #1795 [Michael Koziarski]
+
+* Make the truncate() helper multi-byte safe (assuming $KCODE has been set to something other than "NONE") #2103
+
+* Add routing tests from #1945 [ben@groovie.org]
+
+* Add a routing test case covering #2101 [Nicholas Seckar]
+
+* Cache relative_url_root for all webservers, not just Apache #2193 [skae]
+
+* Speed up cookie use by decreasing string copying #2194 [skae]
+
+* Fixed access to "Host" header with requests made by crappy old HTTP/1.0 clients #2124 [Marcel Molina]
+
+* Added easy assignment of fragment cache store through use of symbols for included stores (old way still works too)
+
+ Before:
+ ActionController::Base.fragment_cache_store =
+ ActionController::Base::Caching::Fragments::FileStore.new("/path/to/cache/directory")
+
+ After:
+ ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory"
+
+* Added ActionController::Base.session_store=, session_store, and session_options to make it easier to tweak the session options (instead of going straight to ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS)
+
+* Added TextHelper#cycle to cycle over an array of values on each hit (useful for alternating row colors etc) #2154 [dave-ml@dribin.org]
+
+* Ensure that request.path never returns nil. Closes #1675 [Nicholas Seckar]
+
+* Add ability to specify Route Regexps for controllers. Closes #1917. [Sebastian Kanthak]
+
+* Provide Named Route's hash methods as helper methods. Closes #1744. [Nicholas Seckar, Steve Purcell]
+
+* Added :multipart option to ActiveRecordHelper#form to make it possible to add file input fields #2034 [jstirk@oobleyboo.com]
+
+* Moved auto-completion and in-place editing into the Macros module and their helper counterparts into JavaScriptMacrosHelper
+
+* Added in-place editing support in the spirit of auto complete with ActionController::Base.in_place_edit_for, JavascriptHelper#in_place_editor_field, and Javascript support from script.aculo.us #2038 [Jon Tirsen]
+
+* Added :disabled option to all data selects that'll make the elements inaccessible for change #2167, #253 [eigentone]
+
+* Fixed that TextHelper#auto_link_urls would include punctuation in the links #2166, #1671 [eigentone]
+
+* Fixed that number_to_currency(1000, {:precision => 0})) should return "$1,000", instead of "$1,000." #2122 [sd@notso.net]
+
+* Allow link_to_remote to use any DOM-element as the parent of the form elements to be submitted #2137 [erik@ruby-lang.nl]. Example:
+
+ <tr id="row023">
+ <td><input name="foo"/></td>
+ <td><input name="bar"/></td>
+ <td><%= link_to_remote 'Save', :update => "row023",
+ :submit => "row023", :url => {:action => 'save_row'} %></td>
+ </tr>
+
+* Fixed that render :partial would fail when :object was a Hash (due to backwards compatibility issues) #2148 [Sam Stephenson]
+
+* Fixed JavascriptHelper#auto_complete_for to only include unique items #2153 [Thomas Fuchs]
+
+* Fixed all AssetHelper methods to work with relative paths, such that javascript_include_tag('stdlib/standard') will look in /javascripts/stdlib/standard instead of '/stdlib/standard/' #1963
+
+* Avoid extending view instance with helper modules each request. Closes #1979
+
+* Performance improvements to CGI methods. Closes #1980 [Skaes]
+
+* Added :post option to UrlHelper#link_to that makes it possible to do POST requests through normal ahref links using Javascript
+
+* Fixed overwrite_params
+
+* Added ActionController::Base.benchmark and ActionController::Base.silence to allow for easy benchmarking and turning off the log
+
+* Updated vendor copy of html-scanner to support better xml parsing
+
+* Added :popup option to UrlHelper#link_to #1996 [gabriel.gironda@gmail.com]. Examples:
+
+ link_to "Help", { :action => "help" }, :popup => true
+ link_to "Busy loop", { :action => "busy" }, :popup => ['new_window', 'height=300,width=600']
+
+* Drop trailing \000 if present on RAW_POST_DATA (works around bug in Safari Ajax implementation) #918
+
+* Fix observe_field to fall back to event-based observation if frequency <= 0 #1916 [michael@schubert.cx]
+
+* Allow use of the :with option for submit_to_remote #1936 [jon@instance-design.co.uk]
+
+* AbstractRequest#domain returns nil when host is an ip address #2012 [kevin.clark@gmail.com]
+
+* ActionController documentation update #2051 [fbeausoleil@ftml.net]
+
+* Yield @content_for_ variables to templates #2058 [Sam Stephenson]
+
+* Make rendering an empty partial collection behave like :nothing => true #2080 [Sam Stephenson]
+
+* Add option to specify the singular name used by pagination.
+
+* Use string key to obtain action value. Allows indifferent hashes to be disabled.
+
+* Added ActionView::Base.cache_template_loading back.
+
+* Rewrote compiled templates to decrease code complexity. Removed template load caching in favour of compiled caching. Fixed template error messages. [Nicholas Seckar]
+
+* Fix Routing to handle :some_param => nil better. [Nicholas Seckar, Luminas]
+
+* Add support for :include with pagination (subject to existing constraints for :include with :limit and :offset) #1478 [michael@schubert.cx]
+
+* Prevent the benchmark module from blowing up if a non-HTTP/1.1 request is processed
+
+* Added :use_short_month option to select_month helper to show month names as abbreviations
+
+* Make link_to escape the javascript in the confirm option #1964 [nicolas.pouillard@gmail.com]
+
+* Make assert_redirected_to properly check URL's passed as strings #1910 [Scott Barron]
+
+* Make sure :layout => false is always used when rendering inside a layout
+
+* Use raise instead of assert_not_nil in Test::Unit::TestCase#process to ensure that the test variables (controller, request, response) have been set
+
+* Make sure assigns are built for every request when testing #1866
+
+* Allow remote_addr to be queried on TestRequest #1668
+
+* Fixed bug when a partial render was passing a local with the same name as the partial
+
+* Improved performance of test app req/sec with ~10% refactoring the render method #1823 [Stefan Kaes]
+
+* Improved performance of test app req/sec with 5-30% through a series of Action Pack optimizations #1811 [Stefan Kaes]
+
+* Changed caching/expiration/hit to report using the DEBUG log level and errors to use the ERROR log level instead of both using INFO
+
+* Added support for per-action session management #1763
+
+* Improved rendering speed on complicated templates by up to 100% (the more complex the templates, the higher the speedup) #1234 [Stephan Kaes]. This did necessasitate a change to the internals of ActionView#render_template that now has four parameters. Developers of custom view handlers (like Amrita) need to update for that.
+
+* Added options hash as third argument to FormHelper#input, so you can do input('person', 'zip', :size=>10) #1719 [jeremye@bsa.ca.gov]
+
+* Added Base#expires_in(seconds)/Base#expires_now to control HTTP content cache headers #1755 [Thomas Fuchs]
+
+* Fixed line number reporting for Builder template errors #1753 [piotr]
+
+* Fixed assert_routing so that testing controllers in modules works as expected [Nicholas Seckar, Rick Olson]
+
+* Fixed bug with :success/:failure callbacks for the JavaScriptHelper methods #1730 [court3nay/Thomas Fuchs]
+
+* Added named_route method to RouteSet instances so that RouteSet instance methods do not prevent certain names from being used. [Nicholas Seckar]
+
+* Fixed routes so that routes which do not specify :action in the path or in the requirements have a default of :action => 'index', In addition, fixed url generation so that :action => 'index' does not need to be provided for such urls. [Nicholas Seckar, Markjuh]
+
+* Worked around a Safari bug where it wouldn't pass headers through if the response was zero length by having render :nothing return ' ' instead of ''
+
+* Fixed Request#subdomains to handle "foo.foo.com" correctly
+
+
+*1.9.1* (11 July, 2005)
+
+* Fixed that auto_complete_for didn't force the input string to lower case even as the db comparison was
+
+* Fixed that Action View should always use the included Builder, never attempt to require the gem, to ensure compatibility
+
+* Added that nil options are not included in tags, so tag("p", :ignore => nil) now returns <p /> not <p ignore="" /> but that tag("p", :ignore => "") still includes it #1465 [michael@schuerig.de]
+
+* Fixed that UrlHelper#link_to_unless/link_to_if used html_escape on the name if no link was to be applied. This is unnecessary and breaks its use with images #1649 [joergd@pobox.com]
+
+* Improved error message for DoubleRenderError
+
+* Fixed routing to allow for testing of *path components #1650 [Nicholas Seckar]
+
+* Added :handle as an option to sortable_element to restrict the drag handle to a given class #1642 [thejohnny]
+
+* Added a bunch of script.aculo.us features #1644, #1677, #1695 [Thomas Fuchs]
+ * Effect.ScrollTo, to smoothly scroll the page to an element
+ * Better Firefox flickering handling on SlideUp/SlideDown
+ * Removed a possible memory leak in IE with draggables
+ * Added support for cancelling dragging my hitting ESC
+ * Added capability to remove draggables/droppables and redeclare sortables in dragdrop.js (this makes it possible to call sortable_element on the same element more than once, e.g. in AJAX returns that modify the sortable element. all current sortable 'stuff' on the element will be discarded and the sortable will be rebuilt)
+ * Always reset background color on Effect.Highlight; this make change backwards-compatibility, to be sure include style="background-color:(target-color)" on your elements or else elements will fall back to their CSS rules (which is a good thing in most circumstances)
+ * Removed circular references from element to prevent memory leaks (still not completely gone in IE)
+ * Changes to class extension in effects.js
+ * Make Effect.Highlight restore any previously set background color when finishing (makes effect work with CSS classes that set a background color)
+ * Fixed myriads of memory leaks in IE and Gecko-based browsers [David Zülke]
+ * Added incremental and local autocompleting and loads of documentation to controls.js [Ivan Krstic]
+ * Extended the auto_complete_field helper to accept tokens option
+ * Changed object extension mechanism to favor Object.extend to make script.aculo.us easily adaptable to support 3rd party libs like IE7.js [David Zülke]
+
+* Fixed that named routes didn't use the default values for action and possible other parameters #1534 [Nicholas Seckar]
+
+* Fixed JavascriptHelper#visual_effect to use camelize such that :blind_up will work #1639 [pelletierm@eastmedia.net]
+
+* Fixed that a SessionRestoreError was thrown if a model object was placed in the session that wasn't available to all controllers. This means that it's no longer necessary to use the 'model :post' work-around in ApplicationController to have a Post model in your session.
+
+
+*1.9.0* (6 July, 2005)
+
+* Added logging of the request URI in the benchmark statement (makes it easy to grep for slow actions)
+
+* Added javascript_include_tag :defaults shortcut that'll include all the default javascripts included with Action Pack (prototype, effects, controls, dragdrop)
+
+* Cache several controller variables that are expensive to calculate #1229 [skaes@web.de]
+
+* The session class backing CGI::Session::ActiveRecordStore may be replaced with any class that duck-types with a subset of Active Record. See docs for details #1238 [skaes@web.de]
+
+* Fixed that hashes was not working properly when passed by GET to lighttpd #849 [Nicholas Seckar]
+
+* Fixed assert_template nil will be true when no template was rendered #1565 [maceywj@telus.net]
+
+* Added :prompt option to FormOptions#select (and the users of it, like FormOptions#select_country etc) to create "Please select" style descriptors #1181 [Michael Schuerig]
+
+* Added JavascriptHelper#update_element_function, which returns a Javascript function (or expression) that'll update a DOM element according to the options passed #933 [mortonda@dgrmm.net]. Examples:
+
+ <%= update_element_function("products", :action => :insert, :position => :bottom, :content => "<p>New product!</p>") %>
+
+ <% update_element_function("products", :action => :replace, :binding => binding) do %>
+ <p>Product 1</p>
+ <p>Product 2</p>
+ <% end %>
+
+* Added :field_name option to DateHelper#select_(year|month|day) to deviate from the year/month/day defaults #1266 [Marcel Molina]
+
+* Added JavascriptHelper#draggable_element and JavascriptHelper#drop_receiving_element to facilitate easy dragging and dropping through the script.aculo.us libraries #1578 [Thomas Fuchs]
+
+* Added that UrlHelper#mail_to will now also encode the default link title #749 [f.svehla@gmail.com]
+
+* Removed the default option of wrap=virtual on FormHelper#text_area to ensure XHTML compatibility #1300 [thomas@columbus.rr.com]
+
+* Adds the ability to include XML CDATA tags using Builder #1563 [Josh Knowles]. Example:
+
+ xml.cdata! "some text" # => <![CDATA[some text]]>
+
+* Added evaluation of <SCRIPT> blocks in content returned to Ajax calls #1577 [Thomas Fuchs/court3nay/Sean Treadway]
+
+* Directly generate paths with a leading slash instead of tacking it on later. #1543 [Nicholas Seckar]
+
+* Fixed errant parameter modification in functional tests. #1542 [Nicholas Seckar]
+
+* Routes fail with leading slash #1540 [Nicholas Seckar]
+
+* Added support for graceful error handling of Ajax calls #1217 [Jamis Buck/Thomas Fuchs]. Example:
+
+ link_to_remote(
+ "test",
+ :url => { :action => "faulty" },
+ :update => { :success => "good", :failure => "bad" },
+ 403 => "alert('Forbidden- got ya!')",
+ 404 => "alert('Nothing there...?')",
+ :failure => "alert('Unkown error ' + request.status)")
+
+* Attempt to explicitly flush the output at the end of CgiProcess#out
+
+* Fixed assert_redirected_to to handle absolute controller paths properly #1472 [Rick Olson/Nicholas Seckar]
+
+* Added event-based observations when frequency is not set on observe_field/form #1474 [flash@vanklinkenbergsoftware.nl]
+
+* Added script.aculo.us Javascripts (controls.js, dragdrop.js, effects.js) (NEEDS MORE DESCRIPTION) #1509 [Thomas Fuchs]
+
+* Fixed prototype to consider all fields it doesn't know as text (such as Safari's search) just like the browser in its serialization #1497 [Sean Treadway]
+
+* Improved performance of Routes generation by a factor of 5 #1434 [Nicholas Seckar]
+
+* Added named routes (NEEDS BETTER DESCRIPTION) #1434 [Nicholas Seckar]
+
+* Improved AbstractRequest documentation #1483 [court3nay@gmail.com]
+
+* Added ActionController::Base.allow_concurrency to control whether the application is thread-safe, so multi-threaded servers like WEBrick knows whether to apply a mutex around the performance of each action. Turned off by default. EXPERIMENTAL FEATURE.
+
+* Added TextHelper#word_wrap(text, line_length = 80) #1449 [tuxie@dekadance.se]
+
+* Added a fall-through action for form_remote_tag that'll be used in case Javascript is unavailable #1459 [Scott Barron]. Example:
+
+ form_remote_tag :html => { :action => url_for(:controller => "some", :action => "place") }
+
+* Added :xhr => true/false option to verify so you can ensure that a request is coming from an Ajax call or not #1464 [Thomas Fuchs]
+
+* Added tag_options as a third parameter to AssetHelper#auto_discovery_link_tag to control options like the title of the link #1430 [kevin.clark@gmail.com]
+
+* Added option to pass in parameters to CaptureHelper#capture, so you can create more advanced view helper methods #1466 [duane.johnson@gmail.com]. Example:
+
+ <% show_calendar(:year => 2005, :month => 6) do |day, options| %>
+ <% options[:bgcolor] = '#dfd' if 10..15.include? day %>
+ [<%= day %>]
+ <% end %>
+
+* Changed the default name of the input tag generated by FormTagHelper#submit_tag from "submit" to "commit" so it doesn't clash with form.submit() calls in Javascript #1271
+
+* Fixed relative urls support for lighttpd #1048 [Nicholas Seckar/maznawak@nerim.net]
+
+* Correct distance_of_time_in_words for integer arguments and make the second arg optional, treating the first arg as a duration in seconds. #1458 [madrobby <thomas@fesch.at>]
+
+* Fixed query parser to deal gracefully with equal signs inside keys and values #1345 [gorou].
+ Example: /?sig=abcdef=:foobar=&x=y will pass now.
+
+* Added Cuba to country list #1351 [todd]
+
+* Fixed radio_button to work with numeric values #1352 [demetrius]
+
+* Added :extension option to NumberHelper#number_to_phone #1361 [delynnb]
+
+* Added button_to as a form-based solution to deal with harmful actions that should be hidden behind POSTs. This makes it just as easy as link_to to create a safe trigger for actions like destroy, although it's limited by being a block element, the fixed look, and a no-no inside other forms. #1371 [tom@moertel.com]
+
+* Fixed image_tag so an exception is not thrown just because the image is missing and alt value can't be generated #1395 [Marcel]
+
+* Added a third parameter to TextHelper#auto_link called href_options for specifying additional tag options on the links generated #1401 [tyler.kovacs@gmail.com]. Example: auto_link(text, :all, { :target => "_blank" }) to have all the generated links open in a new window.
+
+* Fixed TextHelper#highlight to return the text, not nil, if the phrase is blank #1409 [patrick@lenz.sh]
+
+* Fixed TagHelper such that :name and 'name' keys in the options doesn't result in two attributes #1455 [take_tk]
+
+* Ensure that helpers are only available to the controllers where they are defined and their subclasses. #1394 [kdole@tamu.edu]
+
+* render("foo/bar") works with a layout again
+
+* Fixed double-singularization on scaffolded pagination call (Address would be turned into Addres) #1216, #1404 [nilsga]
+
+* Removed the require hack used by functional testing to work around an earlier bug in rake.
+
+* Allow distance_of_time_in_words to work with any value that responds to #to_time (like dates) #969
+
+* Support :render option for :verify #1440 [TobiasLuetke]
+
+* Updated vendor copy of html-scanner lib to 0.5.2, for bug fixes and optimizations. The :content option may be used as expected--to find a tag whose textual content is a particular value--in assert_tag, now.
+
+* Changed test requests to come from 0.0.0.0 instead of 127.0.0.1 such that they don't trigger debugging screens on exceptions, but instead call rescue_action_in_public
+
+* Modernize scaffolding to match the generator: use the new render method and change style from the warty @params["id"] to the sleek params[:id]. #1367
+
+* Include :id in the action generated by the form helper method. Then, for example, the controller can do Model.find(params[:id]) for both edit and update actions. Updated scaffolding to take advantage. #1367
+
+* Add assertions with friendly messages to TestCase#process to ensure that @controller, @request, and @response are set. #1367
+
+* Arrays, hashes sent via multipart posts are converted to strings #1032 [dj@omelia.org, me@julik.nl]
+
+* render(:layout => true) is a synonym for render(:layout => nil)
+
+* Make sure the benchmarking render method always returns the output of the render.
+
+* render(:action), render(:template) and render() are the only three calls that default to using a layout. All other render calls assume :layout => false. This also fixes send_file, which was applying a layout if one existed for the current action.
+
+* verify with :redirect_to won't redirect if a redirect or render has already been performed #1350
+
+* render(:partial => true) is identical to the behavior of the deprecated render_partial()
+
+* Fixed render(:partial => "...") to use an empty Hash for the local assigns #1365
+
+* Fixed Caching::Fragments::FileStore.delete to not raise an exception if the delete fails.
+
+* Deprecated all render_* methods in favor of consolidating all rendering behavior in Base#render(options). This enables more natural use of combining options, such as layouts. Examples:
+
+ +---------------------------------------------------------------+-------------------------------------------------------+
+ | BEFORE | AFTER |
+ +---------------------------------------------------------------+-------------------------------------------------------+
+ | render_with_layout "weblog/show", "200 OK", "layouts/dialog" | render :action => "show", :layout => "dialog" |
+ | render_without_layout "weblog/show" | render :action => "show", :layout => false |
+ | render_action "error", "404 Not Found" | render :action => "error", :status => "404 Not Found" |
+ | render_template "xml.div('stuff')", "200 OK", :rxml | render :inline => "xml.div('stuff')", :type => :rxml |
+ | render_text "hello world!" | render :text => "hello world!" |
+ | render_partial_collection "person", @people, nil, :a => 1 | render :partial => "person", :collection => @people, |
+ | | :locals => { :a => 1 } |
+ +---------------------------------------------------------------+-------------------------------------------------------+
+
+* Deprecated redirect_to_path and redirect_to_url in favor of letting redirect_to do the right thing when passed either a path or url.
+
+* Fixed use of an integer as return code for renders, so render_text "hello world", 404 now works #1327
+
+* Fixed assert_redirect_to to work with redirect_to_path #869 [Nicholas Seckar]
+
+* Fixed escaping of :method option in remote_form_tag #1218 [Rick Olson]
+
+* Added Serbia and Montenegro to the country_select #1239 [todd@robotcoop.com]
+
+* Fixed Request#remote_ip in testing #1251 [Jeremy Kemper]
+
+* Fixed that compute_public_path should recognize external URLs, so image_tag("http://www.example.com/images/icon.gif") is not prefixed with the relative url path #1254 [victor-ronr-trac@carotena.net]
+
+* Added support for descending year values in DateHelper#select_year, like select_year(Date.today, :start_year => 2005, :end_year => 1900), which would count down from 2005 to 1900 instead of the other way #1274 [nwoods@mail.com]
+
+* Fixed that FormHelper#checkbox should return a checked checkbox if the value is the same as checked_value #1286 [Florian Weber]
+
+* Fixed Form.disable in Prototype #1317 [Wintermute]
+
+* Added accessors to logger, params, response, session, flash, and headers from the view, so you can write <% logger.info "stuff" %> instead of <% @logger.info "others" %> -- more consistent with the preferred way of accessing these attributes and collections from the controller
+
+* Added support for POST data in form of YAML or XML, which is controller through the Content-Type header. Example request:
+
+ Content-Type: application/xml
+ <request><item><content>HelloWorld</content></item></request>
+
+ ...is the same as:
+
+ Content-Type: application/x-yaml
+ ---
+ item:
+ content: HelloWorld
+
+ ...is the same as:
+
+ item[content]=HelloWorld
+
+ Which in the end turns into { "item" => { "content" => "HelloWorld" } }. This makes it a lot easier to publish REST web services on top of your regular actions (as they won't care).
+
+ Example Curl call:
+
+ curl -H 'Content-Type: application/xml' -d '<request><item><content>KillMeMore</content></item></request>' http://www.example.com/service
+
+ You can query to find out whether a given request came through as one of these types with:
+ - request.post_format? (:url_encoded, :xml or :yaml)
+ - request.formatted_post? (for either xml or yaml)
+ - request.xml_post?
+ - request.yaml_post?
+
+* Added bundling of XmlSimple by Maik Schmidt
+
+* Fixed that render_partial_collection should always return a string (and not sometimes an array, despite <%= %> not caring)
+
+* Added TextHelper#sanitize that can will remove any Javascript handlers, blocks, and forms from an input of HTML. This allows for use of HTML on public sites, but still be free of XSS issues. #1277 [Jamis Buck]
+
+* Fixed the HTML scanner used by assert_tag where a infinite loop could be caused by a stray less-than sign in the input #1270 [Jamis Buck]
+
+* Added functionality to assert_tag, so you can now do tests on the siblings of a node, to assert that some element comes before or after the element in question, or just to assert that some element exists as a sibling #1226 [Jamis Buck]
+
+* Added better error handling for regexp caching expiration
+
+* Fixed handling of requests coming from unknown HTTP methods not to kill the server
+
+* Added that both AssetHelper#stylesheet_link_tag and AssetHelper#javascript_include_tag now accept an option hash as the last parameter, so you can do stuff like: stylesheet_link_tag "style", :media => "all"
+
+* Added FormTagHelper#image_submit_tag for making submit buttons that uses images
+
+* Added ActionController::Base.asset_host that will then be used by all the asset helpers. This enables you to easily offload static content like javascripts and images to a separate server tuned just for that.
+
+* Fixed action/fragment caching using the filestore when a directory and a file wanted to use the same name. Now there's a .cache prefix that sidesteps the conflict #1188 [imbcmdth@hotmail.com]
+
+* Fixed missing id uniqueness in FormTag#radio_button #1207 [Jarkko]
+
+* Fixed assert_redirected_to to work with :only_path => false #1204 [Alisdair McDiarmid]
+
+* Fixed render_partial_collection to output an empty string instead of nil when handed an empty array #1202 [Ryan Carver]
+
+* Improved the speed of regular expression expirations for caching by a factor of 10 #1221 [Jamis Buck]
+
+* Removed dumping of template assigns on the rescue page as it would very easily include a ton of data making page loads take seconds (and the information was rarely helpful) #1222
+
+* Added BenchmarkHelper that can measure the execution time of a block in a template and reports the result to the log. Example:
+
+ <% benchmark "Notes section" do %>
+ <%= expensive_notes_operation %>
+ <% end %>
+
+ Will add something like "Notes section (0.345234)" to the log.
+
+* Added ActionController::Caching::Sweeper as an improved an easier to use sweeper. The new sweepers work on a single-step approach instead of two-steps like the old ones. Before
+
+ def after_save(record)
+ @list = record.is_a?(List) ? record : record.list
+ end
+
+ def filter(controller)
+ controller.expire_page(:controller => "lists", :action => %w( show public feed ), :id => @list.id)
+ controller.expire_action(:controller => "lists", :action => "all")
+ @list.shares.each { |share| controller.expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
+ end
+
+ ..after:
+
+ def after_save(record)
+ list = record.is_a?(List) ? record : record.list
+ expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id)
+ expire_action(:controller => "lists", :action => "all")
+ list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
+ end
+
+ The new sweepers can also observe on the actions themselves by implementing methods according to (before|after)_$controller_$action. Example of a callback that'll be called after PagesController#update_title has been performed:
+
+ def after_pages_update_title
+ expire_fragment(%r{pages/#{controller.assigns["page"].id}/.*})
+ end
+
+ Note that missing_method is delegated to the controller instance, which is assigned in a before filter. This means that you can call expire_fragment instead of @controller.expire_fragment.
+
+* Added that Fragments#expire_fragment now accepts as a regular expression as the name thereby deprecating expire_matched_fragments
+
+* Fixed that fragments shouldn't use the current host and the path as part of the key like pages does
+
+* Added conditions to around_filters just like before_filter and after_filter
+
+
+*1.8.1* (20th April, 2005)
+
+* Added xml_http_request/xhr method for simulating XMLHttpRequest in functional tests #1151 [Sam Stephenson]. Example:
+
+ xhr :post, :index
+
+* Fixed that Ajax.Base.options.asynchronous wasn't being respected in Ajax.Request (thanks Jon Casey)
+
+* Fixed that :get, :post, and the others should take a flash array as the third argument just like process #1144 [rails@cogentdude.com]
+
+* Fixed a problem with Flash.now
+
+* Fixed stringification on all assigned hashes. The sacrifice is that assigns[:person] won't work in testing. Instead assigns["person"] or assigns(:person) must be used. In other words, the keys of assigns stay strings but we've added a method-based accessor to appease the need for symbols.
+
+* Fixed that rendering a template would require a connection to the database #1146
+
+
+*1.8.0* (19th April, 2005)
+
+* Added assert_tag and assert_no_tag as a much improved alternative to the deprecated assert_template_xpath_match #1126 [Jamis Buck]
+
+* Deprecated the majority of all the testing assertions and replaced them with a much smaller core and access to all the collections the old assertions relied on. That way the regular test/unit assertions can be used against these. Added documentation about how to use it all.
+
+* Added a wide range of new Javascript effects:
+ * Effect.Puff zooms the element out and makes it smoothly transparent at the same time, giving a "puff" illusion #996 [thomas@fesch.at]
+ After the animation is completed, the display property will be set to none.
+ This effect will work on relative and absolute positioned elements.
+
+ * Effect.Appear as the opposite of Effect.Fade #990 [thomas@fesch.at]
+ You should return elements with style="display:none;" or a like class for this to work best and have no chance of flicker.
+
+ * Effect.Squish for scaling down an element and making it disappear at the end #972 [thomas@fesch.at]
+
+ * Effect.Scale for smoothly scaling images or text up and down #972 [thomas@fesch.at]
+
+ * Effect.Fade which smoothly turns opacity from 100 to 0 and then hides the element #960 [thomas@fesch.at]
+
+* Added Request#xml_http_request? (and an alias xhr?) to that'll return true when the request came from one of the Javascript helper methods (Ajax). This can be used to give one behavior for modern browsers supporting Ajax, another to old browsers #1127 [Sam Stephenson]
+
+* Changed render_partial to take local assigns as the second parameter instead of an explicit object and then the assigns. So the API changes from:
+
+ <%= render_partial "account", person, "rules" => regulations.rules %>
+
+ ...to:
+
+ <%= render_partial "account", :account => person, :rules => regulations.rules %>
+
+ The old API will still work, though, and render_partial "account" will still assume :account => @account.
+
+* Added support for web servers that use PATH_INFO instead of REQUEST_URI like IIS #1014 [BradG/Nicholas Seckar]
+
+* Added graceful handling of PUT, DELETE, and OPTIONS requests for a complete coverage of REST functionality #1136 [joshknowles@gmail.com]
+
+* Fixed that you can now pass an alternative :href option to link_to_function/remote in order to point to somewhere other than # if the javascript fails or is turned off. You can do the same with form_remote_tag by passing in :action. #1113 [Sam Stephenson]
+
+* Fixed DateHelper to return values on the option tags such that they'll work properly in IE with form_remote_tag #1024 [Scott Raymond]
+
+* Fixed FormTagHelper#check_box to respect checked #1049 [DelynnB]
+
+* Added that render_partial called from a controller will use the action name as default #828 [Dan Peterson]
+
+* Added Element.toggle, Element.show, and Element.hide to the prototype javascript library. Toggle.display has been deprecated, but will still work #992 [Lucas Carlson]
+
+* Added that deleting a cookie should not just set it to an empty string but also instantly expire it #1118 [todd@robotcoop.com]
+
+* Added AssetTagHelper#image_path, AssetTagHelper#javascript_path, and AssetTagHelper#stylesheet_path #1110 [Larry Halff]
+
+* Fixed url_for(nil) in functional tests #1116 [Alisdair McDiarmid]
+
+* Fixed error handling of broken layouts #1115 [Michael Schubert]
+
+* Added submit_to_remote that allows you to trigger an Ajax form submition at the click of the submission button, which allows for multiple targets in a single form through the use of multiple submit buttons #930 [yrashk@gmail.com]
+
+* Fixed pagination to work with joins #1034 [scott@sigkill.org]
+
+* Fixed that *rest parameter in map.connect couldn't accept an empty list #1037 [Dee.Zsombor@gmail.com]
+
+* Added :confirm option to link_to_remote just like link_to has #1082 [yrashk@fp.org.ua]
+
+* Added minute_step as an option to select_minute (and the helpers that use it) to jump in larger increments than just 1 minute. At 15, it would return 0, 15, 30, 45 options #1085 [ordwaye@evergreen.edu]
+
+* Fixed that an exception would be thrown when an empty form was submitted #1090 [jan@ulbrich-boerwang.de]
+
+* Moved TextHelper#human_size to NumberHelper#number_to_human_size, but kept an deprecated alias to the old method name
+
+* Fixed that the content-type for some browsers could include an additional \r which made wonky things happen #1067 [Thomas Fuchs]
+
+* Fixed that radio buttons shouldn't have a default size attribute #1074 [hendrik@mans.de]
+
+* Added ActionView::Helpers::InstanceTag::DEFAULT_RADIO_OPTIONS that contains a hash of default options for radio buttons #1074 [hendrik@mans.de]
+
+* Fixed that in some circumstances controllers outside of modules may have hidden ones inside modules. For example, admin/content might have been hidden by /content. #1075 [Nicholas Seckar]
+
+* Added JavascriptHelper#periodically_call_remote in order to create areas of a page that update automatically at a set interval #945 [Jon Tirsen]
+
+* Fixed Cache#expire_matched_fragments that couldn't recognize the difference between string and url_for options #1030 [skaes@web.de]
+
+* Added simulation of @request.request_uri in functional tests #1038 [Jamis Buck]
+
+* Fixed autolinking to work better in more cases #1013 [Jamis Buck]
+
+* Added the possible of using symbols in form helpers that relate to instance variables like text_field :account, :name in addition to text_field "account", "name"'
+
+* Fixed javascript_include_tag to output type instead of language and conform to XHTML #1018 [Rick Olson]
+
+* Added NumberHelper for common string representations like phone number, currency, and percentage #1015 [DeLynn]
+
+* Added pagination for scaffolding (10 items per page) #964 [mortonda@dgrmm.net]
+
+* Added assert_no_cookie and fixed assert_cookie_equal to deal with non-existing cookies #979 [Jeremy Kemper]
+
+* Fixed :overwrite_param so it doesn't delete but reject elements from @request.parameters #982 [raphinou@yahoo.com]
+
+* Added :method option to verify for ensuring that either GET, POST, etc is allowed #984 [Jamis Buck]
+
+* Added options to set cc, bcc, subject, and body for UrlHelper#mail_to #966 [DeLynn]
+
+* Fixed include_blank for select_hour/minute/second #527 [edward@debian.org]
+
+* Improved the message display on the exception handler pages #963 [Johan Sorensen]
+
+* Fixed that on very rare occasions, webrick would raise a NoMethodError: private method 'split' called for nil #1001 [Flurin Egger]
+
+* Fixed problem with page caching #958 [Rick Olson]
+
+
+*1.7.0* (27th March, 2005)
+
+* Added ActionController::Base.page_cache_extension for setting the page cache file extension (the default is .html) #903 [Andreas]
+
+* Fixed "bad environment variable value" exception caused by Safari, Apache, and Ajax calls #918
+
+* Fixed that pagination_helper would ignore :params #947 [Sebastian Kanthak]
+
+* Added :owerwrite_params back to url_for and friends -- it was AWL since the introduction of Routes #921 [raphinou]
+
+* Added :position option to link_to_remote/form_remote_tag that can be either :before, :top, :bottom, or :after and specifies where the return from the method should be inserted #952 [Matthew McCray/Sam Stephenson]
+
+* Added Effect.Highlight to prototype.js to do Yellow Fade Technique (of 37signals' fame) on any container #952 [Sam Stephenson/courtenay]
+
+* Added include_seconds option as the third parameter to distance_of_time_in_words which will render "less than a minute" in higher resolution ("less than 10 seconds" etc) #944 [thomas@fesch.at]
+
+* Added fourth option to process in test cases to specify the content of the flash #949 [Jamis Buck]
+
+* Added Verifications that allows you to specify preconditions to actions in form of statements like <tt>verify :only => :update_post, :params => "admin_privileges", :redirect_to => { :action => "settings" }</tt>, which ensure that the update_post action is only called if admin_privileges is available as a parameter -- otherwise the user is redirected to settings. #897 [Jamis Buck]
+
+* Fixed Form.Serialize for the JavascriptHelper to also seriliaze password fields #934 [dweitzman@gmail.com]
+
+* Added JavascriptHelper#escape_javascript as a public method (was private) and made it escape both single and double quotes and new lines #940 [mortonda@dgrmm.net]
+
+* Added trailing_slash option to url_for, so you can generate urls ending in a slash. Note that is currently not recommended unless you need it for special reasons since it breaks caching #937 [stian@grytoyr.net]
+
+* Added expire_matched_fragments(regular_expression) to clear out a lot of fragment caches at once #927 [technoweenie@gmail.com]
+
+* Fixed the problems with : and ? in file names for fragment caches on Windows #927 [technoweenie@gmail.com]
+
+* Added TextHelper#human_size for formatting file sizes, like human_size(1234567) => 1.2 MB #943 [thomas@fesch.at]
+
+* Fixed link_to :confirm #936 [Nicholas Seckar]
+
+* Improved error reporting especially around never shallowing exceptions. Debugging helpers should be much easier now #980 [Nicholas Seckar]
+
+* Fixed Toggle.display in prototype.js #902 [Lucas Carlson]
+
+
+*1.6.0* (22th March, 2005)
+
+* Added a JavascriptHelper and accompanying prototype.js library that opens the world of Ajax to Action Pack with a large array of options for dynamically interacting with an application without reloading the page #884 [Sam Stephenson/David]
+
+* Added pagination support through both a controller and helper add-on #817 [Sam Stephenson]
+
+* Fixed routing and helpers to make Rails work on non-vhost setups #826 [Nicholas Seckar/Tobias Luetke]
+
+* Added a much improved Flash module that allows for finer-grained control on expiration and allows you to flash the current action #839 [Caio Chassot]. Example of flash.now:
+
+ class SomethingController < ApplicationController
+ def save
+ ...
+ if @something.save
+ # will redirect, use flash
+ flash[:message] = 'Save successful'
+ redirect_to :action => 'list'
+ else
+ # no redirect, message is for current action, use flash.now
+ flash.now[:message] = 'Save failed, review'
+ render_action 'edit'
+ end
+ end
+ end
+
+* Added to_param call for parameters when composing an url using url_for from something else than strings #812 [Sam Stephenson]. Example:
+
+ class Page
+   def initialize(number)
+     @number = number
+   end
+   # ...
+   def to_param
+     @number.to_s
+   end
+ end
+
+ You can now use instances of Page with url_for:
+
+ class BarController < ApplicationController
+   def baz
+     page = Page.new(4)
+     url = url_for :page => page # => "http://foo/bar/baz?page=4"
+   end
+ end
+
+* Fixed form helpers to query Model#id_before_type_cast instead of Model#id as a temporary workaround for Ruby 1.8.2 warnings #818 [DeLynn B]
+
+* Fixed TextHelper#markdown to use blank? instead of empty? so it can deal with nil strings passed #814 [Johan Sörensen]
+
+* Added TextHelper#simple_format as a non-dependency text presentation helper #814 [Johan Sörensen]
+
+* Added that the html options disabled, readonly, and multiple can all be treated as booleans. So specifying <tt>disabled => :true</tt> will give <tt>disabled="disabled"</tt>. #809 [mindel]
+
+* Added path collection syntax for Routes that will gobble up the rest of the url and pass it on to the controller #830 [rayners]. Example:
+
+ map.connect 'categories/*path_info', :controller => 'categories', :action => 'show'
+
+ A request for /categories/top-level-cat, would give @params[:path_info] with "top-level-cat".
+ A request for /categories/top-level-cat/level-1-cat, would give @params[:path_info] with "top-level-cat/level-1-cat" and so forth.
+
+ The @params[:path_info] return is really an array, but where to_s has been overwritten to do join("/").
+
+* Fixed options_for_select on selected line issue #624 [Florian Weber]
+
+* Added CaptureHelper with CaptureHelper#capture and CaptureHelper#content_for. See documentation in helper #837 [Tobias Luetke]
+
+* Fixed :anchor use in url_for #821 [Nicholas Seckar]
+
+* Removed the reliance on PATH_INFO as it was causing problems for caching and inhibited the new non-vhost support #822 [Nicholas Seckar]
+
+* Added assigns shortcut for @response.template.assigns to controller test cases [Jeremy Kemper]. Example:
+
+ Before:
+
+ def test_list
+ assert_equal 5, @response.template.assigns['recipes'].size
+ assert_equal 8, @response.template.assigns['categories'].size
+ end
+
+ After:
+
+ def test_list
+ assert_equal 5, assigns(:recipes).size
+ assert_equal 8, assigns(:categories).size
+ end
+
+* Added TagHelper#image_tag and deprecated UrlHelper#link_image_to (recommended approach is to combine image_tag and link_to instead)
+
+* Fixed textilize to be resilient to getting nil parsed (by using Object#blank? instead of String#empty?)
+
+* Fixed that the :multipart option in FormTagHelper#form_tag would be ignored [Yonatan Feldman]
+
+
+*1.5.1* (7th March, 2005)
+
+* Fixed that the routes.rb file wouldn't be found on symlinked setups due to File.expand_path #793 [piotr@t-p-l.com]
+
+* Changed ActiveRecordStore to use Marshal instead of YAML as the latter proved troublesome in persisting circular dependencies. Updating existing applications MUST clear their existing session table from data to start using this updated store #739 [Jamis Buck]
+
+* Added shortcut :id assignment to render_component and friends (before you had to go through :params) #784 [Lucas Carlson]
+
+* Fixed that map.connect should convert arguments to strings #780 [Nicholas Seckar]
+
+* Added UrlHelper#link_to_if/link_to_unless to enable other conditions that just link_to_unless_current #757 [mindel]
+
+* Fixed that single quote was not escaped in a UrlHelper#link_to javascript confirm #549 [Scott Barron]
+
+* Removed the default border on link_image_to (it broke xhtml strict) -- can be specified with :border => 0 #517 [?/caleb]
+
+* Fixed that form helpers would treat string and symbol keys differently in html_options (and possibly create duplicate entries) #112 [Jeremy Kemper]
+
+* Fixed that broken pipe errors (clients disconnecting in mid-request) could bring down a fcgi process
+
+* Added the original exception message to session recall errors (so you can see which class wasnt required)
+
+* Fixed that RAILS_ROOT might not be defined when AP was loaded, so do a late initialization of the ROUTE_FILE #761 [Scott Barron]
+
+* Fix request.path_info and clear up LoadingModule behavior #754 [Nicholas Seckar]
+
+* Fixed caching to be aware of extensions (so you can cache files like api.wsdl or logo.png) #734 [Nicholas Seckar]
+
+* Fixed that Routes would raise NameErrors if a controller component contains characters that are not valid constant names #733 [Nicholas Seckar]
+
+* Added PATH_INFO access from the request that allows urls like the following to be interpreted by rails: http://www.example.com/dispatcher.cgi/controller/action -- that makes it possible to use rails as a CGI under lighttpd and would also allow (for example) Rublog to be ported to rails without breaking existing links to Rublog-powered blogs. #728 [Jamis Buck]
+
+* Fixed that caching the root would result in .html not index.html #731, #734 [alisdair/Nicholas Seckar]
+
+
+*1.5.0* (24th February, 2005)
+
+* Added Routing as a replacement for mod_rewrite pretty urls [Nicholas Seckar]. Read more in ActionController::Base.url_for and on http://manuals.rubyonrails.com/read/book/9
+
+* Added components that allows you to call other actions for their rendered response while execution another action. You can either delegate the entire response rendering or you can mix a partial response in with your other content. Read more on http://manuals.rubyonrails.com/read/book/14
+
+* Fixed that proxy IPs do not follow all RFC1918 nets #251 [caleb@aei-tech.com]
+
+* Added Base#render_to_string to parse a template and get the result back as a string #479
+
+* Fixed that send_file/data can work even if render* has been called before in action processing to render the content of a file to be send for example #601
+
+* Added FormOptionsHelper#time_zone_select and FormOptionsHelper#time_zone_options_for_select to work with the new value object TimeZone in Active Support #688 [Jamis Buck]
+
+* Added FormHelper#file_field and FormTagHelper#file_field_tag for creating file upload fields
+
+* Added :order option for date_select that allows control over the order in which the date dropdowns is used and which of them should be used #619 [Tim Bates]. Examples:
+
+ date_select("post", "written_on", :order => [:day, :month, :year])
+ date_select("user", "birthday", :order => [:month, :day])
+
+* Added ActionView::Base.register_template_handler for easy integration of an alternative template language to ERb and Builder. See test/controller/custom_handler_test.rb for a usage example #656 [Jamis Buck]
+
+* Added AssetTagHelper that provides methods for linking a HTML page together with other assets, such as javascripts, stylesheets, and feeds.
+
+* Added FormTagHelper that provides a number of methods for creating form tags that doesn't rely on conventions with an object assigned to the template like FormHelper does. With the FormTagHelper, you provide the names and values yourself.
+
+* Added Afghanistan, Iran, and Iraq to the countries list used by FormOptions#country_select and FormOptions#country_options_for_select
+
+* Renamed link_to_image to link_image_to (since thats what it actually does) -- kept alias for the old method name
+
+* Fixed textilize for RedCloth3 to keep doing hardbreaks
+
+* Fixed that assert_template_xpath_matches did not indicate when a path was not found #658 [Eric Hodel]
+
+* Added TextHelper#auto_link to turn email addresses and urls into ahrefs
+
+* Fixed that on validation errors, scaffold couldn't find template #654 [mindel]
+
+* Added Base#hide_action(*names) to hide public methods from a controller that would otherwise have been callable through the URL. For the majority of cases, its preferred just to make the methods you don't want to expose protected or private (so they'll automatically be hidden) -- but if you must have a public method, this is a way to make it uncallable. Base#hidden_actions retrieve the list of all hidden actions for the controller #644 [Nicholas Seckar]
+
+* Fixed that a bunch of methods from ActionController::Base was accessible as actions (callable through a URL) when they shouldn't have been #644 [Nicholas Seckar]
+
+* Added UrlHelper#current_page?(options) method to check if the url_for options passed corresponds to the current page
+
+* Fixed https handling on other ports than 443 [Alan Gano]
+
+* Added follow_redirect method for functional tests that'll get-request the redirect that was made. Example:
+
+ def test_create_post
+ post :create, "post" => { "title" => "Exciting!" }
+ assert_redirected_to :action => "show"
+
+ follow_redirect
+ assert_rendered_file "post/show"
+ end
+
+ Limitation: Only works for redirects to other actions within the same controller.
+
+* Fixed double requiring of models with the same name as the controller
+
+* Fixed that query params could be forced to nil on a POST due to the raw post fix #562 [moriq@moriq.com]
+
+* Fixed that cookies shouldn't be frozen in TestRequest #571 [Eric Hodel]
+
+
+*1.4.0* (January 25th, 2005)
+
+* Fixed problems with ActiveRecordStore under the development environment in Rails
+
+* Fixed the ordering of attributes in the xml-decleration of Builder #540 [woeye]
+
+* Added @request.raw_post as a convenience access to @request.env['RAW_POST_DATA'] #534 [Tobias Luetke]
+
+* Added support for automatic id-based indexing for lists of items #532 [dblack]. Example:
+
+ <% @students.each do |@student| %>
+ <%= text_field "student[]", "first_name", :size => "20" %>
+ <%= text_field "student[]", "last_name" %>
+ <%= text_field "student[]", "grade", :size => "5" %>
+ <% end %>
+
+ ...would produce, for say David Black with id 123 and a grace of C+:
+
+ <input id="student_123_first_name" name="student[123][first_name]" size="20" size="30" type="text" value="David" />
+ <input id="student_123_last_name" name="student[123][last_name]" size="30" type="text" value="Black" />
+ <input id="student_123_grade" name="student[123][grade]" size="5" type="text" value="C+" />
+
+* Added :application_prefix to url_for and friends that makes it easier to setup Rails in non-vhost environments #516 [Jamis Buck]
+
+* Added :encode option to mail_to that'll allow you to masquarede the email address behind javascript or hex encoding #494 [Lucas Carlson]
+
+* Fixed that the content-header was being set to application/octet_stream instead of application/octet-stream on send_date/file [Alexey]
+
+* Removed the need for passing the binding when using CacheHelper#cache
+
+* Added TestResponse#binary_content that'll return as a string the data sent through send_data/send_file for testing #500 [Alexey]
+
+* Added @request.env['RAW_POST_DATA'] for people who need access to the data before Ruby's CGI has parsed it #505 [Jeremy Kemper]
+
+* Fixed that a default fragment store wan't being set to MemoryStore as intended.
+
+* Fixed that all redirect and render calls now return true, so you can use the pattern of "do and return". Example:
+
+ def show
+ redirect_to(:action => "login") and return unless @person.authenticated?
+ render_text "I won't happen unless the person is authenticated"
+ end
+
+* Added that renders and redirects called in before_filters will have the same effect as returning false: stopping the chain. Example:
+
+ class WeblogController
+ before_filter { |c| c.send(:redirect_to_url("http://www.farfaraway.com")}) }
+
+ def hello
+ render_text "I will never be called"
+ end
+ end
+
+
+* Added that only one render or redirect can happen per action. The first call wins and subsequent calls are ignored. Example:
+
+ def do_something
+ redirect_to :action => "elsewhere"
+ render_action "overthere"
+ end
+
+ Only the redirect happens. The rendering call is simply ignored.
+
+
+*1.3.1* (January 18th, 2005)
+
+* Fixed a bug where cookies wouldn't be set if a symbol was used instead of a string as the key
+
+* Added assert_cookie_equal to assert the contents of a named cookie
+
+* Fixed bug in page caching that prevented it from working at all
+
+
+*1.3.0* (January 17th, 2005)
+
+* Added an extensive caching module that offers three levels of granularity (page, action, fragment) and a variety of stores.
+ Read more in ActionController::Caching.
+
+* Added the option of passing a block to ActiveRecordHelper#form in order to add more to the auto-generated form #469 [dom@sisna.com]
+
+ form("entry", :action => "sign") do |form|
+ form << content_tag("b", "Department")
+ form << collection_select("department", "id", @departments, "id", "name")
+ end
+
+* Added arrays as a value option for params in url_for and friends #467 [Eric Anderson]. Example:
+
+ url_for(:controller => 'user', :action => 'delete', :params => { 'username' => %( paul john steve ) } )
+ # => /user/delete?username[]=paul&username[]=john&username[]=steve
+
+* Fixed that controller tests can now assert on the use of cookies #466 [Alexey]
+
+* Fixed that send_file would "remember" all the files sent by adding to the headers again and again #458 [Jeremy Kemper]
+
+* Fixed url rewriter confusion when the controller or action name was a substring of the controller_prefix or action_prefix
+
+* Added conditional layouts like <tt>layout "weblog_standard", :except => :rss</tt> #452 [Marcel Molina]
+
+* Fixed that MemCacheStore wasn't included by default and added default MemCache object pointing to localhost #447 [Lucas Carlson]
+
+* Added fourth argument to render_collection_of_partials that allows you to specify local_assigns -- just like render_partial #432 [zenspider]
+
+* Fixed that host would choke when cgi.host returned nil #432 [Tobias Luetke]
+
+* Added that form helpers now take an index option #448 [Tim Bates]
+
+ Example:
+ text_field "person", "name", "index" => 3
+
+ Becomes:
+ <input type="text" name="person[3][name]" id="person_3_name" value="<%= @person.name %>" />
+
+* Fixed three issues with retrying breakpoints #417 [Florian Gross]
+
+ 1. Don't screw up pages that use multiple values for the same parameter (?foo=bar&foo=qux was converted to ?foo=barqux)
+ 2. Don't screw up all forms when you click the "Retry with Breakpoint" link multiple times instead of reloading
+ (This caused the parameters to be added multiple times for GET forms leading to trouble.)
+ 3. Don't add ?BP-RETRY=1 multiple times
+
+* Added that all renders and redirects now return false, so they can be used as the last line in before_filters to stop execution.
+
+ Before:
+ def authenticate
+ unless @session[:authenticated]
+ redirect_to :controller => "account", :action => "login"
+ return false
+ end
+ end
+
+ After:
+ def authenticate
+ redirect_to(:controller => "account", :action => "login") unless @session[:authenticated]
+ end
+
+* Added conditional filters #431 [Marcel]. Example:
+
+ class JournalController < ActionController::Base
+ # only require authentication if the current action is edit or delete
+ before_filter :authorize, :only_on => [ :edit, :delete ]
+
+ private
+ def authorize
+ # redirect to login unless authenticated
+ end
+ end
+
+* Added Base#render_nothing as a cleaner way of doing render_text "" when you're not interested in returning anything but an empty response.
+
+* Added the possibility of passing nil to UrlHelper#link_to to use the link itself as the name
+
+
+*1.2.0* (January 4th, 2005)
+
+* Added MemCacheStore for storing session data in Danga's MemCache system [Bob Cottrell]
+ Depends on: MemCached server (http://www.danga.com/memcached/), MemCache client (http://raa.ruby-lang.org/project/memcache/)
+
+* Added thread-safety to the DRbStore #66, #389 [Ben Stiglitz]
+
+* Added DateHelper#select_time and DateHelper#select_second #373 [Scott Baron]
+
+* Added :host and :protocol options to url_for and friends to redirect to another host and protocol than the current.
+
+* Added class declaration for the MissingFile exception #388 [Kent Sibilev]
+
+* Added "short hypertext note with a hyperlink to the new URI(s)" to redirects to fulfill compliance with RFC 2616 (HTTP/1.1) section 10.3.3 #397 [Tim Bates]
+
+* Added second boolean parameter to Base.redirect_to_url and Response#redirect to control whether the redirect is permanent or not (301 vs 302) #375 [Hodel]
+
+* Fixed redirects when the controller and action is named the same. Still haven't fixed same controller, module, and action, though #201 [Josh]
+
+* Fixed problems with running multiple functional tests in Rails under 1.8.2 by including hack for test/unit weirdness
+
+* Fixed that @request.remote_ip didn't work in the test environment #369 [Bruno Mattarollo]
+
+
+*1.1.0*
+
+* Added search through session to clear out association caches at the end of each request. This makes it possible to place Active Record objects
+ in the session without worrying about stale data in the associations (the main object is still subject to caching, naturally) #347 [Tobias Luetke]
+
+* Added more informative exception when using helper :some_helper and the helper requires another file that fails, you'll get an
+ error message tells you what file actually failed to load, rather than falling back on assuming it was the helper file itself #346 [dblack]
+
+* Added use of *_before_type_cast for all input and text fields. This is helpful for getting "100,000" back on a integer-based
+ validation where the value would normally be "100".
+
+* Added Request#port_string to get something like ":8080" back on 8080 and "" on 80 (or 443 with https).
+
+* Added Request#domain (returns string) and Request#subdomains (returns array).
+
+* Added POST support for the breakpoint retries, so form processing that raises an exception can be retried with the original request [Florian Gross]
+
+* Fixed regression with Base#reset_session that wouldn't use the DEFAULT_SESSION_OPTIONS [adam@the-kramers.net]
+
+* Fixed error rendering of rxml documents to not just swallow the exception and return 0 (still not guessing the right line, but hey)
+
+* Fixed that textilize and markdown would instantiate their engines even on empty strings. This also fixes #333 [Ulysses]
+
+* Fixed UrlHelper#link_to_unless so it doesn't care if the id is a string or fixnum [zenspider]
+
+
+*1.0.1*
+
+* Fixed a bug that would cause an ApplicationController to require itself three times and hence cause filters to be run three times [evl]
+
+
+*1.0*
+
+* Added that controllers will now attempt to require a model dependency with their name and in a singular attempt for their name.
+ So both PostController and PostsController will automatically have the post.rb model required. If no model is found, no error is raised,
+ as it is then expected that no match is available and the programmer will have included his own models.
+
+* Fixed DateHelper#date_select so that you can pass include_blank as an option even if you don't use start_year and end_year #59 [what-a-day]
+
+* Added that controllers will now search for a layout in $template_root/layouts/$controller_name.r(html|xml), so PostsController will look
+ for layouts/posts.rhtml or layouts/posts.rxml and automatically configure this layout if found #307 [Marcel]
+
+* Added FormHelper#radio_button to work with radio buttons like its already possible with check boxes [Michael Koziarski]
+
+* Added TemplateError#backtrace that makes it much easier to debug template errors from unit and functional tests
+
+* Added display of error messages with scaffolded form pages
+
+* Added option to ERB templates to swallow newlines by using <% if something -%> instead of just <% if something %>. Example:
+
+ class SomeController < ApplicationController
+ <% if options[:scaffold] %>
+ scaffold :<%= singular_name %>
+ <% end %>
+ helper :post
+
+ ...produces this on post as singular_name:
+
+ class SomeController < ApplicationController
+
+ scaffold :post
+
+ helper :post
+
+ ...where as:
+
+ class SomeController < ApplicationController
+ <% if options[:scaffold] -%>
+ scaffold :<%= singular_name %>
+ <% end -%>
+ helper :post
+
+ ...produces:
+
+ class SomeController < ApplicationController
+ scaffold :post
+ helper :post
+
+ [This undocumented gem for ERb was uncovered by bitsweat]
+
+* Fixed CgiRequest so that it'll now accept session options with Symbols as keys (as the documentation points out) [Suggested by Andreas]
+
+* Added that render_partial will always by default include a counter with value 1 unless there is a counter passed in via the
+ local_assigns hash that overrides it. As a result, render_collection_of_partials can still be written in terms of render_partial
+ and partials that make use of a counter can be called without problems from both render_collection_of_partials as well as
+ render_partial #295 [marcel]
+
+* Fixed CgiRequest#out to fall back to #write if $stdout doesn't have #syswrite [Jeremy Kemper]
+
+* Fixed all helpers so that they use XHTML compliant double quotes for values instead of single quotes [htonl/bitsweat]
+
+* Added link_to_image(src, options = {}, html_options = {}). Documentation:
+
+ Creates a link tag to the image residing at the +src+ using an URL created by the set of +options+. See the valid options in
+ link:classes/ActionController/Base.html#M000021. It's also possible to pass a string instead of an options hash to
+ get a link tag that just points without consideration. The <tt>html_options</tt> works jointly for the image and ahref tag by
+ letting the following special values enter the options on the image and the rest goes to the ahref:
+
+ ::alt: If no alt text is given, the file name part of the +src+ is used (capitalized and without the extension)
+ ::size: Supplied as "XxY", so "30x45" becomes width="30" and height="45"
+ ::align: Sets the alignment, no special features
+
+ The +src+ can be supplied as a...
+ * full path, like "/my_images/image.gif"
+ * file name, like "rss.gif", that gets expanded to "/images/rss.gif"
+ * file name without extension, like "logo", that gets expanded to "/images/logo.png"
+
+* Fixed to_input_field_tag so it no longer explicitly uses InstanceTag.value if value was specified in the options hash [evl]
+
+* Added the possibility of having validate be protected for assert_(in)valid_column #263 [Tobias Luetke]
+
+* Added that ActiveRecordHelper#form now calls url_for on the :action option.
+
+* Added all the HTTP methods as alternatives to the generic "process" for functional testing #276 [Tobias Luetke]. Examples:
+
+ # Calls Controller#miletone with a GET request
+ process :milestone
+
+ # Calls Controller#miletone with a POST request that has parameters
+ post :milestone, { "name" => "David" }
+
+ # Calls Controller#milestone with a HEAD request that has both parameters and session data
+ head :milestone, { "id" => 1 }, { "user_id" => 23 }
+
+ This is especially useful for testing idiomatic REST web services.
+
+* Added proper handling of HEAD requests, so that content isn't returned (Request#head? added as well) #277 [Eric Hodel]
+
+* Added indifference to whether @headers["Content-Type"], @headers["Content-type"], or @headers["content-type"] is used.
+
+* Added TestSession#session_id that returns an empty string to make it easier to functional test applications that doesn't use
+ cookie-based sessions #275 [jcf]
+
+* Fixed that cached template loading would still check the file system to see if the file existed #258 [Andreas Schwarz]
+
+* Added options to tailor header tag, div id, and div class on ActiveRecordHelper#error_messages_for [Josh Peek]
+
+* Added graceful handling of non-alphanumeric names and misplaced brackets in input parameters [Jeremy Kemper]
+
+* Added a new container for cookies that makes them more intuative to use. The old methods of cookie and @cookies have been deprecated.
+
+ Examples for writing:
+
+ cookies["user_name"] = "david" # => Will set a simple session cookie
+ cookies["login"] = { "value" => "XJ-122", "expires" => Time.now + 360} # => Will set a cookie that expires in 1 hour
+
+ Examples for reading:
+
+ cookies["user_name"] # => "david"
+ cookies.size # => 2
+
+ Read more in ActionController::Cookies
+
+ NOTE: If you were using the old accessor (cookies instead of @cookies), this could potentially break your code -- if you expect a full cookie object!
+
+* Added the opportunity to defined method_missing on a controller which will handle all requests for actions not otherwise defined #223 [timb]
+
+* Fixed AbstractRequest#remote_ip for users going through proxies - Patch #228 [Eric Hodel]
+
+* Added Request#ssl? which is shorthand for @request.protocol == "https://"
+
+* Added the choice to call form_tag with no arguments (resulting in a form posting to current action) [Jeremy Kemper]
+
+* Upgraded to Builder 1.2.1
+
+* Added :module as an alias for :controller_prefix to url_for and friends, so you can do redirect_to(:module => "shop", :controller => "purchases")
+ and go to /shop/purchases/
+
+* Added support for controllers in modules through @params["module"].
+
+* Added reloading for dependencies under cached environments like FastCGI and mod_ruby. This makes it possible to use those environments for development.
+ This is turned on by default, but can be turned off with ActionController::Base.reload_dependencies = false in production environments.
+
+ NOTE: This will only have an effect if you use the new model, service, and observer class methods to mark dependencies. All libraries loaded through
+ require will be "forever" cached. You can, however, use ActionController::Base.load_or_require("library") to get this behavior outside of the new
+ dependency style.
+
+* Added that controllers will automatically require their own helper if possible. So instead of doing:
+
+ class MsgController < ApplicationController
+ helper :msg
+ end
+
+ ...you can just do:
+
+ class MsgController < ApplicationController
+ end
+
+* Added dependencies_on(layer) to query the dependencies of a controller. Examples:
+
+ MsgController.dependencies_on(:model) # => [ :post, :comment, :attachment ]
+ MsgController.dependencies_on(:service) # => [ :notification_service ]
+ MsgController.dependencies_on(:observer) # => [ :comment_observer ]
+
+* Added a new dependency model with the class methods model, service, and observer. Example:
+
+ class MsgController < ApplicationController
+ model :post, :comment, :attachment
+ service :notification_service
+ observer :comment_observer
+ end
+
+ These new "keywords" remove the need for explicitly calling 'require' in most cases. The observer method even instantiates the
+ observer as well as requiring it.
+
+* Fixed that link_to would escape & in the url again after url_for already had done so
+
+
+*0.9.5* (28)
+
+* Added helper_method to designate that a given private or protected method you should available as a helper in the view. [Jeremy Kemper]
+
+* Fixed assert_rendered_file so it actually verifies if that was the rendered file [htonl]
+
+* Added the option for sharing partial spacer templates just like partials themselves [radsaq]
+
+* Fixed that Russia was named twice in country_select [alexey]
+
+* Fixed request_origin to use remote_ip instead of remote_addr [Jeremy Kemper]
+
+* Fixed link_to breakage when nil was passed for html_options [alexey]
+
+* Fixed redirect_to on a virtual server setup with apache with a port other than the default where it would forget the port number [seanohalpin]
+
+* Fixed that auto-loading webrick on Windows would cause file uploads to fail [Jeremy Kemper]
+
+* Fixed issues with sending files on WEBrick by setting the proper binmode [Jeremy Kemper]
+
+* Added send_data as an alternative to send_file when the stream is not read off the filesystem but from a database or generated live [Jeremy Kemper]
+
+* Added a new way to include helpers that doesn't require the include hack and can go without the explicit require. [Jeremy Kemper]
+
+ Before:
+
+ module WeblogHelper
+ def self.included(controller) #:nodoc:
+ controller.ancestors.include?(ActionController::Base) ? controller.add_template_helper(self) : super
+ end
+ end
+
+ require 'weblog_helper'
+ class WeblogController < ActionController::Base
+ include WeblogHelper
+ end
+
+ After:
+
+ module WeblogHelper
+ end
+
+ class WeblogController < ActionController::Base
+ helper :weblog
+ end
+
+* Added a default content-type of "text/xml" to .rxml renders [Ryan Platte]
+
+* Fixed that when /controller/index was requested by the browser, url_for would generates wrong URLs [Ryan Platte]
+
+* Fixed a bug that would share cookies between users when using FastCGI and mod_ruby [The Robot Co-op]
+
+* Added an optional third hash parameter to the process method in functional tests that takes the session data to be used [alexey]
+
+* Added UrlHelper#mail_to to make it easier to create mailto: style ahrefs
+
+* Added better error messages for layouts declared with the .rhtml extension (which they shouldn't) [geech]
+
+* Added another case to DateHelper#distance_in_minutes to return "less than a minute" instead of "0 minutes" and "1 minute" instead of "1 minutes"
+
+* Added a hidden field to checkboxes generated with FormHelper#check_box that will make sure that the unchecked value (usually 0)
+ is sent even if the checkbox is not checked. This relieves the controller from doing custom checking if the checkbox wasn't
+ checked. BEWARE: This might conflict with your run-on-the-mill work-around code. [Tobias Luetke]
+
+* Fixed error_message_on to just use the first if more than one error had been added [marcel]
+
+* Fixed that URL rewriting with /controller/ was working but /controller was not and that you couldn't use :id on index [geech]
+
+* Fixed a bug with link_to where the :confirm option wouldn't be picked up if the link was a straight url instead of an option hash
+
+* Changed scaffolding of forms to use <label> tags instead of <b> to please W3C [evl]
+
+* Added DateHelper#distance_of_time_in_words_to_now(from_time) that works like distance_of_time_in_words,
+ but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
+
+* Added assert_flash_equal(expected, key, message), assert_session_equal(expected, key, message),
+ assert_assigned_equal(expected, key, message) to test the contents of flash, session, and template assigns.
+
+* Improved the failure report on assert_success when the action triggered a redirection [alexey].
+
+* Added "markdown" to accompany "textilize" as a TextHelper method for converting text to HTML using the Markdown syntax.
+ BlueCloth must be installed in order for this method to become available.
+
+* Made sure that an active session exists before we attempt to delete it [Samuel]
+
+* Changed link_to with Javascript confirmation to use onclick instead of onClick for XHTML validity [Scott Barron]
+
+
+*0.9.0 (43)*
+
+* Added support for Builder-based templates for files with the .rxml extension. These new templates are an alternative to ERb that
+ are especially useful for generating XML content, such as this RSS example from Basecamp:
+
+ xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
+ xml.channel do
+ xml.title(@feed_title)
+ xml.link(@url)
+ xml.description "Basecamp: Recent items"
+ xml.language "en-us"
+ xml.ttl "40"
+
+ for item in @recent_items
+ xml.item do
+ xml.title(item_title(item))
+ xml.description(item_description(item)) if item_description(item)
+ xml.pubDate(item_pubDate(item))
+ xml.guid(@person.firm.account.url + @recent_items.url(item))
+ xml.link(@person.firm.account.url + @recent_items.url(item))
+
+ xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
+ end
+ end
+ end
+ end
+
+ ...which will generate something like:
+
+ <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <channel>
+ <title>Web Site Redesign</title>
+ <link>http://www.basecamphq.com/clients/travelcenter/1/</link>
+ <description>Basecamp: Recent items</description>
+ <language>en-us</language>
+ <ttl>40</ttl>
+ <item>
+ <title>Post: don't you know</title>
+ <description>&amp;lt;p&amp;gt;deeper and down&amp;lt;/p&amp;gt;</description>
+ <pubDate>Fri, 20 Aug 2004 21:13:50 CEST</pubDate>
+ <guid>http://www.basecamphq.com/clients/travelcenter/1/msg/assets/96976/comments</guid>
+ <link>http://www.basecamphq.com/clients/travelcenter/1/msg/assets/96976/comments</link>
+ <dc:creator>David H. Heinemeier</dc:creator>
+ </item>
+ <item>
+ <title>Milestone completed: Design Comp 2</title>
+ <pubDate>Mon, 9 Aug 2004 14:42:06 CEST</pubDate>
+ <guid>http://www.basecamphq.com/clients/travelcenter/1/milestones/#49</guid>
+ <link>http://www.basecamphq.com/clients/travelcenter/1/milestones/#49</link>
+ </item>
+ </channel>
+ </rss>
+
+ The "xml" local variable is automatically available in .rxml templates. You construct the template by calling a method with the name
+ of the tag you want. Options for the tag can be specified as a hash parameter to that method.
+
+ Builder-based templates can be mixed and matched with the regular ERb ones. The only thing that differentiates them is the extension.
+ No new methods have been added to the public interface to handle them.
+
+ Action Pack ships with a version of Builder, but it will use the RubyGems version if you have one installed.
+
+ Read more about Builder on: http://onestepback.org/index.cgi/Tech/Ruby/StayingSimple.rdoc
+
+ [Builder is created by Jim Weirich]
+
+* Added much improved support for functional testing [what-a-day].
+
+ # Old style
+ def test_failing_authenticate
+ @request.request_uri = "/login/authenticate"
+ @request.action = "authenticate"
+ @request.request_parameters["user_name"] = "nop"
+ @request.request_parameters["password"] = ""
+
+ response = LoginController.process_test(@request)
+
+ assert_equal "The username and/or password you entered is invalid.", response.session["flash"]["alert"]
+ assert_equal "http://37signals.basecamp.com/login/", response.headers["location"]
+ end
+
+ # New style
+ def test_failing_authenticate
+ process :authenticate, "user_name" => "nop", "password" => ""
+ assert_flash_has 'alert'
+ assert_redirected_to :action => "index"
+ end
+
+ See a full example on http://codepaste.org/view/paste/334
+
+* Increased performance by up to 100% with a revised cookie class that fixes the performance problems with the
+ default one that ships with 1.8.1 and below. It replaces the inheritance on SimpleDelegator with DelegateClass(Array)
+ following the suggestion from Matz on:
+ http://groups.google.com/groups?th=e3a4e68ba042f842&seekm=c3sioe%241qvm%241%40news.cybercity.dk#link14
+
+* Added caching for compiled ERb templates. On Basecamp, it gave between 8.5% and 71% increase in performance [Andreas Schwarz].
+
+* Added implicit counter variable to render_collection_of_partials [Marcel]. From the docs:
+
+ <%= render_collection_of_partials "ad", @advertisements %>
+
+ This will render "advertiser/_ad.rhtml" and pass the local variable +ad+ to the template for display. An iteration counter
+ will automatically be made available to the template with a name of the form +partial_name_counter+. In the case of the
+ example above, the template would be fed +ad_counter+.
+
+* Fixed problems with two sessions being maintained on reset_session that would particularly screw up ActiveRecordStore.
+
+* Fixed reset_session to start an entirely new session instead of merely deleting the old. So you can now safely access @session
+ after calling reset_ression and expect it to work.
+
+* Added @request.get?, @request.post?, @request.put?, @request.delete? as convenience query methods for @request.method [geech]
+
+* Added @request.method that'll return a symbol representing the HTTP method, such as :get, :post, :put, :delete [geech]
+
+* Changed @request.remote_ip and @request.host to work properly even when a proxy is in front of the application [geech]
+
+* Added JavaScript confirm feature to link_to. Documentation:
+
+ The html_options have a special feature for creating javascript confirm alerts where if you pass
+ :confirm => 'Are you sure?', the link will be guarded with a JS popup asking that question.
+ If the user accepts, the link is processed, otherwise not.
+
+* Added link_to_unless_current as a UrlHelper method [Sam Stephenson]. Documentation:
+
+ Creates a link tag of the given +name+ using an URL created by the set of +options+, unless the current
+ controller, action, and id are the same as the link's, in which case only the name is returned (or the
+ given block is yielded, if one exists). This is useful for creating link bars where you don't want to link
+ to the page currently being viewed.
+
+* Fixed that UrlRewriter (the driver for url_for, link_to, etc) would blow up when the anchor was an integer [alexey]
+
+* Added that layouts defined with no directory defaults to layouts. So layout "weblog/standard" will use
+ weblog/standard (as always), but layout "standard" will use layouts/standard.
+
+* Fixed that partials (or any template starting with an underscore) was publically viewable [Marten]
+
+* Added HTML escaping to text_area helper.
+
+* Added :overwrite_params to url_for and friends to keep the parameters as they were passed to the current action and only overwrite a subset.
+ The regular :params will clear the slate so you need to manually add in existing parameters if you want to reuse them. [raphinou]
+
+* Fixed scaffolding problem with composite named objects [Moo Jester]
+
+* Added the possibility for shared partials. Example:
+
+ <%= render_partial "advertisement/ad", ad %>
+
+ This will render the partial "advertisement/_ad.rhtml" regardless of which controller this is being called from.
+
+ [Jacob Fugal]
+
+* Fixed crash when encountering forms that have empty-named fields [James Prudente]
+
+* Added check_box form helper method now accepts true/false as well as 1/0 [what-a-day]
+
+* Fixed the lacking creation of all directories with install.rb [Dave Steinberg]
+
+* Fixed that date_select returns valid XHTML selected options [Andreas Schwarz]
+
+* Fixed referencing an action with the same name as a controller in url_for [what-a-day]
+
+* Fixed the destructive nature of Base#attributes= on the argument [Kevin Watt]
+
+* Changed ActionControllerError to decent from StandardError instead of Exception. It can now be caught by a generic rescue.
+
+* Added SessionRestoreError that is raised when a session being restored holds objects where there is no class available.
+
+* Added block as option for inline filters. So what used to be written as:
+
+ before_filter Proc { |controller| return false if controller.params["stop_action"] }
+
+ ...can now be as:
+
+ before_filter { |controller| return false if controller.params["stop_action"] }
+
+ [Jeremy Kemper]
+
+* Made the following methods public (was protected): url_for, controller_class_name, controller_name, action_name
+ This makes it easier to write filters without cheating around the encapsulation with send.
+
+* ActionController::Base#reset_session now sticks even if you access @session afterwards [Kent Sibilev]
+
+* Improved the exception logging so the log file gets almost as much as in-browser debugging.
+
+* Changed base class setup from AbstractTemplate/ERbTemplate to ActionView::Base. This change should be harmless unless you were
+ accessing Action View directly in which case you now need to reference the Base class.\
+
+* Added that render_collection_of_partials returns nil if the collection is empty. This makes showing a “no items” message easier.
+ For example: <%= render_collection_of_partials("message", @messages) || "No messages found." %> [Sam Stephenson]
+
+* Added :month_before_year as an option to date_select to get the month select before the year. Especially useful for credit card forms.
+
+* Added :add_month_numbers to select_month to get options like "3 - March".
+
+* Removed Base.has_active_layout? as it couldn't answer the question without the instance. Use Base#active_layout instead.
+
+* Removed redundant call to update on ActionController::Base#close_session [Andreas Schwarz]
+
+* Fixed that DRb Store accidently started its own server (instead of just client) [Andreas]
+
+* Fixed strip_links so it now works across multiple lines [Chad Fowler]
+
+* Fixed the TemplateError exception to show the proper trace on to_s (useful for unit test debugging)
+
+* Implemented class inheritable attributes without eval [Caio Chassot]
+
+* Made TextHelper#concat accept binding as it would otherwise not work
+
+* The FormOptionsHelper will now call to_s on the keys and values used to generate options
+
+
+*0.8.5*
+
+* Introduced passing of locally scoped variables between templates:
+
+ You can pass local variables to sub templates by using a hash of with the variable
+ names as keys and the objects as values:
+
+ <%= render "shared/header", { "headline" => "Welcome", "person" => person } %>
+
+ These can now be accessed in shared/header with:
+
+ Headline: <%= headline %>
+ First name: <%= person.first_name %>
+
+* Introduced the concept of partials as a certain type of sub templates:
+
+ There's also a convenience method for rendering sub templates within the current
+ controller that depends on a single object (we call this kind of sub templates for
+ partials). It relies on the fact that partials should follow the naming convention
+ of being prefixed with an underscore -- as to separate them from regular templates
+ that could be rendered on their own. In the template for Advertiser#buy, we could have:
+
+ <% for ad in @advertisements %>
+ <%= render_partial "ad", ad %>
+ <% end %>
+
+ This would render "advertiser/_ad.rhtml" and pass the local variable +ad+
+ for the template to display.
+
+ == Rendering a collection of partials
+
+ The example of partial use describes a familar pattern where a template needs
+ to iterate over a array and render a sub template for each of the elements.
+ This pattern has been implemented as a single method that accepts an array and
+ renders a partial by the same name of as the elements contained within. So the
+ three-lined example in "Using partials" can be rewritten with a single line:
+
+ <%= render_collection_of_partials "ad", @advertisements %>
+
+ So this will render "advertiser/_ad.rhtml" and pass the local variable +ad+ for
+ the template to display.
+
+* Improved send_file by allowing a wide range of options to be applied [Jeremy Kemper]:
+
+ Sends the file by streaming it 4096 bytes at a time. This way the
+ whole file doesn't need to be read into memory at once. This makes
+ it feasible to send even large files.
+
+ Be careful to sanitize the path parameter if it coming from a web
+ page. send_file(@params['path'] allows a malicious user to
+ download any file on your server.
+
+ Options:
+ * <tt>:filename</tt> - specifies the filename the browser will see.
+ Defaults to File.basename(path).
+ * <tt>:type</tt> - specifies an HTTP content type.
+ Defaults to 'application/octet-stream'.
+ * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
+ Valid values are 'inline' and 'attachment' (default).
+ * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream
+ the file. Defaults to 4096.
+
+ The default Content-Type and Content-Disposition headers are
+ set to download arbitrary binary files in as many browsers as
+ possible. IE versions 4, 5, 5.5, and 6 are all known to have
+ a variety of quirks (especially when downloading over SSL).
+
+ Simple download:
+ send_file '/path/to.zip'
+
+ Show a JPEG in browser:
+ send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
+
+ Read about the other Content-* HTTP headers if you'd like to
+ provide the user with more information (such as Content-Description).
+ http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
+
+ Also be aware that the document may be cached by proxies and browsers.
+ The Pragma and Cache-Control headers declare how the file may be cached
+ by intermediaries. They default to require clients to validate with
+ the server before releasing cached responses. See
+ http://www.mnot.net/cache_docs/ for an overview of web caching and
+ http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+ for the Cache-Control header spec.
+
+* Added pluralize method to the TextHelper that makes it easy to get strings like "1 message", "3 messages"
+
+* Added proper escaping for the rescues [Andreas Schwarz]
+
+* Added proper escaping for the option and collection tags [Andreas Schwarz]
+
+* Fixed NaN errors on benchmarking [Jim Weirich]
+
+* Fixed query string parsing for URLs that use the escaped versions of & or ; as part of a key or value
+
+* Fixed bug with custom Content-Type headers being in addition to rather than instead of the default header.
+ (This bug didn't matter with neither CGI or mod_ruby, but FCGI exploded on it) [With help from Ara T. Howard]
+
+
+*0.8.0*
+
+* Added select, collection_select, and country_select to make it easier for Active Records to set attributes through
+ drop-down lists of options. Example:
+
+ <%= select "person", "gender", %w( Male Female ) %>
+
+ ...would give the following:
+
+ <select name="person[gender]" id="person_gender"><option>Male</option><option>Female</option></select>
+
+* Added an option for getting multiple values on a single form name into an array instead of having the last one overwrite.
+ This is especially useful for groups of checkboxes, which can now be written as:
+
+ <input type="checkbox" name="rights[]" value="CREATE" />
+ <input type="checkbox" name="rights[]" value="UPDATE" />
+ <input type="checkbox" name="rights[]" value="DELETE" />
+
+ ...and retrieved in the controller action with:
+
+ @params["rights"] # => [ "CREATE", "UPDATE", "DELETE" ]
+
+ The old behavior (where the last one wins, "DELETE" in the example) is still available. Just don't add "[]" to the
+ end of the name. [Scott Baron]
+
+* Added send_file which uses the new render_text block acceptance to make it feasible to send large files.
+ The files is sent with a bunch of voodoo HTTP headers required to get arbitrary files to download as
+ expected in as many browsers as possible (eg, IE hacks). Example:
+
+ def play_movie
+ send_file "/movies/that_movie.avi"
+ end
+
+ [Jeremy Kemper]
+
+* render_text now accepts a block for deferred rendering. Useful for streaming large files, displaying
+ a “please wait” message during a complex search, etc. Streaming example:
+
+ render_text do |response|
+ File.open(path, 'rb') do |file|
+ while buf = file.read(1024)
+ print buf
+ end
+ end
+ end
+
+ [Jeremy Kemper]
+
+* Added a new Tag Helper that can generate generic tags programmatically insted of through HTML. Example:
+
+ tag("br", "clear" => "all") => <br clear="all" />
+
+ ...that's usually not terribly interesting (unless you have a lot of options already in a hash), but it
+ gives way for more specific tags, like the new form tag:
+
+ form_tag({ :controller => "weblog", :action => "update" }, { :multipart => "true", "style" => "width: 200px"}) =>
+ <form action="/weblog/update" enctype="multipart/formdata" style="width: 200px">
+
+ There's even a "pretty" version for people who don't like to open tags in code and close them in HTML:
+
+ <%= start_form_tag :action => "update" %>
+ # all the input fields
+ <%= end_form_tag %>
+
+ (end_form_tag just returns "</form>")
+
+* The selected parameter in options_for_select may now also an array of values to be selected when
+ using a multiple select. Example:
+
+ options_for_select([ "VISA", "Mastercard", "Discover" ], ["VISA", "Discover"]) =>
+ <option selected>VISA</option>\n<option>Mastercard</option>\n<option selected>Discover</option>
+
+ [Scott Baron]
+
+* Changed the URL rewriter so controller_prefix and action_prefix can be used in isolation. You can now do:
+
+ url_for(:controller_prefix => "clients")
+
+ ...or:
+
+ url_for(:action_prefix => "category/messages")
+
+ Neither would have worked in isolation before (:controller_prefix required a :controller and :action_prefix required an :action)
+
+* Started process of a cleaner separation between Action Controller and ERb-based Action Views by introducing an
+ abstract base class for views. And Amita adapter could be fitted in more easily now.
+
+* The date helper methods date_select and datetime_select now also use the field error wrapping
+ (div with class fieldWithErrors by default).
+
+* The date helper methods date_select and datetime_select can now discard selects
+
+* Added option on AbstractTemplate to specify a different field error wrapping. Example:
+
+ ActionView::AbstractTemplate.field_error_proc = Proc.new do |html, instance|
+ "<p>#{instance.method_name + instance.error_message}</p><div style='background-color: red'>#{html}</div>"
+ end
+
+ ...would give the following on a Post#title (text field) error:
+
+ <p>Title can't be empty</p>
+ <div style='background-color: red'>
+ <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
+ </div>
+
+* The UrlHelper methods url_for and link_to will now by default only return paths, not complete URIs.
+ That should make it easier to fit a Rails application behind a proxy or load-balancer.
+ You can overwrite this by passing :only_path => false as part of the options. [Suggested by U235]
+
+* Fixed bug with having your own layout for use with scaffolding [Kevin Radloff]
+
+* Fixed bug where redirect_to_path didn't append the port on non-standard ports [dhawkins]
+
+* Scaffolding plays nicely with single-table inheritance (LoadErrors are caught) [Jeremy Kemper]
+
+* Scaffolding plays nice with plural models like Category/categories [Jeremy Kemper]
+
+* Fixed missing suffix appending in scaffolding [Kevin Radloff]
+
+
+*0.7.9*
+
+* The "form" method now present boolean fields from PostgreSQL as drop-down menu. [Scott]
+
+* Scaffolding now automatically attempts to require the class that's being scaffolded.
+
+* Scaffolding will use the current active layout, instead of its own, if one has been specified. Example:
+
+ class WeblogController < ActionController::Base
+ layout "layouts/weblog"
+ scaffold :post
+ end
+
+ [Suggested by Scott]
+
+* Changed url_for (and all the that drives, like redirect_to, link_to, link_for) so you can pass it a symbol instead of a hash.
+ This symbol is a method reference which is then called to calculate the url. Example:
+
+ class WeblogController < ActionController::Base
+ def update
+ # do some update
+ redirect_to :dashboard_url
+ end
+
+ protected
+ def dashboard_url
+ if @project.active?
+ url_for :controller => "project", :action => "dashboard"
+ else
+ url_for :controller => "account", :action => "dashboard"
+ end
+ end
+ end
+
+* Added default_url_options to specialize behavior for all url_for (and friends) calls:
+
+ Overwrite to implement a number of default options that all url_for-based methods will use.
+ The default options should come in form of a hash, just like the one you would use for
+ url_for directly. Example:
+
+ def default_url_options(options)
+ { :controller_prefix => @project.active? ? "projects/" : "accounts/" }
+ end
+
+ As you can infer from the example, this is mostly useful for situations where you want to
+ centralize dynamic dissions about the urls as they stem from the business domain. Please note
+ that any individual url_for call can always override the defaults set by this method.
+
+
+* Changed url_for so that an "id" passed in the :params is not treated special. You need to use the dedicated :id to get
+ the special auto path-params treatment. Considering the url http://localhost:81/friends/list
+
+ url_for(:action => "show", :params => { "id" => 5 })
+ ...used to give http://localhost:81/friends/show/5
+ ......now gives http://localhost:81/friends/show?id=5
+
+ If you want the automated id behavior, do:
+
+ url_for(:action => "show", :id => 5 )
+ ....which gives http://localhost:81/friends/show/5
+
+
+* Fixed problem with anchor being inserted before path parameters with url_for (and friends)
+
+
+*0.7.8*
+
+* Fixed session bug where you couldn't store any objects that didn't exist in the standard library
+ (such as Active Record objects).
+
+* Added reset_session method for Action Controller objects to clear out all objects in the session.
+
+* Fixed that exceptions raised during filters are now also caught by the default rescues
+
+* Added new around_filter for doing before and after filtering with a single object [Florian Weber]:
+
+ class WeblogController < ActionController::Base
+ around_filter BenchmarkingFilter.new
+
+ # Before this action is performed, BenchmarkingFilter#before(controller) is executed
+ def index
+ end
+ # After this action has been performed, BenchmarkingFilter#after(controller) is executed
+ end
+
+ class BenchmarkingFilter
+ def initialize
+ @runtime
+ end
+
+ def before
+ start_timer
+ end
+
+ def after
+ stop_timer
+ report_result
+ end
+ end
+
+* Added the options for specifying a different name and id for the form helper methods than what is guessed [Florian Weber]:
+
+ text_field "post", "title"
+ ...just gives: <input id="post_title" name="post[title]" size="30" type="text" value="" />
+
+ text_field "post", "title", "id" => "title_for_post", "name" => "first_post_title"
+ ...can now give: <input id="title_for_post" name="first_post_title" size="30" type="text" value="" />
+
+* Added DebugHelper with a single "debug" method for doing pretty dumps of objects in the view
+ (now used in the default rescues to better present the contents of session and template variables)
+
+* Added note to log about the templates rendered within layouts (before just the layout was shown)
+
+* Fixed redirects on https setups [Andreas]
+
+* Fixed scaffolding problem on the edit action when using :suffix => true [Scott]
+
+* Fixed scaffolding problem where implementing list.rhtml wouldn't work for the index action
+
+* URLs generated now uses &amp; instead of just & so pages using it can validate with W3C [Spotted by Andreas]
+
+
+*0.7.7*
+
+* Fixed bug in CGI extension that prevented multipart forms from working
+
+
+*0.7.6*
+
+* Included ERB::Util so all templates can easily escape HTML content with <%=h @person.content %>
+
+* All requests are now considered local by default, so everyone will be exposed to detailed debugging screens on errors.
+ When the application is ready to go public, set ActionController::Base.consider_all_requests_local to false,
+ and implement the protected method local_request? in the controller to determine when debugging screens should be shown.
+
+* Fixed three bugs with the url_for/redirect_to/link_to handling. Considering the url http://localhost:81/friends/show/1
+
+ url_for(:action => "list")
+ ...used to give http://localhost:81/friends/list/1
+ ......now gives http://localhost:81/friends/list
+
+ url_for(:controller => "friends", :action => "destroy", :id => 5)
+ ...used to give http://localhost:81/friends/destroy
+ ......now gives http://localhost:81/friends/destroy/5
+
+ Considering the url http://localhost:81/teachers/show/t
+
+ url_for(:action => "list", :id => 5)
+ ...used to give http://localhost:81/5eachers/list/t
+ ......now gives http://localhost:81/teachers/list/5
+
+ [Reported by David Morton & Radsaq]
+
+* Logs exception to logfile in addition to showing them for local requests
+
+* Protects the eruby load behind a begin/rescue block. eRuby is not required to run ActionController.
+
+* Fixed install.rb to also install clean_logger and the templates
+
+* Added ActiveRecordStore as a session option. Read more in lib/action_controller/session/active_record_store.rb [Tim Bates]
+
+* Change license to MIT License (and included license file in package)
+
+* Application error page now returns status code 500 instead of 200
+
+* Fixed using Procs as layout handlers [Florian Weber]
+
+* Fixed bug with using redirects ports other than 80
+
+* Added index method that calls list on scaffolding
+
+
+*0.7.5*
+
+* First public release
diff --git a/vendor/rails-2.0.2/actionpack/MIT-LICENSE b/vendor/rails-2.0.2/actionpack/MIT-LICENSE
new file mode 100644
index 000000000..007cc942e
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/MIT-LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2004-2007 David Heinemeier Hansson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/vendor/rails-2.0.2/actionpack/README b/vendor/rails-2.0.2/actionpack/README
new file mode 100644
index 000000000..72f0f6241
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/README
@@ -0,0 +1,469 @@
+= Action Pack -- On rails from request to response
+
+Action Pack splits the response to a web request into a controller part
+(performing the logic) and a view part (rendering a template). This two-step
+approach is known as an action, which will normally create, read, update, or
+delete (CRUD for short) some sort of model part (often backed by a database)
+before choosing either to render a template or redirecting to another action.
+
+Action Pack implements these actions as public methods on Action Controllers
+and uses Action Views to implement the template rendering. Action Controllers
+are then responsible for handling all the actions relating to a certain part
+of an application. This grouping usually consists of actions for lists and for
+CRUDs revolving around a single (or a few) model objects. So ContactController
+would be responsible for listing contacts, creating, deleting, and updating
+contacts. A WeblogController could be responsible for both posts and comments.
+
+Action View templates are written using embedded Ruby in tags mingled in with
+the HTML. To avoid cluttering the templates with code, a bunch of helper
+classes provide common behavior for forms, dates, and strings. And it's easy
+to add specific helpers to keep the separation as the application evolves.
+
+Note: Some of the features, such as scaffolding and form building, are tied to
+ActiveRecord[http://activerecord.rubyonrails.org] (an object-relational
+mapping package), but that doesn't mean that Action Pack depends on Active
+Record. Action Pack is an independent package that can be used with any sort
+of backend (Instiki[http://www.instiki.org], which is based on an older version
+of Action Pack, used Madeleine for example). Read more about the role Action
+Pack can play when used together with Active Record on
+http://www.rubyonrails.org.
+
+A short rundown of the major features:
+
+* Actions grouped in controller as methods instead of separate command objects
+ and can therefore share helper methods.
+
+ BlogController < ActionController::Base
+ def show
+ @customer = find_customer
+ end
+
+ def update
+ @customer = find_customer
+ @customer.attributes = params[:customer]
+ @customer.save ?
+ redirect_to(:action => "display") :
+ render(:action => "edit")
+ end
+
+ private
+ def find_customer() Customer.find(params[:id]) end
+ end
+
+ {Learn more}[link:classes/ActionController/Base.html]
+
+
+* Embedded Ruby for templates (no new "easy" template language)
+
+ <% for post in @posts %>
+ Title: <%= post.title %>
+ <% end %>
+
+ All post titles: <%= @post.collect{ |p| p.title }.join ", " %>
+
+ <% unless @person.is_client? %>
+ Not for clients to see...
+ <% end %>
+
+ {Learn more}[link:classes/ActionView.html]
+
+
+* Builder-based templates (great for XML content, like RSS)
+
+ xml.rss("version" => "2.0") do
+ xml.channel do
+ xml.title(@feed_title)
+ xml.link(@url)
+ xml.description "Basecamp: Recent items"
+ xml.language "en-us"
+ xml.ttl "40"
+
+ for item in @recent_items
+ xml.item do
+ xml.title(item_title(item))
+ xml.description(item_description(item))
+ xml.pubDate(item_pubDate(item))
+ xml.guid(@recent_items.url(item))
+ xml.link(@recent_items.url(item))
+ end
+ end
+ end
+ end
+
+ {Learn more}[link:classes/ActionView/Base.html]
+
+
+* Filters for pre and post processing of the response (as methods, procs, and classes)
+
+ class WeblogController < ActionController::Base
+ before_filter :authenticate, :cache, :audit
+ after_filter { |c| c.response.body = GZip::compress(c.response.body) }
+ after_filter LocalizeFilter
+
+ def index
+ # Before this action is run, the user will be authenticated, the cache
+ # will be examined to see if a valid copy of the results already
+ # exists, and the action will be logged for auditing.
+
+ # After this action has run, the output will first be localized then
+ # compressed to minimize bandwidth usage
+ end
+
+ private
+ def authenticate
+ # Implement the filter with full access to both request and response
+ end
+ end
+
+ {Learn more}[link:classes/ActionController/Filters/ClassMethods.html]
+
+
+* Helpers for forms, dates, action links, and text
+
+ <%= text_field "post", "title", "size" => 30 %>
+ <%= html_date_select(Date.today) %>
+ <%= link_to "New post", :controller => "post", :action => "new" %>
+ <%= truncate(post.title, 25) %>
+
+ {Learn more}[link:classes/ActionView/Helpers.html]
+
+
+* Layout sharing for template reuse (think simple version of Struts
+ Tiles[http://jakarta.apache.org/struts/userGuide/dev_tiles.html])
+
+ class WeblogController < ActionController::Base
+ layout "weblog_layout"
+
+ def hello_world
+ end
+ end
+
+ Layout file (called weblog_layout):
+ <html><body><%= yield %></body></html>
+
+ Template for hello_world action:
+ <h1>Hello world</h1>
+
+ Result of running hello_world action:
+ <html><body><h1>Hello world</h1></body></html>
+
+ {Learn more}[link:classes/ActionController/Layout/ClassMethods.html]
+
+
+* Routing makes pretty urls incredibly easy
+
+ 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]
+
+ From that URL, you can rewrite the redirect in a number of ways:
+
+ redirect_to(:action => "edit") =>
+ /clients/37signals/basecamp/project/dash
+
+ redirect_to(:client_name => "nextangle", :project_name => "rails") =>
+ /clients/nextangle/rails/project/dash
+
+ {Learn more}[link:classes/ActionController/Base.html]
+
+
+* Javascript and Ajax integration.
+
+ link_to_function "Greeting", "alert('Hello world!')"
+ link_to_remote "Delete this post", :update => "posts",
+ :url => { :action => "destroy", :id => post.id }
+
+ {Learn more}[link:classes/ActionView/Helpers/JavaScriptHelper.html]
+
+
+* Pagination for navigating lists of results.
+
+ # controller
+ def list
+ @pages, @people =
+ paginate :people, :order => 'last_name, first_name'
+ end
+
+ # view
+ <%= link_to "Previous page", { :page => @pages.current.previous } if @pages.current.previous %>
+ <%= link_to "Next page", { :page => @pages.current.next } if @pages.current.next %>
+
+ {Learn more}[link:classes/ActionController/Pagination.html]
+
+
+* Easy testing of both controller and template result through TestRequest/Response
+
+ class LoginControllerTest < Test::Unit::TestCase
+ def setup
+ @controller = LoginController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_failing_authenticate
+ process :authenticate, :user_name => "nop", :password => ""
+ assert flash.has_key?(:alert)
+ assert_redirected_to :action => "index"
+ end
+ end
+
+ {Learn more}[link:classes/ActionController/TestRequest.html]
+
+
+* Automated benchmarking and integrated logging
+
+ Processing WeblogController#index (for 127.0.0.1 at Fri May 28 00:41:55)
+ Parameters: {"action"=>"index", "controller"=>"weblog"}
+ Rendering weblog/index (200 OK)
+ Completed in 0.029281 (34 reqs/sec)
+
+ If Active Record is used as the model, you'll have the database debugging
+ as well:
+
+ Processing WeblogController#create (for 127.0.0.1 at Sat Jun 19 14:04:23)
+ Params: {"controller"=>"weblog", "action"=>"create",
+ "post"=>{"title"=>"this is good"} }
+ SQL (0.000627) INSERT INTO posts (title) VALUES('this is good')
+ Redirected to http://test/weblog/display/5
+ Completed in 0.221764 (4 reqs/sec) | DB: 0.059920 (27%)
+
+ You specify a logger through a class method, such as:
+
+ ActionController::Base.logger = Logger.new("Application Log")
+ ActionController::Base.logger = Log4r::Logger.new("Application Log")
+
+
+* Caching at three levels of granularity (page, action, fragment)
+
+ class WeblogController < ActionController::Base
+ caches_page :show
+ caches_action :account
+
+ def show
+ # the output of the method will be cached as
+ # ActionController::Base.page_cache_directory + "/weblog/show/n.html"
+ # and the web server will pick it up without even hitting Rails
+ end
+
+ def account
+ # the output of the method will be cached in the fragment store
+ # but Rails is hit to retrieve it, so filters are run
+ end
+
+ def update
+ List.update(params[:list][:id], params[:list])
+ expire_page :action => "show", :id => params[:list][:id]
+ expire_action :action => "account"
+ redirect_to :action => "show", :id => params[:list][:id]
+ end
+ end
+
+ {Learn more}[link:classes/ActionController/Caching.html]
+
+
+* Component requests from one controller to another
+
+ class WeblogController < ActionController::Base
+ # Performs a method and then lets hello_world output its render
+ def delegate_action
+ do_other_stuff_before_hello_world
+ render_component :controller => "greeter", :action => "hello_world"
+ end
+ end
+
+ class GreeterController < ActionController::Base
+ def hello_world
+ render_text "Hello World!"
+ end
+ end
+
+ The same can be done in a view to do a partial rendering:
+
+ Let's see a greeting:
+ <%= render_component :controller => "greeter", :action => "hello_world" %>
+
+ {Learn more}[link:classes/ActionController/Components.html]
+
+
+* Powerful debugging mechanism for local requests
+
+ All exceptions raised on actions performed on the request of a local user
+ will be presented with a tailored debugging screen that includes exception
+ message, stack trace, request parameters, session contents, and the
+ half-finished response.
+
+ {Learn more}[link:classes/ActionController/Rescue.html]
+
+
+* Scaffolding for Active Record model objects
+
+ class AccountController < ActionController::Base
+ scaffold :account
+ end
+
+ 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]
+
+
+* Form building for Active Record model objects
+
+ The post object has a title (varchar), content (text), and
+ written_on (date)
+
+ <%= form "post" %>
+
+ ...will generate something like (the selects will have more options, of
+ course):
+
+ <form action="create" method="POST">
+ <p>
+ <b>Title:</b><br/>
+ <input type="text" name="post[title]" value="<%= @post.title %>" />
+ </p>
+ <p>
+ <b>Content:</b><br/>
+ <textarea name="post[content]"><%= @post.title %></textarea>
+ </p>
+ <p>
+ <b>Written on:</b><br/>
+ <select name='post[written_on(3i)]'><option>18</option></select>
+ <select name='post[written_on(2i)]'><option value='7'>July</option></select>
+ <select name='post[written_on(1i)]'><option>2004</option></select>
+ </p>
+
+ <input type="submit" value="Create">
+ </form>
+
+ This form generates a params[:post] array that can be used directly in a save action:
+
+ class WeblogController < ActionController::Base
+ def create
+ post = Post.create(params[:post])
+ redirect_to :action => "display", :id => post.id
+ end
+ end
+
+ {Learn more}[link:classes/ActionView/Helpers/ActiveRecordHelper.html]
+
+
+* Runs on top of WEBrick, Mongrel, CGI, FCGI, and mod_ruby
+
+
+== 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
+methods:
+
+ require 'action_controller'
+ require 'post'
+
+ class WeblogController < ActionController::Base
+ layout "weblog/layout"
+
+ def index
+ @posts = Post.find(:all)
+ end
+
+ def display
+ @post = Post.find(params[:id])
+ end
+
+ def new
+ @post = Post.new
+ end
+
+ def create
+ @post = Post.create(params[:post])
+ redirect_to :action => "display", :id => @post.id
+ end
+ end
+
+ WeblogController::Base.view_paths = [ File.dirname(__FILE__) ]
+ WeblogController.process_cgi if $0 == __FILE__
+
+The last two lines are responsible for telling ActionController where the
+template files are located and actually running the controller on a new
+request from the web-server (like to be Apache).
+
+And the templates look like this:
+
+ weblog/layout.erb:
+ <html><body>
+ <%= yield %>
+ </body></html>
+
+ weblog/index.erb:
+ <% for post in @posts %>
+ <p><%= link_to(post.title, :action => "display", :id => post.id %></p>
+ <% end %>
+
+ weblog/display.erb:
+ <p>
+ <b><%= post.title %></b><br/>
+ <b><%= post.content %></b>
+ </p>
+
+ weblog/new.erb:
+ <%= form "post" %>
+
+This simple setup will list all the posts in the system on the index page,
+which is called by accessing /weblog/. It uses the form builder for the Active
+Record model to make the new screen, which in turn hands everything over to
+the create action (that's the default target for the form builder when given a
+new model). After creating the post, it'll redirect to the display page using
+an URL such as /weblog/display/5 (where 5 is the id of the post).
+
+
+== Examples
+
+Action Pack ships with three examples that all demonstrate an increasingly
+detailed view of the possibilities. First is blog_controller that is just a
+single file for the whole MVC (but still split into separate parts). Second is
+the debate_controller that uses separate template files and multiple screens.
+Third is the address_book_controller that uses the layout feature to separate
+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
+
+The latest version of Action Pack can be found at
+
+* http://rubyforge.org/project/showfiles.php?group_id=249
+
+Documentation can be found at
+
+* http://api.rubyonrails.com
+
+
+== Installation
+
+You can install Action Pack with the following command.
+
+ % [sudo] ruby install.rb
+
+from its distribution directory.
+
+
+== License
+
+Action Pack is released under the MIT license.
+
+
+== Support
+
+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. \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/RUNNING_UNIT_TESTS b/vendor/rails-2.0.2/actionpack/RUNNING_UNIT_TESTS
new file mode 100644
index 000000000..16b119878
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/RUNNING_UNIT_TESTS
@@ -0,0 +1,24 @@
+== Running with Rake
+
+The easiest way to run the unit tests is through Rake. The default task runs
+the entire test suite for all classes. For more information, checkout the
+full array of rake tasks with "rake -T"
+
+Rake can be found at http://rake.rubyforge.org
+
+== Running by hand
+
+If you only want to run a single test suite, or don't want to bother with Rake,
+you can do so with something like:
+
+ ruby controller/base_tests.rb
+
+== Dependency on ActiveRecord and database setup
+
+Test cases in the test/controller/active_record/ directory depend on having
+activerecord and sqlite installed. If ActiveRecord is not in
+actionpack/../activerecord directory, or the sqlite rubygem is not installed,
+these tests are skipped.
+
+Other tests are runnable from a fresh copy of actionpack without any configuration.
+
diff --git a/vendor/rails-2.0.2/actionpack/Rakefile b/vendor/rails-2.0.2/actionpack/Rakefile
new file mode 100644
index 000000000..1b1d9a176
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/Rakefile
@@ -0,0 +1,153 @@
+require 'rubygems'
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+require 'rake/packagetask'
+require 'rake/gempackagetask'
+require 'rake/contrib/rubyforgepublisher'
+require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version')
+
+PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
+PKG_NAME = 'actionpack'
+PKG_VERSION = ActionPack::VERSION::STRING + PKG_BUILD
+PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
+
+RELEASE_NAME = "REL #{PKG_VERSION}"
+
+RUBY_FORGE_PROJECT = "actionpack"
+RUBY_FORGE_USER = "webster132"
+
+desc "Default Task"
+task :default => [ :test ]
+
+# Run the unit tests
+
+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.pattern = 'test/*/*_test.rb'
+ t.verbose = true
+}
+
+desc 'ActiveRecord Integration Tests'
+Rake::TestTask.new(:test_active_record_integration) do |t|
+ t.libs << "test"
+ t.test_files = Dir.glob("test/activerecord/*_test.rb")
+ t.verbose = true
+end
+
+
+# Genereate the RDoc documentation
+
+Rake::RDocTask.new { |rdoc|
+ rdoc.rdoc_dir = 'doc'
+ rdoc.title = "Action Pack -- On rails from request to response"
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.options << '--charset' << 'utf-8'
+ rdoc.template = "#{ENV['template']}.rb" if ENV['template']
+ if ENV['DOC_FILES']
+ rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/))
+ else
+ rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+ end
+}
+
+# Create compressed packages
+dist_dirs = [ "lib", "test" ]
+
+spec = Gem::Specification.new do |s|
+ s.platform = Gem::Platform::RUBY
+ s.name = PKG_NAME
+ s.version = PKG_VERSION
+ s.summary = "Web-flow and rendering framework putting the VC in MVC."
+ s.description = %q{Eases web-request routing, handling, and response as a half-way front, half-way page controller. Implemented with specific emphasis on enabling easy unit/integration testing that doesn't require a browser.} #'
+
+ s.author = "David Heinemeier Hansson"
+ s.email = "david@loudthinking.com"
+ s.rubyforge_project = "actionpack"
+ s.homepage = "http://www.rubyonrails.org"
+
+ s.has_rdoc = true
+ s.requirements << 'none'
+
+ s.add_dependency('activesupport', '= 2.0.2' + PKG_BUILD)
+
+ s.require_path = 'lib'
+ s.autorequire = 'action_controller'
+
+ s.files = [ "Rakefile", "install.rb", "README", "RUNNING_UNIT_TESTS", "CHANGELOG", "MIT-LICENSE" ]
+ dist_dirs.each do |dir|
+ s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
+ end
+end
+
+Rake::GemPackageTask.new(spec) do |p|
+ p.gem_spec = spec
+ p.need_tar = true
+ p.need_zip = true
+end
+
+task :lines do
+ lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
+
+ for file_name in FileList["lib/**/*.rb"]
+ next if file_name =~ /vendor/
+ f = File.open(file_name)
+
+ while line = f.gets
+ lines += 1
+ next if line =~ /^\s*$/
+ next if line =~ /^\s*#/
+ codelines += 1
+ end
+ puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
+
+ total_lines += lines
+ total_codelines += codelines
+
+ lines, codelines = 0, 0
+ end
+
+ puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
+end
+
+# Publishing ------------------------------------------------------
+
+task :update_scriptaculous do
+ for js in %w( controls dragdrop effects )
+ system("svn export --force http://dev.rubyonrails.org/svn/rails/spinoffs/scriptaculous/src/#{js}.js #{File.dirname(__FILE__)}/lib/action_view/helpers/javascripts/#{js}.js")
+ end
+end
+
+desc "Updates actionpack to the latest version of the javascript spinoffs"
+task :update_js => [ :update_scriptaculous ]
+
+# Publishing ------------------------------------------------------
+
+desc "Publish the API documentation"
+task :pgem => [:package] do
+ Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
+ `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
+end
+
+desc "Publish the API documentation"
+task :pdoc => [:rdoc] do
+ Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ap", "doc").upload
+end
+
+desc "Publish the release files to RubyForge."
+task :release => [ :package ] do
+ require 'rubyforge'
+
+ packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
+
+ rubyforge = RubyForge.new
+ rubyforge.login
+ rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages)
+end
diff --git a/vendor/rails-2.0.2/actionpack/install.rb b/vendor/rails-2.0.2/actionpack/install.rb
new file mode 100644
index 000000000..d3b83c3b0
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/install.rb
@@ -0,0 +1,30 @@
+require 'rbconfig'
+require 'find'
+require 'ftools'
+
+include Config
+
+# this was adapted from rdoc's install.rb by way of Log4r
+
+$sitedir = CONFIG["sitelibdir"]
+unless $sitedir
+ version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
+ $libdir = File.join(CONFIG["libdir"], "ruby", version)
+ $sitedir = $:.find {|x| x =~ /site_ruby/ }
+ if !$sitedir
+ $sitedir = File.join($libdir, "site_ruby")
+ elsif $sitedir !~ Regexp.quote(version)
+ $sitedir = File.join($sitedir, version)
+ end
+end
+
+# the actual gruntwork
+Dir.chdir("lib")
+
+Find.find("action_controller", "action_controller.rb", "action_view", "action_view.rb") { |f|
+ if f[-3..-1] == ".rb"
+ File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
+ else
+ File::makedirs(File.join($sitedir, *f.split(/\//)))
+ end
+} \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller.rb
new file mode 100755
index 000000000..e7a9eba56
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller.rb
@@ -0,0 +1,79 @@
+#--
+# Copyright (c) 2004-2007 David Heinemeier Hansson
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#++
+
+$:.unshift(File.dirname(__FILE__)) unless
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
+
+unless defined?(ActiveSupport)
+ begin
+ $:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib"
+ require 'active_support'
+ rescue LoadError
+ require 'rubygems'
+ gem 'activesupport'
+ end
+end
+
+$:.unshift "#{File.dirname(__FILE__)}/action_controller/vendor/html-scanner"
+
+require 'action_controller/base'
+require 'action_controller/request'
+require 'action_controller/rescue'
+require 'action_controller/benchmarking'
+require 'action_controller/flash'
+require 'action_controller/filters'
+require 'action_controller/layout'
+require 'action_controller/mime_responds'
+require 'action_controller/helpers'
+require 'action_controller/cookies'
+require 'action_controller/cgi_process'
+require 'action_controller/caching'
+require 'action_controller/verification'
+require 'action_controller/streaming'
+require 'action_controller/session_management'
+require 'action_controller/http_authentication'
+require 'action_controller/components'
+require 'action_controller/record_identifier'
+require 'action_controller/request_forgery_protection'
+
+require 'action_view'
+ActionController::Base.template_class = ActionView::Base
+
+ActionController::Base.class_eval do
+ include ActionController::Flash
+ include ActionController::Filters
+ include ActionController::Layout
+ include ActionController::Benchmarking
+ include ActionController::Rescue
+ include ActionController::MimeResponds
+ include ActionController::Helpers
+ include ActionController::Cookies
+ include ActionController::Caching
+ include ActionController::Verification
+ include ActionController::Streaming
+ include ActionController::SessionManagement
+ include ActionController::HttpAuthentication::Basic::ControllerMethods
+ include ActionController::Components
+ include ActionController::RecordIdentifier
+ include ActionController::RequestForgeryProtection
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions.rb
new file mode 100644
index 000000000..5b9a2b71f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions.rb
@@ -0,0 +1,69 @@
+require 'test/unit/assertions'
+
+module ActionController #:nodoc:
+ # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
+ # can be used against. These collections are:
+ #
+ # * assigns: Instance variables assigned in the action that are available for the view.
+ # * session: Objects being saved in the session.
+ # * flash: The flash objects currently in the session.
+ # * cookies: Cookies being sent to the user on this request.
+ #
+ # These collections can be used just like any other hash:
+ #
+ # assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
+ # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
+ # assert flash.empty? # makes sure that there's nothing in the flash
+ #
+ # For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
+ # appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
+ # So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
+ #
+ # On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
+ #
+ # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
+ # action call which can then be asserted against.
+ #
+ # == Manipulating the request collections
+ #
+ # The collections described above link to the response, so you can test if what the actions were expected to do happened. But
+ # sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions
+ # and cookies, though. For sessions, you just do:
+ #
+ # @request.session[:key] = "value"
+ #
+ # For cookies, you need to manually create the cookie, like this:
+ #
+ # @request.cookies["key"] = CGI::Cookie.new("key", "value")
+ #
+ # == Testing named routes
+ #
+ # If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
+ # Example:
+ #
+ # assert_redirected_to page_url(:title => 'foo')
+ module Assertions
+ def self.included(klass)
+ %w(response selector tag dom routing model).each do |kind|
+ require "action_controller/assertions/#{kind}_assertions"
+ klass.module_eval { include const_get("#{kind.camelize}Assertions") }
+ end
+ end
+
+ def clean_backtrace(&block)
+ yield
+ rescue Test::Unit::AssertionFailedError => error
+ framework_path = Regexp.new(File.expand_path("#{File.dirname(__FILE__)}/assertions"))
+ error.backtrace.reject! { |line| File.expand_path(line) =~ framework_path }
+ raise
+ end
+ end
+end
+
+module Test #:nodoc:
+ module Unit #:nodoc:
+ class TestCase #:nodoc:
+ include ActionController::Assertions
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/dom_assertions.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/dom_assertions.rb
new file mode 100644
index 000000000..5ffe5f188
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/dom_assertions.rb
@@ -0,0 +1,39 @@
+module ActionController
+ module Assertions
+ module DomAssertions
+ # Test two HTML strings for equivalency (e.g., identical up to reordering of attributes)
+ #
+ # ==== Examples
+ #
+ # # assert that the referenced method generates the appropriate HTML string
+ # assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com")
+ #
+ def assert_dom_equal(expected, actual, message = "")
+ clean_backtrace do
+ expected_dom = HTML::Document.new(expected).root
+ actual_dom = HTML::Document.new(actual).root
+ full_message = build_message(message, "<?> expected to be == to\n<?>.", expected_dom.to_s, actual_dom.to_s)
+
+ assert_block(full_message) { expected_dom == actual_dom }
+ end
+ end
+
+ # The negated form of +assert_dom_equivalent+.
+ #
+ # ==== Examples
+ #
+ # # assert that the referenced method does not generate the specified HTML string
+ # assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com")
+ #
+ def assert_dom_not_equal(expected, actual, message = "")
+ clean_backtrace do
+ expected_dom = HTML::Document.new(expected).root
+ actual_dom = HTML::Document.new(actual).root
+ full_message = build_message(message, "<?> expected to be != to\n<?>.", expected_dom.to_s, actual_dom.to_s)
+
+ assert_block(full_message) { expected_dom != actual_dom }
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/model_assertions.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/model_assertions.rb
new file mode 100644
index 000000000..0b4313055
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/model_assertions.rb
@@ -0,0 +1,19 @@
+module ActionController
+ module Assertions
+ module ModelAssertions
+ # Ensures that the passed record is valid by ActiveRecord standards and returns any error messages if it is not.
+ #
+ # ==== Examples
+ #
+ # # assert that a newly created record is valid
+ # model = Model.new
+ # assert_valid(model)
+ #
+ def assert_valid(record)
+ clean_backtrace do
+ assert record.valid?, record.errors.full_messages.join("\n")
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/response_assertions.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/response_assertions.rb
new file mode 100644
index 000000000..42bd7fb3d
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/response_assertions.rb
@@ -0,0 +1,166 @@
+require 'rexml/document'
+require 'html/document'
+
+module ActionController
+ module Assertions
+ # A small suite of assertions that test responses from Rails applications.
+ module ResponseAssertions
+ # Asserts that the response is one of the following types:
+ #
+ # * <tt>:success</tt> - Status code was 200
+ # * <tt>:redirect</tt> - Status code was in the 300-399 range
+ # * <tt>:missing</tt> - Status code was 404
+ # * <tt>:error</tt> - Status code was in the 500-599 range
+ #
+ # You can also pass an explicit status number like assert_response(501)
+ # or its symbolic equivalent assert_response(:not_implemented).
+ # See ActionController::StatusCodes for a full list.
+ #
+ # ==== Examples
+ #
+ # # assert that the response was a redirection
+ # assert_response :redirect
+ #
+ # # assert that the response code was status code 401 (unauthorized)
+ # assert_response 401
+ #
+ def assert_response(type, message = nil)
+ clean_backtrace do
+ if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?")
+ assert_block("") { true } # to count the assertion
+ elsif type.is_a?(Fixnum) && @response.response_code == type
+ assert_block("") { true } # to count the assertion
+ elsif type.is_a?(Symbol) && @response.response_code == ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE[type]
+ assert_block("") { true } # to count the assertion
+ else
+ assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
+ end
+ end
+ end
+
+ # Assert that the redirection options passed in match those of the redirect called in the latest action.
+ # This match can be partial, such that assert_redirected_to(:controller => "weblog") will also
+ # match the redirection of redirect_to(:controller => "weblog", :action => "show") and so on.
+ #
+ # ==== Examples
+ #
+ # # assert that the redirection was to the "index" action on the WeblogController
+ # assert_redirected_to :controller => "weblog", :action => "index"
+ #
+ # # assert that the redirection was to the named route login_url
+ # assert_redirected_to login_url
+ #
+ def assert_redirected_to(options = {}, message=nil)
+ clean_backtrace do
+ assert_response(:redirect, message)
+ return true if options == @response.redirected_to
+ ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
+
+ begin
+ url = {}
+ original = { :expected => options, :actual => @response.redirected_to.is_a?(Symbol) ? @response.redirected_to : @response.redirected_to.dup }
+ original.each do |key, value|
+ if value.is_a?(Symbol)
+ value = @controller.respond_to?(value, true) ? @controller.send(value) : @controller.send("hash_for_#{value}_url")
+ end
+
+ unless value.is_a?(Hash)
+ request = case value
+ when NilClass then nil
+ when /^\w+:\/\// then recognized_request_for(%r{^(\w+://.*?(/|$|\?))(.*)$} =~ value ? $3 : nil)
+ else recognized_request_for(value)
+ end
+ value = request.path_parameters if request
+ end
+
+ if value.is_a?(Hash) # stringify 2 levels of hash keys
+ if name = value.delete(:use_route)
+ route = ActionController::Routing::Routes.named_routes[name]
+ value.update(route.parameter_shell)
+ end
+
+ value.stringify_keys!
+ value.values.select { |v| v.is_a?(Hash) }.collect { |v| v.stringify_keys! }
+ if key == :expected && value['controller'] == @controller.controller_name && original[:actual].is_a?(Hash)
+ original[:actual].stringify_keys!
+ value.delete('controller') if original[:actual]['controller'].nil? || original[:actual]['controller'] == value['controller']
+ end
+ end
+
+ if value.respond_to?(:[]) && value['controller']
+ value['controller'] = value['controller'].to_s
+ if key == :actual && value['controller'].first != '/' && !value['controller'].include?('/')
+ new_controller_path = ActionController::Routing.controller_relative_to(value['controller'], @controller.class.controller_path)
+ value['controller'] = new_controller_path if value['controller'] != new_controller_path && ActionController::Routing.possible_controllers.include?(new_controller_path)
+ end
+ value['controller'] = value['controller'][1..-1] if value['controller'].first == '/' # strip leading hash
+ end
+ url[key] = value
+ end
+
+ @response_diff = url[:actual].diff(url[:expected]) if url[:actual]
+ msg = build_message(message, "expected a redirect to <?>, found one to <?>, a difference of <?> ", url[:expected], url[:actual], @response_diff)
+
+ assert_block(msg) do
+ url[:expected].keys.all? do |k|
+ if k == :controller then url[:expected][k] == ActionController::Routing.controller_relative_to(url[:actual][k], @controller.class.controller_path)
+ else parameterize(url[:expected][k]) == parameterize(url[:actual][k])
+ end
+ end
+ end
+ rescue ActionController::RoutingError # routing failed us, so match the strings only.
+ msg = build_message(message, "expected a redirect to <?>, found one to <?>", options, @response.redirect_url)
+ url_regexp = %r{^(\w+://.*?(/|$|\?))(.*)$}
+ eurl, epath, url, path = [options, @response.redirect_url].collect do |url|
+ u, p = (url_regexp =~ url) ? [$1, $3] : [nil, url]
+ [u, (p.first == '/') ? p : '/' + p]
+ end.flatten
+
+ assert_equal(eurl, url, msg) if eurl && url
+ assert_equal(epath, path, msg) if epath && path
+ end
+ end
+ end
+
+ # Asserts that the request was rendered with the appropriate template file.
+ #
+ # ==== Examples
+ #
+ # # assert that the "new" view template was rendered
+ # assert_template "new"
+ #
+ def assert_template(expected = nil, message=nil)
+ clean_backtrace do
+ rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file
+ msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
+ assert_block(msg) do
+ if expected.nil?
+ !@response.rendered_with_file?
+ else
+ expected == rendered
+ end
+ end
+ end
+ end
+
+ private
+ # Recognizes the route for a given path.
+ def recognized_request_for(path, request_method = nil)
+ path = "/#{path}" unless path.first == '/'
+
+ # Assume given controller
+ request = ActionController::TestRequest.new({}, {}, nil)
+ request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
+ request.path = path
+
+ ActionController::Routing::Routes.recognize(request)
+ request
+ end
+
+ # Proxy to to_param if the object will respond to it.
+ def parameterize(value)
+ value.respond_to?(:to_param) ? value.to_param : value
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/routing_assertions.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/routing_assertions.rb
new file mode 100644
index 000000000..9bff28324
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/routing_assertions.rb
@@ -0,0 +1,143 @@
+module ActionController
+ module Assertions
+ # Suite of assertions to test routes generated by Rails and the handling of requests made to them.
+ module RoutingAssertions
+ # Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
+ # match +path+. Basically, it asserts that Rails recognizes the route given by +expected_options+.
+ #
+ # Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes
+ # requiring a specific HTTP method. The hash should contain a :path with the incoming request path
+ # and a :method containing the required HTTP verb.
+ #
+ # # assert that POSTing to /items will call the create action on ItemsController
+ # assert_recognizes({:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post})
+ #
+ # You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
+ # to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
+ # extras argument, appending the query string on the path directly will not work. For example:
+ #
+ # # assert that a path of '/items/list/1?view=print' returns the correct options
+ # assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" })
+ #
+ # The +message+ parameter allows you to pass in an error message that is displayed upon failure.
+ #
+ # ==== Examples
+ # # Check the default route (i.e., the index action)
+ # assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
+ #
+ # # Test a specific action
+ # assert_recognizes({:controller => 'items', :action => 'list'}, 'items/list')
+ #
+ # # Test an action with a parameter
+ # assert_recognizes({:controller => 'items', :action => 'destroy', :id => '1'}, 'items/destroy/1')
+ #
+ # # Test a custom route
+ # assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1')
+ #
+ # # Check a Simply RESTful generated route
+ # assert_recognizes(list_items_url, 'items/list')
+ def assert_recognizes(expected_options, path, extras={}, message=nil)
+ if path.is_a? Hash
+ request_method = path[:method]
+ path = path[:path]
+ else
+ request_method = nil
+ end
+
+ clean_backtrace do
+ ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
+ request = recognized_request_for(path, request_method)
+
+ expected_options = expected_options.clone
+ extras.each_key { |key| expected_options.delete key } unless extras.nil?
+
+ expected_options.stringify_keys!
+ routing_diff = expected_options.diff(request.path_parameters)
+ msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>",
+ request.path_parameters, expected_options, expected_options.diff(request.path_parameters))
+ assert_block(msg) { request.path_parameters == expected_options }
+ end
+ end
+
+ # Asserts that the provided options can be used to generate the provided path. This is the inverse of #assert_recognizes.
+ # The +extras+ parameter is used to tell the request the names and values of additional request parameters that would be in
+ # a query string. The +message+ parameter allows you to specify a custom error message for assertion failures.
+ #
+ # The +defaults+ parameter is unused.
+ #
+ # ==== Examples
+ # # Asserts that the default action is generated for a route with no action
+ # assert_generates("/items", :controller => "items", :action => "index")
+ #
+ # # Tests that the list action is properly routed
+ # assert_generates("/items/list", :controller => "items", :action => "list")
+ #
+ # # Tests the generation of a route with a parameter
+ # assert_generates("/items/list/1", { :controller => "items", :action => "list", :id => "1" })
+ #
+ # # Asserts that the generated route gives us our custom route
+ # assert_generates "changesets/12", { :controller => 'scm', :action => 'show_diff', :revision => "12" }
+ def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
+ clean_backtrace do
+ expected_path = "/#{expected_path}" unless expected_path[0] == ?/
+ # Load routes.rb if it hasn't been loaded.
+ ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
+
+ generated_path, extra_keys = ActionController::Routing::Routes.generate_extras(options, defaults)
+ found_extras = options.reject {|k, v| ! extra_keys.include? k}
+
+ msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
+ assert_block(msg) { found_extras == extras }
+
+ msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
+ expected_path)
+ assert_block(msg) { expected_path == generated_path }
+ end
+ end
+
+ # Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
+ # <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines #assert_recognizes
+ # and #assert_generates into one step.
+ #
+ # The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
+ # +message+ parameter allows you to specify a custom error message to display upon failure.
+ #
+ # ==== Examples
+ # # Assert a basic route: a controller with the default action (index)
+ # assert_routing('/home', :controller => 'home', :action => 'index')
+ #
+ # # Test a route generated with a specific controller, action, and parameter (id)
+ # assert_routing('/entries/show/23', :controller => 'entries', :action => 'show', id => 23)
+ #
+ # # Assert a basic route (controller + default action), with an error message if it fails
+ # assert_routing('/store', { :controller => 'store', :action => 'index' }, {}, {}, 'Route for store index not generated properly')
+ #
+ # # Tests a route, providing a defaults hash
+ # assert_routing 'controller/action/9', {:id => "9", :item => "square"}, {:controller => "controller", :action => "action"}, {}, {:item => "square"}
+ def assert_routing(path, options, defaults={}, extras={}, message=nil)
+ assert_recognizes(options, path, extras, message)
+
+ controller, default_controller = options[:controller], defaults[:controller]
+ if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
+ options[:controller] = "/#{controller}"
+ end
+
+ assert_generates(path, options, defaults, extras, message)
+ end
+
+ private
+ # Recognizes the route for a given path.
+ def recognized_request_for(path, request_method = nil)
+ path = "/#{path}" unless path.first == '/'
+
+ # Assume given controller
+ request = ActionController::TestRequest.new({}, {}, nil)
+ request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
+ request.path = path
+
+ ActionController::Routing::Routes.recognize(request)
+ request
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/selector_assertions.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/selector_assertions.rb
new file mode 100644
index 000000000..573405c0f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/selector_assertions.rb
@@ -0,0 +1,640 @@
+#--
+# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
+# Under MIT and/or CC By license.
+#++
+
+require 'rexml/document'
+require 'html/document'
+
+module ActionController
+ module Assertions
+ unless const_defined?(:NO_STRIP)
+ NO_STRIP = %w{pre script style textarea}
+ end
+
+ # Adds the #assert_select method for use in Rails functional
+ # test cases, which can be used to make assertions on the response HTML of a controller
+ # action. You can also call #assert_select within another #assert_select to
+ # make assertions on elements selected by the enclosing assertion.
+ #
+ # Use #css_select to select elements without making an assertions, either
+ # from the response HTML or elements selected by the enclosing assertion.
+ #
+ # In addition to HTML responses, you can make the following assertions:
+ # * #assert_select_rjs -- Assertions on HTML content of RJS update and
+ # insertion operations.
+ # * #assert_select_encoded -- Assertions on HTML encoded inside XML,
+ # for example for dealing with feed item descriptions.
+ # * #assert_select_email -- Assertions on the HTML body of an e-mail.
+ #
+ # Also see HTML::Selector to learn how to use selectors.
+ module SelectorAssertions
+ # :call-seq:
+ # css_select(selector) => array
+ # css_select(element, selector) => array
+ #
+ # Select and return all matching elements.
+ #
+ # If called with a single argument, uses that argument as a selector
+ # to match all elements of the current page. Returns an empty array
+ # if no match is found.
+ #
+ # If called with two arguments, uses the first argument as the base
+ # element and the second argument as the selector. Attempts to match the
+ # base element and any of its children. Returns an empty array if no
+ # match is found.
+ #
+ # The selector may be a CSS selector expression (+String+), an expression
+ # with substitution values (+Array+) or an HTML::Selector object.
+ #
+ # ==== Examples
+ # # Selects all div tags
+ # divs = css_select("div")
+ #
+ # # Selects all paragraph tags and does something interesting
+ # pars = css_select("p")
+ # pars.each do |par|
+ # # Do something fun with paragraphs here...
+ # end
+ #
+ # # Selects all list items in unordered lists
+ # items = css_select("ul>li")
+ #
+ # # Selects all form tags and then all inputs inside the form
+ # forms = css_select("form")
+ # forms.each do |form|
+ # inputs = css_select(form, "input")
+ # ...
+ # end
+ #
+ def css_select(*args)
+ # See assert_select to understand what's going on here.
+ arg = args.shift
+
+ if arg.is_a?(HTML::Node)
+ root = arg
+ arg = args.shift
+ elsif arg == nil
+ raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?"
+ elsif @selected
+ matches = []
+
+ @selected.each do |selected|
+ subset = css_select(selected, HTML::Selector.new(arg.dup, args.dup))
+ subset.each do |match|
+ matches << match unless matches.any? { |m| m.equal?(match) }
+ end
+ end
+
+ return matches
+ else
+ root = response_from_page_or_rjs
+ end
+
+ case arg
+ when String
+ selector = HTML::Selector.new(arg, args)
+ when Array
+ selector = HTML::Selector.new(*arg)
+ when HTML::Selector
+ selector = arg
+ else raise ArgumentError, "Expecting a selector as the first argument"
+ end
+
+ selector.select(root)
+ end
+
+ # :call-seq:
+ # assert_select(selector, equality?, message?)
+ # assert_select(element, selector, equality?, message?)
+ #
+ # An assertion that selects elements and makes one or more equality tests.
+ #
+ # If the first argument is an element, selects all matching elements
+ # starting from (and including) that element and all its children in
+ # depth-first order.
+ #
+ # If no element if specified, calling #assert_select will select from the
+ # response HTML. Calling #assert_select inside an #assert_select block will
+ # run the assertion for each element selected by the enclosing assertion.
+ #
+ # ==== Example
+ # assert_select "ol>li" do |elements|
+ # elements.each do |element|
+ # assert_select element, "li"
+ # end
+ # end
+ #
+ # Or for short:
+ # assert_select "ol>li" do
+ # assert_select "li"
+ # end
+ #
+ # The selector may be a CSS selector expression (+String+), an expression
+ # with substitution values, or an HTML::Selector object.
+ #
+ # === Equality Tests
+ #
+ # The equality test may be one of the following:
+ # * <tt>true</tt> -- Assertion is true if at least one element selected.
+ # * <tt>false</tt> -- Assertion is true if no element selected.
+ # * <tt>String/Regexp</tt> -- Assertion is true if the text value of at least
+ # one element matches the string or regular expression.
+ # * <tt>Integer</tt> -- Assertion is true if exactly that number of
+ # elements are selected.
+ # * <tt>Range</tt> -- Assertion is true if the number of selected
+ # elements fit the range.
+ # If no equality test specified, the assertion is true if at least one
+ # element selected.
+ #
+ # To perform more than one equality tests, use a hash with the following keys:
+ # * <tt>:text</tt> -- Narrow the selection to elements that have this text
+ # value (string or regexp).
+ # * <tt>:html</tt> -- Narrow the selection to elements that have this HTML
+ # content (string or regexp).
+ # * <tt>:count</tt> -- Assertion is true if the number of selected elements
+ # is equal to this value.
+ # * <tt>:minimum</tt> -- Assertion is true if the number of selected
+ # elements is at least this value.
+ # * <tt>:maximum</tt> -- Assertion is true if the number of selected
+ # elements is at most this value.
+ #
+ # If the method is called with a block, once all equality tests are
+ # evaluated the block is called with an array of all matched elements.
+ #
+ # ==== Examples
+ #
+ # # At least one form element
+ # assert_select "form"
+ #
+ # # Form element includes four input fields
+ # assert_select "form input", 4
+ #
+ # # Page title is "Welcome"
+ # assert_select "title", "Welcome"
+ #
+ # # Page title is "Welcome" and there is only one title element
+ # assert_select "title", {:count=>1, :text=>"Welcome"},
+ # "Wrong title or more than one title element"
+ #
+ # # Page contains no forms
+ # assert_select "form", false, "This page must contain no forms"
+ #
+ # # Test the content and style
+ # assert_select "body div.header ul.menu"
+ #
+ # # Use substitution values
+ # assert_select "ol>li#?", /item-\d+/
+ #
+ # # All input fields in the form have a name
+ # assert_select "form input" do
+ # assert_select "[name=?]", /.+/ # Not empty
+ # end
+ def assert_select(*args, &block)
+ # Start with optional element followed by mandatory selector.
+ arg = args.shift
+
+ if arg.is_a?(HTML::Node)
+ # First argument is a node (tag or text, but also HTML root),
+ # so we know what we're selecting from.
+ root = arg
+ arg = args.shift
+ elsif arg == nil
+ # This usually happens when passing a node/element that
+ # happens to be nil.
+ raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?"
+ elsif @selected
+ root = HTML::Node.new(nil)
+ root.children.concat @selected
+ else
+ # Otherwise just operate on the response document.
+ root = response_from_page_or_rjs
+ end
+
+ # First or second argument is the selector: string and we pass
+ # all remaining arguments. Array and we pass the argument. Also
+ # accepts selector itself.
+ case arg
+ when String
+ selector = HTML::Selector.new(arg, args)
+ when Array
+ selector = HTML::Selector.new(*arg)
+ when HTML::Selector
+ selector = arg
+ else raise ArgumentError, "Expecting a selector as the first argument"
+ end
+
+ # Next argument is used for equality tests.
+ equals = {}
+ case arg = args.shift
+ when Hash
+ equals = arg
+ when String, Regexp
+ equals[:text] = arg
+ when Integer
+ equals[:count] = arg
+ when Range
+ equals[:minimum] = arg.begin
+ equals[:maximum] = arg.end
+ when FalseClass
+ equals[:count] = 0
+ when NilClass, TrueClass
+ equals[:minimum] = 1
+ else raise ArgumentError, "I don't understand what you're trying to match"
+ end
+
+ # By default we're looking for at least one match.
+ if equals[:count]
+ equals[:minimum] = equals[:maximum] = equals[:count]
+ else
+ equals[:minimum] = 1 unless equals[:minimum]
+ end
+
+ # Last argument is the message we use if the assertion fails.
+ message = args.shift
+ #- message = "No match made with selector #{selector.inspect}" unless message
+ if args.shift
+ raise ArgumentError, "Not expecting that last argument, you either have too many arguments, or they're the wrong type"
+ end
+
+ matches = selector.select(root)
+ # If text/html, narrow down to those elements that match it.
+ content_mismatch = nil
+ if match_with = equals[:text]
+ matches.delete_if do |match|
+ text = ""
+ stack = match.children.reverse
+ while node = stack.pop
+ if node.tag?
+ stack.concat node.children.reverse
+ else
+ text << node.content
+ end
+ end
+ text.strip! unless NO_STRIP.include?(match.name)
+ unless match_with.is_a?(Regexp) ? (text =~ match_with) : (text == match_with.to_s)
+ content_mismatch ||= build_message(message, "<?> expected but was\n<?>.", match_with, text)
+ true
+ end
+ end
+ elsif match_with = equals[:html]
+ matches.delete_if do |match|
+ html = match.children.map(&:to_s).join
+ html.strip! unless NO_STRIP.include?(match.name)
+ unless match_with.is_a?(Regexp) ? (html =~ match_with) : (html == match_with.to_s)
+ content_mismatch ||= build_message(message, "<?> expected but was\n<?>.", match_with, html)
+ true
+ end
+ end
+ end
+ # Expecting foo found bar element only if found zero, not if
+ # found one but expecting two.
+ message ||= content_mismatch if matches.empty?
+ # Test minimum/maximum occurrence.
+ min, max = equals[:minimum], equals[:maximum]
+ message = message || %(Expected #{count_description(min, max)} matching "#{selector.to_s}", found #{matches.size}.)
+ assert matches.size >= min, message if min
+ assert matches.size <= max, message if max
+
+ # If a block is given call that block. Set @selected to allow
+ # nested assert_select, which can be nested several levels deep.
+ if block_given? && !matches.empty?
+ begin
+ in_scope, @selected = @selected, matches
+ yield matches
+ ensure
+ @selected = in_scope
+ end
+ end
+
+ # Returns all matches elements.
+ matches
+ end
+
+ def count_description(min, max) #:nodoc:
+ pluralize = lambda {|word, quantity| word << (quantity == 1 ? '' : 's')}
+
+ if min && max && (max != min)
+ "between #{min} and #{max} elements"
+ elsif min && !(min == 1 && max == 1)
+ "at least #{min} #{pluralize['element', min]}"
+ elsif max
+ "at most #{max} #{pluralize['element', max]}"
+ end
+ end
+
+ # :call-seq:
+ # assert_select_rjs(id?) { |elements| ... }
+ # assert_select_rjs(statement, id?) { |elements| ... }
+ # assert_select_rjs(:insert, position, id?) { |elements| ... }
+ #
+ # Selects content from the RJS response.
+ #
+ # === Narrowing down
+ #
+ # With no arguments, asserts that one or more elements are updated or
+ # inserted by RJS statements.
+ #
+ # Use the +id+ argument to narrow down the assertion to only statements
+ # that update or insert an element with that identifier.
+ #
+ # Use the first argument to narrow down assertions to only statements
+ # of that type. Possible values are <tt>:replace</tt>, <tt>:replace_html</tt>,
+ # <tt>:show</tt>, <tt>:hide</tt>, <tt>:toggle</tt>, <tt>:remove</tt> and
+ # <tt>:insert_html</tt>.
+ #
+ # Use the argument <tt>:insert</tt> followed by an insertion position to narrow
+ # down the assertion to only statements that insert elements in that
+ # position. Possible values are <tt>:top</tt>, <tt>:bottom</tt>, <tt>:before</tt>
+ # and <tt>:after</tt>.
+ #
+ # Using the <tt>:remove</tt> statement, you will be able to pass a block, but it will
+ # be ignored as there is no HTML passed for this statement.
+ #
+ # === Using blocks
+ #
+ # Without a block, #assert_select_rjs merely asserts that the response
+ # contains one or more RJS statements that replace or update content.
+ #
+ # With a block, #assert_select_rjs also selects all elements used in
+ # these statements and passes them to the block. Nested assertions are
+ # supported.
+ #
+ # Calling #assert_select_rjs with no arguments and using nested asserts
+ # asserts that the HTML content is returned by one or more RJS statements.
+ # Using #assert_select directly makes the same assertion on the content,
+ # but without distinguishing whether the content is returned in an HTML
+ # or JavaScript.
+ #
+ # ==== Examples
+ #
+ # # Replacing the element foo.
+ # # page.replace 'foo', ...
+ # assert_select_rjs :replace, "foo"
+ #
+ # # Replacing with the chained RJS proxy.
+ # # page[:foo].replace ...
+ # assert_select_rjs :chained_replace, 'foo'
+ #
+ # # Inserting into the element bar, top position.
+ # assert_select_rjs :insert, :top, "bar"
+ #
+ # # Remove the element bar
+ # assert_select_rjs :remove, "bar"
+ #
+ # # Changing the element foo, with an image.
+ # assert_select_rjs "foo" do
+ # assert_select "img[src=/images/logo.gif""
+ # end
+ #
+ # # RJS inserts or updates a list with four items.
+ # assert_select_rjs do
+ # assert_select "ol>li", 4
+ # end
+ #
+ # # The same, but shorter.
+ # assert_select "ol>li", 4
+ def assert_select_rjs(*args, &block)
+ rjs_type = nil
+ arg = args.shift
+
+ # If the first argument is a symbol, it's the type of RJS statement we're looking
+ # for (update, replace, insertion, etc). Otherwise, we're looking for just about
+ # any RJS statement.
+ if arg.is_a?(Symbol)
+ rjs_type = arg
+
+ if rjs_type == :insert
+ arg = args.shift
+ insertion = "insert_#{arg}".to_sym
+ raise ArgumentError, "Unknown RJS insertion type #{arg}" unless RJS_STATEMENTS[insertion]
+ statement = "(#{RJS_STATEMENTS[insertion]})"
+ else
+ raise ArgumentError, "Unknown RJS statement type #{rjs_type}" unless RJS_STATEMENTS[rjs_type]
+ statement = "(#{RJS_STATEMENTS[rjs_type]})"
+ end
+ arg = args.shift
+ else
+ statement = "#{RJS_STATEMENTS[:any]}"
+ end
+
+ # Next argument we're looking for is the element identifier. If missing, we pick
+ # any element.
+ if arg.is_a?(String)
+ id = Regexp.quote(arg)
+ arg = args.shift
+ else
+ id = "[^\"]*"
+ end
+
+ pattern =
+ case rjs_type
+ when :chained_replace, :chained_replace_html
+ Regexp.new("\\$\\(\"#{id}\"\\)#{statement}\\(#{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE)
+ when :remove, :show, :hide, :toggle
+ Regexp.new("#{statement}\\(\"#{id}\"\\)")
+ else
+ Regexp.new("#{statement}\\(\"#{id}\", #{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE)
+ end
+
+ # Duplicate the body since the next step involves destroying it.
+ matches = nil
+ case rjs_type
+ when :remove, :show, :hide, :toggle
+ matches = @response.body.match(pattern)
+ else
+ @response.body.gsub(pattern) do |match|
+ html = unescape_rjs($2)
+ matches ||= []
+ matches.concat HTML::Document.new(html).root.children.select { |n| n.tag? }
+ ""
+ end
+ end
+
+ if matches
+ assert_block("") { true } # to count the assertion
+ if block_given? && !([:remove, :show, :hide, :toggle].include? rjs_type)
+ begin
+ in_scope, @selected = @selected, matches
+ yield matches
+ ensure
+ @selected = in_scope
+ end
+ end
+ matches
+ else
+ # RJS statement not found.
+ flunk args.shift || "No RJS statement that replaces or inserts HTML content."
+ end
+ end
+
+ # :call-seq:
+ # assert_select_encoded(element?) { |elements| ... }
+ #
+ # Extracts the content of an element, treats it as encoded HTML and runs
+ # nested assertion on it.
+ #
+ # You typically call this method within another assertion to operate on
+ # all currently selected elements. You can also pass an element or array
+ # of elements.
+ #
+ # The content of each element is un-encoded, and wrapped in the root
+ # element +encoded+. It then calls the block with all un-encoded elements.
+ #
+ # ==== Examples
+ # # Selects all bold tags from within the title of an ATOM feed's entries (perhaps to nab a section name prefix)
+ # assert_select_feed :atom, 1.0 do
+ # # Select each entry item and then the title item
+ # assert_select "entry>title" do
+ # # Run assertions on the encoded title elements
+ # assert_select_encoded do
+ # assert_select "b"
+ # end
+ # end
+ # end
+ #
+ #
+ # # Selects all paragraph tags from within the description of an RSS feed
+ # assert_select_feed :rss, 2.0 do
+ # # Select description element of each feed item.
+ # assert_select "channel>item>description" do
+ # # Run assertions on the encoded elements.
+ # assert_select_encoded do
+ # assert_select "p"
+ # end
+ # end
+ # end
+ def assert_select_encoded(element = nil, &block)
+ case element
+ when Array
+ elements = element
+ when HTML::Node
+ elements = [element]
+ when nil
+ unless elements = @selected
+ raise ArgumentError, "First argument is optional, but must be called from a nested assert_select"
+ end
+ else
+ raise ArgumentError, "Argument is optional, and may be node or array of nodes"
+ end
+
+ fix_content = lambda do |node|
+ # Gets around a bug in the Rails 1.1 HTML parser.
+ node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { CGI.escapeHTML($1) }
+ end
+
+ selected = elements.map do |element|
+ text = element.children.select{ |c| not c.tag? }.map{ |c| fix_content[c] }.join
+ root = HTML::Document.new(CGI.unescapeHTML("<encoded>#{text}</encoded>")).root
+ css_select(root, "encoded:root", &block)[0]
+ end
+
+ begin
+ old_selected, @selected = @selected, selected
+ assert_select ":root", &block
+ ensure
+ @selected = old_selected
+ end
+ end
+
+ # :call-seq:
+ # assert_select_email { }
+ #
+ # Extracts the body of an email and runs nested assertions on it.
+ #
+ # You must enable deliveries for this assertion to work, use:
+ # ActionMailer::Base.perform_deliveries = true
+ #
+ # ==== Examples
+ #
+ # assert_select_email do
+ # assert_select "h1", "Email alert"
+ # end
+ #
+ # assert_select_email do
+ # items = assert_select "ol>li"
+ # items.each do
+ # # Work with items here...
+ # end
+ # end
+ #
+ def assert_select_email(&block)
+ deliveries = ActionMailer::Base.deliveries
+ assert !deliveries.empty?, "No e-mail in delivery list"
+
+ for delivery in deliveries
+ for part in delivery.parts
+ if part["Content-Type"].to_s =~ /^text\/html\W/
+ root = HTML::Document.new(part.body).root
+ assert_select root, ":root", &block
+ end
+ end
+ end
+ end
+
+ protected
+ unless const_defined?(:RJS_STATEMENTS)
+ RJS_STATEMENTS = {
+ :replace => /Element\.replace/,
+ :replace_html => /Element\.update/,
+ :chained_replace => /\.replace/,
+ :chained_replace_html => /\.update/,
+ :remove => /Element\.remove/,
+ :show => /Element\.show/,
+ :hide => /Element\.hide/,
+ :toggle => /Element\.toggle/
+ }
+ RJS_INSERTIONS = [:top, :bottom, :before, :after]
+ RJS_INSERTIONS.each do |insertion|
+ RJS_STATEMENTS["insert_#{insertion}".to_sym] = Regexp.new(Regexp.quote("new Insertion.#{insertion.to_s.camelize}"))
+ end
+ RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})")
+ RJS_STATEMENTS[:insert_html] = Regexp.new(RJS_INSERTIONS.collect do |insertion|
+ Regexp.quote("new Insertion.#{insertion.to_s.camelize}")
+ end.join('|'))
+ RJS_PATTERN_HTML = /"((\\"|[^"])*)"/
+ RJS_PATTERN_EVERYTHING = Regexp.new("#{RJS_STATEMENTS[:any]}\\(\"([^\"]*)\", #{RJS_PATTERN_HTML}\\)",
+ Regexp::MULTILINE)
+ RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/
+ end
+
+ # #assert_select and #css_select call this to obtain the content in the HTML
+ # page, or from all the RJS statements, depending on the type of response.
+ def response_from_page_or_rjs()
+ content_type = @response.content_type
+
+ if content_type && content_type =~ /text\/javascript/
+ body = @response.body.dup
+ root = HTML::Node.new(nil)
+
+ while true
+ next if body.sub!(RJS_PATTERN_EVERYTHING) do |match|
+ html = unescape_rjs($3)
+ matches = HTML::Document.new(html).root.children.select { |n| n.tag? }
+ root.children.concat matches
+ ""
+ end
+ break
+ end
+
+ root
+ else
+ html_document.root
+ end
+ end
+
+ # Unescapes a RJS string.
+ def unescape_rjs(rjs_string)
+ # RJS encodes double quotes and line breaks.
+ unescaped= rjs_string.gsub('\"', '"')
+ unescaped.gsub!(/\\\//, '/')
+ unescaped.gsub!('\n', "\n")
+ unescaped.gsub!('\076', '>')
+ unescaped.gsub!('\074', '<')
+ # RJS encodes non-ascii characters.
+ unescaped.gsub!(RJS_PATTERN_UNICODE_ESCAPED_CHAR) {|u| [$1.hex].pack('U*')}
+ unescaped
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/tag_assertions.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/tag_assertions.rb
new file mode 100644
index 000000000..4ac489520
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/tag_assertions.rb
@@ -0,0 +1,130 @@
+require 'rexml/document'
+require 'html/document'
+
+module ActionController
+ module Assertions
+ # Pair of assertions to testing elements in the HTML output of the response.
+ module TagAssertions
+ # Asserts that there is a tag/node/element in the body of the response
+ # that meets all of the given conditions. The +conditions+ parameter must
+ # be a hash of any of the following keys (all are optional):
+ #
+ # * <tt>:tag</tt>: the node type must match the corresponding value
+ # * <tt>:attributes</tt>: a hash. The node's attributes must match the
+ # corresponding values in the hash.
+ # * <tt>:parent</tt>: a hash. The node's parent must match the
+ # corresponding hash.
+ # * <tt>:child</tt>: a hash. At least one of the node's immediate children
+ # must meet the criteria described by the hash.
+ # * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
+ # meet the criteria described by the hash.
+ # * <tt>:descendant</tt>: a hash. At least one of the node's descendants
+ # must meet the criteria described by the hash.
+ # * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
+ # meet the criteria described by the hash.
+ # * <tt>:after</tt>: a hash. The node must be after any sibling meeting
+ # the criteria described by the hash, and at least one sibling must match.
+ # * <tt>:before</tt>: a hash. The node must be before any sibling meeting
+ # the criteria described by the hash, and at least one sibling must match.
+ # * <tt>:children</tt>: a hash, for counting children of a node. Accepts
+ # the keys:
+ # * <tt>:count</tt>: either a number or a range which must equal (or
+ # include) the number of children that match.
+ # * <tt>:less_than</tt>: the number of matching children must be less
+ # than this number.
+ # * <tt>:greater_than</tt>: the number of matching children must be
+ # greater than this number.
+ # * <tt>:only</tt>: another hash consisting of the keys to use
+ # to match on the children, and only matching children will be
+ # counted.
+ # * <tt>:content</tt>: the textual content of the node must match the
+ # given value. This will not match HTML tags in the body of a
+ # tag--only text.
+ #
+ # Conditions are matched using the following algorithm:
+ #
+ # * if the condition is a string, it must be a substring of the value.
+ # * if the condition is a regexp, it must match the value.
+ # * if the condition is a number, the value must match number.to_s.
+ # * if the condition is +true+, the value must not be +nil+.
+ # * if the condition is +false+ or +nil+, the value must be +nil+.
+ #
+ # === Examples
+ #
+ # # Assert that there is a "span" tag
+ # assert_tag :tag => "span"
+ #
+ # # Assert that there is a "span" tag with id="x"
+ # assert_tag :tag => "span", :attributes => { :id => "x" }
+ #
+ # # Assert that there is a "span" tag using the short-hand
+ # assert_tag :span
+ #
+ # # Assert that there is a "span" tag with id="x" using the short-hand
+ # assert_tag :span, :attributes => { :id => "x" }
+ #
+ # # Assert that there is a "span" inside of a "div"
+ # assert_tag :tag => "span", :parent => { :tag => "div" }
+ #
+ # # Assert that there is a "span" somewhere inside a table
+ # assert_tag :tag => "span", :ancestor => { :tag => "table" }
+ #
+ # # Assert that there is a "span" with at least one "em" child
+ # assert_tag :tag => "span", :child => { :tag => "em" }
+ #
+ # # Assert that there is a "span" containing a (possibly nested)
+ # # "strong" tag.
+ # assert_tag :tag => "span", :descendant => { :tag => "strong" }
+ #
+ # # Assert that there is a "span" containing between 2 and 4 "em" tags
+ # # as immediate children
+ # assert_tag :tag => "span",
+ # :children => { :count => 2..4, :only => { :tag => "em" } }
+ #
+ # # Get funky: assert that there is a "div", with an "ul" ancestor
+ # # and an "li" parent (with "class" = "enum"), and containing a
+ # # "span" descendant that contains text matching /hello world/
+ # assert_tag :tag => "div",
+ # :ancestor => { :tag => "ul" },
+ # :parent => { :tag => "li",
+ # :attributes => { :class => "enum" } },
+ # :descendant => { :tag => "span",
+ # :child => /hello world/ }
+ #
+ # <b>Please note</b>: #assert_tag and #assert_no_tag only work
+ # with well-formed XHTML. They recognize a few tags as implicitly self-closing
+ # (like br and hr and such) but will not work correctly with tags
+ # that allow optional closing tags (p, li, td). <em>You must explicitly
+ # close all of your tags to use these assertions.</em>
+ def assert_tag(*opts)
+ clean_backtrace do
+ opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
+ tag = find_tag(opts)
+ assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}"
+ end
+ end
+
+ # Identical to #assert_tag, but asserts that a matching tag does _not_
+ # exist. (See #assert_tag for a full discussion of the syntax.)
+ #
+ # === Examples
+ # # Assert that there is not a "div" containing a "p"
+ # assert_no_tag :tag => "div", :descendant => { :tag => "p" }
+ #
+ # # Assert that an unordered list is empty
+ # assert_no_tag :tag => "ul", :descendant => { :tag => "li" }
+ #
+ # # Assert that there is not a "p" tag with between 1 to 3 "img" tags
+ # # as immediate children
+ # assert_no_tag :tag => "p",
+ # :children => { :count => 1..3, :only => { :tag => "img" } }
+ def assert_no_tag(*opts)
+ clean_backtrace do
+ opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
+ tag = find_tag(opts)
+ assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}"
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/base.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/base.rb
new file mode 100755
index 000000000..ff77e036f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/base.rb
@@ -0,0 +1,1295 @@
+require 'action_controller/mime_type'
+require 'action_controller/request'
+require 'action_controller/response'
+require 'action_controller/routing'
+require 'action_controller/resources'
+require 'action_controller/url_rewriter'
+require 'action_controller/status_codes'
+require 'drb'
+require 'set'
+
+module ActionController #:nodoc:
+ class ActionControllerError < StandardError #:nodoc:
+ end
+
+ class SessionRestoreError < ActionControllerError #:nodoc:
+ end
+
+ class MissingTemplate < ActionControllerError #:nodoc:
+ end
+
+ class RenderError < ActionControllerError #:nodoc:
+ end
+
+ class RoutingError < ActionControllerError #:nodoc:
+ attr_reader :failures
+ def initialize(message, failures=[])
+ super(message)
+ @failures = failures
+ end
+ end
+
+ class MethodNotAllowed < ActionControllerError #:nodoc:
+ attr_reader :allowed_methods
+
+ def initialize(*allowed_methods)
+ super("Only #{allowed_methods.to_sentence} requests are allowed.")
+ @allowed_methods = allowed_methods
+ end
+
+ def allowed_methods_header
+ allowed_methods.map { |method_symbol| method_symbol.to_s.upcase } * ', '
+ end
+
+ def handle_response!(response)
+ response.headers['Allow'] ||= allowed_methods_header
+ end
+ end
+
+ class NotImplemented < MethodNotAllowed #:nodoc:
+ end
+
+ class UnknownController < ActionControllerError #:nodoc:
+ end
+
+ class UnknownAction < ActionControllerError #:nodoc:
+ end
+
+ class MissingFile < ActionControllerError #:nodoc:
+ end
+
+ class RenderError < ActionControllerError #:nodoc:
+ end
+
+ class SessionOverflowError < ActionControllerError #:nodoc:
+ DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.'
+
+ def initialize(message = nil)
+ super(message || DEFAULT_MESSAGE)
+ end
+ end
+
+ class DoubleRenderError < ActionControllerError #:nodoc:
+ DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"."
+
+ def initialize(message = nil)
+ super(message || DEFAULT_MESSAGE)
+ end
+ end
+
+ class RedirectBackError < ActionControllerError #:nodoc:
+ DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].'
+
+ def initialize(message = nil)
+ super(message || DEFAULT_MESSAGE)
+ end
+ end
+
+ class UnknownHttpMethod < ActionControllerError #:nodoc:
+ end
+
+ # Action Controllers are the core of a web request in Rails. They are made up of one or more actions that are executed
+ # on request and then either render a template or redirect to another action. An action is defined as a public method
+ # on the controller, which will automatically be made accessible to the web-server through Rails Routes.
+ #
+ # A sample controller could look like this:
+ #
+ # class GuestBookController < ActionController::Base
+ # def index
+ # @entries = Entry.find(:all)
+ # end
+ #
+ # def sign
+ # Entry.create(params[:entry])
+ # redirect_to :action => "index"
+ # end
+ # end
+ #
+ # Actions, by default, render a template in the <tt>app/views</tt> directory corresponding to the name of the controller and action
+ # after executing code in the action. For example, the +index+ action of the +GuestBookController+ would render the
+ # template <tt>app/views/guestbook/index.erb</tt> by default after populating the <tt>@entries</tt> instance variable.
+ #
+ # Unlike index, the sign action will not render a template. After performing its main purpose (creating a
+ # new entry in the guest book), it initiates a redirect instead. This redirect works by returning an external
+ # "302 Moved" HTTP response that takes the user to the index action.
+ #
+ # The index and sign represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect.
+ # Most actions are variations of these themes.
+ #
+ # == Requests
+ #
+ # Requests are processed by the Action Controller framework by extracting the value of the "action" key in the request parameters.
+ # This value should hold the name of the action to be performed. Once the action has been identified, the remaining
+ # request parameters, the session (if one is available), and the full request with all the http headers are made available to
+ # the action through instance variables. Then the action is performed.
+ #
+ # The full request object is available with the request accessor and is primarily used to query for http headers. These queries
+ # are made by accessing the environment hash, like this:
+ #
+ # def server_ip
+ # location = request.env["SERVER_ADDR"]
+ # render :text => "This server hosted at #{location}"
+ # end
+ #
+ # == Parameters
+ #
+ # All request parameters, whether they come from a GET or POST request, or from the URL, are available through the params method
+ # which returns a hash. For example, an action that was performed through <tt>/weblog/list?category=All&limit=5</tt> will include
+ # <tt>{ "category" => "All", "limit" => 5 }</tt> in params.
+ #
+ # It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
+ #
+ # <input type="text" name="post[name]" value="david">
+ # <input type="text" name="post[address]" value="hyacintvej">
+ #
+ # A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
+ # If the address input had been named "post[address][street]", the params would have included
+ # <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting.
+ #
+ # == Sessions
+ #
+ # Sessions allows you to store objects in between requests. This is useful for objects that are not yet ready to be persisted,
+ # such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such
+ # as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely
+ # they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at.
+ #
+ # You can place objects in the session by using the <tt>session</tt> method, which accesses a hash:
+ #
+ # session[:person] = Person.authenticate(user_name, password)
+ #
+ # And retrieved again through the same hash:
+ #
+ # Hello #{session[:person]}
+ #
+ # For removing objects from the session, you can either assign a single key to nil, like <tt>session[:person] = nil</tt>, or you can
+ # remove the entire session with reset_session.
+ #
+ # Sessions are stored in a browser cookie that's cryptographically signed, but unencrypted, by default. This prevents
+ # the user from tampering with the session but also allows him to see its contents.
+ #
+ # Do not put secret information in session!
+ #
+ # Other options for session storage are:
+ #
+ # ActiveRecordStore: sessions are stored in your database, which works better than PStore with multiple app servers and,
+ # unlike CookieStore, hides your session contents from the user. To use ActiveRecordStore, set
+ #
+ # config.action_controller.session_store = :active_record_store
+ #
+ # in your <tt>environment.rb</tt> and run <tt>rake db:sessions:create</tt>.
+ #
+ # MemCacheStore: sessions are stored as entries in your memcached cache. Set the session store type in <tt>environment.rb</tt>:
+ #
+ # config.action_controller.session_store = :mem_cache_store
+ #
+ # This assumes that memcached has been installed and configured properly. See the MemCacheStore docs for more information.
+ #
+ # == Responses
+ #
+ # Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response
+ # object is generated automatically through the use of renders and redirects and requires no user intervention.
+ #
+ # == Renders
+ #
+ # Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering
+ # of a template. Included in the Action Pack is the Action View, which enables rendering of ERb templates. It's automatically configured.
+ # The controller passes objects to the view by assigning instance variables:
+ #
+ # def show
+ # @post = Post.find(params[:id])
+ # end
+ #
+ # Which are then automatically available to the view:
+ #
+ # Title: <%= @post.title %>
+ #
+ # You don't have to rely on the automated rendering. Especially actions that could result in the rendering of different templates will use
+ # the manual rendering methods:
+ #
+ # def search
+ # @results = Search.find(params[:query])
+ # case @results
+ # when 0 then render :action => "no_results"
+ # when 1 then render :action => "show"
+ # when 2..10 then render :action => "show_many"
+ # end
+ # end
+ #
+ # Read more about writing ERb and Builder templates in link:classes/ActionView/Base.html.
+ #
+ # == Redirects
+ #
+ # Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to a database,
+ # we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're going to reuse (and redirect to)
+ # a <tt>show</tt> action that we'll assume has already been created. The code might look like this:
+ #
+ # def create
+ # @entry = Entry.new(params[:entry])
+ # if @entry.save
+ # # The entry was saved correctly, redirect to show
+ # redirect_to :action => 'show', :id => @entry.id
+ # else
+ # # things didn't go so well, do something else
+ # end
+ # end
+ #
+ # In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method which is then executed.
+ #
+ # == Calling multiple redirects or renders
+ #
+ # An action may contain only a single render or a single redirect. Attempting to try to do either again will result in a DoubleRenderError:
+ #
+ # def do_something
+ # redirect_to :action => "elsewhere"
+ # render :action => "overthere" # raises DoubleRenderError
+ # end
+ #
+ # If you need to redirect on the condition of something, then be sure to add "and return" to halt execution.
+ #
+ # def do_something
+ # redirect_to(:action => "elsewhere") and return if monkeys.nil?
+ # render :action => "overthere" # won't be called unless monkeys is nil
+ # end
+ #
+ class Base
+ DEFAULT_RENDER_STATUS_CODE = "200 OK"
+
+ include StatusCodes
+
+ # Determines whether the view has access to controller internals @request, @response, @session, and @template.
+ # By default, it does.
+ @@view_controller_internals = true
+ cattr_accessor :view_controller_internals
+
+ # Protected instance variable cache
+ @@protected_variables_cache = nil
+ cattr_accessor :protected_variables_cache
+
+ # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets,
+ # and images to a dedicated asset server away from the main web server. Example:
+ # ActionController::Base.asset_host = "http://assets.example.com"
+ @@asset_host = ""
+ cattr_accessor :asset_host
+
+ # All requests are considered local by default, so everyone will be exposed to detailed debugging screens on errors.
+ # When the application is ready to go public, this should be set to false, and the protected method <tt>local_request?</tt>
+ # should instead be implemented in the controller to determine when debugging screens should be shown.
+ @@consider_all_requests_local = true
+ cattr_accessor :consider_all_requests_local
+
+ # Enable or disable the collection of failure information for RoutingErrors.
+ # This information can be extremely useful when tweaking custom routes, but is
+ # pointless once routes have been tested and verified.
+ @@debug_routes = true
+ cattr_accessor :debug_routes
+
+ # Controls whether the application is thread-safe, so multi-threaded servers like WEBrick know whether to apply a mutex
+ # around the performance of each action. Action Pack and Active Record are by default thread-safe, but many applications
+ # may not be. Turned off by default.
+ @@allow_concurrency = false
+ cattr_accessor :allow_concurrency
+
+ # Modern REST web services often need to submit complex data to the web application.
+ # The param_parsers hash lets you register handlers which will process the http body and add parameters to the
+ # <tt>params</tt> hash. These handlers are invoked for post and put requests.
+ #
+ # By default application/xml is enabled. A XmlSimple class with the same param name as the root will be instantiated
+ # in the <tt>params</tt>. This allows XML requests to mask themselves as regular form submissions, so you can have one
+ # action serve both regular forms and web service requests.
+ #
+ # Example of doing your own parser for a custom content type:
+ #
+ # ActionController::Base.param_parsers[Mime::Type.lookup('application/atom+xml')] = Proc.new do |data|
+ # node = REXML::Document.new(post)
+ # { node.root.name => node.root }
+ # end
+ #
+ # Note: Up until release 1.1 of Rails, Action Controller would default to using XmlSimple configured to discard the
+ # root node for such requests. The new default is to keep the root, such that "<r><name>David</name></r>" results
+ # in params[:r][:name] for "David" instead of params[:name]. To get the old behavior, you can
+ # re-register XmlSimple as application/xml handler ike this:
+ #
+ # ActionController::Base.param_parsers[Mime::XML] =
+ # Proc.new { |data| XmlSimple.xml_in(data, 'ForceArray' => false) }
+ #
+ # A YAML parser is also available and can be turned on with:
+ #
+ # ActionController::Base.param_parsers[Mime::YAML] = :yaml
+ @@param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
+ Mime::URL_ENCODED_FORM => :url_encoded_form,
+ Mime::XML => :xml_simple }
+ cattr_accessor :param_parsers
+
+ # Controls the default charset for all renders.
+ @@default_charset = "utf-8"
+ cattr_accessor :default_charset
+
+ # The logger is used for generating information on the action run-time (including benchmarking) if available.
+ # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
+ cattr_accessor :logger
+
+ # Determines which template class should be used by ActionController.
+ cattr_accessor :template_class
+
+ # Turn on +ignore_missing_templates+ if you want to unit test actions without making the associated templates.
+ cattr_accessor :ignore_missing_templates
+
+ # Controls the resource action separator
+ @@resource_action_separator = "/"
+ cattr_accessor :resource_action_separator
+
+ # Sets the token parameter name for RequestForgery. Calling #protect_from_forgery sets it to :authenticity_token by default
+ cattr_accessor :request_forgery_protection_token
+
+ # Indicates whether or not optimise the generated named
+ # route helper methods
+ cattr_accessor :optimise_named_routes
+ self.optimise_named_routes = true
+
+ # Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode.
+ class_inheritable_accessor :allow_forgery_protection
+ self.allow_forgery_protection = true
+
+ # Holds the request object that's primarily used to get environment variables through access like
+ # <tt>request.env["REQUEST_URI"]</tt>.
+ attr_internal :request
+
+ # Holds a hash of all the GET, POST, and Url parameters passed to the action. Accessed like <tt>params["post_id"]</tt>
+ # to get the post_id. No type casts are made, so all values are returned as strings.
+ attr_internal :params
+
+ # Holds the response object that's primarily used to set additional HTTP headers through access like
+ # <tt>response.headers["Cache-Control"] = "no-cache"</tt>. Can also be used to access the final body HTML after a template
+ # has been rendered through response.body -- useful for <tt>after_filter</tt>s that wants to manipulate the output,
+ # such as a OutputCompressionFilter.
+ attr_internal :response
+
+ # Holds a hash of objects in the session. Accessed like <tt>session[:person]</tt> to get the object tied to the "person"
+ # key. The session will hold any type of object as values, but the key should be a string or symbol.
+ attr_internal :session
+
+ # Holds a hash of header names and values. Accessed like <tt>headers["Cache-Control"]</tt> to get the value of the Cache-Control
+ # directive. Values should always be specified as strings.
+ attr_internal :headers
+
+ # Holds the hash of variables that are passed on to the template class to be made available to the view. This hash
+ # is generated by taking a snapshot of all the instance variables in the current scope just before a template is rendered.
+ attr_accessor :assigns
+
+ # Returns the name of the action this controller is processing.
+ attr_accessor :action_name
+
+ # Templates that are exempt from layouts
+ @@exempt_from_layout = Set.new([/\.rjs$/])
+
+ class << self
+ # Factory for the standard create, process loop where the controller is discarded after processing.
+ def process(request, response) #:nodoc:
+ new.process(request, response)
+ end
+
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
+ def controller_class_name
+ @controller_class_name ||= name.demodulize
+ end
+
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".
+ def controller_name
+ @controller_name ||= controller_class_name.sub(/Controller$/, '').underscore
+ end
+
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "one_module/two_module/neat".
+ def controller_path
+ @controller_path ||= name.gsub(/Controller$/, '').underscore
+ end
+
+ # Return an array containing the names of public methods that have been marked hidden from the action processor.
+ # By default, all methods defined in ActionController::Base and included modules are hidden.
+ # More methods can be hidden using <tt>hide_actions</tt>.
+ def hidden_actions
+ unless read_inheritable_attribute(:hidden_actions)
+ write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods.map(&:to_s))
+ end
+
+ read_inheritable_attribute(:hidden_actions)
+ end
+
+ # Hide each of the given methods from being callable as actions.
+ def hide_action(*names)
+ write_inheritable_attribute(:hidden_actions, hidden_actions | names.map(&:to_s))
+ end
+
+ ## View load paths determine the bases from which template references can be made. So a call to
+ ## render("test/template") will be looked up in the view load paths array and the closest match will be
+ ## returned.
+ def view_paths
+ @view_paths || superclass.view_paths
+ end
+
+ def view_paths=(value)
+ @view_paths = value
+ end
+
+ # Adds a view_path to the front of the view_paths array.
+ # If the current class has no view paths, copy them from
+ # the superclass. This change will be visible for all future requests.
+ #
+ # ArticleController.prepend_view_path("views/default")
+ # ArticleController.prepend_view_path(["views/default", "views/custom"])
+ #
+ def prepend_view_path(path)
+ @view_paths = superclass.view_paths.dup if @view_paths.nil?
+ view_paths.unshift(*path)
+ end
+
+ # Adds a view_path to the end of the view_paths array.
+ # If the current class has no view paths, copy them from
+ # the superclass. This change will be visible for all future requests.
+ #
+ # ArticleController.append_view_path("views/default")
+ # ArticleController.append_view_path(["views/default", "views/custom"])
+ #
+ def append_view_path(path)
+ @view_paths = superclass.view_paths.dup if @view_paths.nil?
+ view_paths.push(*path)
+ end
+
+ # Replace sensitive parameter data from the request log.
+ # Filters parameters that have any of the arguments as a substring.
+ # Looks in all subhashes of the param hash for keys to filter.
+ # If a block is given, each key and value of the parameter hash and all
+ # subhashes is passed to it, the value or key
+ # can be replaced using String#replace or similar method.
+ #
+ # Examples:
+ # filter_parameter_logging
+ # => Does nothing, just slows the logging process down
+ #
+ # filter_parameter_logging :password
+ # => replaces the value to all keys matching /password/i with "[FILTERED]"
+ #
+ # filter_parameter_logging :foo, "bar"
+ # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
+ #
+ # filter_parameter_logging { |k,v| v.reverse! if k =~ /secret/i }
+ # => reverses the value to all keys matching /secret/i
+ #
+ # filter_parameter_logging(:foo, "bar") { |k,v| v.reverse! if k =~ /secret/i }
+ # => reverses the value to all keys matching /secret/i, and
+ # replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
+ def filter_parameter_logging(*filter_words, &block)
+ parameter_filter = Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) if filter_words.length > 0
+
+ define_method(:filter_parameters) do |unfiltered_parameters|
+ filtered_parameters = {}
+
+ unfiltered_parameters.each do |key, value|
+ if key =~ parameter_filter
+ filtered_parameters[key] = '[FILTERED]'
+ elsif value.is_a?(Hash)
+ filtered_parameters[key] = filter_parameters(value)
+ elsif block_given?
+ key = key.dup
+ value = value.dup if value
+ yield key, value
+ filtered_parameters[key] = value
+ else
+ filtered_parameters[key] = value
+ end
+ end
+
+ filtered_parameters
+ end
+ end
+
+ # Don't render layouts for templates with the given extensions.
+ def exempt_from_layout(*extensions)
+ regexps = extensions.collect do |extension|
+ extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/
+ end
+ @@exempt_from_layout.merge regexps
+ end
+ end
+
+ public
+ # Extracts the action_name from the request parameters and performs that action.
+ def process(request, response, method = :perform_action, *arguments) #:nodoc:
+ initialize_template_class(response)
+ assign_shortcuts(request, response)
+ initialize_current_url
+ assign_names
+ forget_variables_added_to_assigns
+
+ log_processing
+ send(method, *arguments)
+
+ assign_default_content_type_and_charset
+
+ response.request = request
+ response.prepare! unless component_request?
+ response
+ ensure
+ process_cleanup
+ end
+
+ # Returns a URL that has been rewritten according to the options hash and the defined Routes.
+ # (For doing a complete redirect, use redirect_to).
+ #  
+ # <tt>url_for</tt> is used to:
+ #  
+ # All keys given to url_for are forwarded to the Route module, save for the following:
+ # * <tt>:anchor</tt> -- specifies the anchor name to be appended to the path. For example,
+ # <tt>url_for :controller => 'posts', :action => 'show', :id => 10, :anchor => 'comments'</tt>
+ # will produce "/posts/show/10#comments".
+ # * <tt>:only_path</tt> -- if true, returns the relative URL (omitting the protocol, host name, and port) (<tt>false</tt> by default)
+ # * <tt>:trailing_slash</tt> -- if true, adds a trailing slash, as in "/archive/2005/". Note that this
+ # is currently not recommended since it breaks caching.
+ # * <tt>:host</tt> -- overrides the default (current) host if provided.
+ # * <tt>:protocol</tt> -- overrides the default (current) protocol if provided.
+ # * <tt>:port</tt> -- optionally specify the port to connect to.
+ # * <tt>:user</tt> -- Inline HTTP authentication (only plucked out if :password is also present).
+ # * <tt>:password</tt> -- Inline HTTP authentication (only plucked out if :user is also present).
+ # * <tt>:skip_relative_url_root</tt> -- if true, the url is not constructed using the relative_url_root of the request so the path
+ # will include the web server relative installation directory.
+ #
+ # The URL is generated from the remaining keys in the hash. A URL contains two key parts: the <base> and a query string.
+ # Routes composes a query string as the key/value pairs not included in the <base>.
+ #
+ # The default Routes setup supports a typical Rails path of "controller/action/id" where action and id are optional, with
+ # action defaulting to 'index' when not given. Here are some typical url_for statements and their corresponding URLs:
+ #
+ # url_for :controller => 'posts', :action => 'recent' # => 'proto://host.com/posts/recent'
+ # url_for :controller => 'posts', :action => 'index' # => 'proto://host.com/posts'
+ # url_for :controller => 'posts', :action => 'index', :port=>'8033' # => 'proto://host.com:8033/posts'
+ # url_for :controller => 'posts', :action => 'show', :id => 10 # => 'proto://host.com/posts/show/10'
+ # url_for :controller => 'posts', :user => 'd', :password => '123' # => 'proto://d:123@host.com/posts'
+ #
+ # When generating a new URL, missing values may be filled in from the current request's parameters. For example,
+ # <tt>url_for :action => 'some_action'</tt> will retain the current controller, as expected. This behavior extends to
+ # other parameters, including <tt>:controller</tt>, <tt>:id</tt>, and any other parameters that are placed into a Route's
+ # path.
+ #  
+ # The URL helpers such as <tt>url_for</tt> have a limited form of memory: when generating a new URL, they can look for
+ # missing values in the current request's parameters. Routes attempts to guess when a value should and should not be
+ # taken from the defaults. There are a few simple rules on how this is performed:
+ #
+ # * If the controller name begins with a slash, no defaults are used: <tt>url_for :controller => '/home'</tt>
+ # * If the controller changes, the action will default to index unless provided
+ #
+ # The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the
+ # route given by <tt>map.connect 'people/:last/:first/:action', :action => 'bio', :controller => 'people'</tt>.
+ #
+ # Suppose that the current URL is "people/hh/david/contacts". Let's consider a few different cases of URLs which are generated
+ # from this page.
+ #
+ # * <tt>url_for :action => 'bio'</tt> -- During the generation of this URL, default values will be used for the first and
+ # last components, and the action shall change. The generated URL will be, "people/hh/david/bio".
+ # * <tt>url_for :first => 'davids-little-brother'</tt> This generates the URL 'people/hh/davids-little-brother' -- note
+ # that this URL leaves out the assumed action of 'bio'.
+ #
+ # However, you might ask why the action from the current request, 'contacts', isn't carried over into the new URL. The
+ # answer has to do with the order in which the parameters appear in the generated path. In a nutshell, since the
+ # value that appears in the slot for <tt>:first</tt> is not equal to default value for <tt>:first</tt> we stop using
+ # defaults. On its own, this rule can account for much of the typical Rails URL behavior.
+ #  
+ # Although a convenience, defaults can occasionally get in your way. In some cases a default persists longer than desired.
+ # The default may be cleared by adding <tt>:name => nil</tt> to <tt>url_for</tt>'s options.
+ # This is often required when writing form helpers, since the defaults in play may vary greatly depending upon where the
+ # helper is used from. The following line will redirect to PostController's default action, regardless of the page it is
+ # displayed on:
+ #
+ # url_for :controller => 'posts', :action => nil
+ #
+ # If you explicitly want to create a URL that's almost the same as the current URL, you can do so using the
+ # :overwrite_params options. Say for your posts you have different views for showing and printing them.
+ # Then, in the show view, you get the URL for the print view like this
+ #
+ # url_for :overwrite_params => { :action => 'print' }
+ #
+ # This takes the current URL as is and only exchanges the action. In contrast, <tt>url_for :action => 'print'</tt>
+ # would have slashed-off the path components after the changed action.
+ def url_for(options = nil) #:doc:
+ case options || {}
+ when String
+ options
+ when Hash
+ @url.rewrite(rewrite_options(options))
+ else
+ polymorphic_url(options)
+ end
+ end
+
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
+ def controller_class_name
+ self.class.controller_class_name
+ end
+
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".
+ def controller_name
+ self.class.controller_name
+ end
+
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "one_module/two_module/neat".
+ def controller_path
+ self.class.controller_path
+ end
+
+ def session_enabled?
+ request.session_options && request.session_options[:disabled] != false
+ end
+
+ self.view_paths = []
+
+ # View load paths for controller.
+ def view_paths
+ (@template || self.class).view_paths
+ end
+
+ def view_paths=(value)
+ (@template || self.class).view_paths = value
+ end
+
+ # Adds a view_path to the front of the view_paths array.
+ # This change affects the current request only.
+ #
+ # self.prepend_view_path("views/default")
+ # self.prepend_view_path(["views/default", "views/custom"])
+ #
+ def prepend_view_path(path)
+ (@template || self.class).prepend_view_path(path)
+ end
+
+ # Adds a view_path to the end of the view_paths array.
+ # This change affects the current request only.
+ #
+ # self.append_view_path("views/default")
+ # self.append_view_path(["views/default", "views/custom"])
+ #
+ def append_view_path(path)
+ (@template || self.class).append_view_path(path)
+ end
+
+ protected
+ # Renders the content that will be returned to the browser as the response body.
+ #
+ # === Rendering an action
+ #
+ # Action rendering is the most common form and the type used automatically by Action Controller when nothing else is
+ # specified. By default, actions are rendered within the current layout (if one exists).
+ #
+ # # Renders the template for the action "goal" within the current controller
+ # render :action => "goal"
+ #
+ # # Renders the template for the action "short_goal" within the current controller,
+ # # but without the current active layout
+ # render :action => "short_goal", :layout => false
+ #
+ # # Renders the template for the action "long_goal" within the current controller,
+ # # but with a custom layout
+ # render :action => "long_goal", :layout => "spectacular"
+ #
+ # === Rendering partials
+ #
+ # Partial rendering in a controller is most commonly used together with Ajax calls that only update one or a few elements on a page
+ # without reloading. Rendering of partials from the controller makes it possible to use the same partial template in
+ # both the full-page rendering (by calling it from within the template) and when sub-page updates happen (from the
+ # controller action responding to Ajax calls). By default, the current layout is not used.
+ #
+ # # Renders the same partial with a local variable.
+ # render :partial => "person", :locals => { :name => "david" }
+ #
+ # # Renders the partial, making @new_person available through
+ # # the local variable 'person'
+ # render :partial => "person", :object => @new_person
+ #
+ # # Renders a collection of the same partial by making each element
+ # # of @winners available through the local variable "person" as it
+ # # builds the complete response.
+ # render :partial => "person", :collection => @winners
+ #
+ # # Renders the same collection of partials, but also renders the
+ # # person_divider partial between each person partial.
+ # render :partial => "person", :collection => @winners, :spacer_template => "person_divider"
+ #
+ # # Renders a collection of partials located in a view subfolder
+ # # outside of our current controller. In this example we will be
+ # # rendering app/views/shared/_note.r(html|xml) Inside the partial
+ # # each element of @new_notes is available as the local var "note".
+ # render :partial => "shared/note", :collection => @new_notes
+ #
+ # # Renders the partial with a status code of 500 (internal error).
+ # render :partial => "broken", :status => 500
+ #
+ # Note that the partial filename must also be a valid Ruby variable name,
+ # so e.g. 2005 and register-user are invalid.
+ #
+ #
+ # == Automatic etagging
+ #
+ # Rendering will automatically insert the etag header on 200 OK responses. The etag is calculated using MD5 of the
+ # response body. If a request comes in that has a matching etag, the response will be changed to a 304 Not Modified
+ # and the response body will be set to an empty string. No etag header will be inserted if it's already set.
+ #
+ # === Rendering a template
+ #
+ # Template rendering works just like action rendering except that it takes a path relative to the template root.
+ # The current layout is automatically applied.
+ #
+ # # Renders the template located in [TEMPLATE_ROOT]/weblog/show.r(html|xml) (in Rails, app/views/weblog/show.erb)
+ # render :template => "weblog/show"
+ #
+ # === Rendering a file
+ #
+ # File rendering works just like action rendering except that it takes a filesystem path. By default, the path
+ # is assumed to be absolute, and the current layout is not applied.
+ #
+ # # Renders the template located at the absolute filesystem path
+ # render :file => "/path/to/some/template.erb"
+ # render :file => "c:/path/to/some/template.erb"
+ #
+ # # Renders a template within the current layout, and with a 404 status code
+ # render :file => "/path/to/some/template.erb", :layout => true, :status => 404
+ # render :file => "c:/path/to/some/template.erb", :layout => true, :status => 404
+ #
+ # # Renders a template relative to the template root and chooses the proper file extension
+ # render :file => "some/template", :use_full_path => true
+ #
+ # === Rendering text
+ #
+ # Rendering of text is usually used for tests or for rendering prepared content, such as a cache. By default, text
+ # rendering is not done within the active layout.
+ #
+ # # Renders the clear text "hello world" with status code 200
+ # render :text => "hello world!"
+ #
+ # # Renders the clear text "Explosion!" with status code 500
+ # render :text => "Explosion!", :status => 500
+ #
+ # # Renders the clear text "Hi there!" within the current active layout (if one exists)
+ # render :text => "Hi there!", :layout => true
+ #
+ # # Renders the clear text "Hi there!" within the layout
+ # # placed in "app/views/layouts/special.r(html|xml)"
+ # render :text => "Hi there!", :layout => "special"
+ #
+ # The :text option can also accept a Proc object, which can be used to manually control the page generation. This should
+ # generally be avoided, as it violates the separation between code and content, and because almost everything that can be
+ # done with this method can also be done more cleanly using one of the other rendering methods, most notably templates.
+ #
+ # # Renders "Hello from code!"
+ # render :text => proc { |response, output| output.write("Hello from code!") }
+ #
+ # === Rendering JSON
+ #
+ # Rendering JSON sets the content type to application/json and optionally wraps the JSON in a callback. It is expected
+ # that the response will be parsed (or eval'd) for use as a data structure.
+ #
+ # # Renders '{"name": "David"}'
+ # render :json => {:name => "David"}.to_json
+ #
+ # It's not necessary to call <tt>to_json</tt> on the object you want to render, since <tt>render</tt> will
+ # automatically do that for you:
+ #
+ # # Also renders '{"name": "David"}'
+ # render :json => {:name => "David"}
+ #
+ # Sometimes the result isn't handled directly by a script (such as when the request comes from a SCRIPT tag),
+ # so the <tt>:callback</tt> option is provided for these cases.
+ #
+ # # Renders 'show({"name": "David"})'
+ # render :json => {:name => "David"}.to_json, :callback => 'show'
+ #
+ # === Rendering an inline template
+ #
+ # Rendering of an inline template works as a cross between text and action rendering where the source for the template
+ # is supplied inline, like text, but its interpreted with ERb or Builder, like action. By default, ERb is used for rendering
+ # and the current layout is not used.
+ #
+ # # Renders "hello, hello, hello, again"
+ # render :inline => "<%= 'hello, ' * 3 + 'again' %>"
+ #
+ # # Renders "<p>Good seeing you!</p>" using Builder
+ # render :inline => "xml.p { 'Good seeing you!' }", :type => :builder
+ #
+ # # Renders "hello david"
+ # render :inline => "<%= 'hello ' + name %>", :locals => { :name => "david" }
+ #
+ # === Rendering inline JavaScriptGenerator page updates
+ #
+ # In addition to rendering JavaScriptGenerator page updates with Ajax in RJS templates (see ActionView::Base for details),
+ # you can also pass the <tt>:update</tt> parameter to +render+, along with a block, to render page updates inline.
+ #
+ # render :update do |page|
+ # page.replace_html 'user_list', :partial => 'user', :collection => @users
+ # page.visual_effect :highlight, 'user_list'
+ # end
+ #
+ # === Rendering with status and location headers
+ #
+ # All renders take the :status and :location options and turn them into headers. They can even be used together:
+ #
+ # render :xml => post.to_xml, :status => :created, :location => post_url(post)
+ def render(options = nil, &block) #:doc:
+ raise DoubleRenderError, "Can only render or redirect once per action" if performed?
+
+ if options.nil?
+ return render_for_file(default_template_name, nil, true)
+ else
+ if options == :update
+ options = { :update => true }
+ elsif !options.is_a?(Hash)
+ raise RenderError, "You called render with invalid options : #{options}"
+ end
+ end
+
+ if content_type = options[:content_type]
+ response.content_type = content_type.to_s
+ end
+
+ if location = options[:location]
+ response.headers["Location"] = url_for(location)
+ end
+
+ if text = options[:text]
+ render_for_text(text, options[:status])
+
+ else
+ if file = options[:file]
+ render_for_file(file, options[:status], options[:use_full_path], options[:locals] || {})
+
+ elsif template = options[:template]
+ render_for_file(template, options[:status], true)
+
+ elsif inline = options[:inline]
+ add_variables_to_assigns
+ render_for_text(@template.render_template(options[:type], inline, nil, options[:locals] || {}), options[:status])
+
+ elsif action_name = options[:action]
+ template = default_template_name(action_name.to_s)
+ if options[:layout] && !template_exempt_from_layout?(template)
+ render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true)
+ else
+ render_with_no_layout(:file => template, :status => options[:status], :use_full_path => true)
+ end
+
+ elsif xml = options[:xml]
+ response.content_type ||= Mime::XML
+ render_for_text(xml.respond_to?(:to_xml) ? xml.to_xml : xml, options[:status])
+
+ elsif json = options[:json]
+ json = json.to_json unless json.is_a?(String)
+ json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
+ response.content_type ||= Mime::JSON
+ render_for_text(json, options[:status])
+
+ elsif partial = options[:partial]
+ partial = default_template_name if partial == true
+ add_variables_to_assigns
+
+ if collection = options[:collection]
+ render_for_text(
+ @template.send!(:render_partial_collection, partial, collection,
+ options[:spacer_template], options[:locals]), options[:status]
+ )
+ else
+ render_for_text(
+ @template.send!(:render_partial, partial,
+ ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]), options[:status]
+ )
+ end
+
+ elsif options[:update]
+ add_variables_to_assigns
+ @template.send! :evaluate_assigns
+
+ generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block)
+ response.content_type = Mime::JS
+ render_for_text(generator.to_s)
+
+ elsif options[:nothing]
+ # Safari doesn't pass the headers of the return if the response is zero length
+ render_for_text(" ", options[:status])
+
+ else
+ render_for_file(default_template_name, options[:status], true)
+ end
+ end
+ end
+
+ # Renders according to the same rules as <tt>render</tt>, but returns the result in a string instead
+ # of sending it as the response body to the browser.
+ def render_to_string(options = nil, &block) #:doc:
+ render(options, &block)
+ ensure
+ erase_render_results
+ forget_variables_added_to_assigns
+ reset_variables_added_to_assigns
+ end
+
+ # Return a response that has no content (merely headers). The options
+ # argument is interpreted to be a hash of header names and values.
+ # This allows you to easily return a response that consists only of
+ # significant headers:
+ #
+ # head :created, :location => person_path(@person)
+ #
+ # It can also be used to return exceptional conditions:
+ #
+ # return head(:method_not_allowed) unless request.post?
+ # return head(:bad_request) unless valid_request?
+ # render
+ def head(*args)
+ if args.length > 2
+ raise ArgumentError, "too many arguments to head"
+ elsif args.empty?
+ raise ArgumentError, "too few arguments to head"
+ end
+ options = args.extract_options!
+ status = interpret_status(args.shift || options.delete(:status) || :ok)
+
+ options.each do |key, value|
+ headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s
+ end
+
+ render :nothing => true, :status => status
+ end
+
+
+ # Clears the rendered results, allowing for another render to be performed.
+ def erase_render_results #:nodoc:
+ response.body = nil
+ @performed_render = false
+ end
+
+ # Clears the redirected results from the headers, resets the status to 200 and returns
+ # the URL that was used to redirect or nil if there was no redirected URL
+ # Note that +redirect_to+ will change the body of the response to indicate a redirection.
+ # The response body is not reset here, see +erase_render_results+
+ def erase_redirect_results #:nodoc:
+ @performed_redirect = false
+ response.redirected_to = nil
+ response.redirected_to_method_params = nil
+ response.headers['Status'] = DEFAULT_RENDER_STATUS_CODE
+ response.headers.delete('Location')
+ end
+
+ # Erase both render and redirect results
+ def erase_results #:nodoc:
+ erase_render_results
+ erase_redirect_results
+ end
+
+ def rewrite_options(options) #:nodoc:
+ if defaults = default_url_options(options)
+ defaults.merge(options)
+ else
+ options
+ end
+ end
+
+ # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
+ # the form of a hash, just like the one you would use for url_for directly. Example:
+ #
+ # def default_url_options(options)
+ # { :project => @project.active? ? @project.url_name : "unknown" }
+ # end
+ #
+ # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
+ # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
+ # by this method.
+ def default_url_options(options) #:doc:
+ end
+
+ # Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
+ #
+ # * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
+ # * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
+ # * <tt>String starting with protocol:// (like http://)</tt> - Is passed straight through as the target for redirection.
+ # * <tt>String not containing a protocol</tt> - The current protocol and host is prepended to the string.
+ # * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
+ # Short-hand for redirect_to(request.env["HTTP_REFERER"])
+ #
+ # Examples:
+ # redirect_to :action => "show", :id => 5
+ # redirect_to post
+ # redirect_to "http://www.rubyonrails.org"
+ # redirect_to "/images/screenshot.jpg"
+ # redirect_to articles_url
+ # redirect_to :back
+ #
+ # The redirection happens as a "302 Moved" header unless otherwise specified.
+ #
+ # Examples:
+ # redirect_to post_url(@post), :status=>:found
+ # redirect_to :action=>'atom', :status=>:moved_permanently
+ # redirect_to post_url(@post), :status=>301
+ # redirect_to :action=>'atom', :status=>302
+ #
+ # When using <tt>redirect_to :back</tt>, if there is no referrer,
+ # RedirectBackError will be raised. You may specify some fallback
+ # behavior for this case by rescuing RedirectBackError.
+ def redirect_to(options = {}, response_status = {}) #:doc:
+
+ if options.is_a?(Hash) && options[:status]
+ status = options.delete(:status)
+ elsif response_status[:status]
+ status = response_status[:status]
+ else
+ status = 302
+ end
+
+ case options
+ when %r{^\w+://.*}
+ raise DoubleRenderError if performed?
+ logger.info("Redirected to #{options}") if logger && logger.info?
+ response.redirect(options, interpret_status(status))
+ response.redirected_to = options
+ @performed_redirect = true
+
+ when String
+ redirect_to(request.protocol + request.host_with_port + options, :status=>status)
+
+ when :back
+ request.env["HTTP_REFERER"] ? redirect_to(request.env["HTTP_REFERER"], :status=>status) : raise(RedirectBackError)
+
+ when Hash
+ redirect_to(url_for(options), :status=>status)
+ response.redirected_to = options
+
+ else
+ redirect_to(url_for(options), :status=>status)
+ end
+ end
+
+ # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that
+ # intermediate caches shouldn't cache the response.
+ #
+ # Examples:
+ # expires_in 20.minutes
+ # expires_in 3.hours, :private => false
+ # expires in 3.hours, 'max-stale' => 5.hours, :private => nil, :public => true
+ #
+ # This method will overwrite an existing Cache-Control header.
+ # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
+ def expires_in(seconds, options = {}) #:doc:
+ cache_options = { 'max-age' => seconds, 'private' => true }.symbolize_keys.merge!(options.symbolize_keys)
+ cache_options.delete_if { |k,v| v.nil? or v == false }
+ cache_control = cache_options.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"}
+ response.headers["Cache-Control"] = cache_control.join(', ')
+ end
+
+ # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or
+ # intermediate caches (like caching proxy servers).
+ def expires_now #:doc:
+ response.headers["Cache-Control"] = "no-cache"
+ end
+
+ # Resets the session by clearing out all the objects stored within and initializing a new session object.
+ def reset_session #:doc:
+ request.reset_session
+ @_session = request.session
+ response.session = @_session
+ end
+
+
+ private
+ def render_for_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc:
+ add_variables_to_assigns
+ assert_existence_of_template_file(template_path) if use_full_path
+ logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
+ render_for_text(@template.render_file(template_path, use_full_path, locals), status)
+ end
+
+ def render_for_text(text = nil, status = nil, append_response = false) #:nodoc:
+ @performed_render = true
+
+ response.headers['Status'] = interpret_status(status || DEFAULT_RENDER_STATUS_CODE)
+
+ if append_response
+ response.body ||= ''
+ response.body << text.to_s
+ else
+ response.body = text.is_a?(Proc) ? text : text.to_s
+ end
+ end
+
+ def initialize_template_class(response)
+ unless @@template_class
+ raise "You must assign a template class through ActionController.template_class= before processing a request"
+ end
+
+ response.template = ActionView::Base.new(view_paths, {}, self)
+ response.template.extend self.class.master_helper_module
+ response.redirected_to = nil
+ @performed_render = @performed_redirect = false
+ end
+
+ def assign_shortcuts(request, response)
+ @_request, @_params, @_cookies = request, request.parameters, request.cookies
+
+ @_response = response
+ @_response.session = request.session
+
+ @_session = @_response.session
+ @template = @_response.template
+ @assigns = @_response.template.assigns
+
+ @_headers = @_response.headers
+ end
+
+ def initialize_current_url
+ @url = UrlRewriter.new(request, params.clone)
+ end
+
+ def log_processing
+ if logger && logger.info?
+ logger.info "\n\nProcessing #{controller_class_name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]"
+ logger.info " Session ID: #{@_session.session_id}" if @_session and @_session.respond_to?(:session_id)
+ logger.info " Parameters: #{respond_to?(:filter_parameters) ? filter_parameters(params).inspect : params.inspect}"
+ end
+ end
+
+ def default_render #:nodoc:
+ render
+ end
+
+ def perform_action
+ if self.class.action_methods.include?(action_name)
+ send(action_name)
+ default_render unless performed?
+ elsif respond_to? :method_missing
+ method_missing action_name
+ default_render unless performed?
+ elsif template_exists? && template_public?
+ default_render
+ else
+ raise UnknownAction, "No action responded to #{action_name}", caller
+ end
+ end
+
+ def performed?
+ @performed_render || @performed_redirect
+ end
+
+ def assign_names
+ @action_name = (params['action'] || 'index')
+ end
+
+ def assign_default_content_type_and_charset
+ response.content_type ||= Mime::HTML
+ response.charset ||= self.class.default_charset unless sending_file?
+ end
+
+ def sending_file?
+ response.headers["Content-Transfer-Encoding"] == "binary"
+ end
+
+ def action_methods
+ self.class.action_methods
+ end
+
+ def self.action_methods
+ @action_methods ||= Set.new(public_instance_methods.map(&:to_s)) - hidden_actions
+ end
+
+ def add_variables_to_assigns
+ unless @variables_added
+ add_instance_variables_to_assigns
+ add_class_variables_to_assigns if view_controller_internals
+ @variables_added = true
+ end
+ end
+
+ def forget_variables_added_to_assigns
+ @variables_added = nil
+ end
+
+ def reset_variables_added_to_assigns
+ @template.instance_variable_set("@assigns_added", nil)
+ end
+
+ def add_instance_variables_to_assigns
+ @@protected_variables_cache ||= Set.new(protected_instance_variables)
+ instance_variables.each do |var|
+ next if @@protected_variables_cache.include?(var)
+ @assigns[var[1..-1]] = instance_variable_get(var)
+ end
+ end
+
+ def add_class_variables_to_assigns
+ %w(view_paths logger template_class ignore_missing_templates).each do |cvar|
+ @assigns[cvar] = self.send(cvar)
+ end
+ end
+
+ def protected_instance_variables
+ if view_controller_internals
+ %w(@assigns @performed_redirect @performed_render)
+ else
+ %w(@assigns @performed_redirect @performed_render
+ @_request @request @_response @response @_params @params
+ @_session @session @_cookies @cookies
+ @template @request_origin @parent_controller)
+ end
+ end
+
+ def request_origin
+ # this *needs* to be cached!
+ # otherwise you'd get different results if calling it more than once
+ @request_origin ||= "#{request.remote_ip} at #{Time.now.to_s(:db)}"
+ end
+
+ def complete_request_uri
+ "#{request.protocol}#{request.host}#{request.request_uri}"
+ end
+
+ def close_session
+ @_session.close if @_session && @_session.respond_to?(:close)
+ end
+
+ def template_exists?(template_name = default_template_name)
+ @template.file_exists?(template_name)
+ end
+
+ def template_public?(template_name = default_template_name)
+ @template.file_public?(template_name)
+ end
+
+ def template_exempt_from_layout?(template_name = default_template_name)
+ extension = @template && @template.pick_template_extension(template_name)
+ name_with_extension = !template_name.include?('.') && extension ? "#{template_name}.#{extension}" : template_name
+ @@exempt_from_layout.any? { |ext| name_with_extension =~ ext }
+ end
+
+ def assert_existence_of_template_file(template_name)
+ unless template_exists?(template_name) || ignore_missing_templates
+ full_template_path = template_name.include?('.') ? template_name : "#{template_name}.#{@template.template_format}.erb"
+ display_paths = view_paths.join(':')
+ template_type = (template_name =~ /layouts/i) ? 'layout' : 'template'
+ raise(MissingTemplate, "Missing #{template_type} #{full_template_path} in view path #{display_paths}")
+ end
+ end
+
+ def default_template_name(action_name = self.action_name)
+ if action_name
+ action_name = action_name.to_s
+ if action_name.include?('/') && template_path_includes_controller?(action_name)
+ action_name = strip_out_controller(action_name)
+ end
+ end
+ "#{self.class.controller_path}/#{action_name}"
+ end
+
+ def strip_out_controller(path)
+ path.split('/', 2).last
+ end
+
+ def template_path_includes_controller?(path)
+ self.class.controller_path.split('/')[-1] == path.split('/')[0]
+ end
+
+ def process_cleanup
+ close_session
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/benchmarking.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/benchmarking.rb
new file mode 100644
index 000000000..5201d31b8
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/benchmarking.rb
@@ -0,0 +1,94 @@
+require 'benchmark'
+
+module ActionController #:nodoc:
+ # The benchmarking module times the performance of actions and reports to the logger. If the Active Record
+ # package has been included, a separate timing section for database calls will be added as well.
+ module Benchmarking #:nodoc:
+ def self.included(base)
+ base.extend(ClassMethods)
+
+ base.class_eval do
+ alias_method_chain :perform_action, :benchmark
+ alias_method_chain :render, :benchmark
+ end
+ end
+
+ module ClassMethods
+ # Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it
+ # (unless <tt>use_silence</tt> is set to false).
+ #
+ # The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it
+ # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
+ # will only be conducted if the log level is low enough.
+ def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
+ if logger && logger.level == log_level
+ result = nil
+ seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
+ logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
+ result
+ else
+ yield
+ end
+ end
+
+ # Silences the logger for the duration of the block.
+ def silence
+ old_logger_level, logger.level = logger.level, Logger::ERROR if logger
+ yield
+ ensure
+ logger.level = old_logger_level if logger
+ end
+ end
+
+ protected
+ def render_with_benchmark(options = nil, deprecated_status = nil, &block)
+ unless logger
+ render_without_benchmark(options, &block)
+ else
+ db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
+
+ render_output = nil
+ @rendering_runtime = Benchmark::measure{ render_output = render_without_benchmark(options, &block) }.real
+
+ if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
+ @db_rt_before_render = db_runtime
+ @db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
+ @rendering_runtime -= @db_rt_after_render
+ end
+
+ render_output
+ end
+ end
+
+ private
+ def perform_action_with_benchmark
+ unless logger
+ perform_action_without_benchmark
+ else
+ runtime = [ Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001 ].max
+
+ log_message = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
+ log_message << rendering_runtime(runtime) if defined?(@rendering_runtime)
+ log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
+ log_message << " | #{headers["Status"]}"
+ log_message << " [#{complete_request_uri rescue "unknown"}]"
+
+ logger.info(log_message)
+ response.headers["X-Runtime"] = sprintf("%.5f", runtime)
+ end
+ end
+
+ def rendering_runtime(runtime)
+ percentage = @rendering_runtime * 100 / runtime
+ " | Rendering: %.5f (%d%%)" % [@rendering_runtime, percentage.to_i]
+ end
+
+ def active_record_runtime(runtime)
+ db_runtime = ActiveRecord::Base.connection.reset_runtime
+ db_runtime += @db_rt_before_render if @db_rt_before_render
+ db_runtime += @db_rt_after_render if @db_rt_after_render
+ db_percentage = db_runtime * 100 / runtime
+ " | DB: %.5f (%d%%)" % [db_runtime, db_percentage.to_i]
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/caching.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/caching.rb
new file mode 100644
index 000000000..e3e48f660
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/caching.rb
@@ -0,0 +1,683 @@
+require 'fileutils'
+require 'uri'
+require 'set'
+
+module ActionController #:nodoc:
+ # Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
+ # around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment.
+ #
+ # You can read more about each approach and the sweeping assistance by clicking the modules below.
+ #
+ # Note: To turn off all caching and sweeping, set Base.perform_caching = false.
+ module Caching
+ def self.included(base) #:nodoc:
+ base.class_eval do
+ include Pages, Actions, Fragments
+
+ if defined? ActiveRecord
+ include Sweeping, SqlCache
+ end
+
+ @@perform_caching = true
+ cattr_accessor :perform_caching
+ end
+ end
+
+ # Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server
+ # can serve without going through the Action Pack. This can be as much as 100 times faster than going through the process of dynamically
+ # generating the content. Unfortunately, this incredible speed-up is only available to stateless pages where all visitors
+ # are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are a great fit
+ # for this approach, but account-based systems where people log in and manipulate their own data are often less likely candidates.
+ #
+ # Specifying which actions to cache is done through the <tt>caches</tt> class method:
+ #
+ # class WeblogController < ActionController::Base
+ # caches_page :show, :new
+ # end
+ #
+ # This will generate cache files such as weblog/show/5 and weblog/new, which match the URLs used to trigger the dynamic
+ # generation. This is how the web server is able pick up a cache file when it exists and otherwise let the request pass on to
+ # the Action Pack to generate it.
+ #
+ # Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache
+ # is not restored before another hit is made against it. The API for doing so mimics the options from url_for and friends:
+ #
+ # class WeblogController < ActionController::Base
+ # def update
+ # List.update(params[:list][:id], params[:list])
+ # expire_page :action => "show", :id => params[:list][:id]
+ # redirect_to :action => "show", :id => params[:list][:id]
+ # end
+ # end
+ #
+ # Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
+ # expired.
+ #
+ # == Setting the cache directory
+ #
+ # The cache directory should be the document root for the web server and is set using Base.page_cache_directory = "/document/root".
+ # For Rails, this directory has already been set to RAILS_ROOT + "/public".
+ #
+ # == Setting the cache extension
+ #
+ # By default, the cache extension is .html, which makes it easy for the cached files to be picked up by the web server. If you want
+ # something else, like .php or .shtml, just set Base.page_cache_extension.
+ module Pages
+ def self.included(base) #:nodoc:
+ base.extend(ClassMethods)
+ base.class_eval do
+ @@page_cache_directory = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : ""
+ cattr_accessor :page_cache_directory
+
+ @@page_cache_extension = '.html'
+ cattr_accessor :page_cache_extension
+ end
+ end
+
+ module ClassMethods
+ # Expires the page that was cached with the +path+ as a key. Example:
+ # expire_page "/lists/show"
+ def expire_page(path)
+ return unless perform_caching
+
+ benchmark "Expired page: #{page_cache_file(path)}" do
+ File.delete(page_cache_path(path)) if File.exist?(page_cache_path(path))
+ end
+ end
+
+ # Manually cache the +content+ in the key determined by +path+. Example:
+ # cache_page "I'm the cached content", "/lists/show"
+ def cache_page(content, path)
+ return unless perform_caching
+
+ benchmark "Cached page: #{page_cache_file(path)}" do
+ FileUtils.makedirs(File.dirname(page_cache_path(path)))
+ File.open(page_cache_path(path), "wb+") { |f| f.write(content) }
+ end
+ end
+
+ # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
+ # matches the triggering url.
+ def caches_page(*actions)
+ return unless perform_caching
+ actions = actions.map(&:to_s)
+ after_filter { |c| c.cache_page if actions.include?(c.action_name) }
+ end
+
+ private
+ def page_cache_file(path)
+ name = (path.empty? || path == "/") ? "/index" : URI.unescape(path.chomp('/'))
+ name << page_cache_extension unless (name.split('/').last || name).include? '.'
+ return name
+ end
+
+ def page_cache_path(path)
+ page_cache_directory + page_cache_file(path)
+ end
+ end
+
+ # Expires the page that was cached with the +options+ as a key. Example:
+ # expire_page :controller => "lists", :action => "show"
+ def expire_page(options = {})
+ return unless perform_caching
+
+ if options.is_a?(Hash)
+ if options[:action].is_a?(Array)
+ options[:action].dup.each do |action|
+ self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action)))
+ end
+ else
+ self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true)))
+ end
+ else
+ self.class.expire_page(options)
+ end
+ end
+
+ # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used
+ # If no options are provided, the requested url is used. Example:
+ # cache_page "I'm the cached content", :controller => "lists", :action => "show"
+ def cache_page(content = nil, options = nil)
+ return unless perform_caching && caching_allowed
+
+ path = case options
+ when Hash
+ url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format]))
+ when String
+ options
+ else
+ request.path
+ end
+
+ self.class.cache_page(content || response.body, path)
+ end
+
+ private
+ def caching_allowed
+ request.get? && response.headers['Status'].to_i == 200
+ end
+ end
+
+ # Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching,
+ # every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which
+ # allows for authentication and other restrictions on whether someone is allowed to see the cache. Example:
+ #
+ # class ListsController < ApplicationController
+ # before_filter :authenticate, :except => :public
+ # caches_page :public
+ # caches_action :show, :feed
+ # end
+ #
+ # In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the
+ # show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches.
+ #
+ # Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both
+ # the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named
+ # "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and
+ # "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern.
+ #
+ # Different representations of the same resource, e.g. <tt>http://david.somewhere.com/lists</tt> and <tt>http://david.somewhere.com/lists.xml</tt>
+ # are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that <tt>:action => 'lists'</tt> is not the same
+ # as <tt>:action => 'list', :format => :xml</tt>.
+ #
+ # You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy
+ # for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance.
+ #
+ # class ListsController < ApplicationController
+ # before_filter :authenticate, :except => :public
+ # caches_page :public
+ # caches_action :show, :cache_path => { :project => 1 }
+ # caches_action :show, :cache_path => Proc.new { |controller|
+ # controller.params[:user_id] ?
+ # controller.send(:user_list_url, c.params[:user_id], c.params[:id]) :
+ # controller.send(:list_url, c.params[:id]) }
+ # end
+ module Actions
+ def self.included(base) #:nodoc:
+ base.extend(ClassMethods)
+ base.class_eval do
+ attr_accessor :rendered_action_cache, :action_cache_path
+ alias_method_chain :protected_instance_variables, :action_caching
+ end
+ end
+
+ module ClassMethods
+ # Declares that +actions+ should be cached.
+ # See ActionController::Caching::Actions for details.
+ def caches_action(*actions)
+ return unless perform_caching
+ around_filter(ActionCacheFilter.new(*actions))
+ end
+ end
+
+ def protected_instance_variables_with_action_caching
+ protected_instance_variables_without_action_caching + %w(@action_cache_path)
+ end
+
+ def expire_action(options = {})
+ return unless perform_caching
+ if options[:action].is_a?(Array)
+ options[:action].dup.each do |action|
+ expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action })))
+ end
+ else
+ expire_fragment(ActionCachePath.path_for(self, options))
+ end
+ end
+
+ class ActionCacheFilter #:nodoc:
+ def initialize(*actions, &block)
+ @options = actions.extract_options!
+ @actions = Set.new actions
+ end
+
+ def before(controller)
+ return unless @actions.include?(controller.action_name.intern)
+ cache_path = ActionCachePath.new(controller, path_options_for(controller, @options))
+ if cache = controller.read_fragment(cache_path.path)
+ controller.rendered_action_cache = true
+ set_content_type!(controller, cache_path.extension)
+ controller.send!(:render_for_text, cache)
+ false
+ else
+ controller.action_cache_path = cache_path
+ end
+ end
+
+ def after(controller)
+ return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache || !caching_allowed(controller)
+ controller.write_fragment(controller.action_cache_path.path, controller.response.body)
+ end
+
+ private
+ def set_content_type!(controller, extension)
+ controller.response.content_type = Mime::Type.lookup_by_extension(extension).to_s if extension
+ end
+
+ def path_options_for(controller, options)
+ ((path_options = options[:cache_path]).respond_to?(:call) ? path_options.call(controller) : path_options) || {}
+ end
+
+ def caching_allowed(controller)
+ controller.request.get? && controller.response.headers['Status'].to_i == 200
+ end
+ end
+
+ class ActionCachePath
+ attr_reader :path, :extension
+
+ class << self
+ def path_for(controller, options)
+ new(controller, options).path
+ end
+ end
+
+ def initialize(controller, options = {})
+ @extension = extract_extension(controller.request.path)
+ path = controller.url_for(options).split('://').last
+ normalize!(path)
+ add_extension!(path, @extension)
+ @path = URI.unescape(path)
+ end
+
+ private
+ def normalize!(path)
+ path << 'index' if path[-1] == ?/
+ end
+
+ def add_extension!(path, extension)
+ path << ".#{extension}" if extension
+ end
+
+ def extract_extension(file_path)
+ # Don't want just what comes after the last '.' to accommodate multi part extensions
+ # such as tar.gz.
+ file_path[/^[^.]+\.(.+)$/, 1]
+ end
+ end
+ end
+
+ # Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when
+ # certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple
+ # parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like:
+ #
+ # <b>Hello <%= @name %></b>
+ # <% cache do %>
+ # All the topics in the system:
+ # <%= render :partial => "topic", :collection => Topic.find(:all) %>
+ # <% end %>
+ #
+ # This cache will bind to the name of the action that called it, so if this code was part of the view for the topics/list action, you would
+ # be able to invalidate it using <tt>expire_fragment(:controller => "topics", :action => "list")</tt>.
+ #
+ # This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using
+ # <tt>caches_action</tt>, so we also have the option to qualify the name of the cached fragment with something like:
+ #
+ # <% cache(:action => "list", :action_suffix => "all_topics") do %>
+ #
+ # That would result in a name such as "/topics/list/all_topics", avoiding conflicts with the action cache and with any fragments that use a
+ # different suffix. Note that the URL doesn't have to really exist or be callable - the url_for system is just used to generate unique
+ # cache names that we can refer to when we need to expire the cache.
+ #
+ # The expiration call for this example is:
+ #
+ # expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")
+ #
+ # == Fragment stores
+ #
+ # By default, cached fragments are stored in memory. The available store options are:
+ #
+ # * FileStore: Keeps the fragments on disk in the +cache_path+, which works well for all types of environments and allows all
+ # processes running from the same application directory to access the cached content.
+ # * MemoryStore: Keeps the fragments in memory, which is fine for WEBrick and for FCGI (if you don't care that each FCGI process holds its
+ # own fragment store). It's not suitable for CGI as the process is thrown away at the end of each request. It can potentially also take
+ # up a lot of memory since each process keeps all the caches in memory.
+ # * DRbStore: Keeps the fragments in the memory of a separate, shared DRb process. This works for all environments and only keeps one cache
+ # around for all processes, but requires that you run and manage a separate DRb process.
+ # * MemCacheStore: Works like DRbStore, but uses Danga's MemCache instead.
+ # Requires the ruby-memcache library: gem install ruby-memcache.
+ #
+ # Configuration examples (MemoryStore is the default):
+ #
+ # ActionController::Base.fragment_cache_store = :memory_store
+ # ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory"
+ # ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192"
+ # ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost"
+ # ActionController::Base.fragment_cache_store = MyOwnStore.new("parameter")
+ module Fragments
+ def self.included(base) #:nodoc:
+ base.class_eval do
+ @@fragment_cache_store = MemoryStore.new
+ cattr_reader :fragment_cache_store
+
+ # Defines the storage option for cached fragments
+ def self.fragment_cache_store=(store_option)
+ store, *parameters = *([ store_option ].flatten)
+ @@fragment_cache_store = if store.is_a?(Symbol)
+ store_class_name = (store == :drb_store ? "DRbStore" : store.to_s.camelize)
+ store_class = ActionController::Caching::Fragments.const_get(store_class_name)
+ store_class.new(*parameters)
+ else
+ store
+ end
+ end
+ end
+ end
+
+ # Given a name (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading,
+ # writing, or expiring a cached fragment. If the name is a hash, the generated name is the return
+ # value of url_for on that hash (without the protocol).
+ def fragment_cache_key(name)
+ name.is_a?(Hash) ? url_for(name).split("://").last : name
+ end
+
+ # Called by CacheHelper#cache
+ def cache_erb_fragment(block, name = {}, options = nil)
+ unless perform_caching then block.call; return end
+
+ buffer = eval(ActionView::Base.erb_variable, block.binding)
+
+ if cache = read_fragment(name, options)
+ buffer.concat(cache)
+ else
+ pos = buffer.length
+ block.call
+ write_fragment(name, buffer[pos..-1], options)
+ end
+ end
+
+ # Writes <tt>content</tt> to the location signified by <tt>name</tt> (see <tt>expire_fragment</tt> for acceptable formats)
+ def write_fragment(name, content, options = nil)
+ return unless perform_caching
+
+ key = fragment_cache_key(name)
+ self.class.benchmark "Cached fragment: #{key}" do
+ fragment_cache_store.write(key, content, options)
+ end
+ content
+ end
+
+ # Reads a cached fragment from the location signified by <tt>name</tt> (see <tt>expire_fragment</tt> for acceptable formats)
+ def read_fragment(name, options = nil)
+ return unless perform_caching
+
+ key = fragment_cache_key(name)
+ self.class.benchmark "Fragment read: #{key}" do
+ fragment_cache_store.read(key, options)
+ end
+ end
+
+ # Name can take one of three forms:
+ # * String: This would normally take the form of a path like "pages/45/notes"
+ # * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
+ # * Regexp: Will destroy all the matched fragments, example:
+ # %r{pages/\d*/notes}
+ # Ensure you do not specify start and finish in the regex (^$) because
+ # the actual filename matched looks like ./cache/filename/path.cache
+ # Regexp expiration is only supported on caches that can iterate over
+ # all keys (unlike memcached).
+ def expire_fragment(name, options = nil)
+ return unless perform_caching
+
+ key = fragment_cache_key(name)
+
+ if key.is_a?(Regexp)
+ self.class.benchmark "Expired fragments matching: #{key.source}" do
+ fragment_cache_store.delete_matched(key, options)
+ end
+ else
+ self.class.benchmark "Expired fragment: #{key}" do
+ fragment_cache_store.delete(key, options)
+ end
+ end
+ end
+
+
+ class UnthreadedMemoryStore #:nodoc:
+ def initialize #:nodoc:
+ @data = {}
+ end
+
+ def read(name, options=nil) #:nodoc:
+ @data[name]
+ end
+
+ def write(name, value, options=nil) #:nodoc:
+ @data[name] = value
+ end
+
+ def delete(name, options=nil) #:nodoc:
+ @data.delete(name)
+ end
+
+ def delete_matched(matcher, options=nil) #:nodoc:
+ @data.delete_if { |k,v| k =~ matcher }
+ end
+ end
+
+ module ThreadSafety #:nodoc:
+ def read(name, options=nil) #:nodoc:
+ @mutex.synchronize { super }
+ end
+
+ def write(name, value, options=nil) #:nodoc:
+ @mutex.synchronize { super }
+ end
+
+ def delete(name, options=nil) #:nodoc:
+ @mutex.synchronize { super }
+ end
+
+ def delete_matched(matcher, options=nil) #:nodoc:
+ @mutex.synchronize { super }
+ end
+ end
+
+ class MemoryStore < UnthreadedMemoryStore #:nodoc:
+ def initialize #:nodoc:
+ super
+ if ActionController::Base.allow_concurrency
+ @mutex = Mutex.new
+ MemoryStore.module_eval { include ThreadSafety }
+ end
+ end
+ end
+
+ class DRbStore < MemoryStore #:nodoc:
+ attr_reader :address
+
+ def initialize(address = 'druby://localhost:9192')
+ super()
+ @address = address
+ @data = DRbObject.new(nil, address)
+ end
+ end
+
+ begin
+ require_library_or_gem 'memcache'
+ class MemCacheStore < MemoryStore #:nodoc:
+ attr_reader :addresses
+
+ def initialize(*addresses)
+ super()
+ addresses = addresses.flatten
+ addresses = ["localhost"] if addresses.empty?
+ @addresses = addresses
+ @data = MemCache.new(*addresses)
+ end
+ end
+ rescue LoadError
+ # MemCache wasn't available so neither can the store be
+ end
+
+ class UnthreadedFileStore #:nodoc:
+ attr_reader :cache_path
+
+ def initialize(cache_path)
+ @cache_path = cache_path
+ end
+
+ def write(name, value, options = nil) #:nodoc:
+ ensure_cache_path(File.dirname(real_file_path(name)))
+ File.open(real_file_path(name), "wb+") { |f| f.write(value) }
+ rescue => e
+ Base.logger.error "Couldn't create cache directory: #{name} (#{e.message})" if Base.logger
+ end
+
+ def read(name, options = nil) #:nodoc:
+ File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil
+ end
+
+ def delete(name, options) #:nodoc:
+ File.delete(real_file_path(name))
+ rescue SystemCallError => e
+ # If there's no cache, then there's nothing to complain about
+ end
+
+ def delete_matched(matcher, options) #:nodoc:
+ search_dir(@cache_path) do |f|
+ if f =~ matcher
+ begin
+ File.delete(f)
+ rescue SystemCallError => e
+ # If there's no cache, then there's nothing to complain about
+ end
+ end
+ end
+ end
+
+ private
+ def real_file_path(name)
+ '%s/%s.cache' % [@cache_path, name.gsub('?', '.').gsub(':', '.')]
+ end
+
+ def ensure_cache_path(path)
+ FileUtils.makedirs(path) unless File.exist?(path)
+ end
+
+ def search_dir(dir, &callback)
+ Dir.foreach(dir) do |d|
+ next if d == "." || d == ".."
+ name = File.join(dir, d)
+ if File.directory?(name)
+ search_dir(name, &callback)
+ else
+ callback.call name
+ end
+ end
+ end
+ end
+
+ class FileStore < UnthreadedFileStore #:nodoc:
+ def initialize(cache_path)
+ super(cache_path)
+ if ActionController::Base.allow_concurrency
+ @mutex = Mutex.new
+ FileStore.module_eval { include ThreadSafety }
+ end
+ end
+ end
+ end
+
+ # Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change.
+ # They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example:
+ #
+ # class ListSweeper < ActionController::Caching::Sweeper
+ # observe List, Item
+ #
+ # def after_save(record)
+ # list = record.is_a?(List) ? record : record.list
+ # expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id)
+ # expire_action(:controller => "lists", :action => "all")
+ # list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
+ # end
+ # end
+ #
+ # The sweeper is assigned in the controllers that wish to have its job performed using the <tt>cache_sweeper</tt> class method:
+ #
+ # class ListsController < ApplicationController
+ # caches_action :index, :show, :public, :feed
+ # cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ]
+ # end
+ #
+ # In the example above, four actions are cached and three actions are responsible for expiring those caches.
+ module Sweeping
+ def self.included(base) #:nodoc:
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods #:nodoc:
+ def cache_sweeper(*sweepers)
+ return unless perform_caching
+ configuration = sweepers.extract_options!
+ sweepers.each do |sweeper|
+ ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
+ sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance
+
+ if sweeper_instance.is_a?(Sweeper)
+ around_filter(sweeper_instance, :only => configuration[:only])
+ else
+ after_filter(sweeper_instance, :only => configuration[:only])
+ end
+ end
+ end
+ end
+ end
+
+ if defined?(ActiveRecord) and defined?(ActiveRecord::Observer)
+ class Sweeper < ActiveRecord::Observer #:nodoc:
+ attr_accessor :controller
+
+ def before(controller)
+ self.controller = controller
+ callback(:before)
+ end
+
+ def after(controller)
+ callback(:after)
+ # Clean up, so that the controller can be collected after this request
+ self.controller = nil
+ end
+
+ protected
+ # gets the action cache path for the given options.
+ def action_path_for(options)
+ ActionController::Caching::Actions::ActionCachePath.path_for(controller, options)
+ end
+
+ # Retrieve instance variables set in the controller.
+ def assigns(key)
+ controller.instance_variable_get("@#{key}")
+ end
+
+ private
+ def callback(timing)
+ controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
+ action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
+
+ send!(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
+ send!(action_callback_method_name) if respond_to?(action_callback_method_name, true)
+ end
+
+ def method_missing(method, *arguments)
+ return if @controller.nil?
+ @controller.send!(method, *arguments)
+ end
+ end
+ end
+
+ module SqlCache
+ def self.included(base) #:nodoc:
+ if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache)
+ base.alias_method_chain :perform_action, :caching
+ end
+ end
+
+ def perform_action_with_caching
+ ActiveRecord::Base.cache do
+ perform_action_without_caching
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext.rb
new file mode 100644
index 000000000..f3b8c08d8
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext.rb
@@ -0,0 +1,16 @@
+require 'action_controller/cgi_ext/stdinput'
+require 'action_controller/cgi_ext/query_extension'
+require 'action_controller/cgi_ext/cookie'
+require 'action_controller/cgi_ext/session'
+
+class CGI #:nodoc:
+ include ActionController::CgiExt::Stdinput
+
+ class << self
+ alias :escapeHTML_fail_on_nil :escapeHTML
+
+ def escapeHTML(string)
+ escapeHTML_fail_on_nil(string) unless string.nil?
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/cookie.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/cookie.rb
new file mode 100644
index 000000000..07d2f08d5
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/cookie.rb
@@ -0,0 +1,106 @@
+CGI.module_eval { remove_const "Cookie" }
+
+# TODO: document how this differs from stdlib CGI::Cookie
+class CGI #:nodoc:
+ class Cookie < DelegateClass(Array)
+ attr_accessor :name, :value, :path, :domain, :expires
+ attr_reader :secure, :http_only
+
+ # Create a new CGI::Cookie object.
+ #
+ # The contents of the cookie can be specified as a +name+ and one
+ # or more +value+ arguments. Alternatively, the contents can
+ # be specified as a single hash argument. The possible keywords of
+ # this hash are as follows:
+ #
+ # name:: the name of the cookie. Required.
+ # value:: the cookie's value or list of values.
+ # path:: the path for which this cookie applies. Defaults to the
+ # base directory of the CGI script.
+ # domain:: the domain for which this cookie applies.
+ # expires:: the time at which this cookie expires, as a +Time+ object.
+ # secure:: whether this cookie is a secure cookie or not (default to
+ # false). Secure cookies are only transmitted to HTTPS
+ # servers.
+ # http_only:: whether this cookie can be accessed by client side scripts (e.g. document.cookie) or only over HTTP
+ # More details: http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx
+ # Defaults to false.
+ # These keywords correspond to attributes of the cookie object.
+ def initialize(name = '', *value)
+ if name.kind_of?(String)
+ @name = name
+ @value = Array(value)
+ @domain = nil
+ @expires = nil
+ @secure = false
+ @http_only = false
+ @path = nil
+ else
+ @name = name['name']
+ @value = Array(name['value'])
+ @domain = name['domain']
+ @expires = name['expires']
+ @secure = name['secure'] || false
+ @http_only = name['http_only'] || false
+ @path = name['path']
+ end
+
+ raise ArgumentError, "`name' required" unless @name
+
+ # simple support for IE
+ unless @path
+ %r|^(.*/)|.match(ENV['SCRIPT_NAME'])
+ @path = ($1 or '')
+ end
+
+ super(@value)
+ end
+
+ # Set whether the Cookie is a secure cookie or not.
+ def secure=(val)
+ @secure = val == true
+ end
+
+ # Set whether the Cookie is an HTTP only cookie or not.
+ def http_only=(val)
+ @http_only = val == true
+ end
+
+ # Convert the Cookie to its string representation.
+ def to_s
+ buf = ''
+ buf << @name << '='
+ buf << (@value.kind_of?(String) ? CGI::escape(@value) : @value.collect{|v| CGI::escape(v) }.join("&"))
+ buf << '; domain=' << @domain if @domain
+ buf << '; path=' << @path if @path
+ buf << '; expires=' << CGI::rfc1123_date(@expires) if @expires
+ buf << '; secure' if @secure
+ buf << '; HttpOnly' if @http_only
+ buf
+ end
+
+ # Parse a raw cookie string into a hash of cookie-name=>Cookie
+ # pairs.
+ #
+ # cookies = CGI::Cookie::parse("raw_cookie_string")
+ # # { "name1" => cookie1, "name2" => cookie2, ... }
+ #
+ def self.parse(raw_cookie)
+ cookies = Hash.new([])
+
+ if raw_cookie
+ raw_cookie.split(/[;,]\s?/).each do |pairs|
+ name, values = pairs.split('=',2)
+ next unless name and values
+ name = CGI::unescape(name)
+ values = values.split('&').collect!{|v| CGI::unescape(v) }
+ unless cookies.has_key?(name)
+ cookies[name] = new(name, *values)
+ end
+ end
+ end
+
+ cookies
+ end
+ end # class Cookie
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/query_extension.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/query_extension.rb
new file mode 100644
index 000000000..9620fd287
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/query_extension.rb
@@ -0,0 +1,22 @@
+require 'cgi'
+
+class CGI #:nodoc:
+ module QueryExtension
+ # Remove the old initialize_query method before redefining it.
+ remove_method :initialize_query
+
+ # Neuter CGI parameter parsing.
+ def initialize_query
+ # Fix some strange request environments.
+ env_table['REQUEST_METHOD'] ||= 'GET'
+
+ # POST assumes missing Content-Type is application/x-www-form-urlencoded.
+ if env_table['CONTENT_TYPE'].blank? && env_table['REQUEST_METHOD'] == 'POST'
+ env_table['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
+ end
+
+ @cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
+ @params = {}
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/session.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/session.rb
new file mode 100644
index 000000000..a01f17f9c
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/session.rb
@@ -0,0 +1,73 @@
+require 'digest/md5'
+require 'cgi/session'
+require 'cgi/session/pstore'
+
+class CGI #:nodoc:
+ # * Expose the CGI instance to session stores.
+ # * Don't require 'digest/md5' whenever a new session id is generated.
+ class Session #:nodoc:
+ begin
+ require 'securerandom'
+
+ # Generate a 32-character unique id using SecureRandom.
+ # This is used to generate session ids but may be reused elsewhere.
+ def self.generate_unique_id(constant = nil)
+ SecureRandom.hex(16)
+ end
+ rescue LoadError
+ # Generate an 32-character unique id based on a hash of the current time,
+ # a random number, the process id, and a constant string. This is used
+ # to generate session ids but may be reused elsewhere.
+ def self.generate_unique_id(constant = 'foobar')
+ md5 = Digest::MD5.new
+ now = Time.now
+ md5 << now.to_s
+ md5 << String(now.usec)
+ md5 << String(rand(0))
+ md5 << String($$)
+ md5 << constant
+ md5.hexdigest
+ end
+ end
+
+ # Make the CGI instance available to session stores.
+ attr_reader :cgi
+ attr_reader :dbman
+ alias_method :initialize_without_cgi_reader, :initialize
+ def initialize(cgi, options = {})
+ @cgi = cgi
+ initialize_without_cgi_reader(cgi, options)
+ end
+
+ private
+ # Create a new session id.
+ def create_new_id
+ @new_session = true
+ self.class.generate_unique_id
+ end
+
+ # * Don't require 'digest/md5' whenever a new session is started.
+ class PStore #:nodoc:
+ def initialize(session, option={})
+ dir = option['tmpdir'] || Dir::tmpdir
+ prefix = option['prefix'] || ''
+ id = session.session_id
+ md5 = Digest::MD5.hexdigest(id)[0,16]
+ path = dir+"/"+prefix+md5
+ path.untaint
+ if File::exist?(path)
+ @hash = nil
+ else
+ unless session.new_session
+ raise CGI::Session::NoSession, "uninitialized session"
+ end
+ @hash = {}
+ end
+ @p = ::PStore.new(path)
+ @p.transaction do |p|
+ File.chmod(0600, p.path)
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/stdinput.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/stdinput.rb
new file mode 100644
index 000000000..b0ca63ef2
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/stdinput.rb
@@ -0,0 +1,23 @@
+require 'cgi'
+
+module ActionController
+ module CgiExt
+ # Publicize the CGI's internal input stream so we can lazy-read
+ # request.body. Make it writable so we don't have to play $stdin games.
+ module Stdinput
+ def self.included(base)
+ base.class_eval do
+ remove_method :stdinput
+ attr_accessor :stdinput
+ end
+
+ base.alias_method_chain :initialize, :stdinput
+ end
+
+ def initialize_with_stdinput(type = nil, stdinput = $stdin)
+ @stdinput = stdinput
+ initialize_without_stdinput(type || 'query')
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_process.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_process.rb
new file mode 100644
index 000000000..6a802aa8f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_process.rb
@@ -0,0 +1,221 @@
+require 'action_controller/cgi_ext'
+require 'action_controller/session/cookie_store'
+
+module ActionController #:nodoc:
+ class Base
+ # Process a request extracted from an CGI object and return a response. Pass false as <tt>session_options</tt> to disable
+ # sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
+ #
+ # * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
+ # (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
+ # lib/action_controller/session.
+ # * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
+ # * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ cookie, or
+ # automatically generated for a new session.
+ # * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
+ # exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
+ # an ArgumentError is raised.
+ # * <tt>:session_expires</tt> - the time the current session expires, as a +Time+ object. If not set, the session will continue
+ # indefinitely.
+ # * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the
+ # server.
+ # * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
+ # * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
+ # * <tt>:cookie_only</tt> - if +true+ (the default), session IDs will only be accepted from cookies and not from
+ # the query string or POST parameters. This protects against session fixation attacks.
+ def self.process_cgi(cgi = CGI.new, session_options = {})
+ new.process_cgi(cgi, session_options)
+ end
+
+ def process_cgi(cgi, session_options = {}) #:nodoc:
+ process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
+ end
+ end
+
+ class CgiRequest < AbstractRequest #:nodoc:
+ attr_accessor :cgi, :session_options
+ class SessionFixationAttempt < StandardError; end #:nodoc:
+
+ DEFAULT_SESSION_OPTIONS = {
+ :database_manager => CGI::Session::CookieStore, # store data in cookie
+ :prefix => "ruby_sess.", # prefix session file names
+ :session_path => "/", # available to all paths in app
+ :session_key => "_session_id",
+ :cookie_only => true
+ } unless const_defined?(:DEFAULT_SESSION_OPTIONS)
+
+ def initialize(cgi, session_options = {})
+ @cgi = cgi
+ @session_options = session_options
+ @env = @cgi.send!(:env_table)
+ super()
+ end
+
+ def query_string
+ qs = @cgi.query_string if @cgi.respond_to?(:query_string)
+ if !qs.blank?
+ qs
+ else
+ super
+ end
+ end
+
+ # The request body is an IO input stream. If the RAW_POST_DATA environment
+ # variable is already set, wrap it in a StringIO.
+ def body
+ if raw_post = env['RAW_POST_DATA']
+ StringIO.new(raw_post)
+ else
+ @cgi.stdinput
+ end
+ end
+
+ def query_parameters
+ @query_parameters ||= self.class.parse_query_parameters(query_string)
+ end
+
+ def request_parameters
+ @request_parameters ||= parse_formatted_request_parameters
+ end
+
+ def cookies
+ @cgi.cookies.freeze
+ end
+
+ def host_with_port_without_standard_port_handling
+ if forwarded = env["HTTP_X_FORWARDED_HOST"]
+ forwarded.split(/,\s?/).last
+ elsif http_host = env['HTTP_HOST']
+ http_host
+ elsif server_name = env['SERVER_NAME']
+ server_name
+ else
+ "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
+ end
+ end
+
+ def host
+ host_with_port_without_standard_port_handling.sub(/:\d+$/, '')
+ end
+
+ def port
+ if host_with_port_without_standard_port_handling =~ /:(\d+)$/
+ $1.to_i
+ else
+ standard_port
+ end
+ end
+
+ def session
+ unless defined?(@session)
+ if @session_options == false
+ @session = Hash.new
+ else
+ stale_session_check! do
+ if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
+ raise SessionFixationAttempt
+ end
+ case value = session_options_with_string_keys['new_session']
+ when true
+ @session = new_session
+ when false
+ begin
+ @session = CGI::Session.new(@cgi, session_options_with_string_keys)
+ # CGI::Session raises ArgumentError if 'new_session' == false
+ # and no session cookie or query param is present.
+ rescue ArgumentError
+ @session = Hash.new
+ end
+ when nil
+ @session = CGI::Session.new(@cgi, session_options_with_string_keys)
+ else
+ raise ArgumentError, "Invalid new_session option: #{value}"
+ end
+ @session['__valid_session']
+ end
+ end
+ end
+ @session
+ end
+
+ def reset_session
+ @session.delete if defined?(@session) && @session.is_a?(CGI::Session)
+ @session = new_session
+ end
+
+ def method_missing(method_id, *arguments)
+ @cgi.send!(method_id, *arguments) rescue super
+ end
+
+ private
+ # Delete an old session if it exists then create a new one.
+ def new_session
+ if @session_options == false
+ Hash.new
+ else
+ CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
+ CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
+ end
+ end
+
+ def cookie_only?
+ session_options_with_string_keys['cookie_only']
+ end
+
+ def stale_session_check!
+ yield
+ rescue ArgumentError => argument_error
+ if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
+ begin
+ # Note that the regexp does not allow $1 to end with a ':'
+ $1.constantize
+ rescue LoadError, NameError => const_error
+ raise ActionController::SessionRestoreError, <<-end_msg
+Session contains objects whose class definition isn\'t available.
+Remember to require the classes for all objects kept in the session.
+(Original exception: #{const_error.message} [#{const_error.class}])
+end_msg
+ end
+
+ retry
+ else
+ raise
+ end
+ end
+
+ def session_options_with_string_keys
+ @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
+ end
+ end
+
+ class CgiResponse < AbstractResponse #:nodoc:
+ def initialize(cgi)
+ @cgi = cgi
+ super()
+ end
+
+ def out(output = $stdout)
+ output.binmode if output.respond_to?(:binmode)
+ output.sync = false if output.respond_to?(:sync=)
+
+ begin
+ output.write(@cgi.header(@headers))
+
+ if @cgi.send!(:env_table)['REQUEST_METHOD'] == 'HEAD'
+ return
+ elsif @body.respond_to?(:call)
+ # Flush the output now in case the @body Proc uses
+ # #syswrite.
+ output.flush if output.respond_to?(:flush)
+ @body.call(self, output)
+ else
+ output.write(@body)
+ end
+
+ output.flush if output.respond_to?(:flush)
+ rescue Errno::EPIPE, Errno::ECONNRESET
+ # lost connection to parent process, ignore output
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/components.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/components.rb
new file mode 100644
index 000000000..7f7ecfff7
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/components.rb
@@ -0,0 +1,165 @@
+module ActionController #:nodoc:
+ # Components allow you to call other actions for their rendered response while executing another action. You can either delegate
+ # the entire response rendering or you can mix a partial response in with your other content.
+ #
+ # class WeblogController < ActionController::Base
+ # # Performs a method and then lets hello_world output its render
+ # def delegate_action
+ # do_other_stuff_before_hello_world
+ # render_component :controller => "greeter", :action => "hello_world", :params => { :person => "david" }
+ # end
+ # end
+ #
+ # class GreeterController < ActionController::Base
+ # def hello_world
+ # render :text => "#{params[:person]} says, Hello World!"
+ # end
+ # end
+ #
+ # The same can be done in a view to do a partial rendering:
+ #
+ # Let's see a greeting:
+ # <%= render_component :controller => "greeter", :action => "hello_world" %>
+ #
+ # It is also possible to specify the controller as a class constant, bypassing the inflector
+ # code to compute the controller class at runtime:
+ #
+ # <%= render_component :controller => GreeterController, :action => "hello_world" %>
+ #
+ # == When to use components
+ #
+ # Components should be used with care. They're significantly slower than simply splitting reusable parts into partials and
+ # conceptually more complicated. Don't use components as a way of separating concerns inside a single application. Instead,
+ # reserve components to those rare cases where you truly have reusable view and controller elements that can be employed
+ # across many applications at once.
+ #
+ # So to repeat: Components are a special-purpose approach that can often be replaced with better use of partials and filters.
+ module Components
+ def self.included(base) #:nodoc:
+ base.class_eval do
+ include InstanceMethods
+ extend ClassMethods
+
+ helper do
+ def render_component(options)
+ @controller.send!(:render_component_as_string, options)
+ end
+ end
+
+ # If this controller was instantiated to process a component request,
+ # +parent_controller+ points to the instantiator of this controller.
+ attr_accessor :parent_controller
+
+ alias_method_chain :process_cleanup, :components
+ alias_method_chain :set_session_options, :components
+ alias_method_chain :flash, :components
+
+ alias_method :component_request?, :parent_controller
+ end
+ end
+
+ module ClassMethods
+ # Track parent controller to identify component requests
+ def process_with_components(request, response, parent_controller = nil) #:nodoc:
+ controller = new
+ controller.parent_controller = parent_controller
+ controller.process(request, response)
+ end
+ end
+
+ module InstanceMethods
+ # Extracts the action_name from the request parameters and performs that action.
+ def process_with_components(request, response, method = :perform_action, *arguments) #:nodoc:
+ flash.discard if component_request?
+ process_without_components(request, response, method, *arguments)
+ end
+
+ protected
+ # Renders the component specified as the response for the current method
+ def render_component(options) #:doc:
+ component_logging(options) do
+ render_for_text(component_response(options, true).body, response.headers["Status"])
+ end
+ end
+
+ # Returns the component response as a string
+ def render_component_as_string(options) #:doc:
+ component_logging(options) do
+ response = component_response(options, false)
+
+ if redirected = response.redirected_to
+ render_component_as_string(redirected)
+ else
+ response.body
+ end
+ end
+ end
+
+ def flash_with_components(refresh = false) #:nodoc:
+ if !defined?(@_flash) || refresh
+ @_flash =
+ if defined?(@parent_controller)
+ @parent_controller.flash
+ else
+ flash_without_components
+ end
+ end
+ @_flash
+ end
+
+ private
+ def component_response(options, reuse_response)
+ klass = component_class(options)
+ request = request_for_component(klass.controller_name, options)
+ new_response = reuse_response ? response : response.dup
+
+ klass.process_with_components(request, new_response, self)
+ end
+
+ # determine the controller class for the component request
+ def component_class(options)
+ if controller = options[:controller]
+ controller.is_a?(Class) ? controller : "#{controller.camelize}Controller".constantize
+ else
+ self.class
+ end
+ end
+
+ # Create a new request object based on the current request.
+ # The new request inherits the session from the current request,
+ # bypassing any session options set for the component controller's class
+ def request_for_component(controller_name, options)
+ new_request = request.dup
+ new_request.session = request.session
+
+ new_request.instance_variable_set(
+ :@parameters,
+ (options[:params] || {}).with_indifferent_access.update(
+ "controller" => controller_name, "action" => options[:action], "id" => options[:id]
+ )
+ )
+
+ new_request
+ end
+
+ def component_logging(options)
+ if logger
+ logger.info "Start rendering component (#{options.inspect}): "
+ result = yield
+ logger.info "\n\nEnd of component rendering"
+ result
+ else
+ yield
+ end
+ end
+
+ def set_session_options_with_components(request)
+ set_session_options_without_components(request) unless component_request?
+ end
+
+ def process_cleanup_with_components
+ process_cleanup_without_components unless component_request?
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/cookies.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/cookies.rb
new file mode 100644
index 000000000..19847c695
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/cookies.rb
@@ -0,0 +1,84 @@
+module ActionController #:nodoc:
+ # Cookies are read and written through ActionController#cookies. The cookies being read are what were received along with the request,
+ # the cookies being written are what will be sent out with the response. Cookies are read by value (so you won't get the cookie object
+ # itself back -- just the value it holds). Examples for writing:
+ #
+ # cookies[:user_name] = "david" # => Will set a simple session cookie
+ # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
+ # # => Will set a cookie that expires in 1 hour
+ #
+ # Examples for reading:
+ #
+ # cookies[:user_name] # => "david"
+ # cookies.size # => 2
+ #
+ # Example for deleting:
+ #
+ # cookies.delete :user_name
+ #
+ # All the option symbols for setting cookies are:
+ #
+ # * <tt>value</tt> - the cookie's value or list of values (as an array).
+ # * <tt>path</tt> - the path for which this cookie applies. Defaults to the root of the application.
+ # * <tt>domain</tt> - the domain for which this cookie applies.
+ # * <tt>expires</tt> - the time at which this cookie expires, as a +Time+ object.
+ # * <tt>secure</tt> - whether this cookie is a secure cookie or not (default to false).
+ # Secure cookies are only transmitted to HTTPS servers.
+ # * <tt>http_only</tt> - whether this cookie is accessible via scripting or only HTTP (defaults to false).
+
+ module Cookies
+ def self.included(base)
+ base.helper_method :cookies
+ end
+
+ protected
+ # Returns the cookie container, which operates as described above.
+ def cookies
+ CookieJar.new(self)
+ end
+ end
+
+ class CookieJar < Hash #:nodoc:
+ def initialize(controller)
+ @controller, @cookies = controller, controller.request.cookies
+ super()
+ update(@cookies)
+ end
+
+ # Returns the value of the cookie by +name+ -- or nil if no such cookie exists. You set new cookies using cookies[]=
+ # (for simple name/value cookies without options).
+ def [](name)
+ cookie = @cookies[name.to_s]
+ if cookie && cookie.respond_to?(:value)
+ cookie.size > 1 ? cookie.value : cookie.value[0]
+ end
+ end
+
+ def []=(name, options)
+ if options.is_a?(Hash)
+ options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options }
+ options["name"] = name.to_s
+ else
+ options = { "name" => name.to_s, "value" => options }
+ end
+
+ set_cookie(options)
+ end
+
+ # Removes the cookie on the client machine by setting the value to an empty string
+ # and setting its expiration date into the past. Like []=, you can pass in an options
+ # hash to delete cookies with extra data such as a +path+.
+ def delete(name, options = {})
+ options.stringify_keys!
+ set_cookie(options.merge("name" => name.to_s, "value" => "", "expires" => Time.at(0)))
+ end
+
+ private
+ def set_cookie(options) #:doc:
+ options["path"] = "/" unless options["path"]
+ cookie = CGI::Cookie.new(options)
+ @controller.logger.info "Cookie set: #{cookie}" unless @controller.logger.nil?
+ @controller.response.headers["cookie"] << cookie
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/dispatcher.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/dispatcher.rb
new file mode 100644
index 000000000..c8656e4ba
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/dispatcher.rb
@@ -0,0 +1,195 @@
+module ActionController
+ # Dispatches requests to the appropriate controller and takes care of
+ # reloading the app after each request when Dependencies.load? is true.
+ class Dispatcher
+ class << self
+ # Backward-compatible class method takes CGI-specific args. Deprecated
+ # in favor of Dispatcher.new(output, request, response).dispatch.
+ def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
+ new(output).dispatch_cgi(cgi, session_options)
+ end
+
+ # Declare a block to be called before each dispatch.
+ # Run in the order declared.
+ def before_dispatch(*method_names, &block)
+ callbacks[:before].concat method_names
+ callbacks[:before] << block if block_given?
+ end
+
+ # Declare a block to be called after each dispatch.
+ # Run in reverse of the order declared.
+ def after_dispatch(*method_names, &block)
+ callbacks[:after].concat method_names
+ callbacks[:after] << block if block_given?
+ end
+
+ # Add a preparation callback. Preparation callbacks are run before every
+ # request in development mode, and before the first request in production
+ # mode.
+ #
+ # An optional identifier may be supplied for the callback. If provided,
+ # to_prepare may be called again with the same identifier to replace the
+ # existing callback. Passing an identifier is a suggested practice if the
+ # code adding a preparation block may be reloaded.
+ def to_prepare(identifier = nil, &block)
+ # Already registered: update the existing callback
+ if identifier
+ if callback = callbacks[:prepare].assoc(identifier)
+ callback[1] = block
+ else
+ callbacks[:prepare] << [identifier, block]
+ end
+ else
+ callbacks[:prepare] << block
+ end
+ end
+
+ # If the block raises, send status code as a last-ditch response.
+ def failsafe_response(fallback_output, status, originating_exception = nil)
+ yield
+ rescue Exception => exception
+ begin
+ log_failsafe_exception(status, originating_exception || exception)
+ body = failsafe_response_body(status)
+ fallback_output.write "Status: #{status}\r\nContent-Type: text/html\r\n\r\n#{body}"
+ nil
+ rescue Exception => failsafe_error # Logger or IO errors
+ $stderr.puts "Error during failsafe response: #{failsafe_error}"
+ $stderr.puts "(originally #{originating_exception})" if originating_exception
+ end
+ end
+
+ private
+ def failsafe_response_body(status)
+ error_path = "#{error_file_path}/#{status.to_s[0..3]}.html"
+
+ if File.exist?(error_path)
+ File.read(error_path)
+ else
+ "<html><body><h1>#{status}</h1></body></html>"
+ end
+ end
+
+ def log_failsafe_exception(status, exception)
+ message = "/!\\ FAILSAFE /!\\ #{Time.now}\n Status: #{status}\n"
+ message << " #{exception}\n #{exception.backtrace.join("\n ")}" if exception
+ failsafe_logger.fatal message
+ end
+
+ def failsafe_logger
+ if defined?(::RAILS_DEFAULT_LOGGER) && !::RAILS_DEFAULT_LOGGER.nil?
+ ::RAILS_DEFAULT_LOGGER
+ else
+ Logger.new($stderr)
+ end
+ end
+ end
+
+ cattr_accessor :error_file_path
+ self.error_file_path = "#{::RAILS_ROOT}/public" if defined? ::RAILS_ROOT
+
+ cattr_accessor :callbacks
+ self.callbacks = Hash.new { |h, k| h[k] = [] }
+
+ cattr_accessor :unprepared
+ self.unprepared = true
+
+
+ before_dispatch :reload_application
+ before_dispatch :prepare_application
+ after_dispatch :flush_logger
+ after_dispatch :cleanup_application
+
+ if defined? ActiveRecord
+ to_prepare :activerecord_instantiate_observers do
+ ActiveRecord::Base.instantiate_observers
+ end
+ end
+
+ def initialize(output, request = nil, response = nil)
+ @output, @request, @response = output, request, response
+ end
+
+ def dispatch
+ run_callbacks :before
+ handle_request
+ rescue Exception => exception
+ failsafe_rescue exception
+ ensure
+ run_callbacks :after, :reverse_each
+ end
+
+ def dispatch_cgi(cgi, session_options)
+ if cgi ||= self.class.failsafe_response(@output, '400 Bad Request') { CGI.new }
+ @request = CgiRequest.new(cgi, session_options)
+ @response = CgiResponse.new(cgi)
+ dispatch
+ end
+ rescue Exception => exception
+ failsafe_rescue exception
+ end
+
+ def reload_application
+ if Dependencies.load?
+ Routing::Routes.reload
+ self.unprepared = true
+ end
+ end
+
+ def prepare_application(force = false)
+ begin
+ require_dependency 'application' unless defined?(::ApplicationController)
+ rescue LoadError => error
+ raise unless error.message =~ /application\.rb/
+ end
+
+ ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord)
+
+ if unprepared || force
+ run_callbacks :prepare
+ self.unprepared = false
+ end
+ end
+
+ # Cleanup the application by clearing out loaded classes so they can
+ # be reloaded on the next request without restarting the server.
+ def cleanup_application(force = false)
+ if Dependencies.load? || force
+ ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
+ Dependencies.clear
+ ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
+ end
+ end
+
+ def flush_logger
+ RAILS_DEFAULT_LOGGER.flush if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:flush)
+ end
+
+ protected
+ def handle_request
+ @controller = Routing::Routes.recognize(@request)
+ @controller.process(@request, @response).out(@output)
+ end
+
+ def run_callbacks(kind, enumerator = :each)
+ callbacks[kind].send!(enumerator) do |callback|
+ case callback
+ when Proc; callback.call(self)
+ when String, Symbol; send!(callback)
+ when Array; callback[1].call(self)
+ else raise ArgumentError, "Unrecognized callback #{callback.inspect}"
+ end
+ end
+ end
+
+ def failsafe_rescue(exception)
+ self.class.failsafe_response(@output, '500 Internal Server Error', exception) do
+ if @controller ||= defined?(::ApplicationController) ? ::ApplicationController : Base
+ @controller.process_with_exception(@request, @response, exception).out(@output)
+ else
+ raise exception
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/filters.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/filters.rb
new file mode 100644
index 000000000..d7fb27617
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/filters.rb
@@ -0,0 +1,767 @@
+module ActionController #:nodoc:
+ module Filters #:nodoc:
+ def self.included(base)
+ base.class_eval do
+ extend ClassMethods
+ include ActionController::Filters::InstanceMethods
+ end
+ end
+
+ # Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do
+ # authentication, caching, or auditing before the intended action is performed. Or to do localization or output
+ # compression after the action has been performed. Filters have access to the request, response, and all the instance
+ # variables set by other filters in the chain or by the action (in the case of after filters).
+ #
+ # == Filter inheritance
+ #
+ # Controller inheritance hierarchies share filters downwards, but subclasses can also add or skip filters without
+ # affecting the superclass. For example:
+ #
+ # class BankController < ActionController::Base
+ # before_filter :audit
+ #
+ # private
+ # def audit
+ # # record the action and parameters in an audit log
+ # end
+ # end
+ #
+ # class VaultController < BankController
+ # before_filter :verify_credentials
+ #
+ # private
+ # def verify_credentials
+ # # make sure the user is allowed into the vault
+ # end
+ # end
+ #
+ # Now any actions performed on the BankController will have the audit method called before. On the VaultController,
+ # first the audit method is called, then the verify_credentials method. If the audit method renders or redirects, then
+ # verify_credentials and the intended action are never called.
+ #
+ # == Filter types
+ #
+ # A filter can take one of three forms: method reference (symbol), external class, or inline method (proc). The first
+ # is the most common and works by referencing a protected or private method somewhere in the inheritance hierarchy of
+ # the controller by use of a symbol. In the bank example above, both BankController and VaultController use this form.
+ #
+ # Using an external class makes for more easily reused generic filters, such as output compression. External filter classes
+ # are implemented by having a static +filter+ method on any class and then passing this class to the filter method. Example:
+ #
+ # class OutputCompressionFilter
+ # def self.filter(controller)
+ # controller.response.body = compress(controller.response.body)
+ # end
+ # end
+ #
+ # class NewspaperController < ActionController::Base
+ # after_filter OutputCompressionFilter
+ # end
+ #
+ # The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
+ # manipulate them as it sees fit.
+ #
+ # The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
+ # Or just as a quick test. It works like this:
+ #
+ # class WeblogController < ActionController::Base
+ # before_filter { |controller| head(400) if controller.params["stop_action"] }
+ # end
+ #
+ # As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables.
+ # This means that the block has access to both the request and response objects complete with convenience methods for params,
+ # session, template, and assigns. Note: The inline method doesn't strictly have to be a block; any object that responds to call
+ # and returns 1 or -1 on arity will do (such as a Proc or an Method object).
+ #
+ # Please note that around_filters function a little differently than the normal before and after filters with regard to filter
+ # types. Please see the section dedicated to around_filters below.
+ #
+ # == Filter chain ordering
+ #
+ # Using <tt>before_filter</tt> and <tt>after_filter</tt> appends the specified filters to the existing chain. That's usually
+ # just fine, but some times you care more about the order in which the filters are executed. When that's the case, you
+ # can use <tt>prepend_before_filter</tt> and <tt>prepend_after_filter</tt>. Filters added by these methods will be put at the
+ # beginning of their respective chain and executed before the rest. For example:
+ #
+ # class ShoppingController < ActionController::Base
+ # before_filter :verify_open_shop
+ #
+ # class CheckoutController < ShoppingController
+ # prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
+ #
+ # The filter chain for the CheckoutController is now <tt>:ensure_items_in_cart, :ensure_items_in_stock,</tt>
+ # <tt>:verify_open_shop</tt>. So if either of the ensure filters renders or redirects, we'll never get around to see if the shop
+ # is open or not.
+ #
+ # You may pass multiple filter arguments of each type as well as a filter block.
+ # If a block is given, it is treated as the last argument.
+ #
+ # == Around filters
+ #
+ # Around filters wrap an action, executing code both before and after.
+ # They may be declared as method references, blocks, or objects responding
+ # to #filter or to both #before and #after.
+ #
+ # To use a method as an around_filter, pass a symbol naming the Ruby method.
+ # Yield (or block.call) within the method to run the action.
+ #
+ # around_filter :catch_exceptions
+ #
+ # private
+ # def catch_exceptions
+ # yield
+ # rescue => exception
+ # logger.debug "Caught exception! #{exception}"
+ # raise
+ # end
+ #
+ # To use a block as an around_filter, pass a block taking as args both
+ # the controller and the action block. You can't call yield directly from
+ # an around_filter block; explicitly call the action block instead:
+ #
+ # around_filter do |controller, action|
+ # logger.debug "before #{controller.action_name}"
+ # action.call
+ # logger.debug "after #{controller.action_name}"
+ # end
+ #
+ # To use a filter object with around_filter, pass an object responding
+ # to :filter or both :before and :after. With a filter method, yield to
+ # the block as above:
+ #
+ # around_filter BenchmarkingFilter
+ #
+ # class BenchmarkingFilter
+ # def self.filter(controller, &block)
+ # Benchmark.measure(&block)
+ # end
+ # end
+ #
+ # With before and after methods:
+ #
+ # around_filter Authorizer.new
+ #
+ # class Authorizer
+ # # This will run before the action. Redirecting aborts the action.
+ # def before(controller)
+ # unless user.authorized?
+ # redirect_to(login_url)
+ # end
+ # end
+ #
+ # # This will run after the action if and only if before did not render or redirect.
+ # def after(controller)
+ # end
+ # end
+ #
+ # If the filter has before and after methods, the before method will be
+ # called before the action. If before renders or redirects, the filter chain is
+ # halted and after will not be run. See Filter Chain Halting below for
+ # an example.
+ #
+ # == Filter chain skipping
+ #
+ # Declaring a filter on a base class conveniently applies to its subclasses,
+ # but sometimes a subclass should skip some of its superclass' filters:
+ #
+ # class ApplicationController < ActionController::Base
+ # before_filter :authenticate
+ # around_filter :catch_exceptions
+ # end
+ #
+ # class WeblogController < ApplicationController
+ # # Will run the :authenticate and :catch_exceptions filters.
+ # end
+ #
+ # class SignupController < ApplicationController
+ # # Skip :authenticate, run :catch_exceptions.
+ # skip_before_filter :authenticate
+ # end
+ #
+ # class ProjectsController < ApplicationController
+ # # Skip :catch_exceptions, run :authenticate.
+ # skip_filter :catch_exceptions
+ # end
+ #
+ # class ClientsController < ApplicationController
+ # # Skip :catch_exceptions and :authenticate unless action is index.
+ # skip_filter :catch_exceptions, :authenticate, :except => :index
+ # end
+ #
+ # == Filter conditions
+ #
+ # Filters may be limited to specific actions by declaring the actions to
+ # include or exclude. Both options accept single actions (:only => :index)
+ # or arrays of actions (:except => [:foo, :bar]).
+ #
+ # class Journal < ActionController::Base
+ # # Require authentication for edit and delete.
+ # before_filter :authorize, :only => [:edit, :delete]
+ #
+ # # Passing options to a filter with a block.
+ # around_filter(:except => :index) do |controller, action_block|
+ # results = Profiler.run(&action_block)
+ # controller.response.sub! "</body>", "#{results}</body>"
+ # end
+ #
+ # private
+ # def authorize
+ # # Redirect to login unless authenticated.
+ # end
+ # end
+ #
+ # == Filter Chain Halting
+ #
+ # <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request
+ # before a controller action is run. This is useful, for example, to deny
+ # access to unauthenticated users or to redirect from http to https.
+ # Simply call render or redirect. After filters will not be executed if the filter
+ # chain is halted.
+ #
+ # Around filters halt the request unless the action block is called.
+ # Given these filters
+ # after_filter :after
+ # around_filter :around
+ # before_filter :before
+ #
+ # The filter chain will look like:
+ #
+ # ...
+ # . \
+ # . #around (code before yield)
+ # . . \
+ # . . #before (actual filter code is run)
+ # . . . \
+ # . . . execute controller action
+ # . . . /
+ # . . ...
+ # . . /
+ # . #around (code after yield)
+ # . /
+ # #after (actual filter code is run, unless the around filter does not yield)
+ #
+ # If #around returns before yielding, #after will still not be run. The #before
+ # filter and controller action will not be run. If #before renders or redirects,
+ # the second half of #around and will still run but #after and the
+ # action will not. If #around fails to yield, #after will not be run.
+ module ClassMethods
+ # The passed <tt>filters</tt> will be appended to the filter_chain and
+ # will execute before the action on this controller is performed.
+ def append_before_filter(*filters, &block)
+ append_filter_to_chain(filters, :before, &block)
+ end
+
+ # The passed <tt>filters</tt> will be prepended to the filter_chain and
+ # will execute before the action on this controller is performed.
+ def prepend_before_filter(*filters, &block)
+ prepend_filter_to_chain(filters, :before, &block)
+ end
+
+ # Shorthand for append_before_filter since it's the most common.
+ alias :before_filter :append_before_filter
+
+ # The passed <tt>filters</tt> will be appended to the array of filters
+ # that run _after_ actions on this controller are performed.
+ def append_after_filter(*filters, &block)
+ append_filter_to_chain(filters, :after, &block)
+ end
+
+ # The passed <tt>filters</tt> will be prepended to the array of filters
+ # that run _after_ actions on this controller are performed.
+ def prepend_after_filter(*filters, &block)
+ prepend_filter_to_chain(filters, :after, &block)
+ end
+
+ # Shorthand for append_after_filter since it's the most common.
+ alias :after_filter :append_after_filter
+
+
+ # If you append_around_filter A.new, B.new, the filter chain looks like
+ #
+ # B#before
+ # A#before
+ # # run the action
+ # A#after
+ # B#after
+ #
+ # With around filters which yield to the action block, #before and #after
+ # are the code before and after the yield.
+ def append_around_filter(*filters, &block)
+ filters, conditions = extract_conditions(filters, &block)
+ filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
+ append_filter_to_chain([filter, conditions])
+ end
+ end
+
+ # If you prepend_around_filter A.new, B.new, the filter chain looks like:
+ #
+ # A#before
+ # B#before
+ # # run the action
+ # B#after
+ # A#after
+ #
+ # With around filters which yield to the action block, #before and #after
+ # are the code before and after the yield.
+ def prepend_around_filter(*filters, &block)
+ filters, conditions = extract_conditions(filters, &block)
+ filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
+ prepend_filter_to_chain([filter, conditions])
+ end
+ end
+
+ # Shorthand for append_around_filter since it's the most common.
+ alias :around_filter :append_around_filter
+
+ # Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference
+ # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
+ # of many sub-controllers need a different hierarchy.
+ #
+ # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
+ # just like when you apply the filters.
+ def skip_before_filter(*filters)
+ skip_filter_in_chain(*filters, &:before?)
+ end
+
+ # Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
+ # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
+ # of many sub-controllers need a different hierarchy.
+ #
+ # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
+ # just like when you apply the filters.
+ def skip_after_filter(*filters)
+ skip_filter_in_chain(*filters, &:after?)
+ end
+
+ # Removes the specified filters from the filter chain. This only works for method reference (symbol)
+ # filters, not procs. This method is different from skip_after_filter and skip_before_filter in that
+ # it will match any before, after or yielding around filter.
+ #
+ # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
+ # just like when you apply the filters.
+ def skip_filter(*filters)
+ skip_filter_in_chain(*filters)
+ end
+
+ # Returns an array of Filter objects for this controller.
+ def filter_chain
+ read_inheritable_attribute("filter_chain") || []
+ end
+
+ # Returns all the before filters for this class and all its ancestors.
+ # This method returns the actual filter that was assigned in the controller to maintain existing functionality.
+ def before_filters #:nodoc:
+ filter_chain.select(&:before?).map(&:filter)
+ end
+
+ # Returns all the after filters for this class and all its ancestors.
+ # This method returns the actual filter that was assigned in the controller to maintain existing functionality.
+ def after_filters #:nodoc:
+ filter_chain.select(&:after?).map(&:filter)
+ end
+
+ # Returns a mapping between filters and the actions that may run them.
+ def included_actions #:nodoc:
+ @included_actions ||= read_inheritable_attribute("included_actions") || {}
+ end
+
+ # Returns a mapping between filters and actions that may not run them.
+ def excluded_actions #:nodoc:
+ @excluded_actions ||= read_inheritable_attribute("excluded_actions") || {}
+ end
+
+ # Find a filter in the filter_chain where the filter method matches the _filter_ param
+ # and (optionally) the passed block evaluates to true (mostly used for testing before?
+ # and after? on the filter). Useful for symbol filters.
+ #
+ # The object of type Filter is passed to the block when yielded, not the filter itself.
+ def find_filter(filter, &block) #:nodoc:
+ filter_chain.select { |f| f.filter == filter && (!block_given? || yield(f)) }.first
+ end
+
+ # Returns true if the filter is excluded from the given action
+ def filter_excluded_from_action?(filter,action) #:nodoc:
+ case
+ when ia = included_actions[filter]
+ !ia.include?(action)
+ when ea = excluded_actions[filter]
+ ea.include?(action)
+ end
+ end
+
+ # Filter class is an abstract base class for all filters. Handles all of the included/excluded actions but
+ # contains no logic for calling the actual filters.
+ class Filter #:nodoc:
+ attr_reader :filter, :included_actions, :excluded_actions
+
+ def initialize(filter)
+ @filter = filter
+ end
+
+ def type
+ :around
+ end
+
+ def before?
+ type == :before
+ end
+
+ def after?
+ type == :after
+ end
+
+ def around?
+ type == :around
+ end
+
+ def run(controller)
+ raise ActionControllerError, 'No filter type: Nothing to do here.'
+ end
+
+ def call(controller, &block)
+ run(controller)
+ end
+ end
+
+ # Abstract base class for filter proxies. FilterProxy objects are meant to mimic the behaviour of the old
+ # before_filter and after_filter by moving the logic into the filter itself.
+ class FilterProxy < Filter #:nodoc:
+ def filter
+ @filter.filter
+ end
+ end
+
+ class BeforeFilterProxy < FilterProxy #:nodoc:
+ def type
+ :before
+ end
+
+ def run(controller)
+ # only filters returning false are halted.
+ @filter.call(controller)
+ if controller.send!(:performed?)
+ controller.send!(:halt_filter_chain, @filter, :rendered_or_redirected)
+ end
+ end
+
+ def call(controller)
+ yield unless run(controller)
+ end
+ end
+
+ class AfterFilterProxy < FilterProxy #:nodoc:
+ def type
+ :after
+ end
+
+ def run(controller)
+ @filter.call(controller)
+ end
+
+ def call(controller)
+ yield
+ run(controller)
+ end
+ end
+
+ class SymbolFilter < Filter #:nodoc:
+ def call(controller, &block)
+ controller.send!(@filter, &block)
+ end
+ end
+
+ class ProcFilter < Filter #:nodoc:
+ def call(controller)
+ @filter.call(controller)
+ rescue LocalJumpError # a yield from a proc... no no bad dog.
+ raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
+ end
+ end
+
+ class ProcWithCallFilter < Filter #:nodoc:
+ def call(controller, &block)
+ @filter.call(controller, block)
+ rescue LocalJumpError # a yield from a proc... no no bad dog.
+ raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
+ end
+ end
+
+ class MethodFilter < Filter #:nodoc:
+ def call(controller, &block)
+ @filter.call(controller, &block)
+ end
+ end
+
+ class ClassFilter < Filter #:nodoc:
+ def call(controller, &block)
+ @filter.filter(controller, &block)
+ end
+ end
+
+ class ClassBeforeFilter < Filter #:nodoc:
+ def call(controller, &block)
+ @filter.before(controller)
+ end
+ end
+
+ class ClassAfterFilter < Filter #:nodoc:
+ def call(controller, &block)
+ @filter.after(controller)
+ end
+ end
+
+ protected
+ def append_filter_to_chain(filters, filter_type = :around, &block)
+ pos = find_filter_append_position(filters, filter_type)
+ update_filter_chain(filters, filter_type, pos, &block)
+ end
+
+ def prepend_filter_to_chain(filters, filter_type = :around, &block)
+ pos = find_filter_prepend_position(filters, filter_type)
+ update_filter_chain(filters, filter_type, pos, &block)
+ end
+
+ def update_filter_chain(filters, filter_type, pos, &block)
+ new_filters = create_filters(filters, filter_type, &block)
+ new_chain = filter_chain.insert(pos, new_filters).flatten
+ write_inheritable_attribute('filter_chain', new_chain)
+ end
+
+ def find_filter_append_position(filters, filter_type)
+ # appending an after filter puts it at the end of the call chain
+ # before and around filters go before the first after filter in the chain
+ unless filter_type == :after
+ filter_chain.each_with_index do |f,i|
+ return i if f.after?
+ end
+ end
+ return -1
+ end
+
+ def find_filter_prepend_position(filters, filter_type)
+ # prepending a before or around filter puts it at the front of the call chain
+ # after filters go before the first after filter in the chain
+ if filter_type == :after
+ filter_chain.each_with_index do |f,i|
+ return i if f.after?
+ end
+ return -1
+ end
+ return 0
+ end
+
+ def create_filters(filters, filter_type, &block) #:nodoc:
+ filters, conditions = extract_conditions(filters, &block)
+ filters.map! { |filter| find_or_create_filter(filter, filter_type) }
+ update_conditions(filters, conditions)
+ filters
+ end
+
+ def find_or_create_filter(filter, filter_type)
+ if found_filter = find_filter(filter) { |f| f.type == filter_type }
+ found_filter
+ else
+ f = class_for_filter(filter, filter_type).new(filter)
+ # apply proxy to filter if necessary
+ case filter_type
+ when :before
+ BeforeFilterProxy.new(f)
+ when :after
+ AfterFilterProxy.new(f)
+ else
+ f
+ end
+ end
+ end
+
+ # The determination of the filter type was once done at run time.
+ # This method is here to extract as much logic from the filter run time as possible
+ def class_for_filter(filter, filter_type) #:nodoc:
+ case
+ when filter.is_a?(Symbol)
+ SymbolFilter
+ when filter.respond_to?(:call)
+ if filter.is_a?(Method)
+ MethodFilter
+ elsif filter.arity == 1
+ ProcFilter
+ else
+ ProcWithCallFilter
+ end
+ when filter.respond_to?(:filter)
+ ClassFilter
+ when filter.respond_to?(:before) && filter_type == :before
+ ClassBeforeFilter
+ when filter.respond_to?(:after) && filter_type == :after
+ ClassAfterFilter
+ else
+ raise(ActionControllerError, 'A filter must be a Symbol, Proc, Method, or object responding to filter, after or before.')
+ end
+ end
+
+ def extract_conditions(*filters, &block) #:nodoc:
+ filters.flatten!
+ conditions = filters.extract_options!
+ filters << block if block_given?
+ return filters, conditions
+ end
+
+ def update_conditions(filters, conditions)
+ return if conditions.empty?
+ if conditions[:only]
+ write_inheritable_hash('included_actions', condition_hash(filters, conditions[:only]))
+ elsif conditions[:except]
+ write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except]))
+ end
+ end
+
+ def condition_hash(filters, *actions)
+ actions = actions.flatten.map(&:to_s)
+ filters.inject({}) { |h,f| h.update( f => (actions.blank? ? nil : actions)) }
+ end
+
+ def skip_filter_in_chain(*filters, &test) #:nodoc:
+ filters, conditions = extract_conditions(filters)
+ filters.map! { |f| block_given? ? find_filter(f, &test) : find_filter(f) }
+ filters.compact!
+
+ if conditions.empty?
+ delete_filters_in_chain(filters)
+ else
+ remove_actions_from_included_actions!(filters,conditions[:only] || [])
+ conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
+ update_conditions(filters,conditions)
+ end
+ end
+
+ def remove_actions_from_included_actions!(filters,*actions)
+ actions = actions.flatten.map(&:to_s)
+ updated_hash = filters.inject(read_inheritable_attribute('included_actions')||{}) do |hash,filter|
+ ia = (hash[filter] || []) - actions
+ ia.empty? ? hash.delete(filter) : hash[filter] = ia
+ hash
+ end
+ write_inheritable_attribute('included_actions', updated_hash)
+ end
+
+ def delete_filters_in_chain(filters) #:nodoc:
+ write_inheritable_attribute('filter_chain', filter_chain.reject { |f| filters.include?(f) })
+ end
+
+ def filter_responds_to_before_and_after(filter) #:nodoc:
+ filter.respond_to?(:before) && filter.respond_to?(:after)
+ end
+
+ def proxy_before_and_after_filter(filter) #:nodoc:
+ return filter unless filter_responds_to_before_and_after(filter)
+ Proc.new do |controller, action|
+ filter.before(controller)
+
+ if controller.send!(:performed?)
+ controller.send!(:halt_filter_chain, filter, :rendered_or_redirected)
+ else
+ begin
+ action.call
+ ensure
+ filter.after(controller)
+ end
+ end
+ end
+ end
+ end
+
+ module InstanceMethods # :nodoc:
+ def self.included(base)
+ base.class_eval do
+ alias_method_chain :perform_action, :filters
+ alias_method_chain :process, :filters
+ end
+ end
+
+ protected
+
+ def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
+ @before_filter_chain_aborted = false
+ process_without_filters(request, response, method, *arguments)
+ end
+
+ def perform_action_with_filters
+ call_filters(self.class.filter_chain, 0, 0)
+ end
+
+ private
+
+ def call_filters(chain, index, nesting)
+ index = run_before_filters(chain, index, nesting)
+ aborted = @before_filter_chain_aborted
+ perform_action_without_filters unless performed? || aborted
+ return index if nesting != 0 || aborted
+ run_after_filters(chain, index)
+ end
+
+ def skip_excluded_filters(chain, index)
+ while (filter = chain[index]) && self.class.filter_excluded_from_action?(filter, action_name)
+ index = index.next
+ end
+ [filter, index]
+ end
+
+ def run_before_filters(chain, index, nesting)
+ while chain[index]
+ filter, index = skip_excluded_filters(chain, index)
+ break unless filter # end of call chain reached
+
+ case filter.type
+ when :before
+ filter.run(self) # invoke before filter
+ index = index.next
+ break if @before_filter_chain_aborted
+ when :around
+ yielded = false
+
+ filter.call(self) do
+ yielded = true
+ # all remaining before and around filters will be run in this call
+ index = call_filters(chain, index.next, nesting.next)
+ end
+
+ halt_filter_chain(filter, :did_not_yield) unless yielded
+
+ break
+ else
+ break # no before or around filters left
+ end
+ end
+
+ index
+ end
+
+ def run_after_filters(chain, index)
+ seen_after_filter = false
+
+ while chain[index]
+ filter, index = skip_excluded_filters(chain, index)
+ break unless filter # end of call chain reached
+
+ case filter.type
+ when :after
+ seen_after_filter = true
+ filter.run(self) # invoke after filter
+ else
+ # implementation error or someone has mucked with the filter chain
+ raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter
+ end
+
+ index = index.next
+ end
+
+ index.next
+ end
+
+ def halt_filter_chain(filter, reason)
+ @before_filter_chain_aborted = true
+ logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/flash.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/flash.rb
new file mode 100644
index 000000000..692168f23
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/flash.rb
@@ -0,0 +1,177 @@
+module ActionController #:nodoc:
+ # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
+ # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
+ # action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can
+ # then expose the flash to its template. Actually, that exposure is automatically done. Example:
+ #
+ # class WeblogController < ActionController::Base
+ # def create
+ # # save post
+ # flash[:notice] = "Successfully created post"
+ # redirect_to :action => "display", :params => { :id => post.id }
+ # end
+ #
+ # def display
+ # # doesn't need to assign the flash notice to the template, that's done automatically
+ # end
+ # end
+ #
+ # display.erb
+ # <% if flash[:notice] %><div class="notice"><%= flash[:notice] %></div><% end %>
+ #
+ # This example just places a string in the flash, but you can put any object in there. And of course, you can put as
+ # many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
+ #
+ # See docs on the FlashHash class for more details about the flash.
+ module Flash
+ def self.included(base)
+ base.class_eval do
+ include InstanceMethods
+ alias_method_chain :assign_shortcuts, :flash
+ alias_method_chain :process_cleanup, :flash
+ alias_method_chain :reset_session, :flash
+ end
+ end
+
+
+ class FlashNow #:nodoc:
+ def initialize(flash)
+ @flash = flash
+ end
+
+ def []=(k, v)
+ @flash[k] = v
+ @flash.discard(k)
+ v
+ end
+
+ def [](k)
+ @flash[k]
+ end
+ end
+
+ class FlashHash < Hash
+ def initialize #:nodoc:
+ super
+ @used = {}
+ end
+
+ def []=(k, v) #:nodoc:
+ keep(k)
+ super
+ end
+
+ def update(h) #:nodoc:
+ h.keys.each { |k| keep(k) }
+ super
+ end
+
+ alias :merge! :update
+
+ def replace(h) #:nodoc:
+ @used = {}
+ super
+ end
+
+ # Sets a flash that will not be available to the next action, only to the current.
+ #
+ # flash.now[:message] = "Hello current action"
+ #
+ # This method enables you to use the flash as a central messaging system in your app.
+ # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
+ # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
+ # vanish when the current action is done.
+ #
+ # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
+ def now
+ FlashNow.new(self)
+ end
+
+ # Keeps either the entire current flash or a specific flash entry available for the next action:
+ #
+ # flash.keep # keeps the entire flash
+ # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
+ def keep(k = nil)
+ use(k, false)
+ end
+
+ # Marks the entire flash or a single flash entry to be discarded by the end of the current action:
+ #
+ # flash.discard # discard the entire flash at the end of the current action
+ # flash.discard(:warning) # discard only the "warning" entry at the end of the current action
+ def discard(k = nil)
+ use(k)
+ end
+
+ # Mark for removal entries that were kept, and delete unkept ones.
+ #
+ # This method is called automatically by filters, so you generally don't need to care about it.
+ def sweep #:nodoc:
+ keys.each do |k|
+ unless @used[k]
+ use(k)
+ else
+ delete(k)
+ @used.delete(k)
+ end
+ end
+
+ # clean up after keys that could have been left over by calling reject! or shift on the flash
+ (@used.keys - keys).each{ |k| @used.delete(k) }
+ end
+
+ private
+ # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
+ # use() # marks the entire flash as used
+ # use('msg') # marks the "msg" entry as used
+ # use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
+ # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
+ def use(k=nil, v=true)
+ unless k.nil?
+ @used[k] = v
+ else
+ keys.each{ |key| use(key, v) }
+ end
+ end
+ end
+
+ module InstanceMethods #:nodoc:
+ protected
+ def reset_session_with_flash
+ reset_session_without_flash
+ remove_instance_variable(:@_flash)
+ flash(:refresh)
+ end
+
+ # Access the contents of the flash. Use <tt>flash["notice"]</tt> to read a notice you put there or
+ # <tt>flash["notice"] = "hello"</tt> to put a new one.
+ # Note that if sessions are disabled only flash.now will work.
+ def flash(refresh = false) #:doc:
+ if !defined?(@_flash) || refresh
+ @_flash =
+ if session.is_a?(Hash)
+ # don't put flash in session if disabled
+ FlashHash.new
+ else
+ # otherwise, session is a CGI::Session or a TestSession
+ # so make sure it gets retrieved from/saved to session storage after request processing
+ session["flash"] ||= FlashHash.new
+ end
+ end
+
+ @_flash
+ end
+
+ private
+ def assign_shortcuts_with_flash(request, response) #:nodoc:
+ assign_shortcuts_without_flash(request, response)
+ flash(:refresh)
+ end
+
+ def process_cleanup_with_flash
+ flash.sweep if @_session
+ process_cleanup_without_flash
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/helpers.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/helpers.rb
new file mode 100644
index 000000000..81b8ff975
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/helpers.rb
@@ -0,0 +1,204 @@
+# FIXME: helper { ... } is broken on Ruby 1.9
+module ActionController #:nodoc:
+ module Helpers #:nodoc:
+ HELPERS_DIR = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers")
+
+ def self.included(base)
+ # Initialize the base module to aggregate its helpers.
+ base.class_inheritable_accessor :master_helper_module
+ base.master_helper_module = Module.new
+
+ # Extend base with class methods to declare helpers.
+ base.extend(ClassMethods)
+
+ base.class_eval do
+ # Wrap inherited to create a new master helper module for subclasses.
+ class << self
+ alias_method_chain :inherited, :helper
+ end
+ end
+ end
+
+ # The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+,
+ # +numbers+ and +ActiveRecord+ objects, to name a few. These helpers are available to all templates
+ # by default.
+ #
+ # In addition to using the standard template helpers provided in the Rails framework, creating custom helpers to
+ # extract complicated logic or reusable functionality is strongly encouraged. By default, the controller will
+ # include a helper whose name matches that of the controller, e.g., <tt>MyController</tt> will automatically
+ # include <tt>MyHelper</tt>.
+ #
+ # Additional helpers can be specified using the +helper+ class method in <tt>ActionController::Base</tt> or any
+ # controller which inherits from it.
+ #
+ # ==== Examples
+ # The +to_s+ method from the +Time+ class can be wrapped in a helper method to display a custom message if
+ # the Time object is blank:
+ #
+ # module FormattedTimeHelper
+ # def format_time(time, format=:long, blank_message="&nbsp;")
+ # time.blank? ? blank_message : time.to_s(format)
+ # end
+ # end
+ #
+ # +FormattedTimeHelper+ can now be included in a controller, using the +helper+ class method:
+ #
+ # class EventsController < ActionController::Base
+ # helper FormattedTimeHelper
+ # def index
+ # @events = Event.find(:all)
+ # end
+ # end
+ #
+ # Then, in any view rendered by <tt>EventController</tt>, the <tt>format_time</tt> method can be called:
+ #
+ # <% @events.each do |event| -%>
+ # <p>
+ # <% format_time(event.time, :short, "N/A") %> | <%= event.name %>
+ # </p>
+ # <% end -%>
+ #
+ # Finally, assuming we have two event instances, one which has a time and one which does not,
+ # the output might look like this:
+ #
+ # 23 Aug 11:30 | Carolina Railhawks Soccer Match
+ # N/A | Carolina Railhaws Training Workshop
+ #
+ module ClassMethods
+ # Makes all the (instance) methods in the helper module available to templates rendered through this controller.
+ # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
+ # available to the templates.
+ def add_template_helper(helper_module) #:nodoc:
+ master_helper_module.module_eval { include helper_module }
+ end
+
+ # The +helper+ class method can take a series of helper module names, a block, or both.
+ #
+ # * <tt>*args</tt>: One or more +Modules+, +Strings+ or +Symbols+, or the special symbol <tt>:all</tt>.
+ # * <tt>&block</tt>: A block defining helper methods.
+ #
+ # ==== Examples
+ # When the argument is a +String+ or +Symbol+, the method will provide the "_helper" suffix, require the file
+ # and include the module in the template class. The second form illustrates how to include custom helpers
+ # when working with namespaced controllers, or other cases where the file containing the helper definition is not
+ # in one of Rails' standard load paths:
+ # helper :foo # => requires 'foo_helper' and includes FooHelper
+ # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper
+ #
+ # When the argument is a +Module+, it will be included directly in the template class.
+ # helper FooHelper # => includes FooHelper
+ #
+ # When the argument is the symbol <tt>:all</tt>, the controller will include all helpers from
+ # <tt>app/helpers/**/*.rb</tt> under +RAILS_ROOT+.
+ # helper :all
+ #
+ # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available
+ # to the template.
+ # # One line
+ # helper { def hello() "Hello, world!" end }
+ # # Multi-line
+ # helper do
+ # def foo(bar)
+ # "#{bar} is the very best"
+ # end
+ # end
+ #
+ # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of
+ # +symbols+, +strings+, +modules+ and blocks.
+ # helper(:three, BlindHelper) { def mice() 'mice' end }
+ #
+ def helper(*args, &block)
+ args.flatten.each do |arg|
+ case arg
+ when Module
+ add_template_helper(arg)
+ when :all
+ helper(all_application_helpers)
+ when String, Symbol
+ file_name = arg.to_s.underscore + '_helper'
+ class_name = file_name.camelize
+
+ begin
+ require_dependency(file_name)
+ rescue LoadError => load_error
+ requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1]
+ if requiree == file_name
+ msg = "Missing helper file helpers/#{file_name}.rb"
+ raise LoadError.new(msg).copy_blame!(load_error)
+ else
+ raise
+ end
+ end
+
+ add_template_helper(class_name.constantize)
+ else
+ raise ArgumentError, "helper expects String, Symbol, or Module argument (was: #{args.inspect})"
+ end
+ end
+
+ # Evaluate block in template class if given.
+ master_helper_module.module_eval(&block) if block_given?
+ end
+
+ # Declare a controller method as a helper. For example, the following
+ # makes the +current_user+ controller method available to the view:
+ # class ApplicationController < ActionController::Base
+ # helper_method :current_user
+ # def current_user
+ # @current_user ||= User.find(session[:user])
+ # end
+ # end
+ def helper_method(*methods)
+ methods.flatten.each do |method|
+ master_helper_module.module_eval <<-end_eval
+ def #{method}(*args, &block)
+ controller.send(%(#{method}), *args, &block)
+ end
+ end_eval
+ end
+ end
+
+ # Declares helper accessors for controller attributes. For example, the
+ # following adds new +name+ and <tt>name=</tt> instance methods to a
+ # controller and makes them available to the view:
+ # helper_attr :name
+ # attr_accessor :name
+ def helper_attr(*attrs)
+ attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
+ end
+
+
+ private
+ def default_helper_module!
+ unless name.blank?
+ module_name = name.sub(/Controller$|$/, 'Helper')
+ module_path = module_name.split('::').map { |m| m.underscore }.join('/')
+ require_dependency module_path
+ helper module_name.constantize
+ end
+ rescue MissingSourceFile => e
+ raise unless e.is_missing? module_path
+ rescue NameError => e
+ raise unless e.missing_name? module_name
+ end
+
+ def inherited_with_helper(child)
+ inherited_without_helper(child)
+
+ begin
+ child.master_helper_module = Module.new
+ child.master_helper_module.send! :include, master_helper_module
+ child.send! :default_helper_module!
+ rescue MissingSourceFile => e
+ raise unless e.is_missing?("helpers/#{child.controller_path}_helper")
+ end
+ end
+
+ # Extract helper names from files in app/helpers/**/*.rb
+ def all_application_helpers
+ extract = /^#{Regexp.quote(HELPERS_DIR)}\/?(.*)_helper.rb$/
+ Dir["#{HELPERS_DIR}/**/*_helper.rb"].map { |file| file.sub extract, '\1' }
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/http_authentication.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/http_authentication.rb
new file mode 100644
index 000000000..18a503c3a
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/http_authentication.rb
@@ -0,0 +1,126 @@
+require 'base64'
+
+module ActionController
+ module HttpAuthentication
+ # Makes it dead easy to do HTTP Basic authentication.
+ #
+ # Simple Basic example:
+ #
+ # class PostsController < ApplicationController
+ # USER_NAME, PASSWORD = "dhh", "secret"
+ #
+ # before_filter :authenticate, :except => [ :index ]
+ #
+ # def index
+ # render :text => "Everyone can see me!"
+ # end
+ #
+ # def edit
+ # render :text => "I'm only accessible if you know the password"
+ # end
+ #
+ # private
+ # def authenticate
+ # authenticate_or_request_with_http_basic do |user_name, password|
+ # user_name == USER_NAME && password == PASSWORD
+ # end
+ # end
+ # end
+ #
+ #
+ # Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication,
+ # the regular HTML interface is protected by a session approach:
+ #
+ # class ApplicationController < ActionController::Base
+ # before_filter :set_account, :authenticate
+ #
+ # protected
+ # def set_account
+ # @account = Account.find_by_url_name(request.subdomains.first)
+ # end
+ #
+ # def authenticate
+ # case request.format
+ # when Mime::XML, Mime::ATOM
+ # if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
+ # @current_user = user
+ # else
+ # request_http_basic_authentication
+ # end
+ # else
+ # if session_authenticated?
+ # @current_user = @account.users.find(session[:authenticated][:user_id])
+ # else
+ # redirect_to(login_url) and return false
+ # end
+ # end
+ # end
+ # end
+ #
+ #
+ # In your integration tests, you can do something like this:
+ #
+ # def test_access_granted_from_xml
+ # get(
+ # "/notes/1.xml", nil,
+ # :authorization => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
+ # )
+ #
+ # assert_equal 200, status
+ # end
+ #
+ #
+ # On shared hosts, Apache sometimes doesn't pass authentication headers to
+ # FCGI instances. If your environment matches this description and you cannot
+ # authenticate, try this rule in public/.htaccess (replace the plain one):
+ #
+ # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
+ module Basic
+ extend self
+
+ module ControllerMethods
+ def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
+ authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
+ end
+
+ def authenticate_with_http_basic(&login_procedure)
+ HttpAuthentication::Basic.authenticate(self, &login_procedure)
+ end
+
+ def request_http_basic_authentication(realm = "Application")
+ HttpAuthentication::Basic.authentication_request(self, realm)
+ end
+ end
+
+ def authenticate(controller, &login_procedure)
+ unless authorization(controller.request).blank?
+ login_procedure.call(*user_name_and_password(controller.request))
+ end
+ end
+
+ def user_name_and_password(request)
+ decode_credentials(request).split(/:/, 2)
+ end
+
+ def authorization(request)
+ request.env['HTTP_AUTHORIZATION'] ||
+ request.env['X-HTTP_AUTHORIZATION'] ||
+ request.env['X_HTTP_AUTHORIZATION'] ||
+ request.env['REDIRECT_X_HTTP_AUTHORIZATION']
+ end
+
+ def decode_credentials(request)
+ Base64.decode64(authorization(request).split.last || '')
+ end
+
+ def encode_credentials(user_name, password)
+ "Basic #{Base64.encode64("#{user_name}:#{password}")}"
+ end
+
+ def authentication_request(controller, realm)
+ controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
+ controller.send! :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/integration.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/integration.rb
new file mode 100644
index 000000000..25fb1b93a
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/integration.rb
@@ -0,0 +1,581 @@
+require 'dispatcher'
+require 'stringio'
+require 'uri'
+require 'action_controller/test_process'
+
+module ActionController
+ module Integration #:nodoc:
+ # An integration Session instance represents a set of requests and responses
+ # performed sequentially by some virtual user. Becase you can instantiate
+ # multiple sessions and run them side-by-side, you can also mimic (to some
+ # limited extent) multiple simultaneous users interacting with your system.
+ #
+ # Typically, you will instantiate a new session using IntegrationTest#open_session,
+ # rather than instantiating Integration::Session directly.
+ class Session
+ include Test::Unit::Assertions
+ include ActionController::Assertions
+ include ActionController::TestProcess
+
+ # The integer HTTP status code of the last request.
+ attr_reader :status
+
+ # The status message that accompanied the status code of the last request.
+ attr_reader :status_message
+
+ # The URI of the last request.
+ attr_reader :path
+
+ # The hostname used in the last request.
+ attr_accessor :host
+
+ # The remote_addr used in the last request.
+ attr_accessor :remote_addr
+
+ # The Accept header to send.
+ attr_accessor :accept
+
+ # A map of the cookies returned by the last response, and which will be
+ # sent with the next request.
+ attr_reader :cookies
+
+ # A map of the headers returned by the last response.
+ attr_reader :headers
+
+ # A reference to the controller instance used by the last request.
+ attr_reader :controller
+
+ # A reference to the request instance used by the last request.
+ attr_reader :request
+
+ # A reference to the response instance used by the last request.
+ attr_reader :response
+
+ # A running counter of the number of requests processed.
+ attr_accessor :request_count
+
+ # Create and initialize a new +Session+ instance.
+ def initialize
+ reset!
+ end
+
+ # Resets the instance. This can be used to reset the state information
+ # in an existing session instance, so it can be used from a clean-slate
+ # condition.
+ #
+ # session.reset!
+ def reset!
+ @status = @path = @headers = nil
+ @result = @status_message = nil
+ @https = false
+ @cookies = {}
+ @controller = @request = @response = nil
+ @request_count = 0
+
+ self.host = "www.example.com"
+ self.remote_addr = "127.0.0.1"
+ self.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
+
+ unless defined? @named_routes_configured
+ # install the named routes in this session instance.
+ klass = class<<self; self; end
+ Routing::Routes.install_helpers(klass)
+
+ # the helpers are made protected by default--we make them public for
+ # easier access during testing and troubleshooting.
+ klass.module_eval { public *Routing::Routes.named_routes.helpers }
+ @named_routes_configured = true
+ end
+ end
+
+ # Specify whether or not the session should mimic a secure HTTPS request.
+ #
+ # session.https!
+ # session.https!(false)
+ def https!(flag=true)
+ @https = flag
+ end
+
+ # Return +true+ if the session is mimicing a secure HTTPS request.
+ #
+ # if session.https?
+ # ...
+ # end
+ def https?
+ @https
+ end
+
+ # Set the host name to use in the next request.
+ #
+ # session.host! "www.example.com"
+ def host!(name)
+ @host = name
+ end
+
+ # Follow a single redirect response. If the last response was not a
+ # redirect, an exception will be raised. Otherwise, the redirect is
+ # performed on the location header.
+ def follow_redirect!
+ raise "not a redirect! #{@status} #{@status_message}" unless redirect?
+ get(interpret_uri(headers['location'].first))
+ status
+ end
+
+ # Performs a request using the specified method, following any subsequent
+ # redirect. Note that the redirects are followed until the response is
+ # not a redirect--this means you may run into an infinite loop if your
+ # redirect loops back to itself.
+ def request_via_redirect(http_method, path, parameters = nil, headers = nil)
+ send(http_method, path, parameters, headers)
+ follow_redirect! while redirect?
+ status
+ end
+
+ # Performs a GET request, following any subsequent redirect.
+ # See #request_via_redirect() for more information.
+ def get_via_redirect(path, parameters = nil, headers = nil)
+ request_via_redirect(:get, path, parameters, headers)
+ end
+
+ # Performs a POST request, following any subsequent redirect.
+ # See #request_via_redirect() for more information.
+ def post_via_redirect(path, parameters = nil, headers = nil)
+ request_via_redirect(:post, path, parameters, headers)
+ end
+
+ # Performs a PUT request, following any subsequent redirect.
+ # See #request_via_redirect() for more information.
+ def put_via_redirect(path, parameters = nil, headers = nil)
+ request_via_redirect(:put, path, parameters, headers)
+ end
+
+ # Performs a DELETE request, following any subsequent redirect.
+ # See #request_via_redirect() for more information.
+ def delete_via_redirect(path, parameters = nil, headers = nil)
+ request_via_redirect(:delete, path, parameters, headers)
+ end
+
+ # Returns +true+ if the last response was a redirect.
+ def redirect?
+ status/100 == 3
+ end
+
+ # Performs a GET request with the given parameters. The parameters may
+ # be +nil+, a Hash, or a string that is appropriately encoded
+ # (application/x-www-form-urlencoded or multipart/form-data). The headers
+ # should be a hash. The keys will automatically be upcased, with the
+ # prefix 'HTTP_' added if needed.
+ #
+ # You can also perform POST, PUT, DELETE, and HEAD requests with #post,
+ # #put, #delete, and #head.
+ def get(path, parameters = nil, headers = nil)
+ process :get, path, parameters, headers
+ end
+
+ # Performs a POST request with the given parameters. See get() for more details.
+ def post(path, parameters = nil, headers = nil)
+ process :post, path, parameters, headers
+ end
+
+ # Performs a PUT request with the given parameters. See get() for more details.
+ def put(path, parameters = nil, headers = nil)
+ process :put, path, parameters, headers
+ end
+
+ # Performs a DELETE request with the given parameters. See get() for more details.
+ def delete(path, parameters = nil, headers = nil)
+ process :delete, path, parameters, headers
+ end
+
+ # Performs a HEAD request with the given parameters. See get() for more details.
+ def head(path, parameters = nil, headers = nil)
+ process :head, path, parameters, headers
+ end
+
+ # Performs an XMLHttpRequest request with the given parameters, mirroring
+ # a request from the Prototype library.
+ #
+ # The request_method is :get, :post, :put, :delete or :head; the
+ # parameters are +nil+, a hash, or a url-encoded or multipart string;
+ # the headers are a hash. Keys are automatically upcased and prefixed
+ # with 'HTTP_' if not already.
+ def xml_http_request(request_method, path, parameters = nil, headers = nil)
+ headers ||= {}
+ headers['X-Requested-With'] = 'XMLHttpRequest'
+ headers['Accept'] ||= 'text/javascript, text/html, application/xml, text/xml, */*'
+
+ process(request_method, path, parameters, headers)
+ end
+ alias xhr :xml_http_request
+
+ # Returns the URL for the given options, according to the rules specified
+ # in the application's routes.
+ def url_for(options)
+ controller ? controller.url_for(options) : generic_url_rewriter.rewrite(options)
+ end
+
+ private
+ class StubCGI < CGI #:nodoc:
+ attr_accessor :stdinput, :stdoutput, :env_table
+
+ def initialize(env, stdinput = nil)
+ self.env_table = env
+ self.stdoutput = StringIO.new
+
+ super
+
+ @stdinput = stdinput.is_a?(IO) ? stdinput : StringIO.new(stdinput || '')
+ end
+ end
+
+ # Tailors the session based on the given URI, setting the HTTPS value
+ # and the hostname.
+ def interpret_uri(path)
+ location = URI.parse(path)
+ https! URI::HTTPS === location if location.scheme
+ host! location.host if location.host
+ location.query ? "#{location.path}?#{location.query}" : location.path
+ end
+
+ # Performs the actual request.
+ def process(method, path, parameters = nil, headers = nil)
+ data = requestify(parameters)
+ path = interpret_uri(path) if path =~ %r{://}
+ path = "/#{path}" unless path[0] == ?/
+ @path = path
+ env = {}
+
+ if method == :get
+ env["QUERY_STRING"] = data
+ data = nil
+ end
+
+ env.update(
+ "REQUEST_METHOD" => method.to_s.upcase,
+ "REQUEST_URI" => path,
+ "HTTP_HOST" => host,
+ "REMOTE_ADDR" => remote_addr,
+ "SERVER_PORT" => (https? ? "443" : "80"),
+ "CONTENT_TYPE" => "application/x-www-form-urlencoded",
+ "CONTENT_LENGTH" => data ? data.length.to_s : nil,
+ "HTTP_COOKIE" => encode_cookies,
+ "HTTPS" => https? ? "on" : "off",
+ "HTTP_ACCEPT" => accept
+ )
+
+ (headers || {}).each do |key, value|
+ key = key.to_s.upcase.gsub(/-/, "_")
+ key = "HTTP_#{key}" unless env.has_key?(key) || key =~ /^HTTP_/
+ env[key] = value
+ end
+
+ unless ActionController::Base.respond_to?(:clear_last_instantiation!)
+ ActionController::Base.module_eval { include ControllerCapture }
+ end
+
+ ActionController::Base.clear_last_instantiation!
+
+ cgi = StubCGI.new(env, data)
+ Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)
+ @result = cgi.stdoutput.string
+ @request_count += 1
+
+ @controller = ActionController::Base.last_instantiation
+ @request = @controller.request
+ @response = @controller.response
+
+ # Decorate the response with the standard behavior of the TestResponse
+ # so that things like assert_response can be used in integration
+ # tests.
+ @response.extend(TestResponseBehavior)
+
+ @html_document = nil
+
+ parse_result
+ return status
+ end
+
+ # Parses the result of the response and extracts the various values,
+ # like cookies, status, headers, etc.
+ def parse_result
+ headers, result_body = @result.split(/\r\n\r\n/, 2)
+
+ @headers = Hash.new { |h,k| h[k] = [] }
+ headers.each_line do |line|
+ key, value = line.strip.split(/:\s*/, 2)
+ @headers[key.downcase] << value
+ end
+
+ (@headers['set-cookie'] || [] ).each do |string|
+ name, value = string.match(/^([^=]*)=([^;]*);/)[1,2]
+ @cookies[name] = value
+ end
+
+ @status, @status_message = @headers["status"].first.split(/ /)
+ @status = @status.to_i
+ end
+
+ # Encode the cookies hash in a format suitable for passing to a
+ # request.
+ def encode_cookies
+ cookies.inject("") do |string, (name, value)|
+ string << "#{name}=#{value}; "
+ end
+ end
+
+ # Get a temporary URL writer object
+ def generic_url_rewriter
+ cgi = StubCGI.new('REQUEST_METHOD' => "GET",
+ 'QUERY_STRING' => "",
+ "REQUEST_URI" => "/",
+ "HTTP_HOST" => host,
+ "SERVER_PORT" => https? ? "443" : "80",
+ "HTTPS" => https? ? "on" : "off")
+ ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {})
+ end
+
+ def name_with_prefix(prefix, name)
+ prefix ? "#{prefix}[#{name}]" : name.to_s
+ end
+
+ # Convert the given parameters to a request string. The parameters may
+ # be a string, +nil+, or a Hash.
+ def requestify(parameters, prefix=nil)
+ if Hash === parameters
+ return nil if parameters.empty?
+ parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
+ elsif Array === parameters
+ parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&")
+ elsif prefix.nil?
+ parameters
+ else
+ "#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
+ end
+ end
+ end
+
+ # A module used to extend ActionController::Base, so that integration tests
+ # can capture the controller used to satisfy a request.
+ module ControllerCapture #:nodoc:
+ def self.included(base)
+ base.extend(ClassMethods)
+ base.class_eval do
+ class << self
+ alias_method_chain :new, :capture
+ end
+ end
+ end
+
+ module ClassMethods #:nodoc:
+ mattr_accessor :last_instantiation
+
+ def clear_last_instantiation!
+ self.last_instantiation = nil
+ end
+
+ def new_with_capture(*args)
+ controller = new_without_capture(*args)
+ self.last_instantiation ||= controller
+ controller
+ end
+ end
+ end
+
+ module Runner
+ # Reset the current session. This is useful for testing multiple sessions
+ # in a single test case.
+ def reset!
+ @integration_session = open_session
+ end
+
+ %w(get post put head delete cookies assigns
+ xml_http_request get_via_redirect post_via_redirect).each do |method|
+ define_method(method) do |*args|
+ reset! unless @integration_session
+ # reset the html_document variable, but only for new get/post calls
+ @html_document = nil unless %w(cookies assigns).include?(method)
+ returning @integration_session.send!(method, *args) do
+ copy_session_variables!
+ end
+ end
+ end
+
+ # Open a new session instance. If a block is given, the new session is
+ # yielded to the block before being returned.
+ #
+ # session = open_session do |sess|
+ # sess.extend(CustomAssertions)
+ # end
+ #
+ # By default, a single session is automatically created for you, but you
+ # can use this method to open multiple sessions that ought to be tested
+ # simultaneously.
+ def open_session
+ session = Integration::Session.new
+
+ # delegate the fixture accessors back to the test instance
+ extras = Module.new { attr_accessor :delegate, :test_result }
+ if self.class.respond_to?(:fixture_table_names)
+ self.class.fixture_table_names.each do |table_name|
+ name = table_name.tr(".", "_")
+ next unless respond_to?(name)
+ extras.send!(:define_method, name) { |*args| delegate.send(name, *args) }
+ end
+ end
+
+ # delegate add_assertion to the test case
+ extras.send!(:define_method, :add_assertion) { test_result.add_assertion }
+ session.extend(extras)
+ session.delegate = self
+ session.test_result = @_result
+
+ yield session if block_given?
+ session
+ end
+
+ # Copy the instance variables from the current session instance into the
+ # test instance.
+ def copy_session_variables! #:nodoc:
+ return unless @integration_session
+ %w(controller response request).each do |var|
+ instance_variable_set("@#{var}", @integration_session.send!(var))
+ end
+ end
+
+ # Delegate unhandled messages to the current session instance.
+ def method_missing(sym, *args, &block)
+ reset! unless @integration_session
+ returning @integration_session.send!(sym, *args, &block) do
+ copy_session_variables!
+ end
+ end
+ end
+ end
+
+ # An IntegrationTest is one that spans multiple controllers and actions,
+ # tying them all together to ensure they work together as expected. It tests
+ # more completely than either unit or functional tests do, exercising the
+ # entire stack, from the dispatcher to the database.
+ #
+ # At its simplest, you simply extend IntegrationTest and write your tests
+ # using the get/post methods:
+ #
+ # require "#{File.dirname(__FILE__)}/test_helper"
+ #
+ # class ExampleTest < ActionController::IntegrationTest
+ # fixtures :people
+ #
+ # def test_login
+ # # get the login page
+ # get "/login"
+ # assert_equal 200, status
+ #
+ # # post the login and follow through to the home page
+ # post "/login", :username => people(:jamis).username,
+ # :password => people(:jamis).password
+ # follow_redirect!
+ # assert_equal 200, status
+ # assert_equal "/home", path
+ # end
+ # end
+ #
+ # However, you can also have multiple session instances open per test, and
+ # even extend those instances with assertions and methods to create a very
+ # powerful testing DSL that is specific for your application. You can even
+ # reference any named routes you happen to have defined!
+ #
+ # require "#{File.dirname(__FILE__)}/test_helper"
+ #
+ # class AdvancedTest < ActionController::IntegrationTest
+ # fixtures :people, :rooms
+ #
+ # def test_login_and_speak
+ # jamis, david = login(:jamis), login(:david)
+ # room = rooms(:office)
+ #
+ # jamis.enter(room)
+ # jamis.speak(room, "anybody home?")
+ #
+ # david.enter(room)
+ # david.speak(room, "hello!")
+ # end
+ #
+ # private
+ #
+ # module CustomAssertions
+ # def enter(room)
+ # # reference a named route, for maximum internal consistency!
+ # get(room_url(:id => room.id))
+ # assert(...)
+ # ...
+ # end
+ #
+ # def speak(room, message)
+ # xml_http_request "/say/#{room.id}", :message => message
+ # assert(...)
+ # ...
+ # end
+ # end
+ #
+ # def login(who)
+ # open_session do |sess|
+ # sess.extend(CustomAssertions)
+ # who = people(who)
+ # sess.post "/login", :username => who.username,
+ # :password => who.password
+ # assert(...)
+ # end
+ # end
+ # end
+ class IntegrationTest < Test::Unit::TestCase
+ include Integration::Runner
+
+ # Work around a bug in test/unit caused by the default test being named
+ # as a symbol (:default_test), which causes regex test filters
+ # (like "ruby test.rb -n /foo/") to fail because =~ doesn't work on
+ # symbols.
+ def initialize(name) #:nodoc:
+ super(name.to_s)
+ end
+
+ # Work around test/unit's requirement that every subclass of TestCase have
+ # at least one test method. Note that this implementation extends to all
+ # subclasses, as well, so subclasses of IntegrationTest may also exist
+ # without any test methods.
+ def run(*args) #:nodoc:
+ return if @method_name == "default_test"
+ super
+ end
+
+ # Because of how use_instantiated_fixtures and use_transactional_fixtures
+ # are defined, we need to treat them as special cases. Otherwise, users
+ # would potentially have to set their values for both Test::Unit::TestCase
+ # ActionController::IntegrationTest, since by the time the value is set on
+ # TestCase, IntegrationTest has already been defined and cannot inherit
+ # changes to those variables. So, we make those two attributes copy-on-write.
+
+ class << self
+ def use_transactional_fixtures=(flag) #:nodoc:
+ @_use_transactional_fixtures = true
+ @use_transactional_fixtures = flag
+ end
+
+ def use_instantiated_fixtures=(flag) #:nodoc:
+ @_use_instantiated_fixtures = true
+ @use_instantiated_fixtures = flag
+ end
+
+ def use_transactional_fixtures #:nodoc:
+ @_use_transactional_fixtures ?
+ @use_transactional_fixtures :
+ superclass.use_transactional_fixtures
+ end
+
+ def use_instantiated_fixtures #:nodoc:
+ @_use_instantiated_fixtures ?
+ @use_instantiated_fixtures :
+ superclass.use_instantiated_fixtures
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/layout.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/layout.rb
new file mode 100644
index 000000000..63a68c108
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/layout.rb
@@ -0,0 +1,326 @@
+module ActionController #:nodoc:
+ module Layout #:nodoc:
+ def self.included(base)
+ base.extend(ClassMethods)
+ base.class_eval do
+ # NOTE: Can't use alias_method_chain here because +render_without_layout+ is already
+ # defined as a publicly exposed method
+ alias_method :render_with_no_layout, :render
+ alias_method :render, :render_with_a_layout
+
+ class << self
+ alias_method_chain :inherited, :layout
+ end
+ end
+ end
+
+ # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
+ # repeated setups. The inclusion pattern has pages that look like this:
+ #
+ # <%= render "shared/header" %>
+ # Hello World
+ # <%= render "shared/footer" %>
+ #
+ # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
+ # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
+ #
+ # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
+ # that the header and footer are only mentioned in one place, like this:
+ #
+ # // The header part of this layout
+ # <%= yield %>
+ # // The footer part of this layout -->
+ #
+ # And then you have content pages that look like this:
+ #
+ # hello world
+ #
+ # Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout,
+ # like this:
+ #
+ # // The header part of this layout
+ # hello world
+ # // The footer part of this layout -->
+ #
+ # == Accessing shared variables
+ #
+ # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
+ # references that won't materialize before rendering time:
+ #
+ # <h1><%= @page_title %></h1>
+ # <%= yield %>
+ #
+ # ...and content pages that fulfill these references _at_ rendering time:
+ #
+ # <% @page_title = "Welcome" %>
+ # Off-world colonies offers you a chance to start a new life
+ #
+ # The result after rendering is:
+ #
+ # <h1>Welcome</h1>
+ # Off-world colonies offers you a chance to start a new life
+ #
+ # == Automatic layout assignment
+ #
+ # If there is a template in <tt>app/views/layouts/</tt> with the same name as the current controller then it will be automatically
+ # set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named
+ # <tt>app/views/layouts/weblog.erb</tt> or <tt>app/views/layouts/weblog.builder</tt> exists then it will be automatically set as
+ # the layout for your WeblogController. You can create a layout with the name <tt>application.erb</tt> or <tt>application.builder</tt>
+ # and this will be set as the default controller if there is no layout with the same name as the current controller and there is
+ # no layout explicitly assigned with the +layout+ method. Nested controllers use the same folder structure for automatic layout.
+ # assignment. So an Admin::WeblogController will look for a template named <tt>app/views/layouts/admin/weblog.erb</tt>.
+ # Setting a layout explicitly will always override the automatic behaviour for the controller where the layout is set.
+ # Explicitly setting the layout in a parent class, though, will not override the child class's layout assignment if the child
+ # class has a layout with the same name.
+ #
+ # == Inheritance for layouts
+ #
+ # Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples:
+ #
+ # class BankController < ActionController::Base
+ # layout "bank_standard"
+ #
+ # class InformationController < BankController
+ #
+ # class VaultController < BankController
+ # layout :access_level_layout
+ #
+ # class EmployeeController < BankController
+ # layout nil
+ #
+ # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites
+ # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
+ #
+ # == Types of layouts
+ #
+ # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
+ # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
+ # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
+ #
+ # The method reference is the preferred approach to variable layouts and is used like this:
+ #
+ # class WeblogController < ActionController::Base
+ # layout :writers_and_readers
+ #
+ # def index
+ # # fetching posts
+ # end
+ #
+ # private
+ # def writers_and_readers
+ # logged_in? ? "writer_layout" : "reader_layout"
+ # end
+ #
+ # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
+ # is logged in or not.
+ #
+ # If you want to use an inline method, such as a proc, do something like this:
+ #
+ # class WeblogController < ActionController::Base
+ # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
+ #
+ # Of course, the most common way of specifying a layout is still just as a plain template name:
+ #
+ # class WeblogController < ActionController::Base
+ # layout "weblog_standard"
+ #
+ # If no directory is specified for the template name, the template will by default be looked for in +app/views/layouts/+.
+ # Otherwise, it will be looked up relative to the template root.
+ #
+ # == Conditional layouts
+ #
+ # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
+ # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
+ # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
+ #
+ # class WeblogController < ActionController::Base
+ # layout "weblog_standard", :except => :rss
+ #
+ # # ...
+ #
+ # end
+ #
+ # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
+ # around the rendered view.
+ #
+ # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
+ # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
+ #
+ # == Using a different layout in the action render call
+ #
+ # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
+ # Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller.
+ # This is possible using the <tt>render</tt> method. It's just a bit more manual work as you'll have to supply fully
+ # qualified template and layout names as this example shows:
+ #
+ # class WeblogController < ActionController::Base
+ # def help
+ # render :action => "help/index", :layout => "help"
+ # end
+ # end
+ #
+ # As you can see, you pass the template as the first parameter, the status code as the second ("200" is OK), and the layout
+ # as the third.
+ #
+ # NOTE: The old notation for rendering the view from a layout was to expose the magic <tt>@content_for_layout</tt> instance
+ # variable. The preferred notation now is to use <tt>yield</tt>, as documented above.
+ module ClassMethods
+ # If a layout is specified, all rendered actions will have their result rendered
+ # when the layout <tt>yield</tt>s. This layout can itself depend on instance variables assigned during action
+ # performance and have access to them as any normal template would.
+ def layout(template_name, conditions = {}, auto = false)
+ add_layout_conditions(conditions)
+ write_inheritable_attribute "layout", template_name
+ write_inheritable_attribute "auto_layout", auto
+ end
+
+ def layout_conditions #:nodoc:
+ @layout_conditions ||= read_inheritable_attribute("layout_conditions")
+ end
+
+ def default_layout(format) #:nodoc:
+ layout = read_inheritable_attribute("layout")
+ return layout unless read_inheritable_attribute("auto_layout")
+ @default_layout ||= {}
+ @default_layout[format] ||= default_layout_with_format(format, layout)
+ @default_layout[format]
+ end
+
+ def layout_list #:nodoc:
+ view_paths.collect do |path|
+ Dir["#{path}/layouts/**/*"]
+ end.flatten
+ end
+
+ private
+ def inherited_with_layout(child)
+ inherited_without_layout(child)
+ unless child.name.blank?
+ layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '')
+ child.layout(layout_match, {}, true) unless child.layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty?
+ end
+ end
+
+ def add_layout_conditions(conditions)
+ write_inheritable_hash "layout_conditions", normalize_conditions(conditions)
+ end
+
+ def normalize_conditions(conditions)
+ conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})}
+ end
+
+ def layout_directory_exists_cache
+ @@layout_directory_exists_cache ||= Hash.new do |h, dirname|
+ h[dirname] = File.directory? dirname
+ end
+ end
+
+ def default_layout_with_format(format, layout)
+ list = layout_list
+ if list.grep(%r{layouts/#{layout}\.#{format}(\.[a-z][0-9a-z]*)+$}).empty?
+ (!list.grep(%r{layouts/#{layout}\.([a-z][0-9a-z]*)+$}).empty? && format == :html) ? layout : nil
+ else
+ layout
+ end
+ end
+ end
+
+ # Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method
+ # is called and the return value is used. Likewise if the layout was specified as an inline method (through a proc or method
+ # object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
+ # weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
+ def active_layout(passed_layout = nil)
+ layout = passed_layout || self.class.default_layout(response.template.template_format)
+ active_layout = case layout
+ when String then layout
+ when Symbol then send!(layout)
+ when Proc then layout.call(self)
+ end
+
+ # Explicitly passed layout names with slashes are looked up relative to the template root,
+ # but auto-discovered layouts derived from a nested controller will contain a slash, though be relative
+ # to the 'layouts' directory so we have to check the file system to infer which case the layout name came from.
+ if active_layout
+ if active_layout.include?('/') && ! layout_directory?(active_layout)
+ active_layout
+ else
+ "layouts/#{active_layout}"
+ end
+ end
+ end
+
+ protected
+ def render_with_a_layout(options = nil, &block) #:nodoc:
+ template_with_options = options.is_a?(Hash)
+
+ if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options))
+ assert_existence_of_template_file(layout)
+
+ options = options.merge :layout => false if template_with_options
+ logger.info("Rendering template within #{layout}") if logger
+
+ content_for_layout = render_with_no_layout(options, &block)
+ erase_render_results
+ add_variables_to_assigns
+ @template.instance_variable_set("@content_for_layout", content_for_layout)
+ response.layout = layout
+ status = template_with_options ? options[:status] : nil
+ render_for_text(@template.render_file(layout, true), status)
+ else
+ render_with_no_layout(options, &block)
+ end
+ end
+
+
+ private
+ def apply_layout?(template_with_options, options)
+ return false if options == :update
+ template_with_options ? candidate_for_layout?(options) : !template_exempt_from_layout?
+ end
+
+ def candidate_for_layout?(options)
+ (options.has_key?(:layout) && options[:layout] != false) ||
+ options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing).compact.empty? &&
+ !template_exempt_from_layout?(options[:template] || default_template_name(options[:action]))
+ end
+
+ def pick_layout(template_with_options, options)
+ if template_with_options
+ case layout = options[:layout]
+ when FalseClass
+ nil
+ when NilClass, TrueClass
+ active_layout if action_has_layout?
+ else
+ active_layout(layout)
+ end
+ else
+ active_layout if action_has_layout?
+ end
+ end
+
+ def action_has_layout?
+ if conditions = self.class.layout_conditions
+ case
+ when only = conditions[:only]
+ only.include?(action_name)
+ when except = conditions[:except]
+ !except.include?(action_name)
+ else
+ true
+ end
+ else
+ true
+ end
+ end
+
+ # Does a layout directory for this class exist?
+ # we cache this info in a class level hash
+ def layout_directory?(layout_name)
+ view_paths.find do |path|
+ next unless template_path = Dir[File.join(path, 'layouts', layout_name) + ".*"].first
+ self.class.send!(:layout_directory_exists_cache)[File.dirname(template_path)]
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_responds.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_responds.rb
new file mode 100644
index 000000000..4ba4e626e
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_responds.rb
@@ -0,0 +1,170 @@
+module ActionController #:nodoc:
+ module MimeResponds #:nodoc:
+ def self.included(base)
+ base.module_eval do
+ include ActionController::MimeResponds::InstanceMethods
+ end
+ end
+
+ module InstanceMethods
+ # Without web-service support, an action which collects the data for displaying a list of people
+ # might look something like this:
+ #
+ # def index
+ # @people = Person.find(:all)
+ # end
+ #
+ # Here's the same action, with web-service support baked in:
+ #
+ # def index
+ # @people = Person.find(:all)
+ #
+ # respond_to do |format|
+ # format.html
+ # format.xml { render :xml => @people.to_xml }
+ # end
+ # end
+ #
+ # What that says is, "if the client wants HTML in response to this action, just respond as we
+ # would have before, but if the client wants XML, return them the list of people in XML format."
+ # (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
+ #
+ # Supposing you have an action that adds a new person, optionally creating their company
+ # (by name) if it does not already exist, without web-services, it might look like this:
+ #
+ # def create
+ # @company = Company.find_or_create_by_name(params[:company][:name])
+ # @person = @company.people.create(params[:person])
+ #
+ # redirect_to(person_list_url)
+ # end
+ #
+ # Here's the same action, with web-service support baked in:
+ #
+ # def create
+ # company = params[:person].delete(:company)
+ # @company = Company.find_or_create_by_name(company[:name])
+ # @person = @company.people.create(params[:person])
+ #
+ # respond_to do |format|
+ # format.html { redirect_to(person_list_url) }
+ # format.js
+ # format.xml { render :xml => @person.to_xml(:include => @company) }
+ # end
+ # end
+ #
+ # If the client wants HTML, we just redirect them back to the person list. If they want Javascript
+ # (format.js), then it is an RJS request and we render the RJS template associated with this action.
+ # Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
+ # include the person's company in the rendered XML, so you get something like this:
+ #
+ # <person>
+ # <id>...</id>
+ # ...
+ # <company>
+ # <id>...</id>
+ # <name>...</name>
+ # ...
+ # </company>
+ # </person>
+ #
+ # Note, however, the extra bit at the top of that action:
+ #
+ # company = params[:person].delete(:company)
+ # @company = Company.find_or_create_by_name(company[:name])
+ #
+ # This is because the incoming XML document (if a web-service request is in process) can only contain a
+ # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
+ #
+ # person[name]=...&person[company][name]=...&...
+ #
+ # And, like this (xml-encoded):
+ #
+ # <person>
+ # <name>...</name>
+ # <company>
+ # <name>...</name>
+ # </company>
+ # </person>
+ #
+ # In other words, we make the request so that it operates on a single entity's person. Then, in the action,
+ # we extract the company data from the request, find or create the company, and then create the new person
+ # with the remaining data.
+ #
+ # Note that you can define your own XML parameter parser which would allow you to describe multiple entities
+ # in a single request (i.e., by wrapping them all in a single root note), but if you just go with the flow
+ # and accept Rails' defaults, life will be much easier.
+ #
+ # If you need to use a MIME type which isn't supported by default, you can register your own handlers in
+ # environment.rb as follows.
+ #
+ # Mime::Type.register "image/jpg", :jpg
+ def respond_to(*types, &block)
+ raise ArgumentError, "respond_to takes either types or a block, never both" unless types.any? ^ block
+ block ||= lambda { |responder| types.each { |type| responder.send(type) } }
+ responder = Responder.new(self)
+ block.call(responder)
+ responder.respond
+ end
+ end
+
+ class Responder #:nodoc:
+ def initialize(controller)
+ @controller = controller
+ @request = controller.request
+ @response = controller.response
+
+ @mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
+
+ @order = []
+ @responses = {}
+ end
+
+ def custom(mime_type, &block)
+ mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)
+
+ @order << mime_type
+
+ @responses[mime_type] = Proc.new do
+ @response.template.template_format = mime_type.to_sym
+ @response.content_type = mime_type.to_s
+ block_given? ? block.call : @controller.send(:render, :action => @controller.action_name)
+ end
+ end
+
+ def any(*args, &block)
+ args.each { |type| send(type, &block) }
+ end
+
+ def method_missing(symbol, &block)
+ mime_constant = symbol.to_s.upcase
+
+ if Mime::SET.include?(Mime.const_get(mime_constant))
+ custom(Mime.const_get(mime_constant), &block)
+ else
+ super
+ end
+ end
+
+ def respond
+ for priority in @mime_type_priority
+ if priority == Mime::ALL
+ @responses[@order.first].call
+ return
+ else
+ if @responses[priority]
+ @responses[priority].call
+ return # mime type match found, be happy and return
+ end
+ end
+ end
+
+ if @order.include?(Mime::ALL)
+ @responses[Mime::ALL].call
+ else
+ @controller.send :head, :not_acceptable
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_type.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_type.rb
new file mode 100644
index 000000000..ec9d2eea6
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_type.rb
@@ -0,0 +1,163 @@
+module Mime
+ SET = []
+ EXTENSION_LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
+ LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
+
+ # Encapsulates the notion of a mime type. Can be used at render time, for example, with:
+ #
+ # class PostsController < ActionController::Base
+ # def show
+ # @post = Post.find(params[:id])
+ #
+ # respond_to do |format|
+ # format.html
+ # format.ics { render :text => post.to_ics, :mime_type => Mime::Type["text/calendar"] }
+ # format.xml { render :xml => @people.to_xml }
+ # end
+ # end
+ # end
+ class Type
+ # A simple helper class used in parsing the accept header
+ class AcceptItem #:nodoc:
+ attr_accessor :order, :name, :q
+
+ def initialize(order, name, q=nil)
+ @order = order
+ @name = name.strip
+ q ||= 0.0 if @name == Mime::ALL # default wilcard match to end of list
+ @q = ((q || 1.0).to_f * 100).to_i
+ end
+
+ def to_s
+ @name
+ end
+
+ def <=>(item)
+ result = item.q <=> q
+ result = order <=> item.order if result == 0
+ result
+ end
+
+ def ==(item)
+ name == (item.respond_to?(:name) ? item.name : item)
+ end
+ end
+
+ class << self
+ def lookup(string)
+ LOOKUP[string]
+ end
+
+ def lookup_by_extension(extension)
+ EXTENSION_LOOKUP[extension]
+ end
+
+ # Registers an alias that's not used on mime type lookup, but can be referenced directly. Especially useful for
+ # rendering different HTML versions depending on the user agent, like an iPhone.
+ def register_alias(string, symbol, extension_synonyms = [])
+ register(string, symbol, [], extension_synonyms, true)
+ end
+
+ def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
+ Mime.instance_eval { const_set symbol.to_s.upcase, Type.new(string, symbol, mime_type_synonyms) }
+
+ SET << Mime.const_get(symbol.to_s.upcase)
+
+ ([string] + mime_type_synonyms).each { |string| LOOKUP[string] = SET.last } unless skip_lookup
+ ([symbol.to_s] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext] = SET.last }
+ end
+
+ def parse(accept_header)
+ # keep track of creation order to keep the subsequent sort stable
+ list = []
+ accept_header.split(/,/).each_with_index do |header, index|
+ params = header.split(/;\s*q=/)
+ list << AcceptItem.new(index, *params) unless params.empty?
+ end
+ list.sort!
+
+ # Take care of the broken text/xml entry by renaming or deleting it
+ text_xml = list.index("text/xml")
+ app_xml = list.index(Mime::XML.to_s)
+
+ if text_xml && app_xml
+ # set the q value to the max of the two
+ list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
+
+ # make sure app_xml is ahead of text_xml in the list
+ if app_xml > text_xml
+ list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
+ app_xml, text_xml = text_xml, app_xml
+ end
+
+ # delete text_xml from the list
+ list.delete_at(text_xml)
+
+ elsif text_xml
+ list[text_xml].name = Mime::XML.to_s
+ end
+
+ # Look for more specific xml-based types and sort them ahead of app/xml
+
+ if app_xml
+ idx = app_xml
+ app_xml_type = list[app_xml]
+
+ while(idx < list.length)
+ type = list[idx]
+ break if type.q < app_xml_type.q
+ if type.name =~ /\+xml$/
+ list[app_xml], list[idx] = list[idx], list[app_xml]
+ app_xml = idx
+ end
+ idx += 1
+ end
+ end
+
+ list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
+ list
+ end
+ end
+
+ def initialize(string, symbol = nil, synonyms = [])
+ @symbol, @synonyms = symbol, synonyms
+ @string = string
+ end
+
+ def to_s
+ @string
+ end
+
+ def to_str
+ to_s
+ end
+
+ def to_sym
+ @symbol || @string.to_sym
+ end
+
+ def ===(list)
+ if list.is_a?(Array)
+ (@synonyms + [ self ]).any? { |synonym| list.include?(synonym) }
+ else
+ super
+ end
+ end
+
+ def ==(mime_type)
+ (@synonyms + [ self ]).any? { |synonym| synonym.to_s == mime_type.to_s } if mime_type
+ end
+
+ private
+ def method_missing(method, *args)
+ if method.to_s =~ /(\w+)\?$/
+ mime_type = $1.downcase.to_sym
+ mime_type == @symbol || (mime_type == :html && @symbol == :all)
+ else
+ super
+ end
+ end
+ end
+end
+
+require 'action_controller/mime_types'
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_types.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_types.rb
new file mode 100644
index 000000000..71706b4c4
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_types.rb
@@ -0,0 +1,20 @@
+# Build list of Mime types for HTTP responses
+# http://www.iana.org/assignments/media-types/
+
+Mime::Type.register "*/*", :all
+Mime::Type.register "text/plain", :text, [], %w(txt)
+Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
+Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript )
+Mime::Type.register "text/css", :css
+Mime::Type.register "text/calendar", :ics
+Mime::Type.register "text/csv", :csv
+Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
+Mime::Type.register "application/rss+xml", :rss
+Mime::Type.register "application/atom+xml", :atom
+Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml )
+
+Mime::Type.register "multipart/form-data", :multipart_form
+Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
+
+# http://www.ietf.org/rfc/rfc4627.txt
+Mime::Type.register "application/json", :json, %w( text/x-json )
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/polymorphic_routes.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/polymorphic_routes.rb
new file mode 100644
index 000000000..94aefc9aa
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/polymorphic_routes.rb
@@ -0,0 +1,88 @@
+module ActionController
+ module PolymorphicRoutes
+ def polymorphic_url(record_or_hash_or_array, options = {})
+ record = extract_record(record_or_hash_or_array)
+
+ namespace = extract_namespace(record_or_hash_or_array)
+
+ args = case record_or_hash_or_array
+ when Hash; [ record_or_hash_or_array ]
+ when Array; record_or_hash_or_array.dup
+ else [ record_or_hash_or_array ]
+ end
+
+ inflection =
+ case
+ when options[:action] == "new"
+ args.pop
+ :singular
+ when record.respond_to?(:new_record?) && record.new_record?
+ args.pop
+ :plural
+ else
+ :singular
+ end
+
+ named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
+ send!(named_route, *args)
+ end
+
+ def polymorphic_path(record_or_hash_or_array)
+ polymorphic_url(record_or_hash_or_array, :routing_type => :path)
+ end
+
+ %w(edit new formatted).each do |action|
+ module_eval <<-EOT, __FILE__, __LINE__
+ def #{action}_polymorphic_url(record_or_hash)
+ polymorphic_url(record_or_hash, :action => "#{action}")
+ end
+
+ def #{action}_polymorphic_path(record_or_hash)
+ polymorphic_url(record_or_hash, :action => "#{action}", :routing_type => :path)
+ end
+ EOT
+ end
+
+
+ private
+ def action_prefix(options)
+ options[:action] ? "#{options[:action]}_" : ""
+ end
+
+ def routing_type(options)
+ "#{options[:routing_type] || "url"}"
+ end
+
+ def build_named_route_call(records, namespace, inflection, options = {})
+ records = Array.new([extract_record(records)]) unless records.is_a?(Array)
+ base_segment = "#{RecordIdentifier.send!("#{inflection}_class_name", records.pop)}_"
+
+ method_root = records.reverse.inject(base_segment) do |string, name|
+ segment = "#{RecordIdentifier.send!("singular_class_name", name)}_"
+ segment << string
+ end
+
+ action_prefix(options) + namespace + method_root + routing_type(options)
+ end
+
+ def extract_record(record_or_hash_or_array)
+ case record_or_hash_or_array
+ when Array; record_or_hash_or_array.last
+ when Hash; record_or_hash_or_array[:id]
+ else record_or_hash_or_array
+ end
+ end
+
+ def extract_namespace(record_or_hash_or_array)
+ returning "" do |namespace|
+ if record_or_hash_or_array.is_a?(Array)
+ record_or_hash_or_array.delete_if do |record_or_namespace|
+ if record_or_namespace.is_a?(String) || record_or_namespace.is_a?(Symbol)
+ namespace << "#{record_or_namespace.to_s}_"
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/record_identifier.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/record_identifier.rb
new file mode 100644
index 000000000..bdf2753a7
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/record_identifier.rb
@@ -0,0 +1,91 @@
+module ActionController
+ # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
+ # Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate
+ # the view actions to a higher logical level. Example:
+ #
+ # # routes
+ # map.resources :posts
+ #
+ # # view
+ # <% div_for(post) do %> <div id="post_45" class="post">
+ # <%= post.body %> What a wonderful world!
+ # <% end %> </div>
+ #
+ # # controller
+ # def destroy
+ # post = Post.find(params[:id])
+ # post.destroy
+ #
+ # respond_to do |format|
+ # format.html { redirect_to(post) } # Calls polymorphic_url(post) which in turn calls post_url(post)
+ # format.js do
+ # # Calls: new Effect.fade('post_45');
+ # render(:update) { |page| page[post].visual_effect(:fade) }
+ # end
+ # end
+ # end
+ #
+ # As the example above shows, you can stop caring to a large extent what the actual id of the post is. You just know
+ # that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
+ # convention and allows you to write less code if you follow it.
+ module RecordIdentifier
+ extend self
+
+ # Returns plural/singular for a record or class. Example:
+ #
+ # partial_path(post) # => "posts/post"
+ # partial_path(Person) # => "people/person"
+ def partial_path(record_or_class)
+ klass = class_from_record_or_class(record_or_class)
+ "#{klass.name.tableize}/#{klass.name.demodulize.underscore}"
+ end
+
+ # The DOM class convention is to use the singular form of an object or class. Examples:
+ #
+ # dom_class(post) # => "post"
+ # dom_class(Person) # => "person"
+ #
+ # If you need to address multiple instances of the same class in the same view, you can prefix the dom_class:
+ #
+ # dom_class(post, :edit) # => "edit_post"
+ # dom_class(Person, :edit) # => "edit_person"
+ def dom_class(record_or_class, prefix = nil)
+ [ prefix, singular_class_name(record_or_class) ].compact * '_'
+ end
+
+ # The DOM class convention is to use the singular form of an object or class with the id following an underscore.
+ # If no id is found, prefix with "new_" instead. Examples:
+ #
+ # dom_class(Post.new(:id => 45)) # => "post_45"
+ # dom_class(Post.new) # => "new_post"
+ #
+ # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
+ #
+ # dom_class(Post.new(:id => 45), :edit) # => "edit_post_45"
+ def dom_id(record, prefix = nil)
+ prefix ||= 'new' unless record.id
+ [ prefix, singular_class_name(record), record.id ].compact * '_'
+ end
+
+ # Returns the plural class name of a record or class. Examples:
+ #
+ # plural_class_name(post) # => "posts"
+ # plural_class_name(Highrise::Person) # => "highrise_people"
+ def plural_class_name(record_or_class)
+ singular_class_name(record_or_class).pluralize
+ end
+
+ # Returns the singular class name of a record or class. Examples:
+ #
+ # singular_class_name(post) # => "post"
+ # singular_class_name(Highrise::Person) # => "highrise_person"
+ def singular_class_name(record_or_class)
+ class_from_record_or_class(record_or_class).name.underscore.tr('/', '_')
+ end
+
+ private
+ def class_from_record_or_class(record_or_class)
+ record_or_class.is_a?(Class) ? record_or_class : record_or_class.class
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/request.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/request.rb
new file mode 100755
index 000000000..19948da9c
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/request.rb
@@ -0,0 +1,730 @@
+require 'tempfile'
+require 'stringio'
+require 'strscan'
+
+module ActionController
+ # HTTP methods which are accepted by default.
+ ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete options ))
+
+ # CgiRequest and TestRequest provide concrete implementations.
+ class AbstractRequest
+ cattr_accessor :relative_url_root
+ remove_method :relative_url_root
+
+ # The hash of environment variables for this request,
+ # such as { 'RAILS_ENV' => 'production' }.
+ attr_reader :env
+
+ # The true HTTP request method as a lowercase symbol, such as :get.
+ # UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
+ def request_method
+ @request_method ||= begin
+ method = ((@env['REQUEST_METHOD'] == 'POST' && !parameters[:_method].blank?) ? parameters[:_method].to_s : @env['REQUEST_METHOD']).downcase
+ if ACCEPTED_HTTP_METHODS.include?(method)
+ method.to_sym
+ else
+ raise UnknownHttpMethod, "#{method}, accepted HTTP methods are #{ACCEPTED_HTTP_METHODS.to_a.to_sentence}"
+ end
+ end
+ end
+
+ # The HTTP request method as a lowercase symbol, such as :get.
+ # Note, HEAD is returned as :get since the two are functionally
+ # equivalent from the application's perspective.
+ def method
+ request_method == :head ? :get : request_method
+ end
+
+ # Is this a GET (or HEAD) request? Equivalent to request.method == :get
+ def get?
+ method == :get
+ end
+
+ # Is this a POST request? Equivalent to request.method == :post
+ def post?
+ request_method == :post
+ end
+
+ # Is this a PUT request? Equivalent to request.method == :put
+ def put?
+ request_method == :put
+ end
+
+ # Is this a DELETE request? Equivalent to request.method == :delete
+ def delete?
+ request_method == :delete
+ end
+
+ # Is this a HEAD request? request.method sees HEAD as :get, so check the
+ # HTTP method directly.
+ def head?
+ request_method == :head
+ end
+
+ def headers
+ @env
+ end
+
+ def content_length
+ @content_length ||= env['CONTENT_LENGTH'].to_i
+ end
+
+ # The MIME type of the HTTP request, such as Mime::XML.
+ #
+ # For backward compatibility, the post format is extracted from the
+ # X-Post-Data-Format HTTP header if present.
+ def content_type
+ @content_type ||= Mime::Type.lookup(content_type_without_parameters)
+ end
+
+ # Returns the accepted MIME type for the request
+ def accepts
+ @accepts ||=
+ if @env['HTTP_ACCEPT'].to_s.strip.empty?
+ [ content_type, Mime::ALL ].compact # make sure content_type being nil is not included
+ else
+ Mime::Type.parse(@env['HTTP_ACCEPT'])
+ end
+ end
+
+ # Returns the Mime type for the format used in the request. If there is no format available, the first of the
+ # accept types will be used. Examples:
+ #
+ # GET /posts/5.xml | request.format => Mime::XML
+ # GET /posts/5.xhtml | request.format => Mime::HTML
+ # GET /posts/5 | request.format => request.accepts.first (usually Mime::HTML for browsers)
+ def format
+ @format ||= parameters[:format] ? Mime::Type.lookup_by_extension(parameters[:format]) : accepts.first
+ end
+
+
+ # Sets the format by string extension, which can be used to force custom formats that are not controlled by the extension.
+ # Example:
+ #
+ # class ApplicationController < ActionController::Base
+ # before_filter :adjust_format_for_iphone
+ #
+ # private
+ # def adjust_format_for_iphone
+ # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
+ # end
+ # end
+ def format=(extension)
+ parameters[:format] = extension.to_s
+ format
+ end
+
+ # Returns true if the request's "X-Requested-With" header contains
+ # "XMLHttpRequest". (The Prototype Javascript library sends this header with
+ # every Ajax request.)
+ def xml_http_request?
+ !(@env['HTTP_X_REQUESTED_WITH'] !~ /XMLHttpRequest/i)
+ end
+ alias xhr? :xml_http_request?
+
+ # Determine originating IP address. REMOTE_ADDR is the standard
+ # but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
+ # HTTP_X_FORWARDED_FOR are set by proxies so check for these before
+ # falling back to REMOTE_ADDR. HTTP_X_FORWARDED_FOR may be a comma-
+ # delimited list in the case of multiple chained proxies; the first is
+ # the originating IP.
+ #
+ # Security note: do not use if IP spoofing is a concern for your
+ # application. Since remote_ip checks HTTP headers for addresses forwarded
+ # by proxies, the client may send any IP. remote_addr can't be spoofed but
+ # also doesn't work behind a proxy, since it's always the proxy's IP.
+ def remote_ip
+ return @env['HTTP_CLIENT_IP'] if @env.include? 'HTTP_CLIENT_IP'
+
+ if @env.include? 'HTTP_X_FORWARDED_FOR' then
+ remote_ips = @env['HTTP_X_FORWARDED_FOR'].split(',').reject do |ip|
+ ip.strip =~ /^unknown$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
+ end
+
+ return remote_ips.first.strip unless remote_ips.empty?
+ end
+
+ @env['REMOTE_ADDR']
+ end
+
+ # Returns the lowercase name of the HTTP server software.
+ def server_software
+ (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
+ end
+
+
+ # Returns the complete URL used for this request
+ def url
+ protocol + host_with_port + request_uri
+ end
+
+ # Return 'https://' if this is an SSL request and 'http://' otherwise.
+ def protocol
+ ssl? ? 'https://' : 'http://'
+ end
+
+ # Is this an SSL request?
+ def ssl?
+ @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
+ end
+
+ # Returns the host for this request, such as example.com.
+ def host
+ end
+
+ # Returns a host:port string for this request, such as example.com or
+ # example.com:8080.
+ def host_with_port
+ @host_with_port ||= host + port_string
+ end
+
+ # Returns the port number of this request as an integer.
+ def port
+ @port_as_int ||= @env['SERVER_PORT'].to_i
+ end
+
+ # Returns the standard port number for this request's protocol
+ def standard_port
+ case protocol
+ when 'https://' then 443
+ else 80
+ end
+ end
+
+ # Returns a port suffix like ":8080" if the port number of this request
+ # is not the default HTTP port 80 or HTTPS port 443.
+ def port_string
+ (port == standard_port) ? '' : ":#{port}"
+ end
+
+ # Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify
+ # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
+ def domain(tld_length = 1)
+ return nil unless named_host?(host)
+
+ host.split('.').last(1 + tld_length).join('.')
+ end
+
+ # Returns all the subdomains as an array, so ["dev", "www"] would be returned for "dev.www.rubyonrails.org".
+ # You can specify a different <tt>tld_length</tt>, such as 2 to catch ["www"] instead of ["www", "rubyonrails"]
+ # in "www.rubyonrails.co.uk".
+ def subdomains(tld_length = 1)
+ return [] unless named_host?(host)
+ parts = host.split('.')
+ parts[0..-(tld_length+2)]
+ end
+
+ # Return the query string, accounting for server idiosyncracies.
+ def query_string
+ if uri = @env['REQUEST_URI']
+ uri.split('?', 2)[1] || ''
+ else
+ @env['QUERY_STRING'] || ''
+ end
+ end
+
+ # Return the request URI, accounting for server idiosyncracies.
+ # WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
+ def request_uri
+ if uri = @env['REQUEST_URI']
+ # Remove domain, which webrick puts into the request_uri.
+ (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
+ else
+ # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
+ script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
+ uri = @env['PATH_INFO']
+ uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil?
+ unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty?
+ uri << '?' << env_qs
+ end
+
+ if uri.nil?
+ @env.delete('REQUEST_URI')
+ uri
+ else
+ @env['REQUEST_URI'] = uri
+ end
+ end
+ end
+
+ # Returns the interpreted path to requested resource after all the installation directory of this application was taken into account
+ def path
+ path = (uri = request_uri) ? uri.split('?').first.to_s : ''
+
+ # Cut off the path to the installation directory if given
+ path.sub!(%r/^#{relative_url_root}/, '')
+ path || ''
+ end
+
+ # Returns the path minus the web server relative installation directory.
+ # This can be set with the environment variable RAILS_RELATIVE_URL_ROOT.
+ # It can be automatically extracted for Apache setups. If the server is not
+ # Apache, this method returns an empty string.
+ def relative_url_root
+ @@relative_url_root ||= case
+ when @env["RAILS_RELATIVE_URL_ROOT"]
+ @env["RAILS_RELATIVE_URL_ROOT"]
+ when server_software == 'apache'
+ @env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '')
+ else
+ ''
+ end
+ end
+
+
+ # Read the request body. This is useful for web services that need to
+ # work with raw requests directly.
+ def raw_post
+ unless env.include? 'RAW_POST_DATA'
+ env['RAW_POST_DATA'] = body.read(content_length)
+ body.rewind if body.respond_to?(:rewind)
+ end
+ env['RAW_POST_DATA']
+ end
+
+ # Returns both GET and POST parameters in a single hash.
+ def parameters
+ @parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
+ end
+
+ def path_parameters=(parameters) #:nodoc:
+ @path_parameters = parameters
+ @symbolized_path_parameters = @parameters = nil
+ end
+
+ # The same as <tt>path_parameters</tt> with explicitly symbolized keys
+ def symbolized_path_parameters
+ @symbolized_path_parameters ||= path_parameters.symbolize_keys
+ end
+
+ # Returns a hash with the parameters used to form the path of the request.
+ # Returned hash keys are strings. See <tt>symbolized_path_parameters</tt> for symbolized keys.
+ #
+ # Example:
+ #
+ # {'action' => 'my_action', 'controller' => 'my_controller'}
+ def path_parameters
+ @path_parameters ||= {}
+ end
+
+
+ #--
+ # Must be implemented in the concrete request
+ #++
+
+ # The request body is an IO input stream.
+ def body
+ end
+
+ def query_parameters #:nodoc:
+ end
+
+ def request_parameters #:nodoc:
+ end
+
+ def cookies #:nodoc:
+ end
+
+ def session #:nodoc:
+ end
+
+ def session=(session) #:nodoc:
+ @session = session
+ end
+
+ def reset_session #:nodoc:
+ end
+
+ protected
+ # The raw content type string. Use when you need parameters such as
+ # charset or boundary which aren't included in the content_type MIME type.
+ # Overridden by the X-POST_DATA_FORMAT header for backward compatibility.
+ def content_type_with_parameters
+ content_type_from_legacy_post_data_format_header ||
+ env['CONTENT_TYPE'].to_s
+ end
+
+ # The raw content type string with its parameters stripped off.
+ def content_type_without_parameters
+ @content_type_without_parameters ||= self.class.extract_content_type_without_parameters(content_type_with_parameters)
+ end
+
+ private
+ def content_type_from_legacy_post_data_format_header
+ if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
+ case x_post_format.to_s.downcase
+ when 'yaml'; 'application/x-yaml'
+ when 'xml'; 'application/xml'
+ end
+ end
+ end
+
+ def parse_formatted_request_parameters
+ return {} if content_length.zero?
+
+ content_type, boundary = self.class.extract_multipart_boundary(content_type_with_parameters)
+
+ # Don't parse params for unknown requests.
+ return {} if content_type.blank?
+
+ mime_type = Mime::Type.lookup(content_type)
+ strategy = ActionController::Base.param_parsers[mime_type]
+
+ # Only multipart form parsing expects a stream.
+ body = (strategy && strategy != :multipart_form) ? raw_post : self.body
+
+ case strategy
+ when Proc
+ strategy.call(body)
+ when :url_encoded_form
+ self.class.clean_up_ajax_request_body! body
+ self.class.parse_query_parameters(body)
+ when :multipart_form
+ self.class.parse_multipart_form_parameters(body, boundary, content_length, env)
+ when :xml_simple, :xml_node
+ body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
+ when :yaml
+ YAML.load(body)
+ else
+ {}
+ end
+ rescue Exception => e # YAML, XML or Ruby code block errors
+ raise
+ { "body" => body,
+ "content_type" => content_type_with_parameters,
+ "content_length" => content_length,
+ "exception" => "#{e.message} (#{e.class})",
+ "backtrace" => e.backtrace }
+ end
+
+ def named_host?(host)
+ !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
+ end
+
+ class << self
+ def parse_query_parameters(query_string)
+ return {} if query_string.blank?
+
+ pairs = query_string.split('&').collect do |chunk|
+ next if chunk.empty?
+ key, value = chunk.split('=', 2)
+ next if key.empty?
+ value = value.nil? ? nil : CGI.unescape(value)
+ [ CGI.unescape(key), value ]
+ end.compact
+
+ UrlEncodedPairParser.new(pairs).result
+ end
+
+ def parse_request_parameters(params)
+ parser = UrlEncodedPairParser.new
+
+ params = params.dup
+ until params.empty?
+ for key, value in params
+ if key.blank?
+ params.delete key
+ elsif !key.include?('[')
+ # much faster to test for the most common case first (GET)
+ # and avoid the call to build_deep_hash
+ parser.result[key] = get_typed_value(value[0])
+ params.delete key
+ elsif value.is_a?(Array)
+ parser.parse(key, get_typed_value(value.shift))
+ params.delete key if value.empty?
+ else
+ raise TypeError, "Expected array, found #{value.inspect}"
+ end
+ end
+ end
+
+ parser.result
+ end
+
+ def parse_multipart_form_parameters(body, boundary, content_length, env)
+ parse_request_parameters(read_multipart(body, boundary, content_length, env))
+ end
+
+ def extract_multipart_boundary(content_type_with_parameters)
+ if content_type_with_parameters =~ MULTIPART_BOUNDARY
+ ['multipart/form-data', $1.dup]
+ else
+ extract_content_type_without_parameters(content_type_with_parameters)
+ end
+ end
+
+ def extract_content_type_without_parameters(content_type_with_parameters)
+ $1.strip.downcase if content_type_with_parameters =~ /^([^,\;]*)/
+ end
+
+ def clean_up_ajax_request_body!(body)
+ body.chop! if body[-1] == 0
+ body.gsub!(/&_=$/, '')
+ end
+
+
+ private
+ def get_typed_value(value)
+ case value
+ when String
+ value
+ when NilClass
+ ''
+ when Array
+ value.map { |v| get_typed_value(v) }
+ else
+ if value.is_a?(UploadedFile)
+ # Uploaded file
+ if value.original_filename
+ value
+ # Multipart param
+ else
+ result = value.read
+ value.rewind
+ result
+ end
+ # Unknown value, neither string nor multipart.
+ else
+ raise "Unknown form value: #{value.inspect}"
+ end
+ end
+ end
+
+
+ MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
+
+ EOL = "\015\012"
+
+ def read_multipart(body, boundary, content_length, env)
+ params = Hash.new([])
+ boundary = "--" + boundary
+ quoted_boundary = Regexp.quote(boundary, "n")
+ buf = ""
+ bufsize = 10 * 1024
+ boundary_end=""
+
+ # start multipart/form-data
+ body.binmode if defined? body.binmode
+ boundary_size = boundary.size + EOL.size
+ content_length -= boundary_size
+ status = body.read(boundary_size)
+ if nil == status
+ raise EOFError, "no content body"
+ elsif boundary + EOL != status
+ raise EOFError, "bad content body"
+ end
+
+ loop do
+ head = nil
+ content =
+ if 10240 < content_length
+ UploadedTempfile.new("CGI")
+ else
+ UploadedStringIO.new
+ end
+ content.binmode if defined? content.binmode
+
+ until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
+
+ if (not head) and /#{EOL}#{EOL}/n.match(buf)
+ buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
+ head = $1.dup
+ ""
+ end
+ next
+ end
+
+ if head and ( (EOL + boundary + EOL).size < buf.size )
+ content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
+ buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
+ end
+
+ c = if bufsize < content_length
+ body.read(bufsize)
+ else
+ body.read(content_length)
+ end
+ if c.nil? || c.empty?
+ raise EOFError, "bad content body"
+ end
+ buf.concat(c)
+ content_length -= c.size
+ end
+
+ buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
+ content.print $1
+ if "--" == $2
+ content_length = -1
+ end
+ boundary_end = $2.dup
+ ""
+ end
+
+ content.rewind
+
+ head =~ /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni
+ if filename = $1 || $2
+ if /Mac/ni.match(env['HTTP_USER_AGENT']) and
+ /Mozilla/ni.match(env['HTTP_USER_AGENT']) and
+ (not /MSIE/ni.match(env['HTTP_USER_AGENT']))
+ filename = CGI.unescape(filename)
+ end
+ content.original_path = filename.dup
+ end
+
+ head =~ /Content-Type: ([^\r]*)/ni
+ content.content_type = $1.dup if $1
+
+ head =~ /Content-Disposition:.* name="?([^\";]*)"?/ni
+ name = $1.dup if $1
+
+ if params.has_key?(name)
+ params[name].push(content)
+ else
+ params[name] = [content]
+ end
+ break if buf.size == 0
+ break if content_length == -1
+ end
+ raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
+
+ begin
+ body.rewind if body.respond_to?(:rewind)
+ rescue Errno::ESPIPE
+ # Handles exceptions raised by input streams that cannot be rewound
+ # such as when using plain CGI under Apache
+ end
+
+ params
+ end
+ end
+ end
+
+ class UrlEncodedPairParser < StringScanner #:nodoc:
+ attr_reader :top, :parent, :result
+
+ def initialize(pairs = [])
+ super('')
+ @result = {}
+ pairs.each { |key, value| parse(key, value) }
+ end
+
+ KEY_REGEXP = %r{([^\[\]=&]+)}
+ BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
+
+ # Parse the query string
+ def parse(key, value)
+ self.string = key
+ @top, @parent = result, nil
+
+ # First scan the bare key
+ key = scan(KEY_REGEXP) or return
+ key = post_key_check(key)
+
+ # Then scan as many nestings as present
+ until eos?
+ r = scan(BRACKETED_KEY_REGEXP) or return
+ key = self[1]
+ key = post_key_check(key)
+ end
+
+ bind(key, value)
+ end
+
+ private
+ # After we see a key, we must look ahead to determine our next action. Cases:
+ #
+ # [] follows the key. Then the value must be an array.
+ # = follows the key. (A value comes next)
+ # & or the end of string follows the key. Then the key is a flag.
+ # otherwise, a hash follows the key.
+ def post_key_check(key)
+ if scan(/\[\]/) # a[b][] indicates that b is an array
+ container(key, Array)
+ nil
+ elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
+ container(key, Hash)
+ nil
+ else # End of key? We do nothing.
+ key
+ end
+ end
+
+ # Add a container to the stack.
+ def container(key, klass)
+ type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
+ value = bind(key, klass.new)
+ type_conflict! klass, value unless value.is_a?(klass)
+ push(value)
+ end
+
+ # Push a value onto the 'stack', which is actually only the top 2 items.
+ def push(value)
+ @parent, @top = @top, value
+ end
+
+ # Bind a key (which may be nil for items in an array) to the provided value.
+ def bind(key, value)
+ if top.is_a? Array
+ if key
+ if top[-1].is_a?(Hash) && ! top[-1].key?(key)
+ top[-1][key] = value
+ else
+ top << {key => value}.with_indifferent_access
+ push top.last
+ end
+ else
+ top << value
+ end
+ elsif top.is_a? Hash
+ key = CGI.unescape(key)
+ parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
+ return top[key] ||= value
+ else
+ raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
+ end
+
+ return value
+ end
+
+ def type_conflict!(klass, value)
+ raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value."
+ end
+ end
+
+ module UploadedFile
+ def self.included(base)
+ base.class_eval do
+ attr_accessor :original_path, :content_type
+ alias_method :local_path, :path
+ end
+ end
+
+ # Take the basename of the upload's original filename.
+ # This handles the full Windows paths given by Internet Explorer
+ # (and perhaps other broken user agents) without affecting
+ # those which give the lone filename.
+ # The Windows regexp is adapted from Perl's File::Basename.
+ def original_filename
+ unless defined? @original_filename
+ @original_filename =
+ unless original_path.blank?
+ if original_path =~ /^(?:.*[:\\\/])?(.*)/m
+ $1
+ else
+ File.basename original_path
+ end
+ end
+ end
+ @original_filename
+ end
+ end
+
+ class UploadedStringIO < StringIO
+ include UploadedFile
+ end
+
+ class UploadedTempfile < Tempfile
+ include UploadedFile
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/request_forgery_protection.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/request_forgery_protection.rb
new file mode 100644
index 000000000..75f9c0b28
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/request_forgery_protection.rb
@@ -0,0 +1,132 @@
+module ActionController #:nodoc:
+ class InvalidAuthenticityToken < ActionControllerError #:nodoc:
+ end
+
+ module RequestForgeryProtection
+ def self.included(base)
+ base.class_eval do
+ class_inheritable_accessor :request_forgery_protection_options
+ self.request_forgery_protection_options = {}
+ helper_method :form_authenticity_token
+ helper_method :protect_against_forgery?
+ end
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ # Protect a controller's actions from CSRF attacks by ensuring that all forms are coming from the current web application, not
+ # a forged link from another site. This is done by embedding a token based on the session (which an attacker wouldn't know) in
+ # all forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only
+ # HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication
+ # scheme there anyway). Also, GET requests are not protected as these should be indempotent anyway.
+ #
+ # You turn this on with the #protect_from_forgery method, which will perform the check and raise
+ # an ActionController::InvalidAuthenticityToken if the token doesn't match what was expected. And it will add
+ # a _authenticity_token parameter to all forms that are automatically generated by Rails. You can customize the error message
+ # given through public/422.html.
+ #
+ # Learn more about CSRF (Cross-Site Request Forgery) attacks:
+ #
+ # * http://isc.sans.org/diary.html?storyid=1750
+ # * http://en.wikipedia.org/wiki/Cross-site_request_forgery
+ #
+ # Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security blanket for your rails application.
+ # There are a few guidelines you should follow:
+ #
+ # * Keep your GET requests safe and idempotent. More reading material:
+ # * http://www.xml.com/pub/a/2002/04/24/deviant.html
+ # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
+ # * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look for "Expires: at end of session"
+ #
+ # If you need to construct a request yourself, but still want to take advantage of forgery protection, you can grab the
+ # authenticity_token using the form_authenticity_token helper method and make it part of the parameters yourself.
+ #
+ # Example:
+ #
+ # class FooController < ApplicationController
+ # # uses the cookie session store (then you don't need a separate :secret)
+ # protect_from_forgery :except => :index
+ #
+ # # uses one of the other session stores that uses a session_id value.
+ # protect_from_forgery :secret => 'my-little-pony', :except => :index
+ #
+ # # you can disable csrf protection on controller-by-controller basis:
+ # skip_before_filter :verify_authenticity_token
+ # end
+ #
+ # If you are upgrading from Rails 1.x, disable forgery protection to
+ # simplify your tests. Add this to config/environments/test.rb:
+ #
+ # # Disable request forgery protection in test environment
+ # config.action_controller.allow_forgery_protection = false
+ #
+ # Valid Options:
+ #
+ # * <tt>:only/:except</tt> - passed to the before_filter call. Set which actions are verified.
+ # * <tt>:secret</tt> - Custom salt used to generate the form_authenticity_token.
+ # Leave this off if you are using the cookie session store.
+ # * <tt>:digest</tt> - Message digest used for hashing. Defaults to 'SHA1'
+ def protect_from_forgery(options = {})
+ self.request_forgery_protection_token ||= :authenticity_token
+ before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except)
+ request_forgery_protection_options.update(options)
+ end
+ end
+
+ protected
+ # The actual before_filter that is used. Modify this to change how you handle unverified requests.
+ def verify_authenticity_token
+ verified_request? || raise(ActionController::InvalidAuthenticityToken)
+ end
+
+ # Returns true or false if a request is verified. Checks:
+ #
+ # * is the format restricted? By default, only HTML and AJAX requests are checked.
+ # * is it a GET request? Gets should be safe and idempotent
+ # * Does the form_authenticity_token match the given _token value from the params?
+ def verified_request?
+ !protect_against_forgery? ||
+ request.method == :get ||
+ !verifiable_request_format? ||
+ form_authenticity_token == params[request_forgery_protection_token]
+ end
+
+ def verifiable_request_format?
+ request.format.html? || request.format.js?
+ end
+
+ # Sets the token value for the current session. Pass a :secret option in #protect_from_forgery to add a custom salt to the hash.
+ def form_authenticity_token
+ @form_authenticity_token ||= if request_forgery_protection_options[:secret]
+ authenticity_token_from_session_id
+ elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest)
+ authenticity_token_from_cookie_session
+ elsif session.nil?
+ raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session."
+ else
+ raise InvalidAuthenticityToken, "No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store)."
+ end
+ end
+
+ # Generates a unique digest using the session_id and the CSRF secret.
+ def authenticity_token_from_session_id
+ key = if request_forgery_protection_options[:secret].respond_to?(:call)
+ request_forgery_protection_options[:secret].call(@session)
+ else
+ request_forgery_protection_options[:secret]
+ end
+ digest = request_forgery_protection_options[:digest] ||= 'SHA1'
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(digest), key.to_s, session.session_id.to_s)
+ end
+
+ # No secret was given, so assume this is a cookie session store.
+ def authenticity_token_from_cookie_session
+ session[:csrf_id] ||= CGI::Session.generate_unique_id
+ session.dbman.generate_digest(session[:csrf_id])
+ end
+
+ def protect_against_forgery?
+ allow_forgery_protection && request_forgery_protection_token
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/request_profiler.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/request_profiler.rb
new file mode 100755
index 000000000..62f6e665f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/request_profiler.rb
@@ -0,0 +1,138 @@
+require 'optparse'
+require 'action_controller/integration'
+
+module ActionController
+ class RequestProfiler
+ # Wrap up the integration session runner.
+ class Sandbox
+ include Integration::Runner
+
+ def self.benchmark(n, script)
+ new(script).benchmark(n)
+ end
+
+ def initialize(script_path)
+ @quiet = false
+ define_run_method(File.read(script_path))
+ reset!
+ end
+
+ def benchmark(n)
+ @quiet = true
+ print ' '
+ result = Benchmark.realtime do
+ n.times do |i|
+ run
+ print i % 10 == 0 ? 'x' : '.'
+ $stdout.flush
+ end
+ end
+ puts
+ result
+ ensure
+ @quiet = false
+ end
+
+ def say(message)
+ puts " #{message}" unless @quiet
+ end
+
+ private
+ def define_run_method(script)
+ instance_eval "def run; #{script}; end", __FILE__, __LINE__
+ end
+ end
+
+
+ attr_reader :options
+
+ def initialize(options = {})
+ @options = default_options.merge(options)
+ end
+
+
+ def self.run(args = nil, options = {})
+ profiler = new(options)
+ profiler.parse_options(args) if args
+ profiler.run
+ end
+
+ def run
+ sandbox = Sandbox.new(options[:script])
+
+ puts 'Warming up once'
+
+ elapsed = warmup(sandbox)
+ puts '%.2f sec, %d requests, %d req/sec' % [elapsed, sandbox.request_count, sandbox.request_count / elapsed]
+ puts "\n#{options[:benchmark] ? 'Benchmarking' : 'Profiling'} #{options[:n]}x"
+
+ options[:benchmark] ? benchmark(sandbox) : profile(sandbox)
+ end
+
+ def profile(sandbox)
+ load_ruby_prof
+
+ results = RubyProf.profile { benchmark(sandbox) }
+
+ show_profile_results results
+ results
+ end
+
+ def benchmark(sandbox)
+ sandbox.request_count = 0
+ elapsed = sandbox.benchmark(options[:n]).to_f
+ count = sandbox.request_count.to_i
+ puts '%.2f sec, %d requests, %d req/sec' % [elapsed, count, count / elapsed]
+ end
+
+ def warmup(sandbox)
+ Benchmark.realtime { sandbox.run }
+ end
+
+ def default_options
+ { :n => 100, :open => 'open %s &' }
+ end
+
+ # Parse command-line options
+ def parse_options(args)
+ OptionParser.new do |opt|
+ opt.banner = "USAGE: #{$0} [options] [session script path]"
+
+ opt.on('-n', '--times [0000]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i }
+ opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = v }
+ opt.on('--open [CMD]', 'Command to open profile results. Defaults to "open %s &"') { |v| options[:open] = v }
+ opt.on('-h', '--help', 'Show this help') { puts opt; exit }
+
+ opt.parse args
+
+ if args.empty?
+ puts opt
+ exit
+ end
+ options[:script] = args.pop
+ end
+ end
+
+ protected
+ def load_ruby_prof
+ begin
+ require 'ruby-prof'
+ #RubyProf.measure_mode = RubyProf::ALLOCATED_OBJECTS
+ rescue LoadError
+ abort '`gem install ruby-prof` to use the profiler'
+ end
+ end
+
+ def show_profile_results(results)
+ File.open "#{RAILS_ROOT}/tmp/profile-graph.html", 'w' do |file|
+ RubyProf::GraphHtmlPrinter.new(results).print(file)
+ `#{options[:open] % file.path}` if options[:open]
+ end
+
+ File.open "#{RAILS_ROOT}/tmp/profile-flat.txt", 'w' do |file|
+ RubyProf::FlatPrinter.new(results).print(file)
+ `#{options[:open] % file.path}` if options[:open]
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/rescue.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/rescue.rb
new file mode 100644
index 000000000..b91115a93
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/rescue.rb
@@ -0,0 +1,258 @@
+module ActionController #:nodoc:
+ # Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view
+ # (with a nice user-friendly explanation) or for the developers view (with tons of debugging information). The developers view
+ # is already implemented by the Action Controller, but the public view should be tailored to your specific application.
+ #
+ # The default behavior for public exceptions is to render a static html file with the name of the error code thrown. If no such
+ # file exists, an empty response is sent with the correct status code.
+ #
+ # You can override what constitutes a local request by overriding the <tt>local_request?</tt> method in your own controller.
+ # Custom rescue behavior is achieved by overriding the <tt>rescue_action_in_public</tt> and <tt>rescue_action_locally</tt> methods.
+ module Rescue
+ LOCALHOST = '127.0.0.1'.freeze
+
+ DEFAULT_RESCUE_RESPONSE = :internal_server_error
+ DEFAULT_RESCUE_RESPONSES = {
+ 'ActionController::RoutingError' => :not_found,
+ 'ActionController::UnknownAction' => :not_found,
+ 'ActiveRecord::RecordNotFound' => :not_found,
+ 'ActiveRecord::StaleObjectError' => :conflict,
+ 'ActiveRecord::RecordInvalid' => :unprocessable_entity,
+ 'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
+ 'ActionController::MethodNotAllowed' => :method_not_allowed,
+ 'ActionController::NotImplemented' => :not_implemented,
+ 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
+ }
+
+ DEFAULT_RESCUE_TEMPLATE = 'diagnostics'
+ DEFAULT_RESCUE_TEMPLATES = {
+ 'ActionController::MissingTemplate' => 'missing_template',
+ 'ActionController::RoutingError' => 'routing_error',
+ 'ActionController::UnknownAction' => 'unknown_action',
+ 'ActionView::TemplateError' => 'template_error'
+ }
+
+ def self.included(base) #:nodoc:
+ base.cattr_accessor :rescue_responses
+ base.rescue_responses = Hash.new(DEFAULT_RESCUE_RESPONSE)
+ base.rescue_responses.update DEFAULT_RESCUE_RESPONSES
+
+ base.cattr_accessor :rescue_templates
+ base.rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE)
+ base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES
+
+ base.class_inheritable_array :rescue_handlers
+ base.rescue_handlers = []
+
+ base.extend(ClassMethods)
+ base.class_eval do
+ alias_method_chain :perform_action, :rescue
+ end
+ end
+
+ module ClassMethods
+ def process_with_exception(request, response, exception) #:nodoc:
+ new.process(request, response, :rescue_action, exception)
+ end
+
+ # Rescue exceptions raised in controller actions.
+ #
+ # <tt>rescue_from</tt> receives a series of exception classes or class
+ # names, and a trailing :with option with the name of a method or a Proc
+ # object to be called to handle them. Alternatively a block can be given.
+ #
+ # Handlers that take one argument will be called with the exception, so
+ # that the exception can be inspected when dealing with it.
+ #
+ # Handlers are inherited. They are searched from right to left, from
+ # bottom to top, and up the hierarchy. The handler of the first class for
+ # which exception.is_a?(klass) holds true is the one invoked, if any.
+ #
+ # class ApplicationController < ActionController::Base
+ # rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
+ # rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
+ #
+ # rescue_from 'MyAppError::Base' do |exception|
+ # render :xml => exception, :status => 500
+ # end
+ #
+ # protected
+ # def deny_access
+ # ...
+ # end
+ #
+ # def show_errors(exception)
+ # exception.record.new_record? ? ...
+ # end
+ # end
+ def rescue_from(*klasses, &block)
+ options = klasses.extract_options!
+ unless options.has_key?(:with)
+ block_given? ? options[:with] = block : raise(ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument.")
+ end
+
+ klasses.each do |klass|
+ key = if klass.is_a?(Class) && klass <= Exception
+ klass.name
+ elsif klass.is_a?(String)
+ klass
+ else
+ raise(ArgumentError, "#{klass} is neither an Exception nor a String")
+ end
+
+ # Order is important, we put the pair at the end. When dealing with an
+ # exception we will follow the documented order going from right to left.
+ rescue_handlers << [key, options[:with]]
+ end
+ end
+ end
+
+ protected
+ # Exception handler called when the performance of an action raises an exception.
+ def rescue_action(exception)
+ log_error(exception) if logger
+ erase_results if performed?
+
+ # Let the exception alter the response if it wants.
+ # For example, MethodNotAllowed sets the Allow header.
+ if exception.respond_to?(:handle_response!)
+ exception.handle_response!(response)
+ end
+
+ if consider_all_requests_local || local_request?
+ rescue_action_locally(exception)
+ else
+ rescue_action_in_public(exception)
+ end
+ end
+
+ # Overwrite to implement custom logging of errors. By default logs as fatal.
+ def log_error(exception) #:doc:
+ ActiveSupport::Deprecation.silence do
+ if ActionView::TemplateError === exception
+ logger.fatal(exception.to_s)
+ else
+ logger.fatal(
+ "\n\n#{exception.class} (#{exception.message}):\n " +
+ clean_backtrace(exception).join("\n ") +
+ "\n\n"
+ )
+ end
+ end
+ end
+
+ # Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By
+ # default will call render_optional_error_file. Override this method to provide more user friendly error messages.s
+ def rescue_action_in_public(exception) #:doc:
+ render_optional_error_file response_code_for_rescue(exception)
+ end
+
+ # Attempts to render a static error page based on the <tt>status_code</tt> thrown,
+ # or just return headers if no such file exists. For example, if a 500 error is
+ # being handled Rails will first attempt to render the file at <tt>public/500.html</tt>.
+ # If the file doesn't exist, the body of the response will be left empty.
+ def render_optional_error_file(status_code)
+ status = interpret_status(status_code)
+ path = "#{RAILS_ROOT}/public/#{status[0,3]}.html"
+ if File.exist?(path)
+ render :file => path, :status => status
+ else
+ head status
+ end
+ end
+
+ # True if the request came from localhost, 127.0.0.1. Override this
+ # method if you wish to redefine the meaning of a local request to
+ # include remote IP addresses or other criteria.
+ def local_request? #:doc:
+ request.remote_addr == LOCALHOST and request.remote_ip == LOCALHOST
+ end
+
+ # Render detailed diagnostics for unhandled exceptions rescued from
+ # a controller action.
+ def rescue_action_locally(exception)
+ add_variables_to_assigns
+ @template.instance_variable_set("@exception", exception)
+ @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))
+
+ response.content_type = Mime::HTML
+ render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
+ end
+
+ # Tries to rescue the exception by looking up and calling a registered handler.
+ def rescue_action_with_handler(exception)
+ if handler = handler_for_rescue(exception)
+ if handler.arity != 0
+ handler.call(exception)
+ else
+ handler.call
+ end
+ true # don't rely on the return value of the handler
+ end
+ end
+
+ private
+ def perform_action_with_rescue #:nodoc:
+ perform_action_without_rescue
+ rescue Exception => exception # errors from action performed
+ return if rescue_action_with_handler(exception)
+
+ rescue_action(exception)
+ end
+
+ def rescues_path(template_name)
+ "#{File.dirname(__FILE__)}/templates/rescues/#{template_name}.erb"
+ end
+
+ def template_path_for_local_rescue(exception)
+ rescues_path(rescue_templates[exception.class.name])
+ end
+
+ def response_code_for_rescue(exception)
+ rescue_responses[exception.class.name]
+ end
+
+ def handler_for_rescue(exception)
+ # We go from right to left because pairs are pushed onto rescue_handlers
+ # as rescue_from declarations are found.
+ _, handler = *rescue_handlers.reverse.detect do |klass_name, handler|
+ # The purpose of allowing strings in rescue_from is to support the
+ # declaration of handler associations for exception classes whose
+ # definition is yet unknown.
+ #
+ # Since this loop needs the constants it would be inconsistent to
+ # assume they should exist at this point. An early raised exception
+ # could trigger some other handler and the array could include
+ # precisely a string whose corresponding constant has not yet been
+ # seen. This is why we are tolerant to unknown constants.
+ #
+ # Note that this tolerance only matters if the exception was given as
+ # a string, otherwise a NameError will be raised by the interpreter
+ # itself when rescue_from CONSTANT is executed.
+ klass = self.class.const_get(klass_name) rescue nil
+ klass ||= klass_name.constantize rescue nil
+ exception.is_a?(klass) if klass
+ end
+
+ case handler
+ when Symbol
+ method(handler)
+ when Proc
+ handler.bind(self)
+ end
+ end
+
+ def clean_backtrace(exception)
+ if backtrace = exception.backtrace
+ if defined?(RAILS_ROOT)
+ backtrace.map { |line| line.sub RAILS_ROOT, '' }
+ else
+ backtrace
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/resources.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/resources.rb
new file mode 100644
index 000000000..8ec6b84a5
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/resources.rb
@@ -0,0 +1,529 @@
+module ActionController
+ # == Overview
+ #
+ # ActionController::Resources are a way of defining RESTful resources. A RESTful resource, in basic terms,
+ # is something that can be pointed at and it will respond with a representation of the data requested.
+ # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
+ # requests XML data.
+ #
+ # RESTful design is based on the assumption that there are four generic verbs that a user of an
+ # application can request from a resource (the noun).
+ #
+ # Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
+ # denotes the type of action that should take place.
+ #
+ # === The Different Methods and their Usage
+ #
+ # +GET+ Requests for a resource, no saving or editing of a resource should occur in a GET request
+ # +POST+ Creation of resources
+ # +PUT+ Editing of attributes on a resource
+ # +DELETE+ Deletion of a resource
+ #
+ # === Examples
+ #
+ # # A GET request on the Posts resource is asking for all Posts
+ # GET /posts
+ #
+ # # A GET request on a single Post resource is asking for that particular Post
+ # GET /posts/1
+ #
+ # # A POST request on the Posts resource is asking for a Post to be created with the supplied details
+ # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } }
+ #
+ # # A PUT request on a single Post resource is asking for a Post to be updated
+ # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } }
+ #
+ # # A DELETE request on a single Post resource is asking for it to be deleted
+ # DELETE /posts # with => { :id => 1 }
+ #
+ # By using the REST convention, users of our application can assume certain things about how the data
+ # is requested and how it is returned. Rails simplifies the routing part of RESTful design by
+ # supplying you with methods to create them in your routes.rb file.
+ #
+ # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
+ module Resources
+ class Resource #:nodoc:
+ attr_reader :collection_methods, :member_methods, :new_methods
+ attr_reader :path_prefix, :name_prefix
+ attr_reader :plural, :singular
+ attr_reader :options
+
+ def initialize(entities, options)
+ @plural ||= entities
+ @singular ||= options[:singular] || plural.to_s.singularize
+
+ @options = options
+
+ arrange_actions
+ add_default_actions
+ set_prefixes
+ end
+
+ def controller
+ @controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}"
+ end
+
+ def requirements(with_id = false)
+ @requirements ||= @options[:requirements] || {}
+ @id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ }
+
+ with_id ? @requirements.merge(@id_requirement) : @requirements
+ end
+
+ def conditions
+ @conditions = @options[:conditions] || {}
+ end
+
+ def path
+ @path ||= "#{path_prefix}/#{plural}"
+ end
+
+ def new_path
+ @new_path ||= "#{path}/new"
+ end
+
+ def member_path
+ @member_path ||= "#{path}/:id"
+ end
+
+ def nesting_path_prefix
+ @nesting_path_prefix ||= "#{path}/:#{singular}_id"
+ end
+
+ def nesting_name_prefix
+ "#{name_prefix}#{singular}_"
+ end
+
+ def action_separator
+ @action_separator ||= Base.resource_action_separator
+ end
+
+ def uncountable?
+ @singular.to_s == @plural.to_s
+ end
+
+ protected
+ def arrange_actions
+ @collection_methods = arrange_actions_by_methods(options.delete(:collection))
+ @member_methods = arrange_actions_by_methods(options.delete(:member))
+ @new_methods = arrange_actions_by_methods(options.delete(:new))
+ end
+
+ def add_default_actions
+ add_default_action(member_methods, :get, :edit)
+ add_default_action(new_methods, :get, :new)
+ end
+
+ def set_prefixes
+ @path_prefix = options.delete(:path_prefix)
+ @name_prefix = options.delete(:name_prefix)
+ end
+
+ def arrange_actions_by_methods(actions)
+ (actions || {}).inject({}) do |flipped_hash, (key, value)|
+ (flipped_hash[value] ||= []) << key
+ flipped_hash
+ end
+ end
+
+ def add_default_action(collection, method, action)
+ (collection[method] ||= []).unshift(action)
+ end
+ end
+
+ class SingletonResource < Resource #:nodoc:
+ def initialize(entity, options)
+ @singular = @plural = entity
+ options[:controller] ||= @singular.to_s.pluralize
+ super
+ end
+
+ alias_method :member_path, :path
+ alias_method :nesting_path_prefix, :path
+ end
+
+ # Creates named routes for implementing verb-oriented controllers
+ # for a collection resource.
+ #
+ # For example:
+ #
+ # map.resources :messages
+ #
+ # will map the following actions in the corresponding controller:
+ #
+ # class MessagesController < ActionController::Base
+ # # GET messages_url
+ # def index
+ # # return all messages
+ # end
+ #
+ # # GET new_message_url
+ # def new
+ # # return an HTML form for describing a new message
+ # end
+ #
+ # # POST messages_url
+ # def create
+ # # create a new message
+ # end
+ #
+ # # GET message_url(:id => 1)
+ # def show
+ # # find and return a specific message
+ # end
+ #
+ # # GET edit_message_url(:id => 1)
+ # def edit
+ # # return an HTML form for editing a specific message
+ # end
+ #
+ # # PUT message_url(:id => 1)
+ # def update
+ # # find and update a specific message
+ # end
+ #
+ # # DELETE message_url(:id => 1)
+ # def destroy
+ # # delete a specific message
+ # end
+ # end
+ #
+ # Along with the routes themselves, #resources generates named routes for use in
+ # controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers:
+ #
+ # Named Route Helpers
+ # ============ =====================================================
+ # messages messages_url, hash_for_messages_url,
+ # messages_path, hash_for_messages_path
+ #
+ # message message_url(id), hash_for_message_url(id),
+ # message_path(id), hash_for_message_path(id)
+ #
+ # new_message new_message_url, hash_for_new_message_url,
+ # new_message_path, hash_for_new_message_path
+ #
+ # edit_message edit_message_url(id), hash_for_edit_message_url(id),
+ # edit_message_path(id), hash_for_edit_message_path(id)
+ #
+ # You can use these helpers instead of #url_for or methods that take #url_for parameters. For example:
+ #
+ # redirect_to :controller => 'messages', :action => 'index'
+ # # and
+ # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %>
+ #
+ # now become:
+ #
+ # redirect_to messages_url
+ # # and
+ # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically
+ #
+ # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your
+ # form tags. The form helpers make this a little easier. For an update form with a <tt>@message</tt> object:
+ #
+ # <%= form_tag message_path(@message), :method => :put %>
+ #
+ # or
+ #
+ # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
+ #
+ # The #resources method accepts the following options to customize the resulting routes:
+ # * <tt>:collection</tt> - add named routes for other actions that operate on the collection.
+ # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>
+ # or <tt>:any</tt> if the method does not matter. These routes map to a URL like /messages/rss, with a route of rss_messages_url.
+ # * <tt>:member</tt> - same as :collection, but for actions that operate on a specific member.
+ # * <tt>:new</tt> - same as :collection, but for actions that operate on the new resource action.
+ # * <tt>:controller</tt> - specify the controller name for the routes.
+ # * <tt>:singular</tt> - specify the singular name used in the member routes.
+ # * <tt>:requirements</tt> - set custom routing parameter requirements.
+ # * <tt>:conditions</tt> - specify custom routing recognition conditions. Resources sets the :method value for the method-specific routes.
+ # * <tt>:path_prefix</tt> - set a prefix to the routes with required route variables.
+ #
+ # Weblog comments usually belong to a post, so you might use resources like:
+ #
+ # map.resources :articles
+ # map.resources :comments, :path_prefix => '/articles/:article_id'
+ #
+ # You can nest resources calls to set this automatically:
+ #
+ # map.resources :articles do |article|
+ # article.resources :comments
+ # end
+ #
+ # The comment resources work the same, but must now include a value for :article_id.
+ #
+ # article_comments_url(@article)
+ # article_comment_url(@article, @comment)
+ #
+ # article_comments_url(:article_id => @article)
+ # article_comment_url(:article_id => @article, :id => @comment)
+ #
+ # * <tt>:name_prefix</tt> - define a prefix for all generated routes, usually ending in an underscore.
+ # Use this if you have named routes that may clash.
+ #
+ # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
+ # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
+ #
+ # You may also use :name_prefix to override the generic named routes in a nested resource:
+ #
+ # map.resources :articles do |article|
+ # article.resources :comments, :name_prefix => nil
+ # end
+ #
+ # This will yield named resources like so:
+ #
+ # comments_url(@article)
+ # comment_url(@article, @comment)
+ #
+ # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
+ #
+ # Examples:
+ #
+ # map.resources :messages, :path_prefix => "/thread/:thread_id"
+ # # --> GET /thread/7/messages/1
+ #
+ # map.resources :messages, :collection => { :rss => :get }
+ # # --> GET /messages/rss (maps to the #rss action)
+ # # also adds a named route called "rss_messages"
+ #
+ # map.resources :messages, :member => { :mark => :post }
+ # # --> POST /messages/1/mark (maps to the #mark action)
+ # # also adds a named route called "mark_message"
+ #
+ # map.resources :messages, :new => { :preview => :post }
+ # # --> POST /messages/new/preview (maps to the #preview action)
+ # # also adds a named route called "preview_new_message"
+ #
+ # map.resources :messages, :new => { :new => :any, :preview => :post }
+ # # --> POST /messages/new/preview (maps to the #preview action)
+ # # also adds a named route called "preview_new_message"
+ # # --> /messages/new can be invoked via any request method
+ #
+ # map.resources :messages, :controller => "categories",
+ # :path_prefix => "/category/:category_id",
+ # :name_prefix => "category_"
+ # # --> GET /categories/7/messages/1
+ # # has named route "category_message"
+ #
+ # The #resources method sets HTTP method restrictions on the routes it generates. For example, making an
+ # HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
+ # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for resource routes.
+ def resources(*entities, &block)
+ options = entities.extract_options!
+ entities.each { |entity| map_resource(entity, options.dup, &block) }
+ end
+
+ # Creates named routes for implementing verb-oriented controllers for a singleton resource.
+ # A singleton resource is global to its current context. For unnested singleton resources,
+ # the resource is global to the current user visiting the application, such as a user's
+ # /account profile. For nested singleton resources, the resource is global to its parent
+ # resource, such as a <tt>projects</tt> resource that <tt>has_one :project_manager</tt>.
+ # The <tt>project_manager</tt> should be mapped as a singleton resource under <tt>projects</tt>:
+ #
+ # map.resources :projects do |project|
+ # project.resource :project_manager
+ # end
+ #
+ # See map.resources for general conventions. These are the main differences:
+ # * A singular name is given to map.resource. The default controller name is still taken from the plural name.
+ # * To specify a custom plural name, use the :plural option. There is no :singular option.
+ # * No default index route is created for the singleton resource controller.
+ # * When nesting singleton resources, only the singular name is used as the path prefix (example: 'account/messages/1')
+ #
+ # For example:
+ #
+ # map.resource :account
+ #
+ # maps these actions in the Accounts controller:
+ #
+ # class AccountsController < ActionController::Base
+ # # GET new_account_url
+ # def new
+ # # return an HTML form for describing the new account
+ # end
+ #
+ # # POST account_url
+ # def create
+ # # create an account
+ # end
+ #
+ # # GET account_url
+ # def show
+ # # find and return the account
+ # end
+ #
+ # # GET edit_account_url
+ # def edit
+ # # return an HTML form for editing the account
+ # end
+ #
+ # # PUT account_url
+ # def update
+ # # find and update the account
+ # end
+ #
+ # # DELETE account_url
+ # def destroy
+ # # delete the account
+ # end
+ # end
+ #
+ # Along with the routes themselves, #resource generates named routes for
+ # use in controllers and views. <tt>map.resource :account</tt> produces
+ # these named routes and helpers:
+ #
+ # Named Route Helpers
+ # ============ =============================================
+ # account account_url, hash_for_account_url,
+ # account_path, hash_for_account_path
+ #
+ # new_account new_account_url, hash_for_new_account_url,
+ # new_account_path, hash_for_new_account_path
+ #
+ # edit_account edit_account_url, hash_for_edit_account_url,
+ # edit_account_path, hash_for_edit_account_path
+ def resource(*entities, &block)
+ options = entities.extract_options!
+ entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
+ end
+
+ private
+ def map_resource(entities, options = {}, &block)
+ resource = Resource.new(entities, options)
+
+ with_options :controller => resource.controller do |map|
+ map_collection_actions(map, resource)
+ map_default_collection_actions(map, resource)
+ map_new_actions(map, resource)
+ map_member_actions(map, resource)
+
+ map_associations(resource, options)
+
+ if block_given?
+ with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
+ end
+ end
+ end
+
+ def map_singleton_resource(entities, options = {}, &block)
+ resource = SingletonResource.new(entities, options)
+
+ with_options :controller => resource.controller do |map|
+ map_collection_actions(map, resource)
+ map_default_singleton_actions(map, resource)
+ map_new_actions(map, resource)
+ map_member_actions(map, resource)
+
+ map_associations(resource, options)
+
+ if block_given?
+ with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
+ end
+ end
+ end
+
+ def map_associations(resource, options)
+ path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
+ name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
+
+ Array(options[:has_many]).each do |association|
+ resources(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace])
+ end
+
+ Array(options[:has_one]).each do |association|
+ resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace])
+ end
+ end
+
+ def map_collection_actions(map, resource)
+ resource.collection_methods.each do |method, actions|
+ actions.each do |action|
+ action_options = action_options_for(action, resource, method)
+ map.named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
+ map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}.:format", action_options)
+ end
+ end
+ end
+
+ def map_default_collection_actions(map, resource)
+ index_action_options = action_options_for("index", resource)
+ index_route_name = "#{resource.name_prefix}#{resource.plural}"
+
+ if resource.uncountable?
+ index_route_name << "_index"
+ end
+
+ map.named_route(index_route_name, resource.path, index_action_options)
+ map.named_route("formatted_#{index_route_name}", "#{resource.path}.:format", index_action_options)
+
+ create_action_options = action_options_for("create", resource)
+ map.connect(resource.path, create_action_options)
+ map.connect("#{resource.path}.:format", create_action_options)
+ end
+
+ def map_default_singleton_actions(map, resource)
+ create_action_options = action_options_for("create", resource)
+ map.connect(resource.path, create_action_options)
+ map.connect("#{resource.path}.:format", create_action_options)
+ end
+
+ def map_new_actions(map, resource)
+ resource.new_methods.each do |method, actions|
+ actions.each do |action|
+ action_options = action_options_for(action, resource, method)
+ if action == :new
+ map.named_route("new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options)
+ map.named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}.:format", action_options)
+ else
+ map.named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
+ map.named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}.:format", action_options)
+ end
+ end
+ end
+ end
+
+ def map_member_actions(map, resource)
+ resource.member_methods.each do |method, actions|
+ actions.each do |action|
+ action_options = action_options_for(action, resource, method)
+ map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}", action_options)
+ map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}.:format",action_options)
+ end
+ end
+
+ show_action_options = action_options_for("show", resource)
+ map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
+ map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", show_action_options)
+
+ update_action_options = action_options_for("update", resource)
+ map.connect(resource.member_path, update_action_options)
+ map.connect("#{resource.member_path}.:format", update_action_options)
+
+ destroy_action_options = action_options_for("destroy", resource)
+ map.connect(resource.member_path, destroy_action_options)
+ map.connect("#{resource.member_path}.:format", destroy_action_options)
+ end
+
+ def add_conditions_for(conditions, method)
+ returning({:conditions => conditions.dup}) do |options|
+ options[:conditions][:method] = method unless method == :any
+ end
+ end
+
+ def action_options_for(action, resource, method = nil)
+ default_options = { :action => action.to_s }
+ require_id = !resource.kind_of?(SingletonResource)
+ case default_options[:action]
+ when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
+ when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
+ when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
+ when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
+ when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
+ else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements)
+ end
+ end
+ end
+end
+
+class ActionController::Routing::RouteSet::Mapper
+ include ActionController::Resources
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/response.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/response.rb
new file mode 100755
index 000000000..1d9f6676b
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/response.rb
@@ -0,0 +1,76 @@
+require 'digest/md5'
+
+module ActionController
+ class AbstractResponse #:nodoc:
+ DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
+ attr_accessor :request
+ 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)
+ self.headers["Content-Type"] = charset ? "#{mime_type}; charset=#{charset}" : mime_type
+ end
+
+ def content_type
+ content_type = String(headers["Content-Type"] || headers["type"]).split(";")[0]
+ content_type.blank? ? nil : content_type
+ end
+
+ def charset=(encoding)
+ self.headers["Content-Type"] = "#{content_type || Mime::HTML}; charset=#{encoding}"
+ end
+
+ def charset
+ charset = String(headers["Content-Type"] || headers["type"]).split(";")[1]
+ charset.blank? ? nil : charset.strip.split("=")[1]
+ end
+
+ def redirect(to_url, response_status)
+ self.headers["Status"] = response_status
+ self.headers["Location"] = to_url
+
+ self.body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
+ end
+
+ def prepare!
+ handle_conditional_get!
+ convert_content_type!
+ set_content_length!
+ end
+
+
+ private
+ def handle_conditional_get!
+ if body.is_a?(String) && (headers['Status'] ? headers['Status'][0..2] == '200' : true) && !body.empty?
+ self.headers['ETag'] ||= %("#{Digest::MD5.hexdigest(body)}")
+ self.headers['Cache-Control'] = 'private, max-age=0, must-revalidate' if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
+
+ if request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag']
+ self.headers['Status'] = '304 Not Modified'
+ self.body = ''
+ end
+ end
+ end
+
+ def convert_content_type!
+ if content_type = headers.delete("Content-Type")
+ self.headers["type"] = content_type
+ end
+ if content_type = headers.delete("Content-type")
+ self.headers["type"] = content_type
+ end
+ if content_type = headers.delete("content-type")
+ self.headers["type"] = content_type
+ end
+ end
+
+ # Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice
+ # for, say, a 2GB streaming file.
+ def set_content_length!
+ self.headers["Content-Length"] = body.size unless body.respond_to?(:call)
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/routing.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/routing.rb
new file mode 100644
index 000000000..edd4683f6
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/routing.rb
@@ -0,0 +1,1499 @@
+require 'cgi'
+require 'uri'
+require 'action_controller/polymorphic_routes'
+require 'action_controller/routing_optimisation'
+
+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
+ # == 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 turn is fed some :id.
+ #
+ # Suppose you get an incoming request for <tt>/blog/edit/22</tt>, 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
+ # and will be applied last. If no route matches, 404 is returned.
+ #
+ # Within blocks, the empty pattern 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 - you simply append a
+ # Hash at the end of your mapping to set any 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/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' }
+ #
+ # == Named routes
+ #
+ # Routes can be named with the syntax <tt>map.name_of_route options</tt>,
+ # 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 <tt>map.root</tt> 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 regular 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 <tt>*[string]</tt> 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 <tt>params[:path]</tt> in
+ # this case.
+ #
+ # == Route conditions
+ #
+ # With conditions you can define restrictions on routes. Currently the only valid condition is <tt>:method</tt>.
+ #
+ # * <tt>:method</tt> - Allows you to specify which method can access the route. Possible values are <tt>:post</tt>,
+ # <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
+ # <tt>:any</tt> means that any method can access the route.
+ #
+ # Example:
+ #
+ # map.connect 'post/:id', :controller => 'posts', :action => 'show',
+ # :conditions => { :method => :get }
+ # map.connect 'post/:id', :controller => 'posts', :action => 'create_comment',
+ # :conditions => { :method => :post }
+ #
+ # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
+ # URL will route to the <tt>show</tt> action.
+ #
+ # == Reloading routes
+ #
+ # You can reload routes if you feel you must:
+ #
+ # ActionController::Routing::Routes.reload
+ #
+ # This will clear all named routes and reload routes.rb if the file has been modified from
+ # last load. To absolutely force reloading, use +reload!+.
+ #
+ # == 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
+ # a URL fits options while +assert_recognizes+ tests that a 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
+ #
+ # == View a list of all your routes
+ #
+ # Run <tt>rake routes</tt>.
+ #
+ module Routing
+ SEPARATORS = %w( / . ? )
+
+ HTTP_METHODS = [:get, :head, :post, :put, :delete]
+
+ ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
+
+ # The root paths which may contain controller files
+ mattr_accessor :controller_paths
+ self.controller_paths = []
+
+ # A helper module to hold URL related helpers.
+ module Helpers
+ include PolymorphicRoutes
+ end
+
+ class << self
+ def with_controllers(names)
+ prior_controllers = @possible_controllers
+ use_controllers! names
+ yield
+ ensure
+ use_controllers! prior_controllers
+ end
+
+ 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
+
+ class Route #:nodoc:
+ attr_accessor :segments, :requirements, :conditions, :optimise
+
+ def initialize
+ @segments = []
+ @requirements = {}
+ @conditions = {}
+ @optimise = true
+ end
+
+ # Indicates whether the routes should be optimised with the string interpolation
+ # version of the named routes methods.
+ def optimise?
+ @optimise && ActionController::Base::optimise_named_routes
+ end
+
+ def segment_keys
+ segments.collect do |segment|
+ segment.key if segment.respond_to? :key
+ end.compact
+ 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 = {}"
+
+ # 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(options))\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(options)]\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[:#{key}] == #{req.inspect}"
+ end
+ end
+ 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
+
+ # 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
+
+ # 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 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).each do |key|
+ if value = hash[key]
+ elements << value.to_query(key)
+ end
+ end
+
+ elements.empty? ? '' : "?#{elements.sort * '&'}"
+ 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
+
+ # 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 defined? @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 Segment #:nodoc:
+ RESERVED_PCHAR = ':@&=+$,;'
+ UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
+
+ attr_accessor :is_optional
+ alias_method :optional?, :is_optional
+
+ def initialize
+ self.is_optional = false
+ end
+
+ def extraction_code
+ nil
+ end
+
+ # Continue generating string for the prior segments.
+ def continue_string_structure(prior_segments)
+ if prior_segments.empty?
+ interpolation_statement(prior_segments)
+ else
+ new_priors = prior_segments[0..-2]
+ prior_segments.last.string_structure(new_priors)
+ end
+ end
+
+ def interpolation_chunk
+ URI.escape(value, UNSAFE_PCHAR)
+ end
+
+ # 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 string_structure(prior_segments)
+ optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments)
+ 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
+
+ # Recognition
+
+ def match_extraction(next_capture)
+ nil
+ end
+
+ # 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 StaticSegment < Segment #:nodoc:
+ attr_accessor :value, :raw
+ alias_method :raw?, :raw
+
+ def initialize(value = nil)
+ super()
+ self.value = value
+ end
+
+ def interpolation_chunk
+ raw? ? value : super
+ 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
+
+ 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}] && hash[:#{key}].to_param #{"|| #{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
+ 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(value_code = "#{local_name}")
+ "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}"
+ 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)
+ # All non code-related keys (such as :id, :slug) are URI-unescaped as
+ # path parameters.
+ default_value = default ? default.inspect : nil
+ %[
+ value = if (m = match[#{next_capture}])
+ URI.unescape(m)
+ else
+ #{default_value}
+ end
+ params[:#{key}] = value if value
+ ]
+ 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 URI.escape the controller name since it may contain slashes.
+ def interpolation_chunk(value_code = "#{local_name}")
+ "\#{#{value_code}.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 PathSegment < DynamicSegment #:nodoc:
+ RESERVED_PCHAR = "#{Segment::RESERVED_PCHAR}/"
+ UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
+
+ def interpolation_chunk(value_code = "#{local_name}")
+ "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::PathSegment::UNSAFE_PCHAR)}"
+ end
+
+ def default
+ ''
+ end
+
+ def default=(path)
+ raise RoutingError, "paths cannot have non-empty default values" unless path.blank?
+ end
+
+ 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
+
+ def optionality_implied?
+ true
+ end
+
+ class Result < ::Array #:nodoc:
+ def to_s() join '/' end
+ def self.new_escaped(strings)
+ new strings.collect {|str| URI.unescape str}
+ end
+ end
+ end
+
+ class RouteBuilder #:nodoc:
+ attr_accessor :separators, :optional_separators
+
+ 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, []
+
+ until rest.empty?
+ segment, rest = segment_for rest
+ segments << segment
+ end
+ segments
+ end
+
+ # 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
+ [segment, $~.post_match]
+ 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
+
+ if options[:namespace]
+ options[:controller] = "#{options[:path_prefix]}/#{options[:controller]}"
+ options.delete(:path_prefix)
+ options.delete(:name_prefix)
+ options.delete(:namespace)
+ end
+
+ requirements = (options.delete(:requirements) || {}).dup
+ defaults = (options.delete(:defaults) || {}).dup
+ conditions = (options.delete(:conditions) || {}).dup
+
+ 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
+
+ [defaults, requirements, conditions]
+ end
+
+ # 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] == ?/
+
+ path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix]
+
+ 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
+
+ # Routes cannot use the current string interpolation method
+ # if there are user-supplied :requirements as the interpolation
+ # code won't raise RoutingErrors when generating
+ if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
+ route.optimise = false
+ end
+
+ if !route.significant_keys.include?(:controller)
+ raise ArgumentError, "Illegal route: the :controller must be specified!"
+ end
+
+ route
+ end
+ end
+
+ class RouteSet #:nodoc:
+ # 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 #:doc:
+ def initialize(set) #:nodoc:
+ @set = set
+ end
+
+ # Create an unnamed route with the provided +path+ and +options+. See
+ # ActionController::Routing for an introduction to routes.
+ def connect(path, options = {})
+ @set.add_route(path, options)
+ end
+
+ # Creates a named route called "root" for matching the root level request.
+ def root(options = {})
+ named_route("root", '', options)
+ end
+
+ def named_route(name, path, options = {}) #:nodoc:
+ @set.add_named_route(name, path, options)
+ end
+
+ # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
+ # Example:
+ #
+ # map.namespace(:admin) do |admin|
+ # admin.resources :products,
+ # :has_many => [ :tags, :images, :variants ]
+ # end
+ #
+ # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
+ # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
+ # Admin::TagsController.
+ def namespace(name, options = {}, &block)
+ if options[:namespace]
+ with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
+ else
+ with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
+ end
+ end
+
+ def method_missing(route_name, *args, &proc) #:nodoc:
+ super unless args.length >= 1 && proc.nil?
+ @set.add_named_route(route_name, *args)
+ end
+ end
+
+ # 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
+ include ActionController::Routing::Optimisation
+ attr_reader :routes, :helpers
+
+ def initialize
+ clear!
+ end
+
+ def clear!
+ @routes = {}
+ @helpers = []
+
+ @module ||= Module.new
+ @module.instance_methods.each do |selector|
+ @module.class_eval { remove_method selector }
+ end
+ 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 reset!
+ old_routes = routes.dup
+ clear!
+ old_routes.each do |name, route|
+ add(name, route)
+ end
+ end
+
+ def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
+ reset! if regenerate
+ Array(destinations).each do |dest|
+ dest.send! :include, @module
+ end
+ 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.module_eval <<-end_eval # We use module_eval to avoid leaks
+ def #{selector}(options = nil)
+ options ? #{options.inspect}.merge(options) : #{options.inspect}
+ end
+ protected :#{selector}
+ end_eval
+ helpers << selector
+ end
+
+ def define_url_helper(route, name, kind, options)
+ selector = url_helper_name(name, kind)
+ # The segment keys used for positional paramters
+
+ hash_access_method = hash_access_name(name, kind)
+
+ # 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)
+ #
+ # Also allow options hash, so you can do
+ #
+ # foo_url(bar, baz, bang, :sort_by => 'baz')
+ #
+ @module.module_eval <<-end_eval # We use module_eval to avoid leaks
+ def #{selector}(*args)
+ #{generate_optimisation_block(route, kind)}
+
+ opts = if args.empty? || Hash === args.first
+ args.first || {}
+ else
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
+ h[k] = v
+ h
+ end
+ options.merge(args)
+ end
+
+ url_for(#{hash_access_method}(opts))
+ end
+ protected :#{selector}
+ end_eval
+ helpers << selector
+ end
+ end
+
+ attr_accessor :routes, :named_routes
+
+ def initialize
+ self.routes = []
+ self.named_routes = NamedRouteCollection.new
+ end
+
+ # 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
+ clear!
+ yield Mapper.new(self)
+ install_helpers
+ end
+
+ def clear!
+ routes.clear
+ named_routes.clear
+ @combined_regexp = nil
+ @routes_by_controller = nil
+ end
+
+ def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
+ Array(destinations).each { |d| d.module_eval { include Helpers } }
+ named_routes.install(destinations, regenerate_code)
+ 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!
+ install_helpers
+ end
+
+ # reload! will always force a reload whereas load checks the timestamp first
+ alias reload! load!
+
+ def reload
+ if @routes_last_modified && defined?(RAILS_ROOT)
+ mtime = File.stat("#{RAILS_ROOT}/config/routes.rb").mtime
+ # if it hasn't been changed, then just return
+ return if mtime == @routes_last_modified
+ # if it has changed then record the new time and fall to the load! below
+ @routes_last_modified = mtime
+ end
+ load!
+ end
+
+ def load_routes!
+ if defined?(RAILS_ROOT) && defined?(::ActionController::Routing::Routes) && self == ::ActionController::Routing::Routes
+ load File.join("#{RAILS_ROOT}/config/routes.rb")
+ @routes_last_modified = File.stat("#{RAILS_ROOT}/config/routes.rb").mtime
+ else
+ add_route ":controller/:action/:id"
+ end
+ end
+
+ def add_route(path, options = {})
+ route = builder.build(path, options)
+ routes << route
+ route
+ end
+
+ def add_named_route(name, path, options = {})
+ # TODO - is options EVER used?
+ name = options[:name_prefix] + name.to_s if options[:name_prefix]
+ named_routes[name.to_sym] = 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.clone
+ options_as_params[:action] ||= 'index' if options[:controller]
+ options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
+ options_as_params
+ end
+
+ def build_expiry(options, recall)
+ recall.inject({}) do |expiry, (key, recalled_value)|
+ expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
+ 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)
+ generate_all = options.delete(:generate_all)
+ if named_route_name
+ named_route = named_routes[named_route_name]
+ options = named_route.parameter_shell.merge(options)
+ end
+
+ options = options_as_params(options)
+ expire_on = build_expiry(options, recall)
+
+ if options[:controller]
+ options[:controller] = options[:controller].to_s
+ end
+ # 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
+
+ # 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
+ else
+ merged[:action] ||= 'index'
+ options[:action] ||= 'index'
+
+ controller = merged[:controller]
+ action = merged[:action]
+
+ raise RoutingError, "Need controller and action!" unless controller && action
+
+ if generate_all
+ # Used by caching to expire all paths for a resource
+ return routes.collect do |route|
+ route.send!(method, options, merged, expire_on)
+ end.compact
+ end
+
+ # 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
+
+ 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 satisfied?"
+ 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={})
+ routes.each do |route|
+ result = route.recognize(path, environment) and return result
+ end
+
+ allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } }
+
+ if environment[:method] && !HTTP_METHODS.include?(environment[:method])
+ raise NotImplemented.new(*allows)
+ elsif !allows.empty?
+ raise MethodNotAllowed.new(*allows)
+ else
+ raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}"
+ end
+ 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
+
+ ::Inflector.module_eval do
+ def inflections_with_route_reloading(&block)
+ returning(inflections_without_route_reloading(&block)) {
+ ActionController::Routing::Routes.reload! if block_given?
+ }
+ end
+
+ alias_method_chain :inflections, :route_reloading
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/routing_optimisation.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/routing_optimisation.rb
new file mode 100644
index 000000000..ba4aeb4e8
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/routing_optimisation.rb
@@ -0,0 +1,119 @@
+module ActionController
+ module Routing
+ # Much of the slow performance from routes comes from the
+ # complexity of expiry, :requirements matching, defaults providing
+ # and figuring out which url pattern to use. With named routes
+ # we can avoid the expense of finding the right route. So if
+ # they've provided the right number of arguments, and have no
+ # :requirements, we can just build up a string and return it.
+ #
+ # To support building optimisations for other common cases, the
+ # generation code is separated into several classes
+ module Optimisation
+ def generate_optimisation_block(route, kind)
+ return "" unless route.optimise?
+ OPTIMISERS.inject("") do |memo, klazz|
+ memo << klazz.new(route, kind).source_code
+ memo
+ end
+ end
+
+ class Optimiser
+ attr_reader :route, :kind
+ def initialize(route, kind)
+ @route = route
+ @kind = kind
+ end
+
+ def guard_condition
+ 'false'
+ end
+
+ def generation_code
+ 'nil'
+ end
+
+ def source_code
+ if applicable?
+ "return #{generation_code} if #{guard_condition}\n"
+ else
+ "\n"
+ end
+ end
+
+ # Temporarily disabled :url optimisation pending proper solution to
+ # Issues around request.host etc.
+ def applicable?
+ true
+ end
+ end
+
+ # Given a route:
+ # map.person '/people/:id'
+ #
+ # If the user calls person_url(@person), we can simply
+ # return a string like "/people/#{@person.to_param}"
+ # rather than triggering the expensive logic in url_for
+ class PositionalArguments < Optimiser
+ def guard_condition
+ number_of_arguments = route.segment_keys.size
+ # if they're using foo_url(:id=>2) it's one
+ # argument, but we don't want to generate /foos/id2
+ if number_of_arguments == 1
+ "defined?(request) && request && args.size == 1 && !args.first.is_a?(Hash)"
+ else
+ "defined?(request) && request && args.size == #{number_of_arguments}"
+ end
+ end
+
+ def generation_code
+ elements = []
+ idx = 0
+
+ if kind == :url
+ elements << '#{request.protocol}'
+ elements << '#{request.host_with_port}'
+ end
+
+ elements << '#{request.relative_url_root if request.relative_url_root}'
+
+ # The last entry in route.segments appears to # *always* be a
+ # 'divider segment' for '/' but we have assertions to ensure that
+ # we don't include the trailing slashes, so skip them.
+ (route.segments.size == 1 ? route.segments : route.segments[0..-2]).each do |segment|
+ if segment.is_a?(DynamicSegment)
+ elements << segment.interpolation_chunk("args[#{idx}].to_param")
+ idx += 1
+ else
+ elements << segment.interpolation_chunk
+ end
+ end
+ %("#{elements * ''}")
+ end
+ end
+
+ # This case is mostly the same as the positional arguments case
+ # above, but it supports additional query parameters as the last
+ # argument
+ class PositionalArgumentsWithAdditionalParams < PositionalArguments
+ def guard_condition
+ "defined?(request) && request && args.size == #{route.segment_keys.size + 1} && !args.last.has_key?(:anchor) && !args.last.has_key?(:port) && !args.last.has_key?(:host)"
+ end
+
+ # This case uses almost the same code as positional arguments,
+ # but add an args.last.to_query on the end
+ def generation_code
+ super.insert(-2, '?#{args.last.to_query}')
+ end
+
+ # To avoid generating http://localhost/?host=foo.example.com we
+ # can't use this optimisation on routes without any segments
+ def applicable?
+ super && route.segment_keys.size > 0
+ end
+ end
+
+ OPTIMISERS = [PositionalArguments, PositionalArgumentsWithAdditionalParams]
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/session/active_record_store.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/active_record_store.rb
new file mode 100644
index 000000000..14747c505
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/active_record_store.rb
@@ -0,0 +1,336 @@
+require 'cgi'
+require 'cgi/session'
+require 'digest/md5'
+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
+ end
+
+
+ # A session store backed by an Active Record class. A default class is
+ # provided, but any object duck-typing to an Active Record +Session+ class
+ # with text +session_id+ and +data+ attributes is sufficient.
+ #
+ # The default assumes a +sessions+ tables with columns:
+ # +id+ (numeric primary key),
+ # +session_id+ (text, or longtext if your session data exceeds 65K), and
+ # +data+ (text or longtext; careful if your session data exceeds 65KB).
+ # The +session_id+ column should always be indexed for speedy lookups.
+ # Session data is marshaled to the +data+ column in Base64 format.
+ # If the data you write is larger than the column's size limit,
+ # ActionController::SessionOverflowError will be raised.
+ #
+ # You may configure the table name, primary key, and data column.
+ # For example, at the end of config/environment.rb:
+ # CGI::Session::ActiveRecordStore::Session.table_name = 'legacy_session_table'
+ # CGI::Session::ActiveRecordStore::Session.primary_key = 'session_id'
+ # CGI::Session::ActiveRecordStore::Session.data_column_name = 'legacy_session_data'
+ # Note that setting the primary key to the session_id frees you from
+ # having a separate id column if you don't want it. However, you must
+ # set session.model.id = session.session_id by hand! A before_filter
+ # on ApplicationController is a good place.
+ #
+ # Since the default class is a simple Active Record, you get timestamps
+ # for free if you add +created_at+ and +updated_at+ datetime columns to
+ # the +sessions+ table, making periodic session expiration a snap.
+ #
+ # You may provide your own session class implementation, whether a
+ # feature-packed Active Record or a bare-metal high-performance SQL
+ # store, by setting
+ # +CGI::Session::ActiveRecordStore.session_class = MySessionClass+
+ # You must implement these methods:
+ # self.find_by_session_id(session_id)
+ # initialize(hash_of_session_id_and_data)
+ # attr_reader :session_id
+ # attr_accessor :data
+ # save
+ # destroy
+ #
+ # The example SqlBypass class is a generic SQL session store. You may
+ # use it as a basis for high-performance database-specific stores.
+ class ActiveRecordStore
+ # The default Active Record class.
+ class Session < ActiveRecord::Base
+ # Customizable data column name. Defaults to 'data'.
+ cattr_accessor :data_column_name
+ self.data_column_name = 'data'
+
+ before_save :marshal_data!
+ before_save :raise_on_session_data_overflow!
+
+ class << self
+ # Don't try to reload ARStore::Session in dev mode.
+ def reloadable? #:nodoc:
+ false
+ end
+
+ def data_column_size_limit
+ @data_column_size_limit ||= columns_hash[@@data_column_name].limit
+ end
+
+ # Hook to set up sessid compatibility.
+ def find_by_session_id(session_id)
+ setup_sessid_compatibility!
+ find_by_session_id(session_id)
+ end
+
+ def marshal(data) Base64.encode64(Marshal.dump(data)) if data end
+ def unmarshal(data) Marshal.load(Base64.decode64(data)) if data end
+
+ def create_table!
+ connection.execute <<-end_sql
+ CREATE TABLE #{table_name} (
+ id INTEGER PRIMARY KEY,
+ #{connection.quote_column_name('session_id')} TEXT UNIQUE,
+ #{connection.quote_column_name(@@data_column_name)} TEXT(255)
+ )
+ end_sql
+ end
+
+ def drop_table!
+ connection.execute "DROP TABLE #{table_name}"
+ end
+
+ private
+ # Compatibility with tables using sessid instead of session_id.
+ def setup_sessid_compatibility!
+ # Reset column info since it may be stale.
+ reset_column_information
+ if columns_hash['sessid']
+ def self.find_by_session_id(*args)
+ find_by_sessid(*args)
+ end
+
+ define_method(:session_id) { sessid }
+ define_method(:session_id=) { |session_id| self.sessid = session_id }
+ else
+ def self.find_by_session_id(session_id)
+ find :first, :conditions => ["session_id #{attribute_condition(session_id)}", session_id]
+ end
+ end
+ end
+ end
+
+ # Lazy-unmarshal session state.
+ def data
+ @data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
+ end
+
+ attr_writer :data
+
+ # Has the session been loaded yet?
+ def loaded?
+ !! @data
+ end
+
+ private
+
+ def marshal_data!
+ return false if !loaded?
+ write_attribute(@@data_column_name, self.class.marshal(self.data))
+ end
+
+ # Ensures that the data about to be stored in the database is not
+ # larger than the data storage column. Raises
+ # ActionController::SessionOverflowError.
+ def raise_on_session_data_overflow!
+ return false if !loaded?
+ limit = self.class.data_column_size_limit
+ if loaded? and limit and read_attribute(@@data_column_name).size > limit
+ raise ActionController::SessionOverflowError
+ end
+ end
+ end
+
+ # A barebones session store which duck-types with the default session
+ # store but bypasses Active Record and issues SQL directly. This is
+ # an example session model class meant as a basis for your own classes.
+ #
+ # The database connection, table name, and session id and data columns
+ # are configurable class attributes. Marshaling and unmarshaling
+ # are implemented as class methods that you may override. By default,
+ # marshaling data is +Base64.encode64(Marshal.dump(data))+ and
+ # unmarshaling data is +Marshal.load(Base64.decode64(data))+.
+ #
+ # This marshaling behavior is intended to store the widest range of
+ # binary session data in a +text+ column. For higher performance,
+ # store in a +blob+ column instead and forgo the Base64 encoding.
+ class SqlBypass
+ # Use the ActiveRecord::Base.connection by default.
+ cattr_accessor :connection
+
+ # The table name defaults to 'sessions'.
+ cattr_accessor :table_name
+ @@table_name = 'sessions'
+
+ # The session id field defaults to 'session_id'.
+ cattr_accessor :session_id_column
+ @@session_id_column = 'session_id'
+
+ # The data field defaults to 'data'.
+ cattr_accessor :data_column
+ @@data_column = 'data'
+
+ class << self
+
+ def connection
+ @@connection ||= ActiveRecord::Base.connection
+ end
+
+ # Look up a session by id and unmarshal its data if found.
+ def find_by_session_id(session_id)
+ if record = @@connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{@@connection.quote(session_id)}")
+ new(:session_id => session_id, :marshaled_data => record['data'])
+ end
+ end
+
+ def marshal(data) Base64.encode64(Marshal.dump(data)) if data end
+ def unmarshal(data) Marshal.load(Base64.decode64(data)) if data end
+
+ def create_table!
+ @@connection.execute <<-end_sql
+ CREATE TABLE #{table_name} (
+ id INTEGER PRIMARY KEY,
+ #{@@connection.quote_column_name(session_id_column)} TEXT UNIQUE,
+ #{@@connection.quote_column_name(data_column)} TEXT
+ )
+ end_sql
+ end
+
+ def drop_table!
+ @@connection.execute "DROP TABLE #{table_name}"
+ end
+ end
+
+ attr_reader :session_id
+ attr_writer :data
+
+ # Look for normal and marshaled data, self.find_by_session_id's way of
+ # telling us to postpone unmarshaling until the data is requested.
+ # We need to handle a normal data attribute in case of a new record.
+ def initialize(attributes)
+ @session_id, @data, @marshaled_data = attributes[:session_id], attributes[:data], attributes[:marshaled_data]
+ @new_record = @marshaled_data.nil?
+ end
+
+ def new_record?
+ @new_record
+ end
+
+ # Lazy-unmarshal session state.
+ def data
+ unless @data
+ if @marshaled_data
+ @data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
+ else
+ @data = {}
+ end
+ end
+ @data
+ end
+
+ def loaded?
+ !! @data
+ end
+
+ def save
+ return false if !loaded?
+ marshaled_data = self.class.marshal(data)
+
+ if @new_record
+ @new_record = false
+ @@connection.update <<-end_sql, 'Create session'
+ INSERT INTO #{@@table_name} (
+ #{@@connection.quote_column_name(@@session_id_column)},
+ #{@@connection.quote_column_name(@@data_column)} )
+ VALUES (
+ #{@@connection.quote(session_id)},
+ #{@@connection.quote(marshaled_data)} )
+ end_sql
+ else
+ @@connection.update <<-end_sql, 'Update session'
+ UPDATE #{@@table_name}
+ SET #{@@connection.quote_column_name(@@data_column)}=#{@@connection.quote(marshaled_data)}
+ WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
+ end_sql
+ end
+ end
+
+ def destroy
+ unless @new_record
+ @@connection.delete <<-end_sql, 'Destroy session'
+ DELETE FROM #{@@table_name}
+ WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
+ end_sql
+ end
+ end
+ end
+
+
+ # The class used for session storage. Defaults to
+ # CGI::Session::ActiveRecordStore::Session.
+ cattr_accessor :session_class
+ self.session_class = Session
+
+ # Find or instantiate a session given a CGI::Session.
+ def initialize(session, option = nil)
+ session_id = session.session_id
+ unless @session = ActiveRecord::Base.silence { @@session_class.find_by_session_id(session_id) }
+ unless session.new_session
+ raise CGI::Session::NoSession, 'uninitialized session'
+ end
+ @session = @@session_class.new(:session_id => session_id, :data => {})
+ # session saving can be lazy again, because of improved component implementation
+ # therefore next line gets commented out:
+ # @session.save
+ end
+ end
+
+ # Access the underlying session model.
+ def model
+ @session
+ end
+
+ # Restore session state. The session model handles unmarshaling.
+ def restore
+ if @session
+ @session.data
+ end
+ end
+
+ # Save session store.
+ def update
+ if @session
+ ActiveRecord::Base.silence { @session.save }
+ end
+ end
+
+ # Save and close the session store.
+ def close
+ if @session
+ update
+ @session = nil
+ end
+ end
+
+ # Delete and close the session store.
+ def delete
+ if @session
+ ActiveRecord::Base.silence { @session.destroy }
+ @session = nil
+ end
+ end
+
+ protected
+ def logger
+ ActionController::Base.logger rescue nil
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/session/cookie_store.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/cookie_store.rb
new file mode 100644
index 000000000..086f5a87a
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/cookie_store.rb
@@ -0,0 +1,164 @@
+require 'cgi'
+require 'cgi/session'
+require 'base64' # to convert Marshal.dump to ASCII
+require 'openssl' # to generate the HMAC message digest
+
+# This cookie-based session store is the Rails default. Sessions typically
+# contain at most a user_id and flash message; both fit within the 4K cookie
+# size limit. Cookie-based sessions are dramatically faster than the
+# alternatives.
+#
+# If you have more than 4K of session data or don't want your data to be
+# visible to the user, pick another session store.
+#
+# CookieOverflow is raised if you attempt to store more than 4K of data.
+# TamperedWithCookie is raised if the data integrity check fails.
+#
+# A message digest is included with the cookie to ensure data integrity:
+# a user cannot alter his user_id without knowing the secret key included in
+# the hash. New apps are generated with a pregenerated secret in
+# config/environment.rb. Set your own for old apps you're upgrading.
+#
+# Session options:
+# :secret An application-wide key string or block returning a string
+# called per generated digest. The block is called with the
+# CGI::Session instance as an argument. It's important that the
+# secret is not vulnerable to a dictionary attack. Therefore,
+# you should choose a secret consisting of random numbers and
+# letters and more than 30 characters.
+#
+# Example: :secret => '449fe2e7daee471bffae2fd8dc02313d'
+# :secret => Proc.new { User.current_user.secret_key }
+#
+# :digest The message digest algorithm used to verify session integrity
+# defaults to 'SHA1' but may be any digest provided by OpenSSL,
+# such as 'MD5', 'RIPEMD160', 'SHA256', etc.
+#
+# To generate a secret key for an existing application, run
+# `rake secret` and set the key in config/environment.rb
+#
+# Note that changing digest or secret invalidates all existing sessions!
+class CGI::Session::CookieStore
+ # Cookies can typically store 4096 bytes.
+ MAX = 4096
+ SECRET_MIN_LENGTH = 30 # characters
+
+ # Raised when storing more than 4K of session data.
+ class CookieOverflow < StandardError; end
+
+ # Raised when the cookie fails its integrity check.
+ class TamperedWithCookie < StandardError; end
+
+ # Called from CGI::Session only.
+ def initialize(session, options = {})
+ # The session_key option is required.
+ if options['session_key'].blank?
+ raise ArgumentError, 'A session_key is required to write a cookie containing the session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase" } in config/environment.rb'
+ end
+
+ # The secret option is required.
+ ensure_secret_secure(options['secret'])
+
+ # Keep the session and its secret on hand so we can read and write cookies.
+ @session, @secret = session, options['secret']
+
+ # Message digest defaults to SHA1.
+ @digest = options['digest'] || 'SHA1'
+
+ # Default cookie options derived from session settings.
+ @cookie_options = {
+ 'name' => options['session_key'],
+ 'path' => options['session_path'],
+ 'domain' => options['session_domain'],
+ 'expires' => options['session_expires'],
+ 'secure' => options['session_secure']
+ }
+
+ # Set no_hidden and no_cookies since the session id is unused and we
+ # set our own data cookie.
+ options['no_hidden'] = true
+ options['no_cookies'] = true
+ end
+
+ # To prevent users from using something insecure like "Password" we make sure that the
+ # secret they've provided is at least 30 characters in length.
+ def ensure_secret_secure(secret)
+ # There's no way we can do this check if they've provided a proc for the
+ # secret.
+ return true if secret.is_a?(Proc)
+
+ if secret.blank?
+ raise ArgumentError, %Q{A secret is required to generate an integrity hash for cookie session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase of at least #{SECRET_MIN_LENGTH} characters" } in config/environment.rb}
+ end
+
+ if secret.length < SECRET_MIN_LENGTH
+ raise ArgumentError, %Q{Secret should be something secure, like "#{CGI::Session.generate_unique_id}". The value you provided, "#{secret}", is shorter than the minimum length of #{SECRET_MIN_LENGTH} characters}
+ end
+ end
+
+ # Restore session data from the cookie.
+ def restore
+ @original = read_cookie
+ @data = unmarshal(@original) || {}
+ end
+
+ # Wait until close to write the session data cookie.
+ def update; end
+
+ # Write the session data cookie if it was loaded and has changed.
+ def close
+ if defined?(@data) && !@data.blank?
+ updated = marshal(@data)
+ raise CookieOverflow if updated.size > MAX
+ write_cookie('value' => updated) unless updated == @original
+ end
+ end
+
+ # Delete the session data by setting an expired cookie with no data.
+ def delete
+ @data = nil
+ clear_old_cookie_value
+ write_cookie('value' => '', 'expires' => 1.year.ago)
+ end
+
+ # Generate the HMAC keyed message digest. Uses SHA1 by default.
+ def generate_digest(data)
+ key = @secret.respond_to?(:call) ? @secret.call(@session) : @secret
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), key, data)
+ end
+
+ private
+ # Marshal a session hash into safe cookie data. Include an integrity hash.
+ def marshal(session)
+ data = Base64.encode64(Marshal.dump(session)).chop
+ CGI.escape "#{data}--#{generate_digest(data)}"
+ end
+
+ # Unmarshal cookie data to a hash and verify its integrity.
+ def unmarshal(cookie)
+ if cookie
+ data, digest = CGI.unescape(cookie).split('--')
+ unless digest == generate_digest(data)
+ delete
+ raise TamperedWithCookie
+ end
+ Marshal.load(Base64.decode64(data))
+ end
+ end
+
+ # Read the session data cookie.
+ def read_cookie
+ @session.cgi.cookies[@cookie_options['name']].first
+ end
+
+ # CGI likes to make you hack.
+ def write_cookie(options)
+ cookie = CGI::Cookie.new(@cookie_options.merge(options))
+ @session.cgi.send :instance_variable_set, '@output_cookies', [cookie]
+ end
+
+ # Clear cookie value so subsequent new_session doesn't reload old data.
+ def clear_old_cookie_value
+ @session.cgi.cookies[@cookie_options['name']].clear
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/session/drb_server.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/drb_server.rb
new file mode 100644
index 000000000..6f90db674
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/drb_server.rb
@@ -0,0 +1,32 @@
+#!/usr/local/bin/ruby -w
+
+# This is a really simple session storage daemon, basically just a hash,
+# which is enabled for DRb access.
+
+require 'drb'
+
+session_hash = Hash.new
+session_hash.instance_eval { @mutex = Mutex.new }
+
+class <<session_hash
+ def []=(key, value)
+ @mutex.synchronize do
+ super(key, value)
+ end
+ end
+
+ def [](key)
+ @mutex.synchronize do
+ super(key)
+ end
+ end
+
+ def delete(key)
+ @mutex.synchronize do
+ super(key)
+ end
+ end
+end
+
+DRb.start_service('druby://127.0.0.1:9192', session_hash)
+DRb.thread.join \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/session/drb_store.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/drb_store.rb
new file mode 100644
index 000000000..4feb2636e
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/drb_store.rb
@@ -0,0 +1,35 @@
+require 'cgi'
+require 'cgi/session'
+require 'drb'
+
+class CGI #:nodoc:all
+ class Session
+ class DRbStore
+ @@session_data = DRbObject.new(nil, 'druby://localhost:9192')
+
+ def initialize(session, option=nil)
+ @session_id = session.session_id
+ end
+
+ def restore
+ @h = @@session_data[@session_id] || {}
+ end
+
+ def update
+ @@session_data[@session_id] = @h
+ end
+
+ def close
+ update
+ end
+
+ def delete
+ @@session_data.delete(@session_id)
+ end
+
+ def data
+ @@session_data[@session_id]
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/session/mem_cache_store.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/mem_cache_store.rb
new file mode 100644
index 000000000..2f08af663
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/mem_cache_store.rb
@@ -0,0 +1,98 @@
+# cgi/session/memcached.rb - persistent storage of marshalled session data
+#
+# == Overview
+#
+# This file provides the CGI::Session::MemCache class, which builds
+# persistence of storage data on top of the MemCache library. See
+# cgi/session.rb for more details on session storage managers.
+#
+
+begin
+ require 'cgi/session'
+ require_library_or_gem 'memcache'
+
+ class CGI
+ class Session
+ # MemCache-based session storage class.
+ #
+ # This builds upon the top-level MemCache class provided by the
+ # library file memcache.rb. Session data is marshalled and stored
+ # in a memcached cache.
+ class MemCacheStore
+ def check_id(id) #:nodoc:#
+ /[^0-9a-zA-Z]+/ =~ id.to_s ? false : true
+ end
+
+ # Create a new CGI::Session::MemCache instance
+ #
+ # This constructor is used internally by CGI::Session. The
+ # user does not generally need to call it directly.
+ #
+ # +session+ is the session for which this instance is being
+ # created. The session id must only contain alphanumeric
+ # characters; automatically generated session ids observe
+ # this requirement.
+ #
+ # +options+ is a hash of options for the initializer. The
+ # following options are recognized:
+ #
+ # cache:: an instance of a MemCache client to use as the
+ # session cache.
+ #
+ # expires:: an expiry time value to use for session entries in
+ # the session cache. +expires+ is interpreted in seconds
+ # relative to the current time if its less than 60*60*24*30
+ # (30 days), or as an absolute Unix time (e.g., Time#to_i) if
+ # greater. If +expires+ is +0+, or not passed on +options+,
+ # the entry will never expire.
+ #
+ # This session's memcache entry will be created if it does
+ # not exist, or retrieved if it does.
+ def initialize(session, options = {})
+ id = session.session_id
+ unless check_id(id)
+ raise ArgumentError, "session_id '%s' is invalid" % id
+ end
+ @cache = options['cache'] || MemCache.new('localhost')
+ @expires = options['expires'] || 0
+ @session_key = "session:#{id}"
+ @session_data = {}
+ # Add this key to the store if haven't done so yet
+ unless @cache.get(@session_key)
+ @cache.add(@session_key, @session_data, @expires)
+ end
+ end
+
+ # Restore session state from the session's memcache entry.
+ #
+ # Returns the session state as a hash.
+ def restore
+ @session_data = @cache[@session_key] || {}
+ end
+
+ # Save session state to the session's memcache entry.
+ def update
+ @cache.set(@session_key, @session_data, @expires)
+ end
+
+ # Update and close the session's memcache entry.
+ def close
+ update
+ end
+
+ # Delete the session's memcache entry.
+ def delete
+ @cache.delete(@session_key)
+ @session_data = {}
+ end
+
+ def data
+ @session_data
+ end
+
+ end
+ end
+ end
+rescue LoadError
+ # MemCache wasn't available so neither can the store be
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/session_management.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/session_management.rb
new file mode 100644
index 000000000..fabb6e7f6
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/session_management.rb
@@ -0,0 +1,151 @@
+require 'action_controller/session/cookie_store'
+require 'action_controller/session/drb_store'
+require 'action_controller/session/mem_cache_store'
+if Object.const_defined?(:ActiveRecord)
+ require 'action_controller/session/active_record_store'
+end
+
+module ActionController #:nodoc:
+ module SessionManagement #:nodoc:
+ def self.included(base)
+ base.class_eval do
+ extend ClassMethods
+ alias_method_chain :process, :session_management_support
+ alias_method_chain :process_cleanup, :session_management_support
+ end
+ end
+
+ module ClassMethods
+ # Set the session store to be used for keeping the session data between requests. By default, sessions are stored
+ # in browser cookies (:cookie_store), but you can also specify one of the other included stores
+ # (:active_record_store, :p_store, drb_store, :mem_cache_store, or :memory_store) or your own custom class.
+ def session_store=(store)
+ ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager] =
+ store.is_a?(Symbol) ? CGI::Session.const_get(store == :drb_store ? "DRbStore" : store.to_s.camelize) : store
+ end
+
+ # Returns the session store class currently used.
+ def session_store
+ ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager]
+ end
+
+ # Returns the hash used to configure the session. Example use:
+ #
+ # ActionController::Base.session_options[:session_secure] = true # session only available over HTTPS
+ def session_options
+ ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
+ end
+
+ # Specify how sessions ought to be managed for a subset of the actions on
+ # the controller. Like filters, you can specify <tt>:only</tt> and
+ # <tt>:except</tt> clauses to restrict the subset, otherwise options
+ # apply to all actions on this controller.
+ #
+ # The session options are inheritable, as well, so if you specify them in
+ # a parent controller, they apply to controllers that extend the parent.
+ #
+ # Usage:
+ #
+ # # turn off session management for all actions.
+ # session :off
+ #
+ # # turn off session management for all actions _except_ foo and bar.
+ # session :off, :except => %w(foo bar)
+ #
+ # # turn off session management for only the foo and bar actions.
+ # session :off, :only => %w(foo bar)
+ #
+ # # the session will only work over HTTPS, but only for the foo action
+ # session :only => :foo, :session_secure => true
+ #
+ # # the session will only be disabled for 'foo', and only if it is
+ # # requested as a web service
+ # session :off, :only => :foo,
+ # :if => Proc.new { |req| req.parameters[:ws] }
+ #
+ # # the session will be disabled for non html/ajax requests
+ # session :off,
+ # :if => Proc.new { |req| !(req.format.html? || req.format.js?) }
+ #
+ # All session options described for ActionController::Base.process_cgi
+ # are valid arguments.
+ def session(*args)
+ options = args.extract_options!
+
+ options[:disabled] = true if !args.empty?
+ options[:only] = [*options[:only]].map { |o| o.to_s } if options[:only]
+ options[:except] = [*options[:except]].map { |o| o.to_s } if options[:except]
+ if options[:only] && options[:except]
+ raise ArgumentError, "only one of either :only or :except are allowed"
+ end
+
+ write_inheritable_array("session_options", [options])
+ end
+
+ # So we can declare session options in the Rails initializer.
+ alias_method :session=, :session
+
+ def cached_session_options #:nodoc:
+ @session_options ||= read_inheritable_attribute("session_options") || []
+ end
+
+ def session_options_for(request, action) #:nodoc:
+ if (session_options = cached_session_options).empty?
+ {}
+ else
+ options = {}
+
+ action = action.to_s
+ session_options.each do |opts|
+ next if opts[:if] && !opts[:if].call(request)
+ if opts[:only] && opts[:only].include?(action)
+ options.merge!(opts)
+ elsif opts[:except] && !opts[:except].include?(action)
+ options.merge!(opts)
+ elsif !opts[:only] && !opts[:except]
+ options.merge!(opts)
+ end
+ end
+
+ if options.empty? then options
+ else
+ options.delete :only
+ options.delete :except
+ options.delete :if
+ options[:disabled] ? false : options
+ end
+ end
+ end
+ end
+
+ def process_with_session_management_support(request, response, method = :perform_action, *arguments) #:nodoc:
+ set_session_options(request)
+ process_without_session_management_support(request, response, method, *arguments)
+ end
+
+ private
+ def set_session_options(request)
+ request.session_options = self.class.session_options_for(request, request.parameters["action"] || "index")
+ end
+
+ def process_cleanup_with_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.respond_to?(:data)
+ session_data = @_session.data
+
+ if session_data && session_data.respond_to?(:each_value)
+ session_data.each_value do |obj|
+ obj.clear_association_cache if obj.respond_to?(:clear_association_cache)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/status_codes.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/status_codes.rb
new file mode 100644
index 000000000..4977c7949
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/status_codes.rb
@@ -0,0 +1,88 @@
+module ActionController
+ module StatusCodes #:nodoc:
+ # Defines the standard HTTP status codes, by integer, with their
+ # corresponding default message texts.
+ # Source: http://www.iana.org/assignments/http-status-codes
+ STATUS_CODES = {
+ 100 => "Continue",
+ 101 => "Switching Protocols",
+ 102 => "Processing",
+
+ 200 => "OK",
+ 201 => "Created",
+ 202 => "Accepted",
+ 203 => "Non-Authoritative Information",
+ 204 => "No Content",
+ 205 => "Reset Content",
+ 206 => "Partial Content",
+ 207 => "Multi-Status",
+ 226 => "IM Used",
+
+ 300 => "Multiple Choices",
+ 301 => "Moved Permanently",
+ 302 => "Found",
+ 303 => "See Other",
+ 304 => "Not Modified",
+ 305 => "Use Proxy",
+ 307 => "Temporary Redirect",
+
+ 400 => "Bad Request",
+ 401 => "Unauthorized",
+ 402 => "Payment Required",
+ 403 => "Forbidden",
+ 404 => "Not Found",
+ 405 => "Method Not Allowed",
+ 406 => "Not Acceptable",
+ 407 => "Proxy Authentication Required",
+ 408 => "Request Timeout",
+ 409 => "Conflict",
+ 410 => "Gone",
+ 411 => "Length Required",
+ 412 => "Precondition Failed",
+ 413 => "Request Entity Too Large",
+ 414 => "Request-URI Too Long",
+ 415 => "Unsupported Media Type",
+ 416 => "Requested Range Not Satisfiable",
+ 417 => "Expectation Failed",
+ 422 => "Unprocessable Entity",
+ 423 => "Locked",
+ 424 => "Failed Dependency",
+ 426 => "Upgrade Required",
+
+ 500 => "Internal Server Error",
+ 501 => "Not Implemented",
+ 502 => "Bad Gateway",
+ 503 => "Service Unavailable",
+ 504 => "Gateway Timeout",
+ 505 => "HTTP Version Not Supported",
+ 507 => "Insufficient Storage",
+ 510 => "Not Extended"
+ }
+
+ # Provides a symbol-to-fixnum lookup for converting a symbol (like
+ # :created or :not_implemented) into its corresponding HTTP status
+ # code (like 200 or 501).
+ SYMBOL_TO_STATUS_CODE = STATUS_CODES.inject({}) do |hash, (code, message)|
+ hash[message.gsub(/ /, "").underscore.to_sym] = code
+ hash
+ end
+
+ # Given a status parameter, determine whether it needs to be converted
+ # to a string. If it is a fixnum, use the STATUS_CODES hash to lookup
+ # the default message. If it is a symbol, use the SYMBOL_TO_STATUS_CODE
+ # hash to convert it.
+ def interpret_status(status)
+ case status
+ when Fixnum then
+ "#{status} #{STATUS_CODES[status]}".strip
+ when Symbol then
+ interpret_status(SYMBOL_TO_STATUS_CODE[status] ||
+ "500 Unknown Status #{status.inspect}")
+ else
+ status.to_s
+ end
+ end
+ private :interpret_status
+
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/streaming.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/streaming.rb
new file mode 100644
index 000000000..42fe42983
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/streaming.rb
@@ -0,0 +1,141 @@
+module ActionController #:nodoc:
+ # Methods for sending files and streams to the browser instead of rendering.
+ module Streaming
+ DEFAULT_SEND_FILE_OPTIONS = {
+ :type => 'application/octet-stream'.freeze,
+ :disposition => 'attachment'.freeze,
+ :stream => true,
+ :buffer_size => 4096
+ }.freeze
+
+ protected
+ # Sends the file by streaming it 4096 bytes at a time. This way the
+ # whole file doesn't need to be read into memory at once. This makes
+ # it feasible to send even large files.
+ #
+ # Be careful to sanitize the path parameter if it coming from a web
+ # page. send_file(params[:path]) allows a malicious user to
+ # download any file on your server.
+ #
+ # Options:
+ # * <tt>:filename</tt> - suggests a filename for the browser to use.
+ # Defaults to File.basename(path).
+ # * <tt>:type</tt> - specifies an HTTP content type.
+ # Defaults to 'application/octet-stream'.
+ # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
+ # Valid values are 'inline' and 'attachment' (default).
+ # * <tt>:stream</tt> - whether to send the file to the user agent as it is read (true)
+ # or to read the entire file before sending (false). Defaults to true.
+ # * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
+ # Defaults to 4096.
+ # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
+ # * <tt>:url_based_filename</tt> - set to true if you want the browser guess the filename from
+ # the URL, which is necessary for i18n filenames on certain browsers
+ # (setting :filename overrides this option).
+ #
+ # The default Content-Type and Content-Disposition headers are
+ # set to download arbitrary binary files in as many browsers as
+ # possible. IE versions 4, 5, 5.5, and 6 are all known to have
+ # a variety of quirks (especially when downloading over SSL).
+ #
+ # Simple download:
+ # send_file '/path/to.zip'
+ #
+ # Show a JPEG in the browser:
+ # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
+ #
+ # Show a 404 page in the browser:
+ # send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404
+ #
+ # Read about the other Content-* HTTP headers if you'd like to
+ # provide the user with more information (such as Content-Description).
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
+ #
+ # Also be aware that the document may be cached by proxies and browsers.
+ # The Pragma and Cache-Control headers declare how the file may be cached
+ # by intermediaries. They default to require clients to validate with
+ # the server before releasing cached responses. See
+ # http://www.mnot.net/cache_docs/ for an overview of web caching and
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+ # for the Cache-Control header spec.
+ def send_file(path, options = {}) #:doc:
+ raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
+
+ options[:length] ||= File.size(path)
+ options[:filename] ||= File.basename(path) unless options[:url_based_filename]
+ send_file_headers! options
+
+ @performed_render = false
+
+ if options[:stream]
+ render :status => options[:status], :text => Proc.new { |response, output|
+ logger.info "Streaming file #{path}" unless logger.nil?
+ len = options[:buffer_size] || 4096
+ File.open(path, 'rb') do |file|
+ while buf = file.read(len)
+ output.write(buf)
+ end
+ end
+ }
+ else
+ logger.info "Sending file #{path}" unless logger.nil?
+ File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read }
+ end
+ end
+
+ # Send binary data to the user as a file download. May set content type, apparent file name,
+ # and specify whether to show data inline or download as an attachment.
+ #
+ # Options:
+ # * <tt>:filename</tt> - Suggests a filename for the browser to use.
+ # * <tt>:type</tt> - specifies an HTTP content type.
+ # Defaults to 'application/octet-stream'.
+ # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
+ # Valid values are 'inline' and 'attachment' (default).
+ # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
+ #
+ # Generic data download:
+ # send_data buffer
+ #
+ # Download a dynamically-generated tarball:
+ # send_data generate_tgz('dir'), :filename => 'dir.tgz'
+ #
+ # Display an image Active Record in the browser:
+ # send_data image.data, :type => image.content_type, :disposition => 'inline'
+ #
+ # See +send_file+ for more information on HTTP Content-* headers and caching.
+ def send_data(data, options = {}) #:doc:
+ logger.info "Sending data #{options[:filename]}" unless logger.nil?
+ send_file_headers! options.merge(:length => data.size)
+ @performed_render = false
+ render :status => options[:status], :text => data
+ end
+
+ private
+ def send_file_headers!(options)
+ options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
+ [:length, :type, :disposition].each do |arg|
+ raise ArgumentError, ":#{arg} option required" if options[arg].nil?
+ end
+
+ disposition = options[:disposition].dup || 'attachment'
+
+ disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
+
+ headers.update(
+ 'Content-Length' => options[:length],
+ 'Content-Type' => options[:type].to_s.strip, # fixes a problem with extra '\r' with some browsers
+ 'Content-Disposition' => disposition,
+ 'Content-Transfer-Encoding' => 'binary'
+ )
+
+ # Fix a problem with IE 6.0 on opening downloaded files:
+ # If Cache-Control: no-cache is set (which Rails does by default),
+ # IE removes the file it just downloaded from its cache immediately
+ # 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'
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/_request_and_response.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/_request_and_response.erb
new file mode 100644
index 000000000..64b34650b
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/_request_and_response.erb
@@ -0,0 +1,24 @@
+<% unless @exception.blamed_files.blank? %>
+ <% if (hide = @exception.blamed_files.length > 8) %>
+ <a href="#" onclick="document.getElementById('blame_trace').style.display='block'; return false;">Show blamed files</a>
+ <% end %>
+ <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%=h @exception.describe_blame %></code></pre>
+<% end %>
+
+<%
+ clean_params = request.parameters.clone
+ clean_params.delete("action")
+ clean_params.delete("controller")
+
+ request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
+%>
+
+<h2 style="margin-top: 30px">Request</h2>
+<p><b>Parameters</b>: <pre><%=h request_dump %></pre></p>
+
+<p><a href="#" onclick="document.getElementById('session_dump').style.display='block'; return false;">Show session dump</a></p>
+<div id="session_dump" style="display:none"><%= debug(request.session.instance_variable_get("@data")) %></div>
+
+
+<h2 style="margin-top: 30px">Response</h2>
+<p><b>Headers</b>: <pre><%=h response ? response.headers.inspect.gsub(',', ",\n") : 'None' %></pre></p>
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/_trace.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/_trace.erb
new file mode 100644
index 000000000..b322b0aaa
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/_trace.erb
@@ -0,0 +1,26 @@
+<%
+ traces = [
+ ["Application Trace", @exception.application_backtrace],
+ ["Framework Trace", @exception.framework_backtrace],
+ ["Full Trace", @exception.clean_backtrace]
+ ]
+ names = traces.collect {|name, trace| name}
+%>
+
+<p><code>RAILS_ROOT: <%= defined?(RAILS_ROOT) ? RAILS_ROOT : "unset" %></code></p>
+
+<div id="traces">
+ <% names.each do |name| -%>
+ <%
+ show = "document.getElementById('#{name.gsub /\s/, '-'}').style.display='block';"
+ hide = (names - [name]).collect {|hide_name| "document.getElementById('#{hide_name.gsub /\s/, '-'}').style.display='none';"}
+ %>
+ <a href="#" onclick="<%= hide %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
+ <% end -%>
+
+ <% traces.each do |name, trace| -%>
+ <div id="<%= name.gsub /\s/, '-' %>" style="display: <%= name == "Application Trace" ? 'block' : 'none' %>;">
+ <pre><code><%= trace.join "\n" %></code></pre>
+ </div>
+ <% end -%>
+</div> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/diagnostics.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/diagnostics.erb
new file mode 100644
index 000000000..032f945ed
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/diagnostics.erb
@@ -0,0 +1,11 @@
+<h1>
+ <%=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 %>
+ <% end %>
+</h1>
+<pre><%=h @exception.clean_message %></pre>
+
+<%= render_file(@rescues_path + "/_trace.erb", false) %>
+
+<%= render_file(@rescues_path + "/_request_and_response.erb", false) %>
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/layout.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/layout.erb
new file mode 100644
index 000000000..d38f3e67f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/layout.erb
@@ -0,0 +1,29 @@
+<html>
+<head>
+ <title>Action Controller: Exception caught</title>
+ <style>
+ body { background-color: #fff; color: #333; }
+
+ body, p, ol, ul, td {
+ font-family: verdana, arial, helvetica, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+ }
+
+ pre {
+ background-color: #eee;
+ padding: 10px;
+ font-size: 11px;
+ }
+
+ a { color: #000; }
+ a:visited { color: #666; }
+ a:hover { color: #fff; background-color:#000; }
+ </style>
+</head>
+<body>
+
+<%= @contents %>
+
+</body>
+</html> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/missing_template.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/missing_template.erb
new file mode 100644
index 000000000..dbfdf7694
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/missing_template.erb
@@ -0,0 +1,2 @@
+<h1>Template is missing</h1>
+<p><%=h @exception.message %></p>
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/routing_error.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/routing_error.erb
new file mode 100644
index 000000000..ccfa858cc
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/routing_error.erb
@@ -0,0 +1,10 @@
+<h1>Routing Error</h1>
+<p><pre><%=h @exception.message %></pre></p>
+<% unless @exception.failures.empty? %><p>
+ <h2>Failure reasons:</h2>
+ <ol>
+ <% @exception.failures.each do |route, reason| %>
+ <li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
+ <% end %>
+ </ol>
+</p><% end %>
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/template_error.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/template_error.erb
new file mode 100644
index 000000000..eda64db3e
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/template_error.erb
@@ -0,0 +1,21 @@
+<h1>
+ <%=h @exception.original_exception.class.to_s %> in
+ <%=h request.parameters["controller"].capitalize if request.parameters["controller"]%>#<%=h request.parameters["action"] %>
+</h1>
+
+<p>
+ Showing <i><%=h @exception.file_name %></i> where line <b>#<%=h @exception.line_number %></b> raised:
+ <pre><code><%=h @exception.message %></code></pre>
+</p>
+
+<p>Extracted source (around line <b>#<%=h @exception.line_number %></b>):
+<pre><code><%=h @exception.source_extract %></code></pre></p>
+
+<p><%=h @exception.sub_template_message %></p>
+
+<% @real_exception = @exception
+ @exception = @exception.original_exception || @exception %>
+<%= render_file(@rescues_path + "/_trace.erb", false) %>
+<% @exception = @real_exception %>
+
+<%= render_file(@rescues_path + "/_request_and_response.erb", false) %>
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/unknown_action.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/unknown_action.erb
new file mode 100644
index 000000000..683379da1
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/unknown_action.erb
@@ -0,0 +1,2 @@
+<h1>Unknown action</h1>
+<p><%=h @exception.message %></p>
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/test_case.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/test_case.rb
new file mode 100644
index 000000000..d81cb5dae
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/test_case.rb
@@ -0,0 +1,53 @@
+require 'active_support/test_case'
+
+module ActionController
+ class NonInferrableControllerError < ActionControllerError
+ def initialize(name)
+ super "Unable to determine the controller to test from #{name}. " +
+ "You'll need to specify it using 'tests YourController' in your " +
+ "test case definition"
+ end
+ end
+
+ class TestCase < ActiveSupport::TestCase
+ @@controller_class = nil
+ class << self
+ def tests(controller_class)
+ self.controller_class = controller_class
+ end
+
+ def controller_class=(new_class)
+ prepare_controller_class(new_class)
+ write_inheritable_attribute(:controller_class, new_class)
+ end
+
+ def controller_class
+ if current_controller_class = read_inheritable_attribute(:controller_class)
+ current_controller_class
+ else
+ self.controller_class= determine_default_controller_class(name)
+ end
+ end
+
+ def determine_default_controller_class(name)
+ name.sub(/Test$/, '').constantize
+ rescue NameError
+ raise NonInferrableControllerError.new(name)
+ end
+
+ def prepare_controller_class(new_class)
+ new_class.class_eval do
+ def rescue_action(e)
+ raise e
+ end
+ end
+ end
+ end
+
+ def setup
+ @controller = self.class.controller_class.new
+ @request = TestRequest.new
+ @response = TestResponse.new
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/test_process.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/test_process.rb
new file mode 100644
index 000000000..885331159
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/test_process.rb
@@ -0,0 +1,520 @@
+require 'action_controller/assertions'
+
+module ActionController #:nodoc:
+ class Base
+ # Process a test request called with a +TestRequest+ object.
+ def self.process_test(request)
+ new.process_test(request)
+ end
+
+ def process_test(request) #:nodoc:
+ process(request, TestResponse.new)
+ end
+
+ def process_with_test(*args)
+ returning process_without_test(*args) do
+ add_variables_to_assigns
+ end
+ end
+
+ alias_method_chain :process, :test
+ end
+
+ class TestRequest < AbstractRequest #:nodoc:
+ attr_accessor :cookies, :session_options
+ attr_accessor :query_parameters, :request_parameters, :path, :session, :env
+ attr_accessor :host, :user_agent
+
+ def initialize(query_parameters = nil, request_parameters = nil, session = nil)
+ @query_parameters = query_parameters || {}
+ @request_parameters = request_parameters || {}
+ @session = session || TestSession.new
+
+ initialize_containers
+ initialize_default_values
+
+ super()
+ end
+
+ def reset_session
+ @session = TestSession.new
+ end
+
+ # Wraps raw_post in a StringIO.
+ def body
+ StringIO.new(raw_post)
+ end
+
+ # Either the RAW_POST_DATA environment variable or the URL-encoded request
+ # parameters.
+ def raw_post
+ env['RAW_POST_DATA'] ||= url_encoded_request_parameters
+ end
+
+ def port=(number)
+ @env["SERVER_PORT"] = number.to_i
+ @port_as_int = nil
+ end
+
+ def action=(action_name)
+ @query_parameters.update({ "action" => action_name })
+ @parameters = nil
+ end
+
+ # Used to check AbstractRequest's request_uri functionality.
+ # Disables the use of @path and @request_uri so superclass can handle those.
+ def set_REQUEST_URI(value)
+ @env["REQUEST_URI"] = value
+ @request_uri = nil
+ @path = nil
+ end
+
+ def request_uri=(uri)
+ @request_uri = uri
+ @path = uri.split("?").first
+ end
+
+ def accept=(mime_types)
+ @env["HTTP_ACCEPT"] = Array(mime_types).collect { |mime_types| mime_types.to_s }.join(",")
+ end
+
+ def remote_addr=(addr)
+ @env['REMOTE_ADDR'] = addr
+ end
+
+ def remote_addr
+ @env['REMOTE_ADDR']
+ end
+
+ def request_uri
+ @request_uri || super
+ end
+
+ def path
+ @path || super
+ end
+
+ def assign_parameters(controller_path, action, parameters)
+ parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
+ extra_keys = ActionController::Routing::Routes.extra_keys(parameters)
+ non_path_parameters = get? ? query_parameters : request_parameters
+ parameters.each do |key, value|
+ if value.is_a? Fixnum
+ value = value.to_s
+ elsif value.is_a? Array
+ value = ActionController::Routing::PathSegment::Result.new(value)
+ end
+
+ if extra_keys.include?(key.to_sym)
+ non_path_parameters[key] = value
+ else
+ path_parameters[key.to_s] = value
+ end
+ end
+ @parameters = nil # reset TestRequest#parameters to use the new path_parameters
+ end
+
+ def recycle!
+ self.request_parameters = {}
+ self.query_parameters = {}
+ self.path_parameters = {}
+ @request_method, @accepts, @content_type = nil, nil, nil
+ end
+
+ def referer
+ @env["HTTP_REFERER"]
+ end
+
+ private
+ def initialize_containers
+ @env, @cookies = {}, {}
+ end
+
+ def initialize_default_values
+ @host = "test.host"
+ @request_uri = "/"
+ @user_agent = "Rails Testing"
+ self.remote_addr = "0.0.0.0"
+ @env["SERVER_PORT"] = 80
+ @env['REQUEST_METHOD'] = "GET"
+ end
+
+ def url_encoded_request_parameters
+ params = self.request_parameters.dup
+
+ %w(controller action only_path).each do |k|
+ params.delete(k)
+ params.delete(k.to_sym)
+ end
+
+ params.to_query
+ end
+ end
+
+ # A refactoring of TestResponse to allow the same behavior to be applied
+ # to the "real" CgiResponse class in integration tests.
+ module TestResponseBehavior #:nodoc:
+ # the response code of the request
+ def response_code
+ headers['Status'][0,3].to_i rescue 0
+ end
+
+ # returns a String to ensure compatibility with Net::HTTPResponse
+ def code
+ headers['Status'].to_s.split(' ')[0]
+ end
+
+ def message
+ headers['Status'].to_s.split(' ',2)[1]
+ end
+
+ # was the response successful?
+ def success?
+ response_code == 200
+ end
+
+ # was the URL not found?
+ def missing?
+ response_code == 404
+ end
+
+ # were we redirected?
+ def redirect?
+ (300..399).include?(response_code)
+ end
+
+ # was there a server-side error?
+ def error?
+ (500..599).include?(response_code)
+ end
+
+ alias_method :server_error?, :error?
+
+ # returns the redirection location or nil
+ def redirect_url
+ headers['Location']
+ end
+
+ # does the redirect location match this regexp pattern?
+ def redirect_url_match?( pattern )
+ return false if redirect_url.nil?
+ p = Regexp.new(pattern) if pattern.class == String
+ p = pattern if pattern.class == Regexp
+ return false if p.nil?
+ p.match(redirect_url) != nil
+ end
+
+ # returns the template path of the file which was used to
+ # render this response (or nil)
+ def rendered_file(with_controller=false)
+ unless template.first_render.nil?
+ unless with_controller
+ template.first_render
+ else
+ template.first_render.split('/').last || template.first_render
+ end
+ end
+ end
+
+ # was this template rendered by a file?
+ def rendered_with_file?
+ !rendered_file.nil?
+ end
+
+ # a shortcut to the flash (or an empty hash if no flash.. hey! that rhymes!)
+ def flash
+ session['flash'] || {}
+ end
+
+ # do we have a flash?
+ def has_flash?
+ !session['flash'].empty?
+ end
+
+ # do we have a flash that has contents?
+ def has_flash_with_contents?
+ !flash.empty?
+ end
+
+ # does the specified flash object exist?
+ def has_flash_object?(name=nil)
+ !flash[name].nil?
+ end
+
+ # does the specified object exist in the session?
+ def has_session_object?(name=nil)
+ !session[name].nil?
+ end
+
+ # a shortcut to the template.assigns
+ def template_objects
+ template.assigns || {}
+ end
+
+ # does the specified template object exist?
+ def has_template_object?(name=nil)
+ !template_objects[name].nil?
+ end
+
+ # Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs
+ # Example:
+ #
+ # assert_equal ['AuthorOfNewPage'], r.cookies['author'].value
+ def cookies
+ headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash }
+ end
+
+ # Returns binary content (downloadable file), converted to a String
+ def binary_content
+ raise "Response body is not a Proc: #{body.inspect}" unless body.kind_of?(Proc)
+ require 'stringio'
+
+ sio = StringIO.new
+ body.call(self, sio)
+
+ sio.rewind
+ sio.read
+ end
+ end
+
+ class TestResponse < AbstractResponse #:nodoc:
+ include TestResponseBehavior
+ end
+
+ class TestSession #:nodoc:
+ attr_accessor :session_id
+
+ def initialize(attributes = nil)
+ @session_id = ''
+ @attributes = attributes
+ @saved_attributes = nil
+ end
+
+ def data
+ @attributes ||= @saved_attributes || {}
+ end
+
+ def [](key)
+ data[key]
+ end
+
+ def []=(key, value)
+ data[key] = value
+ end
+
+ def update
+ @saved_attributes = @attributes
+ end
+
+ def delete
+ @attributes = nil
+ end
+
+ def close
+ update
+ delete
+ end
+ end
+
+ # Essentially generates a modified Tempfile object similar to the object
+ # you'd get from the standard library CGI module in a multipart
+ # request. This means you can use an ActionController::TestUploadedFile
+ # object in the params of a test request in order to simulate
+ # a file upload.
+ #
+ # Usage example, within a functional test:
+ # post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png')
+ #
+ # Pass a true third parameter to ensure the uploaded file is opened in binary mode (only required for Windows):
+ # post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png', :binary)
+ require 'tempfile'
+ class TestUploadedFile
+ # The filename, *not* including the path, of the "uploaded" file
+ attr_reader :original_filename
+
+ # The content type of the "uploaded" file
+ attr_reader :content_type
+
+ def initialize(path, content_type = Mime::TEXT, binary = false)
+ raise "#{path} file does not exist" unless File.exist?(path)
+ @content_type = content_type
+ @original_filename = path.sub(/^.*#{File::SEPARATOR}([^#{File::SEPARATOR}]+)$/) { $1 }
+ @tempfile = Tempfile.new(@original_filename)
+ @tempfile.binmode if binary
+ FileUtils.copy_file(path, @tempfile.path)
+ end
+
+ def path #:nodoc:
+ @tempfile.path
+ end
+
+ alias local_path path
+
+ def method_missing(method_name, *args, &block) #:nodoc:
+ @tempfile.send!(method_name, *args, &block)
+ end
+ end
+
+ module TestProcess
+ def self.included(base)
+ # execute the request simulating a specific http method and set/volley the response
+ %w( get post put delete head ).each do |method|
+ base.class_eval <<-EOV, __FILE__, __LINE__
+ def #{method}(action, parameters = nil, session = nil, flash = nil)
+ @request.env['REQUEST_METHOD'] = "#{method.upcase}" if defined?(@request)
+ process(action, parameters, session, flash)
+ end
+ EOV
+ end
+ end
+
+ # execute the request and set/volley the response
+ def process(action, parameters = nil, session = nil, flash = nil)
+ # Sanity check for required instance variables so we can give an
+ # understandable error message.
+ %w(@controller @request @response).each do |iv_name|
+ if !(instance_variables.include?(iv_name) || instance_variables.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
+ raise "#{iv_name} is nil: make sure you set it in your test's setup method."
+ end
+ end
+
+ @request.recycle!
+
+ @html_document = nil
+ @request.env['REQUEST_METHOD'] ||= "GET"
+ @request.action = action.to_s
+
+ parameters ||= {}
+ @request.assign_parameters(@controller.class.controller_path, action.to_s, parameters)
+
+ @request.session = ActionController::TestSession.new(session) unless session.nil?
+ @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
+ build_request_uri(action, parameters)
+ @controller.process(@request, @response)
+ end
+
+ def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
+ @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+ @request.env['HTTP_ACCEPT'] = 'text/javascript, text/html, application/xml, text/xml, */*'
+ returning send!(request_method, action, parameters, session, flash) do
+ @request.env.delete 'HTTP_X_REQUESTED_WITH'
+ @request.env.delete 'HTTP_ACCEPT'
+ end
+ end
+ alias xhr :xml_http_request
+
+ def follow_redirect
+ redirected_controller = @response.redirected_to[:controller]
+ if redirected_controller && redirected_controller != @controller.controller_name
+ raise "Can't follow redirects outside of current controller (from #{@controller.controller_name} to #{redirected_controller})"
+ end
+
+ get(@response.redirected_to.delete(:action), @response.redirected_to.stringify_keys)
+ end
+
+ def assigns(key = nil)
+ if key.nil?
+ @response.template.assigns
+ else
+ @response.template.assigns[key.to_s]
+ end
+ end
+
+ def session
+ @response.session
+ end
+
+ def flash
+ @response.flash
+ end
+
+ def cookies
+ @response.cookies
+ end
+
+ def redirect_to_url
+ @response.redirect_url
+ end
+
+ def build_request_uri(action, parameters)
+ unless @request.env['REQUEST_URI']
+ options = @controller.send!(:rewrite_options, parameters)
+ options.update(:only_path => true, :action => action)
+
+ url = ActionController::UrlRewriter.new(@request, parameters)
+ @request.set_REQUEST_URI(url.rewrite(options))
+ end
+ end
+
+ def html_document
+ xml = @response.content_type =~ /xml$/
+ @html_document ||= HTML::Document.new(@response.body, false, xml)
+ end
+
+ def find_tag(conditions)
+ html_document.find(conditions)
+ end
+
+ def find_all_tag(conditions)
+ html_document.find_all(conditions)
+ end
+
+ def method_missing(selector, *args)
+ return @controller.send!(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
+ return super
+ end
+
+ # Shortcut for ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type). Example:
+ # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
+ #
+ # To upload binary files on Windows, pass :binary as the last parameter. This will not affect other platforms.
+ # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
+ def fixture_file_upload(path, mime_type = nil, binary = false)
+ ActionController::TestUploadedFile.new(
+ Test::Unit::TestCase.respond_to?(:fixture_path) ? Test::Unit::TestCase.fixture_path + path : path,
+ mime_type,
+ binary
+ )
+ end
+
+ # A helper to make it easier to test different route configurations.
+ # This method temporarily replaces ActionController::Routing::Routes
+ # with a new RouteSet instance.
+ #
+ # The new instance is yielded to the passed block. Typically the block
+ # will create some routes using map.draw { map.connect ... }:
+ #
+ # with_routing do |set|
+ # set.draw do |map|
+ # map.connect ':controller/:action/:id'
+ # assert_equal(
+ # ['/content/10/show', {}],
+ # map.generate(:controller => 'content', :id => 10, :action => 'show')
+ # end
+ # end
+ # end
+ #
+ def with_routing
+ real_routes = ActionController::Routing::Routes
+ ActionController::Routing.module_eval { remove_const :Routes }
+
+ temporary_routes = ActionController::Routing::RouteSet.new
+ ActionController::Routing.module_eval { const_set :Routes, temporary_routes }
+
+ yield temporary_routes
+ ensure
+ if ActionController::Routing.const_defined? :Routes
+ ActionController::Routing.module_eval { remove_const :Routes }
+ end
+ ActionController::Routing.const_set(:Routes, real_routes) if real_routes
+ end
+ end
+end
+
+module Test
+ module Unit
+ class TestCase #:nodoc:
+ include ActionController::TestProcess
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/url_rewriter.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/url_rewriter.rb
new file mode 100644
index 000000000..c650763fd
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/url_rewriter.rb
@@ -0,0 +1,135 @@
+module ActionController
+ # Write URLs from arbitrary places in your codebase, such as your mailers.
+ #
+ # Example:
+ #
+ # class MyMailer
+ # include ActionController::UrlWriter
+ # default_url_options[:host] = 'www.basecamphq.com'
+ #
+ # def signup_url(token)
+ # url_for(:controller => 'signup', action => 'index', :token => token)
+ # end
+ # end
+ #
+ # In addition to providing +url_for+, named routes are also accessible after
+ # including UrlWriter.
+ module UrlWriter
+ # The default options for urls written by this writer. Typically a :host pair
+ # is provided.
+ mattr_accessor :default_url_options
+ self.default_url_options = {}
+
+ def self.included(base) #:nodoc:
+ ActionController::Routing::Routes.install_helpers base
+ base.mattr_accessor :default_url_options
+ base.default_url_options ||= default_url_options
+ end
+
+ # Generate a url based on the options provided, default_url_options and the
+ # routes defined in routes.rb. The following options are supported:
+ #
+ # * <tt>:only_path</tt> If true, the relative url is returned. Defaults to false.
+ # * <tt>:protocol</tt> The protocol to connect to. Defaults to 'http'.
+ # * <tt>:host</tt> Specifies the host the link should be targetted at. If <tt>:only_path</tt> is false, this option must be
+ # provided either explicitly, or via default_url_options.
+ # * <tt>:port</tt> Optionally specify the port to connect to.
+ # * <tt>:anchor</tt> An anchor name to be appended to the path.
+ #
+ # Any other key(:controller, :action, etc...) given to <tt>url_for</tt> is forwarded to the Routes module.
+ #
+ # Examples:
+ #
+ # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
+ # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
+ # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
+ #
+ def url_for(options)
+ options = self.class.default_url_options.merge(options)
+
+ url = ''
+
+ unless options.delete :only_path
+ url << (options.delete(:protocol) || 'http')
+ url << '://' unless url.match("://") #dont add separator if its already been specified in :protocol
+
+ raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
+
+ url << options.delete(:host)
+ url << ":#{options.delete(:port)}" if options.key?(:port)
+ else
+ # Delete the unused options to prevent their appearance in the query string
+ [:protocol, :host, :port].each { |k| options.delete k }
+ end
+
+ anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options.key?(:anchor)
+ url << Routing::Routes.generate(options, {})
+ url << anchor if anchor
+
+ return url
+ end
+ end
+
+ # Rewrites URLs for Base.redirect_to and Base.url_for in the controller.
+ class UrlRewriter #:nodoc:
+ RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root]
+ def initialize(request, parameters)
+ @request, @parameters = request, parameters
+ end
+
+ def rewrite(options = {})
+ rewrite_url(options)
+ end
+
+ def to_str
+ "#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@parameters[:controller]}, #{@parameters[:action]}, #{@request.parameters.inspect}"
+ end
+
+ alias_method :to_s, :to_str
+
+ private
+ # Given a path and options, returns a rewritten URL string
+ def rewrite_url(options)
+ rewritten_url = ""
+
+ unless options[:only_path]
+ rewritten_url << (options[:protocol] || @request.protocol)
+ rewritten_url << "://" unless rewritten_url.match("://")
+ rewritten_url << rewrite_authentication(options)
+ rewritten_url << (options[:host] || @request.host_with_port)
+ rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
+ end
+
+ path = rewrite_path(options)
+ rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root]
+ rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
+ rewritten_url << "##{options[:anchor]}" if options[:anchor]
+
+ rewritten_url
+ end
+
+ # Given a Hash of options, generates a route
+ def rewrite_path(options)
+ options = options.symbolize_keys
+ options.update(options[:params].symbolize_keys) if options[:params]
+
+ if (overwrite = options.delete(:overwrite_params))
+ options.update(@parameters.symbolize_keys)
+ options.update(overwrite.symbolize_keys)
+ end
+
+ RESERVED_OPTIONS.each { |k| options.delete(k) }
+
+ # Generates the query string, too
+ Routing::Routes.generate(options, @request.symbolized_path_parameters)
+ end
+
+ def rewrite_authentication(options)
+ if options[:user] && options[:password]
+ "#{CGI.escape(options.delete(:user))}:#{CGI.escape(options.delete(:password))}@"
+ else
+ ""
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
new file mode 100644
index 000000000..607fd186b
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
@@ -0,0 +1,68 @@
+require 'html/tokenizer'
+require 'html/node'
+require 'html/selector'
+require 'html/sanitizer'
+
+module HTML #:nodoc:
+ # A top-level HTMl document. You give it a body of text, and it will parse that
+ # text into a tree of nodes.
+ class Document #:nodoc:
+
+ # The root of the parsed document.
+ attr_reader :root
+
+ # Create a new Document from the given text.
+ def initialize(text, strict=false, xml=false)
+ tokenizer = Tokenizer.new(text)
+ @root = Node.new(nil)
+ node_stack = [ @root ]
+ while token = tokenizer.next
+ node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token)
+
+ node_stack.last.children << node unless node.tag? && node.closing == :close
+ if node.tag?
+ if node_stack.length > 1 && node.closing == :close
+ if node_stack.last.name == node.name
+ if node_stack.last.children.empty?
+ node_stack.last.children << Text.new(node_stack.last, node.line, node.position, "")
+ end
+ node_stack.pop
+ else
+ open_start = node_stack.last.position - 20
+ open_start = 0 if open_start < 0
+ close_start = node.position - 20
+ close_start = 0 if close_start < 0
+ msg = <<EOF.strip
+ignoring attempt to close #{node_stack.last.name} with #{node.name}
+ opened at byte #{node_stack.last.position}, line #{node_stack.last.line}
+ closed at byte #{node.position}, line #{node.line}
+ attributes at open: #{node_stack.last.attributes.inspect}
+ text around open: #{text[open_start,40].inspect}
+ text around close: #{text[close_start,40].inspect}
+EOF
+ strict ? raise(msg) : warn(msg)
+ end
+ elsif !node.childless?(xml) && node.closing != :close
+ node_stack.push node
+ end
+ end
+ end
+ end
+
+ # Search the tree for (and return) the first node that matches the given
+ # conditions. The conditions are interpreted differently for different node
+ # types, see HTML::Text#find and HTML::Tag#find.
+ def find(conditions)
+ @root.find(conditions)
+ end
+
+ # Search the tree for (and return) all nodes that match the given
+ # conditions. The conditions are interpreted differently for different node
+ # types, see HTML::Text#find and HTML::Tag#find.
+ def find_all(conditions)
+ @root.find_all(conditions)
+ end
+
+ end
+
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
new file mode 100644
index 000000000..472c5b2ba
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
@@ -0,0 +1,530 @@
+require 'strscan'
+
+module HTML #:nodoc:
+
+ class Conditions < Hash #:nodoc:
+ def initialize(hash)
+ super()
+ hash = { :content => hash } unless Hash === hash
+ hash = keys_to_symbols(hash)
+ hash.each do |k,v|
+ case k
+ when :tag, :content then
+ # keys are valid, and require no further processing
+ when :attributes then
+ hash[k] = keys_to_strings(v)
+ when :parent, :child, :ancestor, :descendant, :sibling, :before,
+ :after
+ hash[k] = Conditions.new(v)
+ when :children
+ hash[k] = v = keys_to_symbols(v)
+ v.each do |k,v2|
+ case k
+ when :count, :greater_than, :less_than
+ # keys are valid, and require no further processing
+ when :only
+ v[k] = Conditions.new(v2)
+ else
+ raise "illegal key #{k.inspect} => #{v2.inspect}"
+ end
+ end
+ else
+ raise "illegal key #{k.inspect} => #{v.inspect}"
+ end
+ end
+ update hash
+ end
+
+ private
+
+ def keys_to_strings(hash)
+ hash.keys.inject({}) do |h,k|
+ h[k.to_s] = hash[k]
+ h
+ end
+ end
+
+ def keys_to_symbols(hash)
+ hash.keys.inject({}) do |h,k|
+ raise "illegal key #{k.inspect}" unless k.respond_to?(:to_sym)
+ h[k.to_sym] = hash[k]
+ h
+ end
+ end
+ end
+
+ # The base class of all nodes, textual and otherwise, in an HTML document.
+ class Node #:nodoc:
+ # The array of children of this node. Not all nodes have children.
+ attr_reader :children
+
+ # The parent node of this node. All nodes have a parent, except for the
+ # root node.
+ attr_reader :parent
+
+ # The line number of the input where this node was begun
+ attr_reader :line
+
+ # The byte position in the input where this node was begun
+ attr_reader :position
+
+ # Create a new node as a child of the given parent.
+ def initialize(parent, line=0, pos=0)
+ @parent = parent
+ @children = []
+ @line, @position = line, pos
+ end
+
+ # Return a textual representation of the node.
+ def to_s
+ s = ""
+ @children.each { |child| s << child.to_s }
+ s
+ end
+
+ # Return false (subclasses must override this to provide specific matching
+ # behavior.) +conditions+ may be of any type.
+ def match(conditions)
+ false
+ end
+
+ # Search the children of this node for the first node for which #find
+ # returns non +nil+. Returns the result of the #find call that succeeded.
+ def find(conditions)
+ conditions = validate_conditions(conditions)
+ @children.each do |child|
+ node = child.find(conditions)
+ return node if node
+ end
+ nil
+ end
+
+ # Search for all nodes that match the given conditions, and return them
+ # as an array.
+ def find_all(conditions)
+ conditions = validate_conditions(conditions)
+
+ matches = []
+ matches << self if match(conditions)
+ @children.each do |child|
+ matches.concat child.find_all(conditions)
+ end
+ matches
+ end
+
+ # Returns +false+. Subclasses may override this if they define a kind of
+ # tag.
+ def tag?
+ false
+ end
+
+ def validate_conditions(conditions)
+ Conditions === conditions ? conditions : Conditions.new(conditions)
+ end
+
+ def ==(node)
+ return false unless self.class == node.class && children.size == node.children.size
+
+ equivalent = true
+
+ children.size.times do |i|
+ equivalent &&= children[i] == node.children[i]
+ end
+
+ equivalent
+ end
+
+ class <<self
+ def parse(parent, line, pos, content, strict=true)
+ if content !~ /^<\S/
+ Text.new(parent, line, pos, content)
+ else
+ scanner = StringScanner.new(content)
+
+ unless scanner.skip(/</)
+ if strict
+ raise "expected <"
+ else
+ return Text.new(parent, line, pos, content)
+ end
+ end
+
+ if scanner.skip(/!\[CDATA\[/)
+ scanner.scan_until(/\]\]>/)
+ return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, ''))
+ end
+
+ closing = ( scanner.scan(/\//) ? :close : nil )
+ return Text.new(parent, line, pos, content) unless name = scanner.scan(/[\w:-]+/)
+ name.downcase!
+
+ unless closing
+ scanner.skip(/\s*/)
+ attributes = {}
+ while attr = scanner.scan(/[-\w:]+/)
+ value = true
+ if scanner.scan(/\s*=\s*/)
+ if delim = scanner.scan(/['"]/)
+ value = ""
+ while text = scanner.scan(/[^#{delim}\\]+|./)
+ case text
+ when "\\" then
+ value << text
+ value << scanner.getch
+ when delim
+ break
+ else value << text
+ end
+ end
+ else
+ value = scanner.scan(/[^\s>\/]+/)
+ end
+ end
+ attributes[attr.downcase] = value
+ scanner.skip(/\s*/)
+ end
+
+ closing = ( scanner.scan(/\//) ? :self : nil )
+ end
+
+ unless scanner.scan(/\s*>/)
+ if strict
+ raise "expected > (got #{scanner.rest.inspect} for #{content}, #{attributes.inspect})"
+ else
+ # throw away all text until we find what we're looking for
+ scanner.skip_until(/>/) or scanner.terminate
+ end
+ end
+
+ Tag.new(parent, line, pos, name, attributes, closing)
+ end
+ end
+ end
+ end
+
+ # A node that represents text, rather than markup.
+ class Text < Node #:nodoc:
+
+ attr_reader :content
+
+ # Creates a new text node as a child of the given parent, with the given
+ # content.
+ def initialize(parent, line, pos, content)
+ super(parent, line, pos)
+ @content = content
+ end
+
+ # Returns the content of this node.
+ def to_s
+ @content
+ end
+
+ # Returns +self+ if this node meets the given conditions. Text nodes support
+ # conditions of the following kinds:
+ #
+ # * if +conditions+ is a string, it must be a substring of the node's
+ # content
+ # * if +conditions+ is a regular expression, it must match the node's
+ # content
+ # * if +conditions+ is a hash, it must contain a <tt>:content</tt> key that
+ # is either a string or a regexp, and which is interpreted as described
+ # above.
+ def find(conditions)
+ match(conditions) && self
+ end
+
+ # Returns non-+nil+ if this node meets the given conditions, or +nil+
+ # otherwise. See the discussion of #find for the valid conditions.
+ def match(conditions)
+ case conditions
+ when String
+ @content == conditions
+ when Regexp
+ @content =~ conditions
+ when Hash
+ conditions = validate_conditions(conditions)
+
+ # Text nodes only have :content, :parent, :ancestor
+ unless (conditions.keys - [:content, :parent, :ancestor]).empty?
+ return false
+ end
+
+ match(conditions[:content])
+ else
+ nil
+ end
+ end
+
+ def ==(node)
+ return false unless super
+ content == node.content
+ end
+ end
+
+ # A CDATA node is simply a text node with a specialized way of displaying
+ # itself.
+ class CDATA < Text #:nodoc:
+ def to_s
+ "<![CDATA[#{super}]>"
+ end
+ end
+
+ # A Tag is any node that represents markup. It may be an opening tag, a
+ # closing tag, or a self-closing tag. It has a name, and may have a hash of
+ # attributes.
+ class Tag < Node #:nodoc:
+
+ # Either +nil+, <tt>:close</tt>, or <tt>:self</tt>
+ attr_reader :closing
+
+ # Either +nil+, or a hash of attributes for this node.
+ attr_reader :attributes
+
+ # The name of this tag.
+ attr_reader :name
+
+ # Create a new node as a child of the given parent, using the given content
+ # to describe the node. It will be parsed and the node name, attributes and
+ # closing status extracted.
+ def initialize(parent, line, pos, name, attributes, closing)
+ super(parent, line, pos)
+ @name = name
+ @attributes = attributes
+ @closing = closing
+ end
+
+ # A convenience for obtaining an attribute of the node. Returns +nil+ if
+ # the node has no attributes.
+ def [](attr)
+ @attributes ? @attributes[attr] : nil
+ end
+
+ # Returns non-+nil+ if this tag can contain child nodes.
+ def childless?(xml = false)
+ return false if xml && @closing.nil?
+ !@closing.nil? ||
+ @name =~ /^(img|br|hr|link|meta|area|base|basefont|
+ col|frame|input|isindex|param)$/ox
+ end
+
+ # Returns a textual representation of the node
+ def to_s
+ if @closing == :close
+ "</#{@name}>"
+ else
+ s = "<#{@name}"
+ @attributes.each do |k,v|
+ s << " #{k}"
+ s << "=\"#{v}\"" if String === v
+ end
+ s << " /" if @closing == :self
+ s << ">"
+ @children.each { |child| s << child.to_s }
+ s << "</#{@name}>" if @closing != :self && !@children.empty?
+ s
+ end
+ end
+
+ # If either the node or any of its children meet the given conditions, the
+ # matching node is returned. Otherwise, +nil+ is returned. (See the
+ # description of the valid conditions in the +match+ method.)
+ def find(conditions)
+ match(conditions) && self || super
+ end
+
+ # Returns +true+, indicating that this node represents an HTML tag.
+ def tag?
+ true
+ end
+
+ # Returns +true+ if the node meets any of the given conditions. The
+ # +conditions+ parameter must be a hash of any of the following keys
+ # (all are optional):
+ #
+ # * <tt>:tag</tt>: the node name must match the corresponding value
+ # * <tt>:attributes</tt>: a hash. The node's values must match the
+ # corresponding values in the hash.
+ # * <tt>:parent</tt>: a hash. The node's parent must match the
+ # corresponding hash.
+ # * <tt>:child</tt>: a hash. At least one of the node's immediate children
+ # must meet the criteria described by the hash.
+ # * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
+ # meet the criteria described by the hash.
+ # * <tt>:descendant</tt>: a hash. At least one of the node's descendants
+ # must meet the criteria described by the hash.
+ # * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
+ # meet the criteria described by the hash.
+ # * <tt>:after</tt>: a hash. The node must be after any sibling meeting
+ # the criteria described by the hash, and at least one sibling must match.
+ # * <tt>:before</tt>: a hash. The node must be before any sibling meeting
+ # the criteria described by the hash, and at least one sibling must match.
+ # * <tt>:children</tt>: a hash, for counting children of a node. Accepts the
+ # keys:
+ # ** <tt>:count</tt>: either a number or a range which must equal (or
+ # include) the number of children that match.
+ # ** <tt>:less_than</tt>: the number of matching children must be less than
+ # this number.
+ # ** <tt>:greater_than</tt>: the number of matching children must be
+ # greater than this number.
+ # ** <tt>:only</tt>: another hash consisting of the keys to use
+ # to match on the children, and only matching children will be
+ # counted.
+ #
+ # Conditions are matched using the following algorithm:
+ #
+ # * if the condition is a string, it must be a substring of the value.
+ # * if the condition is a regexp, it must match the value.
+ # * if the condition is a number, the value must match number.to_s.
+ # * if the condition is +true+, the value must not be +nil+.
+ # * if the condition is +false+ or +nil+, the value must be +nil+.
+ #
+ # Usage:
+ #
+ # # test if the node is a "span" tag
+ # node.match :tag => "span"
+ #
+ # # test if the node's parent is a "div"
+ # node.match :parent => { :tag => "div" }
+ #
+ # # test if any of the node's ancestors are "table" tags
+ # node.match :ancestor => { :tag => "table" }
+ #
+ # # test if any of the node's immediate children are "em" tags
+ # node.match :child => { :tag => "em" }
+ #
+ # # test if any of the node's descendants are "strong" tags
+ # node.match :descendant => { :tag => "strong" }
+ #
+ # # test if the node has between 2 and 4 span tags as immediate children
+ # node.match :children => { :count => 2..4, :only => { :tag => "span" } }
+ #
+ # # get funky: test to see if the node is a "div", has a "ul" ancestor
+ # # and an "li" parent (with "class" = "enum"), and whether or not it has
+ # # a "span" descendant that contains # text matching /hello world/:
+ # node.match :tag => "div",
+ # :ancestor => { :tag => "ul" },
+ # :parent => { :tag => "li",
+ # :attributes => { :class => "enum" } },
+ # :descendant => { :tag => "span",
+ # :child => /hello world/ }
+ def match(conditions)
+ conditions = validate_conditions(conditions)
+ # check content of child nodes
+ if conditions[:content]
+ if children.empty?
+ return false unless match_condition("", conditions[:content])
+ else
+ return false unless children.find { |child| child.match(conditions[:content]) }
+ end
+ end
+
+ # test the name
+ return false unless match_condition(@name, conditions[:tag]) if conditions[:tag]
+
+ # test attributes
+ (conditions[:attributes] || {}).each do |key, value|
+ return false unless match_condition(self[key], value)
+ end
+
+ # test parent
+ return false unless parent.match(conditions[:parent]) if conditions[:parent]
+
+ # test children
+ return false unless children.find { |child| child.match(conditions[:child]) } if conditions[:child]
+
+ # test ancestors
+ if conditions[:ancestor]
+ return false unless catch :found do
+ p = self
+ throw :found, true if p.match(conditions[:ancestor]) while p = p.parent
+ end
+ end
+
+ # test descendants
+ if conditions[:descendant]
+ return false unless children.find do |child|
+ # test the child
+ child.match(conditions[:descendant]) ||
+ # test the child's descendants
+ child.match(:descendant => conditions[:descendant])
+ end
+ end
+
+ # count children
+ if opts = conditions[:children]
+ matches = children.select do |c|
+ (c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?))
+ end
+
+ matches = matches.select { |c| c.match(opts[:only]) } if opts[:only]
+ opts.each do |key, value|
+ next if key == :only
+ case key
+ when :count
+ if Integer === value
+ return false if matches.length != value
+ else
+ return false unless value.include?(matches.length)
+ end
+ when :less_than
+ return false unless matches.length < value
+ when :greater_than
+ return false unless matches.length > value
+ else raise "unknown count condition #{key}"
+ end
+ end
+ end
+
+ # test siblings
+ if conditions[:sibling] || conditions[:before] || conditions[:after]
+ siblings = parent ? parent.children : []
+ self_index = siblings.index(self)
+
+ if conditions[:sibling]
+ return false unless siblings.detect do |s|
+ s != self && s.match(conditions[:sibling])
+ end
+ end
+
+ if conditions[:before]
+ return false unless siblings[self_index+1..-1].detect do |s|
+ s != self && s.match(conditions[:before])
+ end
+ end
+
+ if conditions[:after]
+ return false unless siblings[0,self_index].detect do |s|
+ s != self && s.match(conditions[:after])
+ end
+ end
+ end
+
+ true
+ end
+
+ def ==(node)
+ return false unless super
+ return false unless closing == node.closing && self.name == node.name
+ attributes == node.attributes
+ end
+
+ private
+ # Match the given value to the given condition.
+ def match_condition(value, condition)
+ case condition
+ when String
+ value && value == condition
+ when Regexp
+ value && value.match(condition)
+ when Numeric
+ value == condition.to_s
+ when true
+ !value.nil?
+ when false, nil
+ value.nil?
+ else
+ false
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
new file mode 100644
index 000000000..1eb426aea
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
@@ -0,0 +1,173 @@
+module HTML
+ class Sanitizer
+ def sanitize(text, options = {})
+ return text unless sanitizeable?(text)
+ tokenize(text, options).join
+ end
+
+ def sanitizeable?(text)
+ !(text.nil? || text.empty? || !text.index("<"))
+ end
+
+ protected
+ def tokenize(text, options)
+ tokenizer = HTML::Tokenizer.new(text)
+ result = []
+ while token = tokenizer.next
+ node = Node.parse(nil, 0, 0, token, false)
+ process_node node, result, options
+ end
+ result
+ end
+
+ def process_node(node, result, options)
+ result << node.to_s
+ end
+ end
+
+ class FullSanitizer < Sanitizer
+ def sanitize(text, options = {})
+ result = super
+ # strip any comments, and if they have a newline at the end (ie. line with
+ # only a comment) strip that too
+ result.gsub!(/<!--(.*?)-->[\n]?/m, "") if result
+ # Recurse - handle all dirty nested tags
+ result == text ? result : sanitize(result, options)
+ end
+
+ def process_node(node, result, options)
+ result << node.to_s if node.class == HTML::Text
+ end
+ end
+
+ class LinkSanitizer < FullSanitizer
+ cattr_accessor :included_tags, :instance_writer => false
+ self.included_tags = Set.new(%w(a href))
+
+ def sanitizeable?(text)
+ !(text.nil? || text.empty? || !((text.index("<a") || text.index("<href")) && text.index(">")))
+ end
+
+ protected
+ def process_node(node, result, options)
+ result << node.to_s unless node.is_a?(HTML::Tag) && included_tags.include?(node.name)
+ end
+ end
+
+ class WhiteListSanitizer < Sanitizer
+ [:protocol_separator, :uri_attributes, :allowed_attributes, :allowed_tags, :allowed_protocols, :bad_tags,
+ :allowed_css_properties, :allowed_css_keywords, :shorthand_css_properties].each do |attr|
+ class_inheritable_accessor attr, :instance_writer => false
+ end
+
+ # A regular expression of the valid characters used to separate protocols like
+ # the ':' in 'http://foo.com'
+ self.protocol_separator = /:|(&#0*58)|(&#x70)|(%|&#37;)3A/
+
+ # Specifies a Set of HTML attributes that can have URIs.
+ self.uri_attributes = Set.new(%w(href src cite action longdesc xlink:href lowsrc))
+
+ # Specifies a Set of 'bad' tags that the #sanitize helper will remove completely, as opposed
+ # to just escaping harmless tags like &lt;font&gt;
+ self.bad_tags = Set.new(%w(script))
+
+ # Specifies the default Set of tags that the #sanitize helper will allow unscathed.
+ self.allowed_tags = Set.new(%w(strong em b i p code pre tt samp kbd var sub
+ sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol li dt dd abbr
+ acronym a img blockquote del ins))
+
+ # Specifies the default Set of html attributes that the #sanitize helper will leave
+ # in the allowed tag.
+ self.allowed_attributes = Set.new(%w(href src width height alt cite datetime title class name xml:lang abbr))
+
+ # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept.
+ self.allowed_protocols = Set.new(%w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto
+ feed svn urn aim rsync tag ssh sftp rtsp afs))
+
+ # Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept.
+ self.allowed_css_properties = Set.new(%w(azimuth background-color border-bottom-color border-collapse
+ border-color border-left-color border-right-color border-top-color clear color cursor direction display
+ elevation float font font-family font-size font-style font-variant font-weight height letter-spacing line-height
+ overflow pause pause-after pause-before pitch pitch-range richness speak speak-header speak-numeral speak-punctuation
+ speech-rate stress text-align text-decoration text-indent unicode-bidi vertical-align voice-family volume white-space
+ width))
+
+ # Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept.
+ self.allowed_css_keywords = Set.new(%w(auto aqua black block blue bold both bottom brown center
+ collapse dashed dotted fuchsia gray green !important italic left lime maroon medium none navy normal
+ nowrap olive pointer purple red right solid silver teal top transparent underline white yellow))
+
+ # Specifies the default Set of allowed shorthand css properties for the #sanitize and #sanitize_css helpers.
+ self.shorthand_css_properties = Set.new(%w(background border margin padding))
+
+ # Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute
+ def sanitize_css(style)
+ # disallow urls
+ style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ')
+
+ # gauntlet
+ if style !~ /^([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*$/ ||
+ style !~ /^(\s*[-\w]+\s*:\s*[^:;]*(;|$))*$/
+ return ''
+ end
+
+ clean = []
+ style.scan(/([-\w]+)\s*:\s*([^:;]*)/) do |prop,val|
+ if allowed_css_properties.include?(prop.downcase)
+ clean << prop + ': ' + val + ';'
+ elsif shorthand_css_properties.include?(prop.split('-')[0].downcase)
+ unless val.split().any? do |keyword|
+ !allowed_css_keywords.include?(keyword) &&
+ keyword !~ /^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$/
+ end
+ clean << prop + ': ' + val + ';'
+ end
+ end
+ end
+ clean.join(' ')
+ end
+
+ protected
+ def tokenize(text, options)
+ options[:parent] = []
+ options[:attributes] ||= allowed_attributes
+ options[:tags] ||= allowed_tags
+ super
+ end
+
+ def process_node(node, result, options)
+ result << case node
+ when HTML::Tag
+ if node.closing == :close
+ options[:parent].shift
+ else
+ options[:parent].unshift node.name
+ end
+
+ process_attributes_for node, options
+
+ options[:tags].include?(node.name) ? node : nil
+ else
+ bad_tags.include?(options[:parent].first) ? nil : node.to_s.gsub(/</, "&lt;")
+ end
+ end
+
+ def process_attributes_for(node, options)
+ return unless node.attributes
+ node.attributes.keys.each do |attr_name|
+ value = node.attributes[attr_name].to_s
+
+ if !options[:attributes].include?(attr_name) || contains_bad_protocols?(attr_name, value)
+ node.attributes.delete(attr_name)
+ else
+ node.attributes[attr_name] = attr_name == 'style' ? sanitize_css(value) : CGI::escapeHTML(value)
+ end
+ end
+ end
+
+ def contains_bad_protocols?(attr_name, value)
+ uri_attributes.include?(attr_name) &&
+ (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first))
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb
new file mode 100644
index 000000000..1a3c77025
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb
@@ -0,0 +1,828 @@
+#--
+# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
+# Under MIT and/or CC By license.
+#++
+
+module HTML
+
+ # Selects HTML elements using CSS 2 selectors.
+ #
+ # The +Selector+ class uses CSS selector expressions to match and select
+ # HTML elements.
+ #
+ # For example:
+ # selector = HTML::Selector.new "form.login[action=/login]"
+ # creates a new selector that matches any +form+ element with the class
+ # +login+ and an attribute +action+ with the value <tt>/login</tt>.
+ #
+ # === Matching Elements
+ #
+ # Use the #match method to determine if an element matches the selector.
+ #
+ # For simple selectors, the method returns an array with that element,
+ # or +nil+ if the element does not match. For complex selectors (see below)
+ # the method returns an array with all matched elements, of +nil+ if no
+ # match found.
+ #
+ # For example:
+ # if selector.match(element)
+ # puts "Element is a login form"
+ # end
+ #
+ # === Selecting Elements
+ #
+ # Use the #select method to select all matching elements starting with
+ # one element and going through all children in depth-first order.
+ #
+ # This method returns an array of all matching elements, an empty array
+ # if no match is found
+ #
+ # For example:
+ # selector = HTML::Selector.new "input[type=text]"
+ # matches = selector.select(element)
+ # matches.each do |match|
+ # puts "Found text field with name #{match.attributes['name']}"
+ # end
+ #
+ # === Expressions
+ #
+ # Selectors can match elements using any of the following criteria:
+ # * <tt>name</tt> -- Match an element based on its name (tag name).
+ # For example, <tt>p</tt> to match a paragraph. You can use <tt>*</tt>
+ # to match any element.
+ # * <tt>#</tt><tt>id</tt> -- Match an element based on its identifier (the
+ # <tt>id</tt> attribute). For example, <tt>#</tt><tt>page</tt>.
+ # * <tt>.class</tt> -- Match an element based on its class name, all
+ # class names if more than one specified.
+ # * <tt>[attr]</tt> -- Match an element that has the specified attribute.
+ # * <tt>[attr=value]</tt> -- Match an element that has the specified
+ # attribute and value. (More operators are supported see below)
+ # * <tt>:pseudo-class</tt> -- Match an element based on a pseudo class,
+ # such as <tt>:nth-child</tt> and <tt>:empty</tt>.
+ # * <tt>:not(expr)</tt> -- Match an element that does not match the
+ # negation expression.
+ #
+ # When using a combination of the above, the element name comes first
+ # followed by identifier, class names, attributes, pseudo classes and
+ # negation in any order. Do not seprate these parts with spaces!
+ # Space separation is used for descendant selectors.
+ #
+ # For example:
+ # selector = HTML::Selector.new "form.login[action=/login]"
+ # The matched element must be of type +form+ and have the class +login+.
+ # It may have other classes, but the class +login+ is required to match.
+ # It must also have an attribute called +action+ with the value
+ # <tt>/login</tt>.
+ #
+ # This selector will match the following element:
+ # <form class="login form" method="post" action="/login">
+ # but will not match the element:
+ # <form method="post" action="/logout">
+ #
+ # === Attribute Values
+ #
+ # Several operators are supported for matching attributes:
+ # * <tt>name</tt> -- The element must have an attribute with that name.
+ # * <tt>name=value</tt> -- The element must have an attribute with that
+ # name and value.
+ # * <tt>name^=value</tt> -- The attribute value must start with the
+ # specified value.
+ # * <tt>name$=value</tt> -- The attribute value must end with the
+ # specified value.
+ # * <tt>name*=value</tt> -- The attribute value must contain the
+ # specified value.
+ # * <tt>name~=word</tt> -- The attribute value must contain the specified
+ # word (space separated).
+ # * <tt>name|=word</tt> -- The attribute value must start with specified
+ # word.
+ #
+ # For example, the following two selectors match the same element:
+ # #my_id
+ # [id=my_id]
+ # and so do the following two selectors:
+ # .my_class
+ # [class~=my_class]
+ #
+ # === Alternatives, siblings, children
+ #
+ # Complex selectors use a combination of expressions to match elements:
+ # * <tt>expr1 expr2</tt> -- Match any element against the second expression
+ # if it has some parent element that matches the first expression.
+ # * <tt>expr1 > expr2</tt> -- Match any element against the second expression
+ # if it is the child of an element that matches the first expression.
+ # * <tt>expr1 + expr2</tt> -- Match any element against the second expression
+ # if it immediately follows an element that matches the first expression.
+ # * <tt>expr1 ~ expr2</tt> -- Match any element against the second expression
+ # that comes after an element that matches the first expression.
+ # * <tt>expr1, expr2</tt> -- Match any element against the first expression,
+ # or against the second expression.
+ #
+ # Since children and sibling selectors may match more than one element given
+ # the first element, the #match method may return more than one match.
+ #
+ # === Pseudo classes
+ #
+ # Pseudo classes were introduced in CSS 3. They are most often used to select
+ # elements in a given position:
+ # * <tt>:root</tt> -- Match the element only if it is the root element
+ # (no parent element).
+ # * <tt>:empty</tt> -- Match the element only if it has no child elements,
+ # and no text content.
+ # * <tt>:only-child</tt> -- Match the element if it is the only child (element)
+ # of its parent element.
+ # * <tt>:only-of-type</tt> -- Match the element if it is the only child (element)
+ # of its parent element and its type.
+ # * <tt>:first-child</tt> -- Match the element if it is the first child (element)
+ # of its parent element.
+ # * <tt>:first-of-type</tt> -- Match the element if it is the first child (element)
+ # of its parent element of its type.
+ # * <tt>:last-child</tt> -- Match the element if it is the last child (element)
+ # of its parent element.
+ # * <tt>:last-of-type</tt> -- Match the element if it is the last child (element)
+ # of its parent element of its type.
+ # * <tt>:nth-child(b)</tt> -- Match the element if it is the b-th child (element)
+ # of its parent element. The value <tt>b</tt> specifies its index, starting with 1.
+ # * <tt>:nth-child(an+b)</tt> -- Match the element if it is the b-th child (element)
+ # in each group of <tt>a</tt> child elements of its parent element.
+ # * <tt>:nth-child(-an+b)</tt> -- Match the element if it is the first child (element)
+ # in each group of <tt>a</tt> child elements, up to the first <tt>b</tt> child
+ # elements of its parent element.
+ # * <tt>:nth-child(odd)</tt> -- Match element in the odd position (i.e. first, third).
+ # Same as <tt>:nth-child(2n+1)</tt>.
+ # * <tt>:nth-child(even)</tt> -- Match element in the even position (i.e. second,
+ # fourth). Same as <tt>:nth-child(2n+2)</tt>.
+ # * <tt>:nth-of-type(..)</tt> -- As above, but only counts elements of its type.
+ # * <tt>:nth-last-child(..)</tt> -- As above, but counts from the last child.
+ # * <tt>:nth-last-of-type(..)</tt> -- As above, but counts from the last child and
+ # only elements of its type.
+ # * <tt>:not(selector)</tt> -- Match the element only if the element does not
+ # match the simple selector.
+ #
+ # As you can see, <tt>:nth-child<tt> pseudo class and its varient can get quite
+ # tricky and the CSS specification doesn't do a much better job explaining it.
+ # But after reading the examples and trying a few combinations, it's easy to
+ # figure out.
+ #
+ # For example:
+ # table tr:nth-child(odd)
+ # Selects every second row in the table starting with the first one.
+ #
+ # div p:nth-child(4)
+ # Selects the fourth paragraph in the +div+, but not if the +div+ contains
+ # other elements, since those are also counted.
+ #
+ # div p:nth-of-type(4)
+ # Selects the fourth paragraph in the +div+, counting only paragraphs, and
+ # ignoring all other elements.
+ #
+ # div p:nth-of-type(-n+4)
+ # Selects the first four paragraphs, ignoring all others.
+ #
+ # And you can always select an element that matches one set of rules but
+ # not another using <tt>:not</tt>. For example:
+ # p:not(.post)
+ # Matches all paragraphs that do not have the class <tt>.post</tt>.
+ #
+ # === Substitution Values
+ #
+ # You can use substitution with identifiers, class names and element values.
+ # A substitution takes the form of a question mark (<tt>?</tt>) and uses the
+ # next value in the argument list following the CSS expression.
+ #
+ # The substitution value may be a string or a regular expression. All other
+ # values are converted to strings.
+ #
+ # For example:
+ # selector = HTML::Selector.new "#?", /^\d+$/
+ # matches any element whose identifier consists of one or more digits.
+ #
+ # See http://www.w3.org/TR/css3-selectors/
+ class Selector
+
+
+ # An invalid selector.
+ class InvalidSelectorError < StandardError #:nodoc:
+ end
+
+
+ class << self
+
+ # :call-seq:
+ # Selector.for_class(cls) => selector
+ #
+ # Creates a new selector for the given class name.
+ def for_class(cls)
+ self.new([".?", cls])
+ end
+
+
+ # :call-seq:
+ # Selector.for_id(id) => selector
+ #
+ # Creates a new selector for the given id.
+ def for_id(id)
+ self.new(["#?", id])
+ end
+
+ end
+
+
+ # :call-seq:
+ # Selector.new(string, [values ...]) => selector
+ #
+ # Creates a new selector from a CSS 2 selector expression.
+ #
+ # The first argument is the selector expression. All other arguments
+ # are used for value substitution.
+ #
+ # Throws InvalidSelectorError is the selector expression is invalid.
+ def initialize(selector, *values)
+ raise ArgumentError, "CSS expression cannot be empty" if selector.empty?
+ @source = ""
+ values = values[0] if values.size == 1 && values[0].is_a?(Array)
+
+ # We need a copy to determine if we failed to parse, and also
+ # preserve the original pass by-ref statement.
+ statement = selector.strip.dup
+
+ # Create a simple selector, along with negation.
+ simple_selector(statement, values).each { |name, value| instance_variable_set("@#{name}", value) }
+
+ @alternates = []
+ @depends = nil
+
+ # Alternative selector.
+ if statement.sub!(/^\s*,\s*/, "")
+ second = Selector.new(statement, values)
+ @alternates << second
+ # If there are alternate selectors, we group them in the top selector.
+ if alternates = second.instance_variable_get(:@alternates)
+ second.instance_variable_set(:@alternates, [])
+ @alternates.concat alternates
+ end
+ @source << " , " << second.to_s
+ # Sibling selector: create a dependency into second selector that will
+ # match element immediately following this one.
+ elsif statement.sub!(/^\s*\+\s*/, "")
+ second = next_selector(statement, values)
+ @depends = lambda do |element, first|
+ if element = next_element(element)
+ second.match(element, first)
+ end
+ end
+ @source << " + " << second.to_s
+ # Adjacent selector: create a dependency into second selector that will
+ # match all elements following this one.
+ elsif statement.sub!(/^\s*~\s*/, "")
+ second = next_selector(statement, values)
+ @depends = lambda do |element, first|
+ matches = []
+ while element = next_element(element)
+ if subset = second.match(element, first)
+ if first && !subset.empty?
+ matches << subset.first
+ break
+ else
+ matches.concat subset
+ end
+ end
+ end
+ matches.empty? ? nil : matches
+ end
+ @source << " ~ " << second.to_s
+ # Child selector: create a dependency into second selector that will
+ # match a child element of this one.
+ elsif statement.sub!(/^\s*>\s*/, "")
+ second = next_selector(statement, values)
+ @depends = lambda do |element, first|
+ matches = []
+ element.children.each do |child|
+ if child.tag? && subset = second.match(child, first)
+ if first && !subset.empty?
+ matches << subset.first
+ break
+ else
+ matches.concat subset
+ end
+ end
+ end
+ matches.empty? ? nil : matches
+ end
+ @source << " > " << second.to_s
+ # Descendant selector: create a dependency into second selector that
+ # will match all descendant elements of this one. Note,
+ elsif statement =~ /^\s+\S+/ && statement != selector
+ second = next_selector(statement, values)
+ @depends = lambda do |element, first|
+ matches = []
+ stack = element.children.reverse
+ while node = stack.pop
+ next unless node.tag?
+ if subset = second.match(node, first)
+ if first && !subset.empty?
+ matches << subset.first
+ break
+ else
+ matches.concat subset
+ end
+ elsif children = node.children
+ stack.concat children.reverse
+ end
+ end
+ matches.empty? ? nil : matches
+ end
+ @source << " " << second.to_s
+ else
+ # The last selector is where we check that we parsed
+ # all the parts.
+ unless statement.empty? || statement.strip.empty?
+ raise ArgumentError, "Invalid selector: #{statement}"
+ end
+ end
+ end
+
+
+ # :call-seq:
+ # match(element, first?) => array or nil
+ #
+ # Matches an element against the selector.
+ #
+ # For a simple selector this method returns an array with the
+ # element if the element matches, nil otherwise.
+ #
+ # For a complex selector (sibling and descendant) this method
+ # returns an array with all matching elements, nil if no match is
+ # found.
+ #
+ # Use +first_only=true+ if you are only interested in the first element.
+ #
+ # For example:
+ # if selector.match(element)
+ # puts "Element is a login form"
+ # end
+ def match(element, first_only = false)
+ # Match element if no element name or element name same as element name
+ if matched = (!@tag_name || @tag_name == element.name)
+ # No match if one of the attribute matches failed
+ for attr in @attributes
+ if element.attributes[attr[0]] !~ attr[1]
+ matched = false
+ break
+ end
+ end
+ end
+
+ # Pseudo class matches (nth-child, empty, etc).
+ if matched
+ for pseudo in @pseudo
+ unless pseudo.call(element)
+ matched = false
+ break
+ end
+ end
+ end
+
+ # Negation. Same rules as above, but we fail if a match is made.
+ if matched && @negation
+ for negation in @negation
+ if negation[:tag_name] == element.name
+ matched = false
+ else
+ for attr in negation[:attributes]
+ if element.attributes[attr[0]] =~ attr[1]
+ matched = false
+ break
+ end
+ end
+ end
+ if matched
+ for pseudo in negation[:pseudo]
+ if pseudo.call(element)
+ matched = false
+ break
+ end
+ end
+ end
+ break unless matched
+ end
+ end
+
+ # If element matched but depends on another element (child,
+ # sibling, etc), apply the dependent matches instead.
+ if matched && @depends
+ matches = @depends.call(element, first_only)
+ else
+ matches = matched ? [element] : nil
+ end
+
+ # If this selector is part of the group, try all the alternative
+ # selectors (unless first_only).
+ if !first_only || !matches
+ @alternates.each do |alternate|
+ break if matches && first_only
+ if subset = alternate.match(element, first_only)
+ if matches
+ matches.concat subset
+ else
+ matches = subset
+ end
+ end
+ end
+ end
+
+ matches
+ end
+
+
+ # :call-seq:
+ # select(root) => array
+ #
+ # Selects and returns an array with all matching elements, beginning
+ # with one node and traversing through all children depth-first.
+ # Returns an empty array if no match is found.
+ #
+ # The root node may be any element in the document, or the document
+ # itself.
+ #
+ # For example:
+ # selector = HTML::Selector.new "input[type=text]"
+ # matches = selector.select(element)
+ # matches.each do |match|
+ # puts "Found text field with name #{match.attributes['name']}"
+ # end
+ def select(root)
+ matches = []
+ stack = [root]
+ while node = stack.pop
+ if node.tag? && subset = match(node, false)
+ subset.each do |match|
+ matches << match unless matches.any? { |item| item.equal?(match) }
+ end
+ elsif children = node.children
+ stack.concat children.reverse
+ end
+ end
+ matches
+ end
+
+
+ # Similar to #select but returns the first matching element. Returns +nil+
+ # if no element matches the selector.
+ def select_first(root)
+ stack = [root]
+ while node = stack.pop
+ if node.tag? && subset = match(node, true)
+ return subset.first if !subset.empty?
+ elsif children = node.children
+ stack.concat children.reverse
+ end
+ end
+ nil
+ end
+
+
+ def to_s #:nodoc:
+ @source
+ end
+
+
+ # Return the next element after this one. Skips sibling text nodes.
+ #
+ # With the +name+ argument, returns the next element with that name,
+ # skipping other sibling elements.
+ def next_element(element, name = nil)
+ if siblings = element.parent.children
+ found = false
+ siblings.each do |node|
+ if node.equal?(element)
+ found = true
+ elsif found && node.tag?
+ return node if (name.nil? || node.name == name)
+ end
+ end
+ end
+ nil
+ end
+
+
+ protected
+
+
+ # Creates a simple selector given the statement and array of
+ # substitution values.
+ #
+ # Returns a hash with the values +tag_name+, +attributes+,
+ # +pseudo+ (classes) and +negation+.
+ #
+ # Called the first time with +can_negate+ true to allow
+ # negation. Called a second time with false since negation
+ # cannot be negated.
+ def simple_selector(statement, values, can_negate = true)
+ tag_name = nil
+ attributes = []
+ pseudo = []
+ negation = []
+
+ # Element name. (Note that in negation, this can come at
+ # any order, but for simplicity we allow if only first).
+ statement.sub!(/^(\*|[[:alpha:]][\w\-]*)/) do |match|
+ match.strip!
+ tag_name = match.downcase unless match == "*"
+ @source << match
+ "" # Remove
+ end
+
+ # Get identifier, class, attribute name, pseudo or negation.
+ while true
+ # Element identifier.
+ next if statement.sub!(/^#(\?|[\w\-]+)/) do |match|
+ id = $1
+ if id == "?"
+ id = values.shift
+ end
+ @source << "##{id}"
+ id = Regexp.new("^#{Regexp.escape(id.to_s)}$") unless id.is_a?(Regexp)
+ attributes << ["id", id]
+ "" # Remove
+ end
+
+ # Class name.
+ next if statement.sub!(/^\.([\w\-]+)/) do |match|
+ class_name = $1
+ @source << ".#{class_name}"
+ class_name = Regexp.new("(^|\s)#{Regexp.escape(class_name)}($|\s)") unless class_name.is_a?(Regexp)
+ attributes << ["class", class_name]
+ "" # Remove
+ end
+
+ # Attribute value.
+ next if statement.sub!(/^\[\s*([[:alpha:]][\w\-]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do |match|
+ name, equality, value = $1, $2, $3
+ if value == "?"
+ value = values.shift
+ else
+ # Handle single and double quotes.
+ value.strip!
+ if (value[0] == ?" || value[0] == ?') && value[0] == value[-1]
+ value = value[1..-2]
+ end
+ end
+ @source << "[#{name}#{equality}'#{value}']"
+ attributes << [name.downcase.strip, attribute_match(equality, value)]
+ "" # Remove
+ end
+
+ # Root element only.
+ next if statement.sub!(/^:root/) do |match|
+ pseudo << lambda do |element|
+ element.parent.nil? || !element.parent.tag?
+ end
+ @source << ":root"
+ "" # Remove
+ end
+
+ # Nth-child including last and of-type.
+ next if statement.sub!(/^:nth-(last-)?(child|of-type)\((odd|even|(\d+|\?)|(-?\d*|\?)?n([+\-]\d+|\?)?)\)/) do |match|
+ reverse = $1 == "last-"
+ of_type = $2 == "of-type"
+ @source << ":nth-#{$1}#{$2}("
+ case $3
+ when "odd"
+ pseudo << nth_child(2, 1, of_type, reverse)
+ @source << "odd)"
+ when "even"
+ pseudo << nth_child(2, 2, of_type, reverse)
+ @source << "even)"
+ when /^(\d+|\?)$/ # b only
+ b = ($1 == "?" ? values.shift : $1).to_i
+ pseudo << nth_child(0, b, of_type, reverse)
+ @source << "#{b})"
+ when /^(-?\d*|\?)?n([+\-]\d+|\?)?$/
+ a = ($1 == "?" ? values.shift :
+ $1 == "" ? 1 : $1 == "-" ? -1 : $1).to_i
+ b = ($2 == "?" ? values.shift : $2).to_i
+ pseudo << nth_child(a, b, of_type, reverse)
+ @source << (b >= 0 ? "#{a}n+#{b})" : "#{a}n#{b})")
+ else
+ raise ArgumentError, "Invalid nth-child #{match}"
+ end
+ "" # Remove
+ end
+ # First/last child (of type).
+ next if statement.sub!(/^:(first|last)-(child|of-type)/) do |match|
+ reverse = $1 == "last"
+ of_type = $2 == "of-type"
+ pseudo << nth_child(0, 1, of_type, reverse)
+ @source << ":#{$1}-#{$2}"
+ "" # Remove
+ end
+ # Only child (of type).
+ next if statement.sub!(/^:only-(child|of-type)/) do |match|
+ of_type = $1 == "of-type"
+ pseudo << only_child(of_type)
+ @source << ":only-#{$1}"
+ "" # Remove
+ end
+
+ # Empty: no child elements or meaningful content (whitespaces
+ # are ignored).
+ next if statement.sub!(/^:empty/) do |match|
+ pseudo << lambda do |element|
+ empty = true
+ for child in element.children
+ if child.tag? || !child.content.strip.empty?
+ empty = false
+ break
+ end
+ end
+ empty
+ end
+ @source << ":empty"
+ "" # Remove
+ end
+ # Content: match the text content of the element, stripping
+ # leading and trailing spaces.
+ next if statement.sub!(/^:content\(\s*(\?|'[^']*'|"[^"]*"|[^)]*)\s*\)/) do |match|
+ content = $1
+ if content == "?"
+ content = values.shift
+ elsif (content[0] == ?" || content[0] == ?') && content[0] == content[-1]
+ content = content[1..-2]
+ end
+ @source << ":content('#{content}')"
+ content = Regexp.new("^#{Regexp.escape(content.to_s)}$") unless content.is_a?(Regexp)
+ pseudo << lambda do |element|
+ text = ""
+ for child in element.children
+ unless child.tag?
+ text << child.content
+ end
+ end
+ text.strip =~ content
+ end
+ "" # Remove
+ end
+
+ # Negation. Create another simple selector to handle it.
+ if statement.sub!(/^:not\(\s*/, "")
+ raise ArgumentError, "Double negatives are not missing feature" unless can_negate
+ @source << ":not("
+ negation << simple_selector(statement, values, false)
+ raise ArgumentError, "Negation not closed" unless statement.sub!(/^\s*\)/, "")
+ @source << ")"
+ next
+ end
+
+ # No match: moving on.
+ break
+ end
+
+ # Return hash. The keys are mapped to instance variables.
+ {:tag_name=>tag_name, :attributes=>attributes, :pseudo=>pseudo, :negation=>negation}
+ end
+
+
+ # Create a regular expression to match an attribute value based
+ # on the equality operator (=, ^=, |=, etc).
+ def attribute_match(equality, value)
+ regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s)
+ case equality
+ when "=" then
+ # Match the attribute value in full
+ Regexp.new("^#{regexp}$")
+ when "~=" then
+ # Match a space-separated word within the attribute value
+ Regexp.new("(^|\s)#{regexp}($|\s)")
+ when "^="
+ # Match the beginning of the attribute value
+ Regexp.new("^#{regexp}")
+ when "$="
+ # Match the end of the attribute value
+ Regexp.new("#{regexp}$")
+ when "*="
+ # Match substring of the attribute value
+ regexp.is_a?(Regexp) ? regexp : Regexp.new(regexp)
+ when "|=" then
+ # Match the first space-separated item of the attribute value
+ Regexp.new("^#{regexp}($|\s)")
+ else
+ raise InvalidSelectorError, "Invalid operation/value" unless value.empty?
+ # Match all attributes values (existence check)
+ //
+ end
+ end
+
+
+ # Returns a lambda that can match an element against the nth-child
+ # pseudo class, given the following arguments:
+ # * +a+ -- Value of a part.
+ # * +b+ -- Value of b part.
+ # * +of_type+ -- True to test only elements of this type (of-type).
+ # * +reverse+ -- True to count in reverse order (last-).
+ def nth_child(a, b, of_type, reverse)
+ # a = 0 means select at index b, if b = 0 nothing selected
+ return lambda { |element| false } if a == 0 && b == 0
+ # a < 0 and b < 0 will never match against an index
+ return lambda { |element| false } if a < 0 && b < 0
+ b = a + b + 1 if b < 0 # b < 0 just picks last element from each group
+ b -= 1 unless b == 0 # b == 0 is same as b == 1, otherwise zero based
+ lambda do |element|
+ # Element must be inside parent element.
+ return false unless element.parent && element.parent.tag?
+ index = 0
+ # Get siblings, reverse if counting from last.
+ siblings = element.parent.children
+ siblings = siblings.reverse if reverse
+ # Match element name if of-type, otherwise ignore name.
+ name = of_type ? element.name : nil
+ found = false
+ for child in siblings
+ # Skip text nodes/comments.
+ if child.tag? && (name == nil || child.name == name)
+ if a == 0
+ # Shortcut when a == 0 no need to go past count
+ if index == b
+ found = child.equal?(element)
+ break
+ end
+ elsif a < 0
+ # Only look for first b elements
+ break if index > b
+ if child.equal?(element)
+ found = (index % a) == 0
+ break
+ end
+ else
+ # Otherwise, break if child found and count == an+b
+ if child.equal?(element)
+ found = (index % a) == b
+ break
+ end
+ end
+ index += 1
+ end
+ end
+ found
+ end
+ end
+
+
+ # Creates a only child lambda. Pass +of-type+ to only look at
+ # elements of its type.
+ def only_child(of_type)
+ lambda do |element|
+ # Element must be inside parent element.
+ return false unless element.parent && element.parent.tag?
+ name = of_type ? element.name : nil
+ other = false
+ for child in element.parent.children
+ # Skip text nodes/comments.
+ if child.tag? && (name == nil || child.name == name)
+ unless child.equal?(element)
+ other = true
+ break
+ end
+ end
+ end
+ !other
+ end
+ end
+
+
+ # Called to create a dependent selector (sibling, descendant, etc).
+ # Passes the remainder of the statement that will be reduced to zero
+ # eventually, and array of substitution values.
+ #
+ # This method is called from four places, so it helps to put it here
+ # for reuse. The only logic deals with the need to detect comma
+ # separators (alternate) and apply them to the selector group of the
+ # top selector.
+ def next_selector(statement, values)
+ second = Selector.new(statement, values)
+ # If there are alternate selectors, we group them in the top selector.
+ if alternates = second.instance_variable_get(:@alternates)
+ second.instance_variable_set(:@alternates, [])
+ @alternates.concat alternates
+ end
+ second
+ end
+
+ end
+
+
+ # See HTML::Selector.new
+ def self.selector(statement, *values)
+ Selector.new(statement, *values)
+ end
+
+
+ class Tag
+
+ def select(selector, *values)
+ selector = HTML::Selector.new(selector, values)
+ selector.select(self)
+ end
+
+ end
+
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
new file mode 100644
index 000000000..b950e8462
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
@@ -0,0 +1,105 @@
+require 'strscan'
+
+module HTML #:nodoc:
+
+ # A simple HTML tokenizer. It simply breaks a stream of text into tokens, where each
+ # token is a string. Each string represents either "text", or an HTML element.
+ #
+ # This currently assumes valid XHTML, which means no free < or > characters.
+ #
+ # Usage:
+ #
+ # tokenizer = HTML::Tokenizer.new(text)
+ # while token = tokenizer.next
+ # p token
+ # end
+ class Tokenizer #:nodoc:
+
+ # The current (byte) position in the text
+ attr_reader :position
+
+ # The current line number
+ attr_reader :line
+
+ # Create a new Tokenizer for the given text.
+ def initialize(text)
+ @scanner = StringScanner.new(text)
+ @position = 0
+ @line = 0
+ @current_line = 1
+ end
+
+ # Return the next token in the sequence, or +nil+ if there are no more tokens in
+ # the stream.
+ def next
+ return nil if @scanner.eos?
+ @position = @scanner.pos
+ @line = @current_line
+ if @scanner.check(/<\S/)
+ update_current_line(scan_tag)
+ else
+ update_current_line(scan_text)
+ end
+ end
+
+ private
+
+ # Treat the text at the current position as a tag, and scan it. Supports
+ # comments, doctype tags, and regular tags, and ignores less-than and
+ # greater-than characters within quoted strings.
+ def scan_tag
+ tag = @scanner.getch
+ if @scanner.scan(/!--/) # comment
+ tag << @scanner.matched
+ tag << (@scanner.scan_until(/--\s*>/) || @scanner.scan_until(/\Z/))
+ elsif @scanner.scan(/!\[CDATA\[/)
+ tag << @scanner.matched
+ tag << @scanner.scan_until(/\]\]>/)
+ elsif @scanner.scan(/!/) # doctype
+ tag << @scanner.matched
+ tag << consume_quoted_regions
+ else
+ tag << consume_quoted_regions
+ end
+ tag
+ end
+
+ # Scan all text up to the next < character and return it.
+ def scan_text
+ "#{@scanner.getch}#{@scanner.scan(/[^<]*/)}"
+ end
+
+ # Counts the number of newlines in the text and updates the current line
+ # accordingly.
+ def update_current_line(text)
+ text.scan(/\r?\n/) { @current_line += 1 }
+ end
+
+ # Skips over quoted strings, so that less-than and greater-than characters
+ # within the strings are ignored.
+ def consume_quoted_regions
+ text = ""
+ loop do
+ match = @scanner.scan_until(/['"<>]/) or break
+
+ delim = @scanner.matched
+ if delim == "<"
+ match = match.chop
+ @scanner.pos -= 1
+ end
+
+ text << match
+ break if delim == "<" || delim == ">"
+
+ # consume the quoted region
+ while match = @scanner.scan_until(/[\\#{delim}]/)
+ text << match
+ break if @scanner.matched == delim
+ text << @scanner.getch # skip the escaped character
+ end
+ end
+ text
+ end
+ end
+
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/version.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/version.rb
new file mode 100644
index 000000000..6d645c3e1
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/version.rb
@@ -0,0 +1,11 @@
+module HTML #:nodoc:
+ module Version #:nodoc:
+
+ MAJOR = 0
+ MINOR = 5
+ TINY = 3
+
+ STRING = [ MAJOR, MINOR, TINY ].join(".")
+
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/verification.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/verification.rb
new file mode 100644
index 000000000..e5045fba7
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/verification.rb
@@ -0,0 +1,114 @@
+module ActionController #:nodoc:
+ module Verification #:nodoc:
+ def self.included(base) #:nodoc:
+ base.extend(ClassMethods)
+ end
+
+ # This module provides a class-level method for specifying that certain
+ # actions are guarded against being called without certain prerequisites
+ # being met. This is essentially a special kind of before_filter.
+ #
+ # An action may be guarded against being invoked without certain request
+ # parameters being set, or without certain session values existing.
+ #
+ # When a verification is violated, values may be inserted into the flash, and
+ # a specified redirection is triggered. If no specific action is configured,
+ # verification failures will by default result in a 400 Bad Request response.
+ #
+ # Usage:
+ #
+ # class GlobalController < ActionController::Base
+ # # Prevent the #update_settings action from being invoked unless
+ # # the 'admin_privileges' request parameter exists. The
+ # # settings action will be redirected to in current controller
+ # # if verification fails.
+ # verify :params => "admin_privileges", :only => :update_post,
+ # :redirect_to => { :action => "settings" }
+ #
+ # # Disallow a post from being updated if there was no information
+ # # submitted with the post, and if there is no active post in the
+ # # session, and if there is no "note" key in the flash. The route
+ # # named category_url will be redirected to if verification fails.
+ #
+ # verify :params => "post", :session => "post", "flash" => "note",
+ # :only => :update_post,
+ # :add_flash => { "alert" => "Failed to create your message" },
+ # :redirect_to => :category_url
+ #
+ # Note that these prerequisites are not business rules. They do not examine
+ # the content of the session or the parameters. That level of validation should
+ # be encapsulated by your domain model or helper methods in the controller.
+ module ClassMethods
+ # Verify the given actions so that if certain prerequisites are not met,
+ # the user is redirected to a different action. The +options+ parameter
+ # is a hash consisting of the following key/value pairs:
+ #
+ # * <tt>:params</tt> - a single key or an array of keys that must
+ # be in the <tt>params</tt> hash in order for the action(s) to be safely
+ # called.
+ # * <tt>:session</tt> - a single key or an array of keys that must
+ # be in the <tt>session</tt> in order for the action(s) to be safely called.
+ # * <tt>:flash</tt> - a single key or an array of keys that must
+ # be in the flash in order for the action(s) to be safely called.
+ # * <tt>:method</tt> - a single key or an array of keys--any one of which
+ # must match the current request method in order for the action(s) to
+ # be safely called. (The key should be a symbol: <tt>:get</tt> or
+ # <tt>:post</tt>, for example.)
+ # * <tt>:xhr</tt> - true/false option to ensure that the request is coming
+ # from an Ajax call or not.
+ # * <tt>:add_flash</tt> - a hash of name/value pairs that should be merged
+ # into the session's flash if the prerequisites cannot be satisfied.
+ # * <tt>:add_headers</tt> - a hash of name/value pairs that should be
+ # merged into the response's headers hash if the prerequisites cannot
+ # be satisfied.
+ # * <tt>:redirect_to</tt> - the redirection parameters to be used when
+ # redirecting if the prerequisites cannot be satisfied. You can
+ # redirect either to named route or to the action in some controller.
+ # * <tt>:render</tt> - the render parameters to be used when
+ # the prerequisites cannot be satisfied.
+ # * <tt>:only</tt> - only apply this verification to the actions specified
+ # in the associated array (may also be a single value).
+ # * <tt>:except</tt> - do not apply this verification to the actions
+ # specified in the associated array (may also be a single value).
+ def verify(options={})
+ filter_opts = { :only => options[:only], :except => options[:except] }
+ before_filter(filter_opts) do |c|
+ c.send! :verify_action, options
+ end
+ end
+ end
+
+ def verify_action(options) #:nodoc:
+ prereqs_invalid =
+ [*options[:params] ].find { |v| params[v].nil? } ||
+ [*options[:session]].find { |v| session[v].nil? } ||
+ [*options[:flash] ].find { |v| flash[v].nil? }
+
+ if !prereqs_invalid && options[:method]
+ prereqs_invalid ||=
+ [*options[:method]].all? { |v| request.method != v.to_sym }
+ end
+
+ prereqs_invalid ||= (request.xhr? != options[:xhr]) unless options[:xhr].nil?
+
+ if prereqs_invalid
+ flash.update(options[:add_flash]) if options[:add_flash]
+ response.headers.update(options[:add_headers]) if options[:add_headers]
+
+ unless performed?
+ case
+ when options[:render]
+ render(options[:render])
+ when options[:redirect_to]
+ options[:redirect_to] = self.send!(options[:redirect_to]) if options[:redirect_to].is_a?(Symbol)
+ redirect_to(options[:redirect_to])
+ else
+ head(:bad_request)
+ end
+ end
+ end
+ end
+
+ private :verify_action
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_pack.rb b/vendor/rails-2.0.2/actionpack/lib/action_pack.rb
new file mode 100644
index 000000000..006c83dbc
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_pack.rb
@@ -0,0 +1,24 @@
+#--
+# Copyright (c) 2004-2007 David Heinemeier Hansson
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#++
+
+require 'action_pack/version'
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_pack/version.rb b/vendor/rails-2.0.2/actionpack/lib/action_pack/version.rb
new file mode 100644
index 000000000..7aa6a5db9
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_pack/version.rb
@@ -0,0 +1,9 @@
+module ActionPack #:nodoc:
+ module VERSION #:nodoc:
+ MAJOR = 2
+ MINOR = 0
+ TINY = 2
+
+ STRING = [MAJOR, MINOR, TINY].join('.')
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view.rb b/vendor/rails-2.0.2/actionpack/lib/action_view.rb
new file mode 100644
index 000000000..bfcfcab0b
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view.rb
@@ -0,0 +1,37 @@
+#--
+# Copyright (c) 2004-2007 David Heinemeier Hansson
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#++
+
+require 'action_view/template_handler'
+require 'action_view/template_handlers/builder'
+require 'action_view/template_handlers/erb'
+require 'action_view/template_handlers/rjs'
+
+require 'action_view/base'
+require 'action_view/partials'
+require 'action_view/template_error'
+
+ActionView::Base.class_eval do
+ include ActionView::Partials
+end
+
+ActionView::Base.load_helpers
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/base.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/base.rb
new file mode 100644
index 000000000..0f966addb
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/base.rb
@@ -0,0 +1,642 @@
+module ActionView #:nodoc:
+ class ActionViewError < StandardError #:nodoc:
+ end
+
+ # Action View templates can be written in three ways. If the template file has a +.erb+ (or +.rhtml+) extension then it uses a mixture of ERb
+ # (included in Ruby) and HTML. If the template file has a +.builder+ (or +.rxml+) extension then Jim Weirich's Builder::XmlMarkup library is used.
+ # If the template file has a +.rjs+ extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.
+ #
+ # = ERb
+ #
+ # You trigger ERb by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
+ # following loop for names:
+ #
+ # <b>Names of all the people</b>
+ # <% for person in @people %>
+ # Name: <%= person.name %><br/>
+ # <% end %>
+ #
+ # The loop is setup in regular embedding tags <% %> and the name is written using the output embedding tag <%= %>. Note that this
+ # is not just a usage suggestion. Regular output functions like print or puts won't work with ERb templates. So this would be wrong:
+ #
+ # Hi, Mr. <% puts "Frodo" %>
+ #
+ # If you absolutely must write from within a function, you can use the TextHelper#concat
+ #
+ # <%- and -%> suppress leading and trailing whitespace, including the trailing newline, and can be used interchangeably with <% and %>.
+ #
+ # == Using sub templates
+ #
+ # Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The
+ # classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts):
+ #
+ # <%= render "shared/header" %>
+ # Something really specific and terrific
+ # <%= render "shared/footer" %>
+ #
+ # As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the
+ # result of the rendering. The output embedding writes it to the current template.
+ #
+ # But you don't have to restrict yourself to static includes. Templates can share variables amongst themselves by using instance
+ # variables defined using the regular embedding tags. Like this:
+ #
+ # <% @page_title = "A Wonderful Hello" %>
+ # <%= render "shared/header" %>
+ #
+ # Now the header can pick up on the @page_title variable and use it for outputting a title tag:
+ #
+ # <title><%= @page_title %></title>
+ #
+ # == Passing local variables to sub templates
+ #
+ # You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values:
+ #
+ # <%= render "shared/header", { :headline => "Welcome", :person => person } %>
+ #
+ # These can now be accessed in shared/header with:
+ #
+ # Headline: <%= headline %>
+ # First name: <%= person.first_name %>
+ #
+ # If you need to find out whether a certain local variable has been assigned a value in a particular render call,
+ # you need to use the following pattern:
+ #
+ # <% if local_assigns.has_key? :headline %>
+ # Headline: <%= headline %>
+ # <% end %>
+ #
+ # Testing using <tt>defined? headline</tt> will not work. This is an implementation restriction.
+ #
+ # == Template caching
+ #
+ # By default, Rails will compile each template to a method in order to render it. When you alter a template, Rails will
+ # check the file's modification time and recompile it.
+ #
+ # == Builder
+ #
+ # Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An +XmlMarkup+ object
+ # named +xml+ is automatically made available to templates with a +.builder+ extension.
+ #
+ # Here are some basic examples:
+ #
+ # xml.em("emphasized") # => <em>emphasized</em>
+ # xml.em { xml.b("emph & bold") } # => <em><b>emph &amp; bold</b></em>
+ # xml.a("A Link", "href"=>"http://onestepback.org") # => <a href="http://onestepback.org">A Link</a>
+ # xml.target("name"=>"compile", "option"=>"fast") # => <target option="fast" name="compile"\>
+ # # NOTE: order of attributes is not specified.
+ #
+ # Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following:
+ #
+ # xml.div {
+ # xml.h1(@person.name)
+ # xml.p(@person.bio)
+ # }
+ #
+ # would produce something like:
+ #
+ # <div>
+ # <h1>David Heinemeier Hansson</h1>
+ # <p>A product of Danish Design during the Winter of '79...</p>
+ # </div>
+ #
+ # A full-length RSS example actually used on Basecamp:
+ #
+ # xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
+ # xml.channel do
+ # xml.title(@feed_title)
+ # xml.link(@url)
+ # xml.description "Basecamp: Recent items"
+ # xml.language "en-us"
+ # xml.ttl "40"
+ #
+ # for item in @recent_items
+ # xml.item do
+ # xml.title(item_title(item))
+ # xml.description(item_description(item)) if item_description(item)
+ # xml.pubDate(item_pubDate(item))
+ # xml.guid(@person.firm.account.url + @recent_items.url(item))
+ # xml.link(@person.firm.account.url + @recent_items.url(item))
+ #
+ # xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
+ # end
+ # end
+ # end
+ # end
+ #
+ # More builder documentation can be found at http://builder.rubyforge.org.
+ #
+ # == JavaScriptGenerator
+ #
+ # JavaScriptGenerator templates end in +.rjs+. Unlike conventional templates which are used to
+ # render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to
+ # modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax
+ # and make updates to the page where the request originated from.
+ #
+ # An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block.
+ #
+ # When an .rjs action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example:
+ #
+ # link_to_remote :url => {:action => 'delete'}
+ #
+ # The subsequently rendered +delete.rjs+ might look like:
+ #
+ # page.replace_html 'sidebar', :partial => 'sidebar'
+ # page.remove "person-#{@person.id}"
+ # page.visual_effect :highlight, 'user-list'
+ #
+ # This refreshes the sidebar, removes a person element and highlights the user list.
+ #
+ # See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details.
+ class Base
+ include ERB::Util
+
+ attr_reader :first_render
+ attr_accessor :base_path, :assigns, :template_extension
+ attr_accessor :controller, :view_paths
+
+ attr_reader :logger, :response, :headers
+ attr_internal :cookies, :flash, :headers, :params, :request, :response, :session
+
+ attr_writer :template_format
+
+ # Specify trim mode for the ERB compiler. Defaults to '-'.
+ # See ERb documentation for suitable values.
+ @@erb_trim_mode = '-'
+ cattr_accessor :erb_trim_mode
+
+ # Specify whether file modification times should be checked to see if a template needs recompilation
+ @@cache_template_loading = false
+ cattr_accessor :cache_template_loading
+
+ # Specify whether file extension lookup should be cached, and whether template base path lookup should be cached.
+ # Should be +false+ for development environments. Defaults to +true+.
+ @@cache_template_extensions = true
+ cattr_accessor :cache_template_extensions
+
+ # Specify whether local_assigns should be able to use string keys.
+ # Defaults to +true+. String keys are deprecated and will be removed
+ # shortly.
+ @@local_assigns_support_string_keys = true
+ cattr_accessor :local_assigns_support_string_keys
+
+ # Specify whether RJS responses should be wrapped in a try/catch block
+ # that alert()s the caught exception (and then re-raises it).
+ @@debug_rjs = false
+ cattr_accessor :debug_rjs
+
+ @@erb_variable = '_erbout'
+ cattr_accessor :erb_variable
+
+ delegate :request_forgery_protection_token, :to => :controller
+
+ @@template_handlers = HashWithIndifferentAccess.new
+
+ module CompiledTemplates #:nodoc:
+ # holds compiled template code
+ end
+ include CompiledTemplates
+
+ # Maps inline templates to their method names
+ @@method_names = {}
+ # Map method names to their compile time
+ @@compile_time = {}
+ # Map method names to the names passed in local assigns so far
+ @@template_args = {}
+ # Count the number of inline templates
+ @@inline_template_count = 0
+ # Maps template paths without extension to their file extension returned by pick_template_extension.
+ # If for a given path, path.ext1 and path.ext2 exist on the file system, the order of extensions
+ # used by pick_template_extension determines whether ext1 or ext2 will be stored.
+ @@cached_template_extension = {}
+ # Maps template paths / extensions to
+ @@cached_base_paths = {}
+
+ # Cache public asset paths
+ cattr_reader :computed_public_paths
+ @@computed_public_paths = {}
+
+ @@template_handlers = {}
+ @@default_template_handlers = nil
+
+ class ObjectWrapper < Struct.new(:value) #:nodoc:
+ end
+
+ def self.load_helpers #:nodoc:
+ Dir.entries("#{File.dirname(__FILE__)}/helpers").sort.each do |file|
+ next unless file =~ /^([a-z][a-z_]*_helper).rb$/
+ require "action_view/helpers/#{$1}"
+ helper_module_name = $1.camelize
+ if Helpers.const_defined?(helper_module_name)
+ include Helpers.const_get(helper_module_name)
+ end
+ end
+ end
+
+ # Register a class that knows how to handle template files with the given
+ # extension. This can be used to implement new template types.
+ # The constructor for the class must take the ActiveView::Base instance
+ # as a parameter, and the class must implement a #render method that
+ # takes the contents of the template to render as well as the Hash of
+ # local assigns available to the template. The #render method ought to
+ # return the rendered template as a string.
+ def self.register_template_handler(extension, klass)
+ @@template_handlers[extension.to_sym] = klass
+ end
+
+ def self.template_handler_extensions
+ @@template_handler_extensions ||= @@template_handlers.keys.map(&:to_s).sort
+ end
+
+ def self.register_default_template_handler(extension, klass)
+ register_template_handler(extension, klass)
+ @@default_template_handlers = klass
+ end
+
+ def self.handler_for_extension(extension)
+ (extension && @@template_handlers[extension.to_sym]) || @@default_template_handlers
+ end
+
+ register_default_template_handler :erb, TemplateHandlers::ERB
+ register_template_handler :rjs, TemplateHandlers::RJS
+ register_template_handler :builder, TemplateHandlers::Builder
+
+ # TODO: Depreciate old template extensions
+ register_template_handler :rhtml, TemplateHandlers::ERB
+ register_template_handler :rxml, TemplateHandlers::Builder
+
+ def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc:
+ @view_paths = view_paths.respond_to?(:find) ? view_paths.dup : [*view_paths].compact
+ @assigns = assigns_for_first_render
+ @assigns_added = nil
+ @controller = controller
+ @logger = controller && controller.logger
+ end
+
+ # Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true,
+ # it's relative to the view_paths array, otherwise it's absolute. The hash in <tt>local_assigns</tt>
+ # is made available as local variables.
+ def render_file(template_path, use_full_path = true, local_assigns = {}) #:nodoc:
+ if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) && !template_path.include?("/")
+ raise ActionViewError, <<-END_ERROR
+Due to changes in ActionMailer, you need to provide the mailer_name along with the template name.
+
+ render "user_mailer/signup"
+ render :file => "user_mailer/signup"
+
+If you are rendering a subtemplate, you must now use controller-like partial syntax:
+
+ render :partial => 'signup' # no mailer_name necessary
+ END_ERROR
+ end
+
+ @first_render ||= template_path
+ template_path_without_extension, template_extension = path_and_extension(template_path)
+ if use_full_path
+ if template_extension
+ template_file_name = full_template_path(template_path_without_extension, template_extension)
+ else
+ template_extension = pick_template_extension(template_path).to_s
+ unless template_extension
+ raise ActionViewError, "No template found for #{template_path} in #{view_paths.inspect}"
+ end
+ template_file_name = full_template_path(template_path, template_extension)
+ template_extension = template_extension.gsub(/^.+\./, '') # strip off any formats
+ end
+ else
+ template_file_name = template_path
+ end
+
+ template_source = nil # Don't read the source until we know that it is required
+
+ if template_file_name.blank?
+ raise ActionViewError, "Couldn't find template file for #{template_path} in #{view_paths.inspect}"
+ end
+
+ begin
+ render_template(template_extension, template_source, template_file_name, local_assigns)
+ rescue Exception => e
+ if TemplateError === e
+ e.sub_template_of(template_file_name)
+ raise e
+ else
+ raise TemplateError.new(find_base_path_for("#{template_path_without_extension}.#{template_extension}") || view_paths.first, template_file_name, @assigns, template_source, e)
+ end
+ end
+ end
+
+ # Renders the template present at <tt>template_path</tt> (relative to the view_paths array).
+ # The hash in <tt>local_assigns</tt> is made available as local variables.
+ def render(options = {}, old_local_assigns = {}, &block) #:nodoc:
+ if options.is_a?(String)
+ render_file(options, true, old_local_assigns)
+ elsif options == :update
+ update_page(&block)
+ elsif options.is_a?(Hash)
+ options = options.reverse_merge(:locals => {}, :use_full_path => true)
+
+ if options[:layout]
+ path, partial_name = partial_pieces(options.delete(:layout))
+
+ if block_given?
+ @content_for_layout = capture(&block)
+ concat(render(options.merge(:partial => "#{path}/#{partial_name}")), block.binding)
+ else
+ @content_for_layout = render(options)
+ render(options.merge(:partial => "#{path}/#{partial_name}"))
+ end
+ elsif options[:file]
+ render_file(options[:file], options[:use_full_path], options[:locals])
+ elsif options[:partial] && options[:collection]
+ render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals])
+ elsif options[:partial]
+ render_partial(options[:partial], ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals])
+ elsif options[:inline]
+ render_template(options[:type], options[:inline], nil, options[:locals])
+ end
+ end
+ end
+
+ # Renders the +template+ which is given as a string as either erb or builder depending on <tt>template_extension</tt>.
+ # The hash in <tt>local_assigns</tt> is made available as local variables.
+ def render_template(template_extension, template, file_path = nil, local_assigns = {}) #:nodoc:
+ handler = self.class.handler_for_extension(template_extension)
+
+ if template_handler_is_compilable?(handler)
+ compile_and_render_template(handler, template, file_path, local_assigns)
+ else
+ template ||= read_template_file(file_path, template_extension) # Make sure that a lazyily-read template is loaded.
+ delegate_render(handler, template, local_assigns)
+ end
+ end
+
+ # Gets the full template path with base path for the given template_path and extension.
+ #
+ # full_template_path('users/show', 'html.erb')
+ # # => '~/rails/app/views/users/show.html.erb
+ #
+ def full_template_path(template_path, extension)
+ if @@cache_template_extensions
+ (@@cached_base_paths[template_path] ||= {})[extension.to_s] ||= find_full_template_path(template_path, extension)
+ else
+ find_full_template_path(template_path, extension)
+ end
+ end
+
+ # Gets the extension for an existing template with the given template_path.
+ # Returns the format with the extension if that template exists.
+ #
+ # pick_template_extension('users/show')
+ # # => 'html.erb'
+ #
+ # pick_template_extension('users/legacy')
+ # # => "rhtml"
+ #
+ def pick_template_extension(template_path)#:nodoc:
+ if @@cache_template_extensions
+ (@@cached_template_extension[template_path] ||= {})[template_format] ||= find_template_extension_for(template_path)
+ else
+ find_template_extension_for(template_path)
+ end
+ end
+
+ def file_exists?(template_path)#:nodoc:
+ template_file_name, template_file_extension = path_and_extension(template_path)
+ if template_file_extension
+ template_exists?(template_file_name, template_file_extension)
+ else
+ template_exists?(template_file_name, pick_template_extension(template_path))
+ end
+ end
+
+ # Returns true is the file may be rendered implicitly.
+ def file_public?(template_path)#:nodoc:
+ template_path.split('/').last[0,1] != '_'
+ end
+
+ # symbolized version of the :format parameter of the request, or :html by default.
+ def template_format
+ return @template_format if @template_format
+ format = controller && controller.respond_to?(:request) && controller.request.parameters[:format]
+ @template_format = format.blank? ? :html : format.to_sym
+ end
+
+ # Adds a view_path to the front of the view_paths array.
+ # This change affects the current request only.
+ #
+ # @template.prepend_view_path("views/default")
+ # @template.prepend_view_path(["views/default", "views/custom"])
+ #
+ def prepend_view_path(path)
+ @view_paths.unshift(*path)
+ end
+
+ # Adds a view_path to the end of the view_paths array.
+ # This change affects the current request only.
+ #
+ # @template.append_view_path("views/default")
+ # @template.append_view_path(["views/default", "views/custom"])
+ #
+ def append_view_path(path)
+ @view_paths.push(*path)
+ end
+
+ private
+ def find_full_template_path(template_path, extension)
+ file_name = "#{template_path}.#{extension}"
+ base_path = find_base_path_for(file_name)
+ base_path.blank? ? "" : "#{base_path}/#{file_name}"
+ end
+
+ # Asserts the existence of a template.
+ def template_exists?(template_path, extension)
+ file_path = full_template_path(template_path, extension)
+ !file_path.blank? && @@method_names.has_key?(file_path) || File.exist?(file_path)
+ end
+
+ # Splits the path and extension from the given template_path and returns as an array.
+ def path_and_extension(template_path)
+ template_path_without_extension = template_path.sub(/\.(\w+)$/, '')
+ [ template_path_without_extension, $1 ]
+ end
+
+ # Returns the view path that contains the given relative template path.
+ def find_base_path_for(template_file_name)
+ view_paths.find { |p| File.file?(File.join(p, template_file_name)) }
+ end
+
+ # Returns the view path that the full path resides in.
+ def extract_base_path_from(full_path)
+ view_paths.find { |p| full_path[0..p.size - 1] == p }
+ end
+
+ # Determines the template's file extension, such as rhtml, rxml, or rjs.
+ def find_template_extension_for(template_path)
+ find_template_extension_from_handler(template_path, true) ||
+ find_template_extension_from_handler(template_path) ||
+ find_template_extension_from_first_render()
+ end
+
+ def find_template_extension_from_handler(template_path, formatted = nil)
+ checked_template_path = formatted ? "#{template_path}.#{template_format}" : template_path
+
+ self.class.template_handler_extensions.each do |extension|
+ if template_exists?(checked_template_path, extension)
+ return formatted ? "#{template_format}.#{extension}" : extension.to_s
+ end
+ end
+ nil
+ end
+
+ # Determine the template extension from the <tt>@first_render</tt> filename
+ def find_template_extension_from_first_render
+ File.basename(@first_render.to_s)[/^[^.]+\.(.+)$/, 1]
+ end
+
+ # This method reads a template file.
+ def read_template_file(template_path, extension)
+ File.read(template_path)
+ end
+
+ # Evaluate the local assigns and pushes them to the view.
+ def evaluate_assigns
+ unless @assigns_added
+ assign_variables_from_controller
+ @assigns_added = true
+ end
+ end
+
+ def delegate_render(handler, template, local_assigns)
+ handler.new(self).render(template, local_assigns)
+ end
+
+ def delegate_compile(handler, template)
+ handler.new(self).compile(template)
+ end
+
+ def template_handler_is_compilable?(handler)
+ handler.new(self).respond_to?(:compile)
+ end
+
+ # Assigns instance variables from the controller to the view.
+ def assign_variables_from_controller
+ @assigns.each { |key, value| instance_variable_set("@#{key}", value) }
+ end
+
+
+ # Return true if the given template was compiled for a superset of the keys in local_assigns
+ def supports_local_assigns?(render_symbol, local_assigns)
+ local_assigns.empty? ||
+ ((args = @@template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) })
+ end
+
+ # Method to check whether template compilation is necessary.
+ # The template will be compiled if the inline template or file has not been compiled yet,
+ # if local_assigns has a new key, which isn't supported by the compiled code yet,
+ # or if the file has changed on disk and checking file mods hasn't been disabled.
+ def compile_template?(template, file_name, local_assigns)
+ method_key = file_name || template
+ render_symbol = @@method_names[method_key]
+
+ compile_time = @@compile_time[render_symbol]
+ if compile_time && supports_local_assigns?(render_symbol, local_assigns)
+ if file_name && !@@cache_template_loading
+ template_changed_since?(file_name, compile_time)
+ end
+ else
+ true
+ end
+ end
+
+ # Method to handle checking a whether a template has changed since last compile; isolated so that templates
+ # not stored on the file system can hook and extend appropriately.
+ def template_changed_since?(file_name, compile_time)
+ lstat = File.lstat(file_name)
+ compile_time < lstat.mtime ||
+ (lstat.symlink? && compile_time < File.stat(file_name).mtime)
+ end
+
+ # Method to create the source code for a given template.
+ def create_template_source(handler, template, render_symbol, locals)
+ body = delegate_compile(handler, template)
+
+ @@template_args[render_symbol] ||= {}
+ locals_keys = @@template_args[render_symbol].keys | locals
+ @@template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h }
+
+ locals_code = ""
+ locals_keys.each do |key|
+ locals_code << "#{key} = local_assigns[:#{key}]\n"
+ end
+
+ "def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend"
+ end
+
+ def assign_method_name(handler, template, file_name)
+ method_key = file_name || template
+ @@method_names[method_key] ||= compiled_method_name(handler, template, file_name)
+ end
+
+ def compiled_method_name(handler, template, file_name)
+ ['_run', handler.to_s.demodulize.underscore, compiled_method_name_file_path_segment(file_name)].compact.join('_').to_sym
+ end
+
+ def compiled_method_name_file_path_segment(file_name)
+ if file_name
+ s = File.expand_path(file_name)
+ s.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT)
+ s.gsub!(/([^a-zA-Z0-9_])/) { $1.ord }
+ s
+ else
+ (@@inline_template_count += 1).to_s
+ end
+ end
+
+ # Compile and evaluate the template's code
+ def compile_template(handler, template, file_name, local_assigns)
+ render_symbol = assign_method_name(handler, template, file_name)
+ render_source = create_template_source(handler, template, render_symbol, local_assigns.keys)
+ line_offset = @@template_args[render_symbol].size + handler.line_offset
+
+ begin
+ file_name = 'compiled-template' if file_name.blank?
+ CompiledTemplates.module_eval(render_source, file_name, -line_offset)
+ rescue Exception => e # errors from template code
+ if logger
+ logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
+ logger.debug "Function body: #{render_source}"
+ logger.debug "Backtrace: #{e.backtrace.join("\n")}"
+ end
+
+ raise TemplateError.new(extract_base_path_from(file_name) || view_paths.first, file_name || template, @assigns, template, e)
+ end
+
+ @@compile_time[render_symbol] = Time.now
+ # logger.debug "Compiled template #{file_name || template}\n ==> #{render_symbol}" if logger
+ end
+
+ # Render the provided template with the given local assigns. If the template has not been rendered with the provided
+ # local assigns yet, or if the template has been updated on disk, then the template will be compiled to a method.
+ #
+ # Either, but not both, of template and file_path may be nil. If file_path is given, the template
+ # will only be read if it has to be compiled.
+ #
+ def compile_and_render_template(handler, template = nil, file_path = nil, local_assigns = {}) #:nodoc:
+ # convert string keys to symbols if requested
+ local_assigns = local_assigns.symbolize_keys if @@local_assigns_support_string_keys
+
+ # compile the given template, if necessary
+ if compile_template?(template, file_path, local_assigns)
+ template ||= read_template_file(file_path, nil)
+ compile_template(handler, template, file_path, local_assigns)
+ end
+
+ # Get the method name for this template and run it
+ method_name = @@method_names[file_path || template]
+ evaluate_assigns
+
+ send(method_name, local_assigns) do |*name|
+ instance_variable_get "@content_for_#{name.first || 'layout'}"
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/compiled_templates.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/compiled_templates.rb
new file mode 100644
index 000000000..5a286432e
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/compiled_templates.rb
@@ -0,0 +1,69 @@
+module ActionView
+
+ # CompiledTemplates modules hold methods that have been compiled.
+ # Templates are compiled into these methods so that they do not need to be
+ # read and parsed for each request.
+ #
+ # Each template may be compiled into one or more methods. Each method accepts a given
+ # set of parameters which is used to implement local assigns passing.
+ #
+ # To use a compiled template module, create a new instance and include it into the class
+ # in which you want the template to be rendered.
+ class CompiledTemplates < Module
+ attr_reader :method_names
+
+ def initialize
+ @method_names = Hash.new do |hash, key|
+ hash[key] = "__compiled_method_#{(hash.length + 1)}"
+ end
+ @mtimes = {}
+ end
+
+ # Return the full key for the given identifier and argument names
+ def full_key(identifier, arg_names)
+ [identifier, arg_names]
+ end
+
+ # Return the selector for this method or nil if it has not been compiled
+ def selector(identifier, arg_names)
+ key = full_key(identifier, arg_names)
+ method_names.key?(key) ? method_names[key] : nil
+ end
+ alias :compiled? :selector
+
+ # Return the time at which the method for the given identifier and argument names was compiled.
+ def mtime(identifier, arg_names)
+ @mtimes[full_key(identifier, arg_names)]
+ end
+
+ # Compile the provided source code for the given argument names and with the given initial line number.
+ # The identifier should be unique to this source.
+ #
+ # The file_name, if provided will appear in backtraces. If not provided, the file_name defaults
+ # to the identifier.
+ #
+ # This method will return the selector for the compiled version of this method.
+ def compile_source(identifier, arg_names, source, initial_line_number = 0, file_name = nil)
+ file_name ||= identifier
+ name = method_names[full_key(identifier, arg_names)]
+ arg_desc = arg_names.empty? ? '' : "(#{arg_names * ', '})"
+ fake_file_name = "#{file_name}#{arg_desc}" # Include the arguments for this version (for now)
+
+ method_def = wrap_source(name, arg_names, source)
+
+ begin
+ module_eval(method_def, fake_file_name, initial_line_number)
+ @mtimes[full_key(identifier, arg_names)] = Time.now
+ rescue Exception => e # errors from compiled source
+ e.blame_file! identifier
+ raise
+ end
+ name
+ end
+
+ # Wrap the provided source in a def ... end block.
+ def wrap_source(name, arg_names, source)
+ "def #{name}(#{arg_names * ', '})\n#{source}\nend"
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/active_record_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/active_record_helper.rb
new file mode 100644
index 000000000..9b3292a9b
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/active_record_helper.rb
@@ -0,0 +1,255 @@
+require 'cgi'
+require 'action_view/helpers/form_helper'
+
+module ActionView
+ class Base
+ @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>" }
+ cattr_accessor :field_error_proc
+ end
+
+ module Helpers
+ # The Active Record Helper makes it easier to create forms for records kept in instance variables. The most far-reaching is the form
+ # method that creates a complete form for all the basic content types of the record (not associations or aggregations, though). This
+ # is a great way of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form.
+ # In that case, it's better to use the input method and the specialized form methods in link:classes/ActionView/Helpers/FormHelper.html
+ module ActiveRecordHelper
+ # Returns a default input tag for the type of object returned by the method. For example, let's say you have a model
+ # that has an attribute +title+ of type VARCHAR column, and this instance holds "Hello World":
+ # input("post", "title") =>
+ # <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
+ def input(record_name, method, options = {})
+ InstanceTag.new(record_name, method, self).to_tag(options)
+ end
+
+ # Returns an entire form with all needed input tags for a specified Active Record object. For example, let's say you
+ # have a table model <tt>Post</tt> with attributes named <tt>title</tt> of type <tt>VARCHAR</tt> and <tt>body</tt> of type <tt>TEXT</tt>:
+ # form("post")
+ # That line would yield a form like the following:
+ # <form action='/post/create' method='post'>
+ # <p>
+ # <label for="post_title">Title</label><br />
+ # <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
+ # </p>
+ # <p>
+ # <label for="post_body">Body</label><br />
+ # <textarea cols="40" id="post_body" name="post[body]" rows="20">
+ # </textarea>
+ # </p>
+ # <input type='submit' value='Create' />
+ # </form>
+ #
+ # It's possible to specialize the form builder by using a different action name and by supplying another
+ # block renderer. For example, let's say you have a model <tt>Entry</tt> with an attribute <tt>message</tt> of type <tt>VARCHAR</tt>:
+ #
+ # form("entry", :action => "sign", :input_block =>
+ # Proc.new { |record, column| "#{column.human_name}: #{input(record, column.name)}<br />" }) =>
+ #
+ # <form action='/post/sign' method='post'>
+ # Message:
+ # <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /><br />
+ # <input type='submit' value='Sign' />
+ # </form>
+ #
+ # It's also possible to add additional content to the form by giving it a block, such as:
+ #
+ # form("entry", :action => "sign") do |form|
+ # form << content_tag("b", "Department")
+ # form << collection_select("department", "id", @departments, "id", "name")
+ # end
+ def form(record_name, options = {})
+ record = instance_variable_get("@#{record_name}")
+
+ options = options.symbolize_keys
+ options[:action] ||= record.new_record? ? "create" : "update"
+ action = url_for(:action => options[:action], :id => record)
+
+ submit_value = options[:submit_value] || options[:action].gsub(/[^\w]/, '').capitalize
+
+ contents = ''
+ contents << hidden_field(record_name, :id) unless record.new_record?
+ contents << all_input_tags(record, record_name, options)
+ yield contents if block_given?
+ contents << submit_tag(submit_value)
+
+ content_tag('form', contents, :action => action, :method => 'post', :enctype => options[:multipart] ? 'multipart/form-data': nil)
+ end
+
+ # Returns a string containing the error message attached to the +method+ on the +object+ if one exists.
+ # This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a +prepend_text+ and/or +append_text+
+ # (to properly explain the error), and a +css_class+ to style it accordingly. +object+ should either be the name of an instance variable or
+ # the actual object. As an example, let's say you have a model
+ # +post+ that has an error message on the +title+ attribute:
+ #
+ # <%= error_message_on "post", "title" %> =>
+ # <div class="formError">can't be empty</div>
+ #
+ # <%= error_message_on @post, "title" %> =>
+ # <div class="formError">can't be empty</div>
+ #
+ # <%= error_message_on "post", "title", "Title simply ", " (or it won't work).", "inputError" %> =>
+ # <div class="inputError">Title simply can't be empty (or it won't work).</div>
+ def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError")
+ if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
+ (errors = obj.errors.on(method))
+ content_tag("div", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", :class => css_class)
+ else
+ ''
+ end
+ end
+
+ # Returns a string with a <tt>DIV</tt> containing all of the error messages for the objects located as instance variables by the names
+ # given. If more than one object is specified, the errors for the objects are displayed in the order that the object names are
+ # provided.
+ #
+ # This <tt>DIV</tt> can be tailored by the following options:
+ #
+ # * <tt>header_tag</tt> - Used for the header of the error div (default: h2)
+ # * <tt>id</tt> - The id of the error div (default: errorExplanation)
+ # * <tt>class</tt> - The class of the error div (default: errorExplanation)
+ # * <tt>object</tt> - The object (or array of objects) for which to display errors, if you need to escape the instance variable convention
+ # * <tt>object_name</tt> - The object name to use in the header, or any text that you prefer. If <tt>object_name</tt> is not set, the name of the first object will be used.
+ # * <tt>header_message</tt> - The message in the header of the error div. Pass +nil+ or an empty string to avoid the header message altogether. (default: X errors prohibited this object from being saved)
+ # * <tt>message</tt> - The explanation message after the header message and before the error list. Pass +nil+ or an empty string to avoid the explanation message altogether. (default: There were problems with the following fields:)
+ #
+ # To specify the display for one object, you simply provide its name as a parameter. For example, for the +User+ model:
+ #
+ # error_messages_for 'user'
+ #
+ # To specify more than one object, you simply list them; optionally, you can add an extra +object_name+ parameter, which
+ # will be the name used in the header message.
+ #
+ # error_messages_for 'user_common', 'user', :object_name => 'user'
+ #
+ # If the objects cannot be located as instance variables, you can add an extra +object+ paremeter which gives the actual
+ # object (or array of objects to use)
+ #
+ # error_messages_for 'user', :object => @question.user
+ #
+ # NOTE: This is a pre-packaged presentation of the errors with embedded strings and a certain HTML structure. If what
+ # you need is significantly different from the default presentation, it makes plenty of sense to access the object.errors
+ # instance yourself and set it up. View the source of this method to see how easy it is.
+ def error_messages_for(*params)
+ options = params.extract_options!.symbolize_keys
+ if object = options.delete(:object)
+ objects = [object].flatten
+ else
+ objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
+ end
+ count = objects.inject(0) {|sum, object| sum + object.errors.count }
+ unless count.zero?
+ html = {}
+ [:id, :class].each do |key|
+ if options.include?(key)
+ value = options[key]
+ html[key] = value unless value.blank?
+ else
+ html[key] = 'errorExplanation'
+ end
+ end
+ options[:object_name] ||= params.first
+ options[:header_message] = "#{pluralize(count, 'error')} prohibited this #{options[:object_name].to_s.gsub('_', ' ')} from being saved" unless options.include?(:header_message)
+ options[:message] ||= 'There were problems with the following fields:' unless options.include?(:message)
+ error_messages = objects.map {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }
+
+ contents = ''
+ contents << content_tag(options[:header_tag] || :h2, options[:header_message]) unless options[:header_message].blank?
+ contents << content_tag(:p, options[:message]) unless options[:message].blank?
+ contents << content_tag(:ul, error_messages)
+
+ content_tag(:div, contents, html)
+ else
+ ''
+ end
+ end
+
+ private
+ def all_input_tags(record, record_name, options)
+ input_block = options[:input_block] || default_input_block
+ record.class.content_columns.collect{ |column| input_block.call(record_name, column) }.join("\n")
+ end
+
+ def default_input_block
+ Proc.new { |record, column| %(<p><label for="#{record}_#{column.name}">#{column.human_name}</label><br />#{input(record, column.name)}</p>) }
+ end
+ end
+
+ class InstanceTag #:nodoc:
+ def to_tag(options = {})
+ case column_type
+ when :string
+ field_type = @method_name.include?("password") ? "password" : "text"
+ to_input_field_tag(field_type, options)
+ when :text
+ to_text_area_tag(options)
+ when :integer, :float, :decimal
+ to_input_field_tag("text", options)
+ when :date
+ to_date_select_tag(options)
+ when :datetime, :timestamp
+ to_datetime_select_tag(options)
+ when :time
+ to_time_select_tag(options)
+ when :boolean
+ to_boolean_select_tag(options)
+ end
+ end
+
+ alias_method :tag_without_error_wrapping, :tag
+ def tag(name, options)
+ if object.respond_to?("errors") && object.errors.respond_to?("on")
+ error_wrapping(tag_without_error_wrapping(name, options), object.errors.on(@method_name))
+ else
+ tag_without_error_wrapping(name, options)
+ end
+ end
+
+ alias_method :content_tag_without_error_wrapping, :content_tag
+ def content_tag(name, value, options)
+ if object.respond_to?("errors") && object.errors.respond_to?("on")
+ error_wrapping(content_tag_without_error_wrapping(name, value, options), object.errors.on(@method_name))
+ else
+ content_tag_without_error_wrapping(name, value, options)
+ end
+ end
+
+ alias_method :to_date_select_tag_without_error_wrapping, :to_date_select_tag
+ def to_date_select_tag(options = {})
+ if object.respond_to?("errors") && object.errors.respond_to?("on")
+ error_wrapping(to_date_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
+ else
+ to_date_select_tag_without_error_wrapping(options)
+ end
+ end
+
+ alias_method :to_datetime_select_tag_without_error_wrapping, :to_datetime_select_tag
+ def to_datetime_select_tag(options = {})
+ if object.respond_to?("errors") && object.errors.respond_to?("on")
+ error_wrapping(to_datetime_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
+ else
+ to_datetime_select_tag_without_error_wrapping(options)
+ end
+ end
+
+ alias_method :to_time_select_tag_without_error_wrapping, :to_time_select_tag
+ def to_time_select_tag(options = {})
+ if object.respond_to?("errors") && object.errors.respond_to?("on")
+ error_wrapping(to_time_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
+ else
+ to_time_select_tag_without_error_wrapping(options)
+ end
+ end
+
+ def error_wrapping(html_tag, has_error)
+ has_error ? Base.field_error_proc.call(html_tag, self) : html_tag
+ end
+
+ def error_message
+ object.errors.on(@method_name)
+ end
+
+ def column_type
+ object.send("column_for_attribute", @method_name).type
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/asset_tag_helper.rb
new file mode 100644
index 000000000..ba474a8ff
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -0,0 +1,548 @@
+require 'cgi'
+require 'action_view/helpers/url_helper'
+require 'action_view/helpers/tag_helper'
+
+module ActionView
+ module Helpers #:nodoc:
+ # This module provides methods for generating HTML that links views to assets such
+ # as images, javascripts, stylesheets, and feeds. These methods do not verify
+ # the assets exist before linking to them.
+ #
+ # === Using asset hosts
+ # By default, Rails links to these assets on the current host in the public
+ # folder, but you can direct Rails to link to assets from a dedicated assets server by
+ # setting ActionController::Base.asset_host in your environment.rb. For example,
+ # let's say your asset host is assets.example.com.
+ #
+ # ActionController::Base.asset_host = "assets.example.com"
+ # image_tag("rails.png")
+ # => <img src="http://assets.example.com/images/rails.png" alt="Rails" />
+ # stylesheet_include_tag("application")
+ # => <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # This is useful since browsers typically open at most two connections to a single host,
+ # which means your assets often wait in single file for their turn to load. You can
+ # alleviate this by using a %d wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
+ # to automatically distribute asset requests among four hosts (e.g., assets0.example.com through assets3.example.com)
+ # so browsers will open eight connections rather than two.
+ #
+ # image_tag("rails.png")
+ # => <img src="http://assets0.example.com/images/rails.png" alt="Rails" />
+ # stylesheet_include_tag("application")
+ # => <link href="http://assets3.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME
+ # the wildcard to a single asset host. You can read more about setting up your DNS CNAME records from
+ # your ISP.
+ #
+ # Note: This is purely a browser performance optimization and is not meant
+ # for server load balancing. See http://www.die.net/musings/page_load_time/
+ # for background.
+ #
+ # Alternatively, you can exert more control over the asset host by setting <tt>asset_host</tt> to a proc
+ # that takes a single source argument. This is useful if you are unable to setup 4 actual hosts or have
+ # fewer/more than 4 hosts. The example proc below generates http://assets1.example.com and
+ # http://assets2.example.com randomly.
+ #
+ # ActionController::Base.asset_host = Proc.new { |source| "http://assets#{rand(2) + 1}.example.com" }
+ # image_tag("rails.png")
+ # => <img src="http://assets2.example.com/images/rails.png" alt="Rails" />
+ # stylesheet_include_tag("application")
+ # => <link href="http://assets1.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # The proc takes a single <tt>source</tt> parameter which is the path of the source asset. This can be used to
+ # generate a particular asset host depending on the asset path.
+ #
+ # ActionController::Base.asset_host = Proc.new { |source|
+ # if source.starts_with?('/images')
+ # "http://images.example.com"
+ # else
+ # "http://assets.example.com"
+ # end
+ # }
+ # image_tag("rails.png")
+ # => <img src="http://images.example.com/images/rails.png" alt="Rails" />
+ # stylesheet_include_tag("application")
+ # => <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # === Using asset timestamps
+ #
+ # By default, Rails will append all asset paths with that asset's timestamp. This allows you to set a cache-expiration date for the
+ # asset far into the future, but still be able to instantly invalidate it by simply updating the file (and hence updating the timestamp,
+ # which then updates the URL as the timestamp is part of that, which in turn busts the cache).
+ #
+ # It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take
+ # advantage of this feature. Here's an example for Apache:
+ #
+ # # Asset Expiration
+ # ExpiresActive On
+ # <FilesMatch "\.(ico|gif|jpe?g|png|js|css)$">
+ # ExpiresDefault "access plus 1 year"
+ # </FilesMatch>
+ #
+ # Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must
+ # have their clocks synchronized. If one of them drift out of sync, you'll see different timestamps at random and the cache won't
+ # work. Which means that the browser will request the same assets over and over again even thought they didn't change. You can use
+ # something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being
+ # requested over and over).
+ module AssetTagHelper
+ ASSETS_DIR = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : "public"
+ JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts"
+ STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets"
+
+ # Returns a link tag that browsers and news readers can use to auto-detect
+ # an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
+ # <tt>:atom</tt>. Control the link options in url_for format using the
+ # +url_options+. You can modify the LINK tag itself in +tag_options+.
+ #
+ # ==== Options:
+ # * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
+ # * <tt>:type</tt> - Override the auto-generated mime type
+ # * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
+ #
+ # ==== Examples
+ # auto_discovery_link_tag # =>
+ # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
+ # auto_discovery_link_tag(:atom) # =>
+ # <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
+ # auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
+ # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
+ # auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
+ # <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" />
+ # auto_discovery_link_tag(:rss, {:controller => "news", :action => "feed"}) # =>
+ # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
+ # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "Example RSS"}) # =>
+ # <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" />
+ def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
+ tag(
+ "link",
+ "rel" => tag_options[:rel] || "alternate",
+ "type" => tag_options[:type] || Mime::Type.lookup_by_extension(type.to_s).to_s,
+ "title" => tag_options[:title] || type.to_s.upcase,
+ "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
+ )
+ end
+
+ # Computes the path to a javascript asset in the public javascripts directory.
+ # If the +source+ filename has no extension, .js will be appended.
+ # Full paths from the document root will be passed through.
+ # Used internally by javascript_include_tag to build the script path.
+ #
+ # ==== Examples
+ # javascript_path "xmlhr" # => /javascripts/xmlhr.js
+ # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
+ # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
+ # javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr.js
+ # javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js
+ def javascript_path(source)
+ compute_public_path(source, 'javascripts', 'js')
+ end
+ alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
+
+ JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
+ @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
+
+ # Returns an html script tag for each of the +sources+ provided. You
+ # can pass in the filename (.js extension is optional) of javascript files
+ # that exist in your public/javascripts directory for inclusion into the
+ # current page or you can pass the full path relative to your document
+ # root. To include the Prototype and Scriptaculous javascript libraries in
+ # your application, pass <tt>:defaults</tt> as the source. When using
+ # :defaults, if an <tt>application.js</tt> file exists in your public
+ # javascripts directory, it will be included as well. You can modify the
+ # html attributes of the script tag by passing a hash as the last argument.
+ #
+ # ==== Examples
+ # javascript_include_tag "xmlhr" # =>
+ # <script type="text/javascript" src="/javascripts/xmlhr.js"></script>
+ #
+ # javascript_include_tag "xmlhr.js" # =>
+ # <script type="text/javascript" src="/javascripts/xmlhr.js"></script>
+ #
+ # javascript_include_tag "common.javascript", "/elsewhere/cools" # =>
+ # <script type="text/javascript" src="/javascripts/common.javascript"></script>
+ # <script type="text/javascript" src="/elsewhere/cools.js"></script>
+ #
+ # javascript_include_tag "http://www.railsapplication.com/xmlhr" # =>
+ # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js"></script>
+ #
+ # javascript_include_tag "http://www.railsapplication.com/xmlhr.js" # =>
+ # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js"></script>
+ #
+ # javascript_include_tag :defaults # =>
+ # <script type="text/javascript" src="/javascripts/prototype.js"></script>
+ # <script type="text/javascript" src="/javascripts/effects.js"></script>
+ # ...
+ # <script type="text/javascript" src="/javascripts/application.js"></script>
+ #
+ # * = The application.js file is only referenced if it exists
+ #
+ # Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason
+ # (e.g., you're going to be using a certain .js file in every action), then take a look at the register_javascript_include_default method.
+ #
+ # You can also include all javascripts in the javascripts directory using <tt>:all</tt> as the source:
+ #
+ # javascript_include_tag :all # =>
+ # <script type="text/javascript" src="/javascripts/prototype.js"></script>
+ # <script type="text/javascript" src="/javascripts/effects.js"></script>
+ # ...
+ # <script type="text/javascript" src="/javascripts/application.js"></script>
+ # <script type="text/javascript" src="/javascripts/shop.js"></script>
+ # <script type="text/javascript" src="/javascripts/checkout.js"></script>
+ #
+ # Note that the default javascript files will be included first. So Prototype and Scriptaculous are available to
+ # all subsequently included files.
+ #
+ # == Caching multiple javascripts into one
+ #
+ # You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
+ # compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
+ # is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development
+ # environment).
+ #
+ # ==== Examples
+ # javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
+ # <script type="text/javascript" src="/javascripts/prototype.js"></script>
+ # <script type="text/javascript" src="/javascripts/effects.js"></script>
+ # ...
+ # <script type="text/javascript" src="/javascripts/application.js"></script>
+ # <script type="text/javascript" src="/javascripts/shop.js"></script>
+ # <script type="text/javascript" src="/javascripts/checkout.js"></script>
+ #
+ # javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is true =>
+ # <script type="text/javascript" src="/javascripts/all.js"></script>
+ #
+ # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is false =>
+ # <script type="text/javascript" src="/javascripts/prototype.js"></script>
+ # <script type="text/javascript" src="/javascripts/cart.js"></script>
+ # <script type="text/javascript" src="/javascripts/checkout.js"></script>
+ #
+ # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is false =>
+ # <script type="text/javascript" src="/javascripts/shop.js"></script>
+ def javascript_include_tag(*sources)
+ options = sources.extract_options!.stringify_keys
+ cache = options.delete("cache")
+
+ if ActionController::Base.perform_caching && cache
+ joined_javascript_name = (cache == true ? "all" : cache) + ".js"
+ joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name)
+
+ write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources))
+ javascript_src_tag(joined_javascript_name, options)
+ else
+ expand_javascript_sources(sources).collect { |source| javascript_src_tag(source, options) }.join("\n")
+ end
+ end
+
+ # Register one or more additional JavaScript files to be included when
+ # <tt>javascript_include_tag :defaults</tt> is called. This method is
+ # typically intended to be called from plugin initialization to register additional
+ # .js files that the plugin installed in <tt>public/javascripts</tt>.
+ def self.register_javascript_include_default(*sources)
+ @@javascript_default_sources.concat(sources)
+ end
+
+ def self.reset_javascript_include_default #:nodoc:
+ @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
+ end
+
+ # Computes the path to a stylesheet asset in the public stylesheets directory.
+ # If the +source+ filename has no extension, .css will be appended.
+ # Full paths from the document root will be passed through.
+ # Used internally by stylesheet_link_tag to build the stylesheet path.
+ #
+ # ==== Examples
+ # stylesheet_path "style" # => /stylesheets/style.css
+ # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
+ # stylesheet_path "/dir/style.css" # => /dir/style.css
+ # stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style.css
+ # stylesheet_path "http://www.railsapplication.com/css/style.js" # => http://www.railsapplication.com/css/style.css
+ def stylesheet_path(source)
+ compute_public_path(source, 'stylesheets', 'css')
+ end
+ alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
+
+ # Returns a stylesheet link tag for the sources specified as arguments. If
+ # you don't specify an extension, .css will be appended automatically.
+ # You can modify the link attributes by passing a hash as the last argument.
+ #
+ # ==== Examples
+ # stylesheet_link_tag "style" # =>
+ # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # stylesheet_link_tag "style.css" # =>
+ # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # stylesheet_link_tag "http://www.railsapplication.com/style.css" # =>
+ # <link href="http://www.railsapplication.com/style.css" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # stylesheet_link_tag "style", :media => "all" # =>
+ # <link href="/stylesheets/style.css" media="all" rel="stylesheet" type="text/css" />
+ #
+ # stylesheet_link_tag "style", :media => "print" # =>
+ # <link href="/stylesheets/style.css" media="print" rel="stylesheet" type="text/css" />
+ #
+ # stylesheet_link_tag "random.styles", "/css/stylish" # =>
+ # <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # You can also include all styles in the stylesheet directory using :all as the source:
+ #
+ # stylesheet_link_tag :all # =>
+ # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # == Caching multiple stylesheets into one
+ #
+ # You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
+ # compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
+ # is set to true (which is the case by default for the Rails production environment, but not for the development
+ # environment). Examples:
+ #
+ # ==== Examples
+ # stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
+ # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is true =>
+ # <link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is false =>
+ # <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is true =>
+ # <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" />
+ def stylesheet_link_tag(*sources)
+ options = sources.extract_options!.stringify_keys
+ cache = options.delete("cache")
+
+ if ActionController::Base.perform_caching && cache
+ joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
+ joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name)
+
+ write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources))
+ stylesheet_tag(joined_stylesheet_name, options)
+ else
+ expand_stylesheet_sources(sources).collect { |source| stylesheet_tag(source, options) }.join("\n")
+ end
+ end
+
+ # Computes the path to an image asset in the public images directory.
+ # Full paths from the document root will be passed through.
+ # Used internally by image_tag to build the image path.
+ #
+ # ==== Examples
+ # image_path("edit") # => /images/edit
+ # image_path("edit.png") # => /images/edit.png
+ # image_path("icons/edit.png") # => /images/icons/edit.png
+ # image_path("/icons/edit.png") # => /icons/edit.png
+ # image_path("http://www.railsapplication.com/img/edit.png") # => http://www.railsapplication.com/img/edit.png
+ def image_path(source)
+ compute_public_path(source, 'images')
+ end
+ alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
+
+ # Returns an html image tag for the +source+. The +source+ can be a full
+ # path or a file that exists in your public images directory.
+ #
+ # ==== Options
+ # You can add HTML attributes using the +options+. The +options+ supports
+ # three additional keys for convenience and conformance:
+ #
+ # * <tt>:alt</tt> - If no alt text is given, the file name part of the
+ # +source+ is used (capitalized and without the extension)
+ # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
+ # width="30" and height="45". <tt>:size</tt> will be ignored if the
+ # value is not in the correct format.
+ # * <tt>:mouseover</tt> - Set an alternate image to be used when the onmouseover
+ # event is fired, and sets the original image to be replaced onmouseout.
+ # This can be used to implement an easy image toggle that fires on onmouseover.
+ #
+ # ==== Examples
+ # image_tag("icon") # =>
+ # <img src="/images/icon" alt="Icon" />
+ # image_tag("icon.png") # =>
+ # <img src="/images/icon.png" alt="Icon" />
+ # image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") # =>
+ # <img src="/images/icon.png" width="16" height="10" alt="Edit Entry" />
+ # image_tag("/icons/icon.gif", :size => "16x16") # =>
+ # <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
+ # image_tag("/icons/icon.gif", :height => '32', :width => '32') # =>
+ # <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
+ # image_tag("/icons/icon.gif", :class => "menu_icon") # =>
+ # <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
+ # image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
+ # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
+ # image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
+ # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
+ def image_tag(source, options = {})
+ options.symbolize_keys!
+
+ options[:src] = path_to_image(source)
+ options[:alt] ||= File.basename(options[:src], '.*').split('.').first.capitalize
+
+ if size = options.delete(:size)
+ options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
+ end
+
+ if mouseover = options.delete(:mouseover)
+ options[:onmouseover] = "this.src='#{image_path(mouseover)}'"
+ options[:onmouseout] = "this.src='#{image_path(options[:src])}'"
+ end
+
+ tag("img", options)
+ end
+
+ private
+ def file_exist?(path)
+ @@file_exist_cache ||= {}
+ if !(@@file_exist_cache[path] ||= File.exist?(path))
+ @@file_exist_cache[path] = true
+ false
+ else
+ true
+ end
+ end
+
+ # Add the .ext if not present. Return full URLs otherwise untouched.
+ # Prefix with /dir/ if lacking a leading /. Account for relative URL
+ # roots. Rewrite the asset path for cache-busting asset ids. Include
+ # asset host, if configured, with the correct request protocol.
+ def compute_public_path(source, dir, ext = nil, include_host = true)
+ has_request = @controller.respond_to?(:request)
+
+ cache_key =
+ if has_request
+ [ @controller.request.protocol,
+ ActionController::Base.asset_host.to_s,
+ @controller.request.relative_url_root,
+ dir, source, ext, include_host ].join
+ else
+ [ ActionController::Base.asset_host.to_s,
+ dir, source, ext, include_host ].join
+ end
+
+ ActionView::Base.computed_public_paths[cache_key] ||=
+ begin
+ source += ".#{ext}" if File.extname(source).blank? && ext
+
+ if source =~ %r{^[-a-z]+://}
+ source
+ else
+ source = "/#{dir}/#{source}" unless source[0] == ?/
+ if has_request
+ source = "#{@controller.request.relative_url_root}#{source}"
+ end
+ rewrite_asset_path!(source)
+
+ if include_host
+ host = compute_asset_host(source)
+
+ if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
+ host = "#{@controller.request.protocol}#{host}"
+ end
+
+ "#{host}#{source}"
+ else
+ source
+ end
+ end
+ end
+ end
+
+ # Pick an asset host for this source. Returns nil if no host is set,
+ # the host if no wildcard is set, the host interpolated with the
+ # numbers 0-3 if it contains %d (the number is the source hash mod 4),
+ # or the value returned from invoking the proc if it's a proc.
+ def compute_asset_host(source)
+ if host = ActionController::Base.asset_host
+ if host.is_a?(Proc)
+ host.call(source)
+ else
+ host % (source.hash % 4)
+ end
+ end
+ end
+
+ # Use the RAILS_ASSET_ID environment variable or the source's
+ # modification time as its cache-busting asset id.
+ def rails_asset_id(source)
+ if asset_id = ENV["RAILS_ASSET_ID"]
+ asset_id
+ else
+ path = File.join(ASSETS_DIR, source)
+
+ if File.exist?(path)
+ File.mtime(path).to_i.to_s
+ else
+ ''
+ end
+ end
+ end
+
+ # Break out the asset path rewrite so you wish to put the asset id
+ # someplace other than the query string.
+ def rewrite_asset_path!(source)
+ asset_id = rails_asset_id(source)
+ source << "?#{asset_id}" if !asset_id.blank?
+ end
+
+ def javascript_src_tag(source, options)
+ content_tag("script", "", { "type" => Mime::JS, "src" => path_to_javascript(source) }.merge(options))
+ end
+
+ def stylesheet_tag(source, options)
+ tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
+ end
+
+ def compute_javascript_paths(sources)
+ expand_javascript_sources(sources).collect { |source| compute_public_path(source, 'javascripts', 'js', false) }
+ end
+
+ def compute_stylesheet_paths(sources)
+ expand_stylesheet_sources(sources).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) }
+ end
+
+ def expand_javascript_sources(sources)
+ case
+ when sources.include?(:all)
+ all_javascript_files = Dir[File.join(JAVASCRIPTS_DIR, '*.js')].collect { |file| File.basename(file).split(".", 0).first }.sort
+ sources = ((@@javascript_default_sources.dup & all_javascript_files) + all_javascript_files).uniq
+
+ when sources.include?(:defaults)
+ sources = sources[0..(sources.index(:defaults))] +
+ @@javascript_default_sources.dup +
+ sources[(sources.index(:defaults) + 1)..sources.length]
+
+ sources.delete(:defaults)
+ sources << "application" if file_exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
+ end
+
+ sources
+ end
+
+ def expand_stylesheet_sources(sources)
+ if sources.first == :all
+ @@all_stylesheet_sources ||= Dir[File.join(STYLESHEETS_DIR, '*.css')].collect { |file| File.basename(file).split(".", 1).first }.sort
+ else
+ sources
+ end
+ end
+
+ def join_asset_file_contents(paths)
+ paths.collect { |path| File.read(File.join(ASSETS_DIR, path.split("?").first)) }.join("\n\n")
+ end
+
+ def write_asset_file_contents(joined_asset_path, asset_paths)
+ unless file_exist?(joined_asset_path)
+ FileUtils.mkdir_p(File.dirname(joined_asset_path))
+ File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/atom_feed_helper.rb
new file mode 100644
index 000000000..c3ceecbf5
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/atom_feed_helper.rb
@@ -0,0 +1,111 @@
+# Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERb or any other
+# template languages).
+module ActionView
+ module Helpers #:nodoc:
+ module AtomFeedHelper
+ # Full usage example:
+ #
+ # config/routes.rb:
+ # ActionController::Routing::Routes.draw do |map|
+ # map.resources :posts
+ # map.root :controller => "posts"
+ # end
+ #
+ # app/controllers/posts_controller.rb:
+ # class PostsController < ApplicationController::Base
+ # # GET /posts.html
+ # # GET /posts.atom
+ # def index
+ # @posts = Post.find(:all)
+ #
+ # respond_to do |format|
+ # format.html
+ # format.atom
+ # end
+ # end
+ # end
+ #
+ # app/views/posts/index.atom.builder:
+ # atom_feed do |feed|
+ # feed.title("My great blog!")
+ # feed.updated((@posts.first.created_at))
+ #
+ # for post in @posts
+ # feed.entry(post) do |entry|
+ # entry.title(post.title)
+ # entry.content(post.body, :type => 'html')
+ #
+ # entry.author do |author|
+ # author.name("DHH")
+ # end
+ # end
+ # end
+ # end
+ #
+ # The options are for atom_feed are:
+ #
+ # * <tt>:language</tt>: Defaults to "en-US".
+ # * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
+ # * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
+ #
+ # atom_feed yields a AtomFeedBuilder instance.
+ def atom_feed(options = {}, &block)
+ xml = options[:xml] || eval("xml", block.binding)
+ xml.instruct!
+
+ xml.feed "xml:lang" => options[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom' do
+ xml.id("tag:#{request.host}:#{request.request_uri.split(".")[0].gsub("/", "")}")
+ xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port))
+
+ if options[:url]
+ xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url)
+ end
+
+ yield AtomFeedBuilder.new(xml, self)
+ end
+ end
+
+
+ class AtomFeedBuilder
+ def initialize(xml, view)
+ @xml, @view = xml, view
+ end
+
+ # Accepts a Date or Time object and inserts it in the proper format. If nil is passed, current time in UTC is used.
+ def updated(date_or_time = nil)
+ @xml.updated((date_or_time || Time.now.utc).xmlschema)
+ end
+
+ # Creates an entry tag for a specific record and prefills the id using class and id.
+ #
+ # Options:
+ #
+ # * <tt>:updated</tt>: Time of update. Defaults to the created_at attribute on the record if one such exists.
+ # * <tt>:published</tt>: Time first published. Defaults to the updated_at attribute on the record if one such exists.
+ # * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
+ def entry(record, options = {})
+ @xml.entry do
+ @xml.id("tag:#{@view.request.host_with_port}:#{record.class}#{record.id}")
+
+ if options[:published] || (record.respond_to?(:created_at) && record.created_at)
+ @xml.published((options[:published] || record.created_at).xmlschema)
+ end
+
+ if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at)
+ @xml.updated((options[:updated] || record.updated_at).xmlschema)
+ end
+
+ @xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:url] || @view.polymorphic_url(record))
+
+ yield @xml
+ end
+ end
+
+ private
+ def method_missing(method, *arguments)
+ @xml.__send__(method, *arguments)
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/benchmark_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/benchmark_helper.rb
new file mode 100644
index 000000000..fefa28f4d
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/benchmark_helper.rb
@@ -0,0 +1,31 @@
+require 'benchmark'
+
+module ActionView
+ module Helpers
+ # This helper offers a method to measure the execution time of a block
+ # in a template.
+ module BenchmarkHelper
+ # Allows you to measure the execution time of a block
+ # in a template and records the result to the log. Wrap this block around
+ # expensive operations or possible bottlenecks to get a time reading
+ # for the operation. For example, let's say you thought your file
+ # processing method was taking too long; you could wrap it in a benchmark block.
+ #
+ # <% benchmark "Process data files" do %>
+ # <%= expensive_files_operation %>
+ # <% end %>
+ #
+ # That would add something like "Process data files (0.34523)" to the log,
+ # which you can then use to compare timings when optimizing your code.
+ #
+ # You may give an optional logger level as the second argument
+ # (:debug, :info, :warn, :error); the default value is :info.
+ def benchmark(message = "Benchmarking", level = :info)
+ if @logger
+ real = Benchmark.realtime { yield }
+ @logger.send level, "#{message} (#{'%.5f' % real})"
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/cache_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/cache_helper.rb
new file mode 100644
index 000000000..cf5420a35
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -0,0 +1,39 @@
+module ActionView
+ module Helpers
+ # This helper to exposes a method for caching of view fragments.
+ # See ActionController::Caching::Fragments for usage instructions.
+ module CacheHelper
+ # A method for caching fragments of a view rather than an entire
+ # action or page. This technique is useful caching pieces like
+ # menus, lists of news topics, static HTML fragments, and so on.
+ # This method takes a block that contains the content you wish
+ # to cache. See ActionController::Caching::Fragments for more
+ # information.
+ #
+ # ==== Examples
+ # If you wanted to cache a navigation menu, you could do the
+ # following.
+ #
+ # <% cache do %>
+ # <%= render :partial => "menu" %>
+ # <% end %>
+ #
+ # You can also cache static content...
+ #
+ # <% cache do %>
+ # <p>Hello users! Welcome to our website!</p>
+ # <% end %>
+ #
+ # ...and static content mixed with RHTML content.
+ #
+ # <% cache do %>
+ # Topics:
+ # <%= render :partial => "topics", :collection => @topic_list %>
+ # <i>Topics listed alphabetically</i>
+ # <% end %>
+ def cache(name = {}, &block)
+ @controller.cache_erb_fragment(block, name)
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/capture_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/capture_helper.rb
new file mode 100644
index 000000000..fc8cd66d7
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -0,0 +1,162 @@
+module ActionView
+ module Helpers
+ # CaptureHelper exposes methods to let you extract generated markup which
+ # can be used in other parts of a template or layout file.
+ # It provides a method to capture blocks into variables through capture and
+ # a way to capture a block of markup for use in a layout through content_for.
+ module CaptureHelper
+ # The capture method allows you to extract part of a template into a
+ # variable. You can then use this variable anywhere in your templates or layout.
+ #
+ # ==== Examples
+ # The capture method can be used in ERb templates...
+ #
+ # <% @greeting = capture do %>
+ # Welcome to my shiny new web page! The date and time is
+ # <%= Time.now %>
+ # <% end %>
+ #
+ # ...and Builder (RXML) templates.
+ #
+ # @timestamp = capture do
+ # "The current timestamp is #{Time.now}."
+ # end
+ #
+ # You can then use that variable anywhere else. For example:
+ #
+ # <html>
+ # <head><title><%= @greeting %></title></head>
+ # <body>
+ # <b><%= @greeting %></b>
+ # </body></html>
+ #
+ def capture(*args, &block)
+ # execute the block
+ begin
+ buffer = eval(ActionView::Base.erb_variable, block.binding)
+ rescue
+ buffer = nil
+ end
+
+ if buffer.nil?
+ capture_block(*args, &block).to_s
+ else
+ capture_erb_with_buffer(buffer, *args, &block).to_s
+ end
+ end
+
+ # Calling content_for stores a block of markup in an identifier for later use.
+ # You can make subsequent calls to the stored content in other templates or the layout
+ # by passing the identifier as an argument to <tt>yield</tt>.
+ #
+ # ==== Examples
+ #
+ # <% content_for :not_authorized do %>
+ # alert('You are not authorized to do that!')
+ # <% end %>
+ #
+ # You can then use <tt>yield :not_authorized</tt> anywhere in your templates.
+ #
+ # <%= yield :not_authorized if current_user.nil? %>
+ #
+ # You can also use this syntax alongside an existing call to <tt>yield</tt> in a layout. For example:
+ #
+ # <%# This is the layout %>
+ # <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ # <head>
+ # <title>My Website</title>
+ # <%= yield :script %>
+ # </head>
+ # <body>
+ # <%= yield %>
+ # </body>
+ # </html>
+ #
+ # And now, we'll create a view that has a content_for call that
+ # creates the <tt>script</tt> identifier.
+ #
+ # <%# This is our view %>
+ # Please login!
+ #
+ # <% content_for :script do %>
+ # <script type="text/javascript">alert('You are not authorized to view this page!')</script>
+ # <% end %>
+ #
+ # Then, in another view, you could to do something like this:
+ #
+ # <%= link_to_remote 'Logout', :action => 'logout' %>
+ #
+ # <% content_for :script do %>
+ # <%= javascript_include_tag :defaults %>
+ # <% end %>
+ #
+ # That will place <script> tags for Prototype, Scriptaculous, and application.js (if it exists)
+ # on the page; this technique is useful if you'll only be using these scripts in a few views.
+ #
+ # Note that content_for concatenates the blocks it is given for a particular
+ # identifier in order. For example:
+ #
+ # <% content_for :navigation do %>
+ # <li><%= link_to 'Home', :action => 'index' %></li>
+ # <% end %>
+ #
+ # <%# Add some other content, or use a different template: %>
+ #
+ # <% content_for :navigation do %>
+ # <li><%= link_to 'Login', :action => 'login' %></li>
+ # <% end %>
+ #
+ # Then, in another template or layout, this code would render both links in order:
+ #
+ # <ul><%= yield :navigation %></ul>
+ #
+ # Lastly, simple content can be passed as a parameter:
+ #
+ # <% content_for :script, javascript_include_tag(:defaults) %>
+ #
+ # WARNING: content_for is ignored in caches. So you shouldn't use it
+ # for elements that will be fragment cached.
+ #
+ # The deprecated way of accessing a content_for block is to use an instance variable
+ # named <tt>@content_for_#{name_of_the_content_block}</tt>. So <tt><%= content_for :footer %></tt>
+ # would be available as <tt><%= @content_for_footer %></tt>. The preferred usage is now
+ # <tt><%= yield :footer %></tt>.
+ def content_for(name, content = nil, &block)
+ existing_content_for = instance_variable_get("@content_for_#{name}").to_s
+ new_content_for = existing_content_for + (block_given? ? capture(&block) : content)
+ instance_variable_set("@content_for_#{name}", new_content_for)
+ end
+
+ private
+ def capture_block(*args, &block)
+ block.call(*args)
+ end
+
+ def capture_erb(*args, &block)
+ buffer = eval(ActionView::Base.erb_variable, block.binding)
+ capture_erb_with_buffer(buffer, *args, &block)
+ end
+
+ def capture_erb_with_buffer(buffer, *args, &block)
+ pos = buffer.length
+ block.call(*args)
+
+ # extract the block
+ data = buffer[pos..-1]
+
+ # replace it in the original with empty string
+ buffer[pos..-1] = ''
+
+ data
+ end
+
+ def erb_content_for(name, &block)
+ eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_erb(&block)"
+ end
+
+ def block_content_for(name, &block)
+ eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_block(&block)"
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/date_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/date_helper.rb
new file mode 100755
index 000000000..c2091dfd4
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/date_helper.rb
@@ -0,0 +1,689 @@
+require "date"
+
+module ActionView
+ module Helpers
+ # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the select-type methods
+ # share a number of common options that are as follows:
+ #
+ # * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday" would give
+ # birthday[month] instead of date[month] if passed to the select_month method.
+ # * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date.
+ # * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true, the select_month
+ # method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead of "date[month]".
+ module DateHelper
+ DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX')
+
+ # Reports the approximate distance in time between two Time or Date objects or integers as seconds.
+ # Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs
+ # Distances are reported base on the following table:
+ #
+ # 0 <-> 29 secs # => less than a minute
+ # 30 secs <-> 1 min, 29 secs # => 1 minute
+ # 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
+ # 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
+ # 89 mins, 29 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
+ # 23 hrs, 59 mins, 29 secs <-> 47 hrs, 59 mins, 29 secs # => 1 day
+ # 47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
+ # 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month
+ # 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months
+ # 1 yr <-> 2 yrs minus 1 secs # => about 1 year
+ # 2 yrs <-> max time or date # => over [2..X] years
+ #
+ # With include_seconds = true and the difference < 1 minute 29 seconds
+ # 0-4 secs # => less than 5 seconds
+ # 5-9 secs # => less than 10 seconds
+ # 10-19 secs # => less than 20 seconds
+ # 20-39 secs # => half a minute
+ # 40-59 secs # => less than a minute
+ # 60-89 secs # => 1 minute
+ #
+ # ==== Examples
+ # from_time = Time.now
+ # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
+ # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
+ # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
+ # distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds
+ # distance_of_time_in_words(from_time, 3.years.from_now) # => over 3 years
+ # distance_of_time_in_words(from_time, from_time + 60.hours) # => about 3 days
+ # distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute
+ # distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute
+ # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
+ # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
+ # distance_of_time_in_words(from_time, from_time + 4.years + 15.days + 30.minutes + 5.seconds) # => over 4 years
+ #
+ # to_time = Time.now + 6.years + 19.days
+ # distance_of_time_in_words(from_time, to_time, true) # => over 6 years
+ # distance_of_time_in_words(to_time, from_time, true) # => over 6 years
+ # distance_of_time_in_words(Time.now, Time.now) # => less than a minute
+ #
+ def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
+ from_time = from_time.to_time if from_time.respond_to?(:to_time)
+ to_time = to_time.to_time if to_time.respond_to?(:to_time)
+ distance_in_minutes = (((to_time - from_time).abs)/60).round
+ distance_in_seconds = ((to_time - from_time).abs).round
+
+ case distance_in_minutes
+ when 0..1
+ return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
+ case distance_in_seconds
+ when 0..4 then 'less than 5 seconds'
+ when 5..9 then 'less than 10 seconds'
+ when 10..19 then 'less than 20 seconds'
+ when 20..39 then 'half a minute'
+ when 40..59 then 'less than a minute'
+ else '1 minute'
+ end
+
+ when 2..44 then "#{distance_in_minutes} minutes"
+ when 45..89 then 'about 1 hour'
+ when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
+ when 1440..2879 then '1 day'
+ when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
+ when 43200..86399 then 'about 1 month'
+ when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
+ when 525600..1051199 then 'about 1 year'
+ else "over #{(distance_in_minutes / 525600).round} years"
+ end
+ end
+
+ # Like distance_of_time_in_words, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
+ #
+ # ==== Examples
+ # time_ago_in_words(3.minutes.from_now) # => 3 minutes
+ # time_ago_in_words(Time.now - 15.hours) # => 15 hours
+ # time_ago_in_words(Time.now) # => less than a minute
+ #
+ # from_time = Time.now - 3.days - 14.minutes - 25.seconds # => 3 days
+ def time_ago_in_words(from_time, include_seconds = false)
+ distance_of_time_in_words(from_time, Time.now, include_seconds)
+ end
+
+ alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
+
+ # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by
+ # +method+) on an object assigned to the template (identified by +object+). It's possible to tailor the selects through the +options+ hash,
+ # which accepts all the keys that each of the individual select builders do (like :use_month_numbers for select_month) as well as a range of
+ # discard options. The discard options are <tt>:discard_year</tt>, <tt>:discard_month</tt> and <tt>:discard_day</tt>. Set to true, they'll
+ # drop the respective select. Discarding the month select will also automatically discard the day select. It's also possible to explicitly
+ # set the order of the tags using the <tt>:order</tt> option with an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in
+ # the desired order. Symbols may be omitted and the respective select is not included.
+ #
+ # Pass the <tt>:default</tt> option to set the default date. Use a Time object or a Hash of :year, :month, :day, :hour, :minute, and :second.
+ #
+ # Passing :disabled => true as part of the +options+ will make elements inaccessible for change.
+ #
+ # NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed.
+ #
+ # ==== Examples
+ # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute
+ # date_select("post", "written_on")
+ #
+ # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute,
+ # # with the year in the year drop down box starting at 1995.
+ # date_select("post", "written_on", :start_year => 1995)
+ #
+ # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute,
+ # # with the year in the year drop down box starting at 1995, numbers used for months instead of words,
+ # # and without a day select box.
+ # date_select("post", "written_on", :start_year => 1995, :use_month_numbers => true,
+ # :discard_day => true, :include_blank => true)
+ #
+ # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute
+ # # with the fields ordered as day, month, year rather than month, day, year.
+ # date_select("post", "written_on", :order => [:day, :month, :year])
+ #
+ # # Generates a date select that when POSTed is stored in the user variable, in the birthday attribute
+ # # lacking a year field.
+ # date_select("user", "birthday", :order => [:month, :day])
+ #
+ # # Generates a date select that when POSTed is stored in the user variable, in the birthday attribute
+ # # which is initially set to the date 3 days from the current date
+ # date_select("post", "written_on", :default => 3.days.from_now)
+ #
+ # # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute
+ # # that will have a default day of 20.
+ # date_select("credit_card", "bill_due", :default => { :day => 20 })
+ #
+ # The selects are prepared for multi-parameter assignment to an Active Record object.
+ #
+ # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
+ # choices are valid.
+ def date_select(object_name, method, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(options)
+ end
+
+ # Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified
+ # time-based attribute (identified by +method+) on an object assigned to the template (identified by +object+).
+ # You can include the seconds with <tt>:include_seconds</tt>.
+ #
+ # ==== Examples
+ # # Creates a time select tag that, when POSTed, will be stored in the post variable in the sunrise attribute
+ # time_select("post", "sunrise")
+ #
+ # # Creates a time select tag that, when POSTed, will be stored in the order variable in the submitted attribute
+ # time_select("order", "submitted")
+ #
+ # # Creates a time select tag that, when POSTed, will be stored in the mail variable in the sent_at attribute
+ # time_select("mail", "sent_at")
+ #
+ # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the post variables in
+ # # the sunrise attribute.
+ # time_select("post", "start_time", :include_seconds => true)
+ #
+ # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the entry variables in
+ # # the submission_time attribute.
+ # time_select("entry", "submission_time", :include_seconds => true)
+ #
+ # # You can set the :minute_step to 15 which will give you: 00, 15, 30 and 45.
+ # time_select 'game', 'game_time', {:minute_step => 15}
+ #
+ # The selects are prepared for multi-parameter assignment to an Active Record object.
+ #
+ # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
+ # choices are valid.
+ def time_select(object_name, method, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_time_select_tag(options)
+ end
+
+ # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based
+ # attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples:
+ #
+ # ==== Examples
+ # # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on attribute
+ # datetime_select("post", "written_on")
+ #
+ # # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
+ # # post variable in the written_on attribute.
+ # datetime_select("post", "written_on", :start_year => 1995)
+ #
+ # # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will be stored in the
+ # # trip variable in the departing attribute.
+ # datetime_select("trip", "departing", :default => 3.days.from_now)
+ #
+ # # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable as the written_on
+ # # attribute.
+ # datetime_select("post", "written_on", :discard_type => true)
+ #
+ # The selects are prepared for multi-parameter assignment to an Active Record object.
+ def datetime_select(object_name, method, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datetime_select_tag(options)
+ end
+
+ # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
+ # It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
+ # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
+ # will be appended onto the <tt>:order</tt> passed in. You can also add <tt>:date_separator</tt> and <tt>:time_separator</tt>
+ # keys to the +options+ to control visual display of the elements.
+ #
+ # ==== Examples
+ # my_date_time = Time.now + 4.days
+ #
+ # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
+ # select_datetime(my_date_time)
+ #
+ # # Generates a datetime select that defaults to today (no specified datetime)
+ # select_datetime()
+ #
+ # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
+ # # with the fields ordered year, month, day rather than month, day, year.
+ # select_datetime(my_date_time, :order => [:year, :month, :day])
+ #
+ # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
+ # # with a '/' between each date field.
+ # select_datetime(my_date_time, :date_separator => '/')
+ #
+ # # Generates a datetime select that discards the type of the field and defaults to the datetime in
+ # # my_date_time (four days after today)
+ # select_datetime(my_date_time, :discard_type => true)
+ #
+ # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
+ # # prefixed with 'payday' rather than 'date'
+ # select_datetime(my_date_time, :prefix => 'payday')
+ #
+ def select_datetime(datetime = Time.now, options = {})
+ separator = options[:datetime_separator] || ''
+ select_date(datetime, options) + separator + select_time(datetime, options)
+ end
+
+ # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
+ # It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
+ # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
+ # will be appended onto the <tt>:order</tt> passed in.
+ #
+ # ==== Examples
+ # my_date = Time.today + 6.days
+ #
+ # # Generates a date select that defaults to the date in my_date (six days after today)
+ # select_date(my_date)
+ #
+ # # Generates a date select that defaults to today (no specified date)
+ # select_date()
+ #
+ # # Generates a date select that defaults to the date in my_date (six days after today)
+ # # with the fields ordered year, month, day rather than month, day, year.
+ # select_date(my_date, :order => [:year, :month, :day])
+ #
+ # # Generates a date select that discards the type of the field and defaults to the date in
+ # # my_date (six days after today)
+ # select_datetime(my_date_time, :discard_type => true)
+ #
+ # # Generates a date select that defaults to the datetime in my_date (six days after today)
+ # # prefixed with 'payday' rather than 'date'
+ # select_datetime(my_date_time, :prefix => 'payday')
+ #
+ def select_date(date = Date.today, options = {})
+ options[:order] ||= []
+ [:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) }
+
+ select_date = ''
+ options[:order].each do |o|
+ select_date << self.send("select_#{o}", date, options)
+ end
+ select_date
+ end
+
+ # Returns a set of html select-tags (one for hour and minute)
+ # You can set <tt>:time_separator</tt> key to format the output, and
+ # the <tt>:include_seconds</tt> option to include an input for seconds.
+ #
+ # ==== Examples
+ # my_time = Time.now + 5.days + 7.hours + 3.minutes + 14.seconds
+ #
+ # # Generates a time select that defaults to the time in my_time
+ # select_time(my_time)
+ #
+ # # Generates a time select that defaults to the current time (no specified time)
+ # select_time()
+ #
+ # # Generates a time select that defaults to the time in my_time,
+ # # which has fields separated by ':'
+ # select_time(my_time, :time_separator => ':')
+ #
+ # # Generates a time select that defaults to the time in my_time,
+ # # that also includes an input for seconds
+ # select_time(my_time, :include_seconds => true)
+ #
+ # # Generates a time select that defaults to the time in my_time, that has fields
+ # # separated by ':' and includes an input for seconds
+ # select_time(my_time, :time_separator => ':', :include_seconds => true)
+ #
+ def select_time(datetime = Time.now, options = {})
+ separator = options[:time_separator] || ''
+ select_hour(datetime, options) + separator + select_minute(datetime, options) + (options[:include_seconds] ? separator + select_second(datetime, options) : '')
+ end
+
+ # Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
+ # The <tt>second</tt> can also be substituted for a second number.
+ # Override the field name using the <tt>:field_name</tt> option, 'second' by default.
+ #
+ # ==== Examples
+ # my_time = Time.now + 16.minutes
+ #
+ # # Generates a select field for seconds that defaults to the seconds for the time in my_time
+ # select_second(my_time)
+ #
+ # # Generates a select field for seconds that defaults to the number given
+ # select_second(33)
+ #
+ # # Generates a select field for seconds that defaults to the seconds for the time in my_time
+ # # that is named 'interval' rather than 'second'
+ # select_second(my_time, :field_name => 'interval')
+ #
+ def select_second(datetime, options = {})
+ val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) : ''
+ if options[:use_hidden]
+ options[:include_seconds] ? hidden_html(options[:field_name] || 'second', val, options) : ''
+ else
+ second_options = []
+ 0.upto(59) do |second|
+ second_options << ((val == second) ?
+ %(<option value="#{leading_zero_on_single_digits(second)}" selected="selected">#{leading_zero_on_single_digits(second)}</option>\n) :
+ %(<option value="#{leading_zero_on_single_digits(second)}">#{leading_zero_on_single_digits(second)}</option>\n)
+ )
+ end
+ select_html(options[:field_name] || 'second', second_options, options)
+ end
+ end
+
+ # Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
+ # Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute selected
+ # The <tt>minute</tt> can also be substituted for a minute number.
+ # Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
+ #
+ # ==== Examples
+ # my_time = Time.now + 6.hours
+ #
+ # # Generates a select field for minutes that defaults to the minutes for the time in my_time
+ # select_minute(my_time)
+ #
+ # # Generates a select field for minutes that defaults to the number given
+ # select_minute(14)
+ #
+ # # Generates a select field for minutes that defaults to the minutes for the time in my_time
+ # # that is named 'stride' rather than 'second'
+ # select_minute(my_time, :field_name => 'stride')
+ #
+ def select_minute(datetime, options = {})
+ val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.min) : ''
+ if options[:use_hidden]
+ hidden_html(options[:field_name] || 'minute', val, options)
+ else
+ minute_options = []
+ 0.step(59, options[:minute_step] || 1) do |minute|
+ minute_options << ((val == minute) ?
+ %(<option value="#{leading_zero_on_single_digits(minute)}" selected="selected">#{leading_zero_on_single_digits(minute)}</option>\n) :
+ %(<option value="#{leading_zero_on_single_digits(minute)}">#{leading_zero_on_single_digits(minute)}</option>\n)
+ )
+ end
+ select_html(options[:field_name] || 'minute', minute_options, options)
+ end
+ end
+
+ # Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
+ # The <tt>hour</tt> can also be substituted for a hour number.
+ # Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
+ #
+ # ==== Examples
+ # my_time = Time.now + 6.hours
+ #
+ # # Generates a select field for minutes that defaults to the minutes for the time in my_time
+ # select_minute(my_time)
+ #
+ # # Generates a select field for minutes that defaults to the number given
+ # select_minute(14)
+ #
+ # # Generates a select field for minutes that defaults to the minutes for the time in my_time
+ # # that is named 'stride' rather than 'second'
+ # select_minute(my_time, :field_name => 'stride')
+ #
+ def select_hour(datetime, options = {})
+ val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) : ''
+ if options[:use_hidden]
+ hidden_html(options[:field_name] || 'hour', val, options)
+ else
+ hour_options = []
+ 0.upto(23) do |hour|
+ hour_options << ((val == hour) ?
+ %(<option value="#{leading_zero_on_single_digits(hour)}" selected="selected">#{leading_zero_on_single_digits(hour)}</option>\n) :
+ %(<option value="#{leading_zero_on_single_digits(hour)}">#{leading_zero_on_single_digits(hour)}</option>\n)
+ )
+ end
+ select_html(options[:field_name] || 'hour', hour_options, options)
+ end
+ end
+
+ # Returns a select tag with options for each of the days 1 through 31 with the current day selected.
+ # The <tt>date</tt> can also be substituted for a hour number.
+ # Override the field name using the <tt>:field_name</tt> option, 'day' by default.
+ #
+ # ==== Examples
+ # my_date = Time.today + 2.days
+ #
+ # # Generates a select field for days that defaults to the day for the date in my_date
+ # select_day(my_time)
+ #
+ # # Generates a select field for days that defaults to the number given
+ # select_day(5)
+ #
+ # # Generates a select field for days that defaults to the day for the date in my_date
+ # # that is named 'due' rather than 'day'
+ # select_day(my_time, :field_name => 'due')
+ #
+ def select_day(date, options = {})
+ val = date ? (date.kind_of?(Fixnum) ? date : date.day) : ''
+ if options[:use_hidden]
+ hidden_html(options[:field_name] || 'day', val, options)
+ else
+ day_options = []
+ 1.upto(31) do |day|
+ day_options << ((val == day) ?
+ %(<option value="#{day}" selected="selected">#{day}</option>\n) :
+ %(<option value="#{day}">#{day}</option>\n)
+ )
+ end
+ select_html(options[:field_name] || 'day', day_options, options)
+ end
+ end
+
+ # Returns a select tag with options for each of the months January through December with the current month selected.
+ # The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values
+ # (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names --
+ # set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you want both numbers and names,
+ # set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer to show month names as abbreviations,
+ # set the <tt>:use_short_month</tt> key in +options+ to true. If you want to use your own month names, set the
+ # <tt>:use_month_names</tt> key in +options+ to an array of 12 month names. Override the field name using the
+ # <tt>:field_name</tt> option, 'month' by default.
+ #
+ # ==== Examples
+ # # Generates a select field for months that defaults to the current month that
+ # # will use keys like "January", "March".
+ # select_month(Date.today)
+ #
+ # # Generates a select field for months that defaults to the current month that
+ # # is named "start" rather than "month"
+ # select_month(Date.today, :field_name => 'start')
+ #
+ # # Generates a select field for months that defaults to the current month that
+ # # will use keys like "1", "3".
+ # select_month(Date.today, :use_month_numbers => true)
+ #
+ # # Generates a select field for months that defaults to the current month that
+ # # will use keys like "1 - January", "3 - March".
+ # select_month(Date.today, :add_month_numbers => true)
+ #
+ # # Generates a select field for months that defaults to the current month that
+ # # will use keys like "Jan", "Mar".
+ # select_month(Date.today, :use_short_month => true)
+ #
+ # # Generates a select field for months that defaults to the current month that
+ # # will use keys like "Januar", "Marts."
+ # select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...))
+ #
+ def select_month(date, options = {})
+ val = date ? (date.kind_of?(Fixnum) ? date : date.month) : ''
+ if options[:use_hidden]
+ hidden_html(options[:field_name] || 'month', val, options)
+ else
+ month_options = []
+ month_names = options[:use_month_names] || (options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES)
+ month_names.unshift(nil) if month_names.size < 13
+ 1.upto(12) do |month_number|
+ month_name = if options[:use_month_numbers]
+ month_number
+ elsif options[:add_month_numbers]
+ month_number.to_s + ' - ' + month_names[month_number]
+ else
+ month_names[month_number]
+ end
+
+ month_options << ((val == month_number) ?
+ %(<option value="#{month_number}" selected="selected">#{month_name}</option>\n) :
+ %(<option value="#{month_number}">#{month_name}</option>\n)
+ )
+ end
+ select_html(options[:field_name] || 'month', month_options, options)
+ end
+ end
+
+ # Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius
+ # can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the +options+. Both ascending and descending year
+ # lists are supported by making <tt>:start_year</tt> less than or greater than <tt>:end_year</tt>. The <tt>date</tt> can also be
+ # substituted for a year given as a number. Override the field name using the <tt>:field_name</tt> option, 'year' by default.
+ #
+ # ==== Examples
+ # # Generates a select field for years that defaults to the current year that
+ # # has ascending year values
+ # select_year(Date.today, :start_year => 1992, :end_year => 2007)
+ #
+ # # Generates a select field for years that defaults to the current year that
+ # # is named 'birth' rather than 'year'
+ # select_year(Date.today, :field_name => 'birth')
+ #
+ # # Generates a select field for years that defaults to the current year that
+ # # has descending year values
+ # select_year(Date.today, :start_year => 2005, :end_year => 1900)
+ #
+ # # Generates a select field for years that defaults to the year 2006 that
+ # # has ascending year values
+ # select_year(2006, :start_year => 2000, :end_year => 2010)
+ #
+ def select_year(date, options = {})
+ val = date ? (date.kind_of?(Fixnum) ? date : date.year) : ''
+ if options[:use_hidden]
+ hidden_html(options[:field_name] || 'year', val, options)
+ else
+ year_options = []
+ y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
+
+ start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
+ step_val = start_year < end_year ? 1 : -1
+ start_year.step(end_year, step_val) do |year|
+ year_options << ((val == year) ?
+ %(<option value="#{year}" selected="selected">#{year}</option>\n) :
+ %(<option value="#{year}">#{year}</option>\n)
+ )
+ end
+ select_html(options[:field_name] || 'year', year_options, options)
+ end
+ end
+
+ private
+
+ def select_html(type, html_options, options)
+ name_and_id_from_options(options, type)
+ select_html = %(<select id="#{options[:id]}" name="#{options[:name]}")
+ select_html << %( disabled="disabled") if options[:disabled]
+ select_html << %(>\n)
+ select_html << %(<option value=""></option>\n) if options[:include_blank]
+ select_html << html_options.to_s
+ select_html << "</select>\n"
+ end
+
+ def hidden_html(type, value, options)
+ name_and_id_from_options(options, type)
+ hidden_html = %(<input type="hidden" id="#{options[:id]}" name="#{options[:name]}" value="#{value}" />\n)
+ end
+
+ def name_and_id_from_options(options, type)
+ options[:name] = (options[:prefix] || DEFAULT_PREFIX) + (options[:discard_type] ? '' : "[#{type}]")
+ options[:id] = options[:name].gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
+ end
+
+ def leading_zero_on_single_digits(number)
+ number > 9 ? number : "0#{number}"
+ end
+ end
+
+ class InstanceTag #:nodoc:
+ include DateHelper
+
+ def to_date_select_tag(options = {})
+ date_or_time_select(options.merge(:discard_hour => true))
+ end
+
+ def to_time_select_tag(options = {})
+ date_or_time_select options.merge(:discard_year => true, :discard_month => true)
+ end
+
+ def to_datetime_select_tag(options = {})
+ date_or_time_select options
+ end
+
+ private
+ def date_or_time_select(options)
+ defaults = { :discard_type => true }
+ options = defaults.merge(options)
+ datetime = value(object)
+ datetime ||= default_time_from_options(options[:default]) unless options[:include_blank]
+
+ position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
+
+ order = (options[:order] ||= [:year, :month, :day])
+
+ # Discard explicit and implicit by not being included in the :order
+ discard = {}
+ discard[:year] = true if options[:discard_year] or !order.include?(:year)
+ discard[:month] = true if options[:discard_month] or !order.include?(:month)
+ discard[:day] = true if options[:discard_day] or discard[:month] or !order.include?(:day)
+ discard[:hour] = true if options[:discard_hour]
+ discard[:minute] = true if options[:discard_minute] or discard[:hour]
+ discard[:second] = true unless options[:include_seconds] && !discard[:minute]
+
+ # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are valid
+ # (otherwise it could be 31 and february wouldn't be a valid date)
+ if datetime && discard[:day] && !discard[:month]
+ datetime = datetime.change(:day => 1)
+ end
+
+ # Maintain valid dates by including hidden fields for discarded elements
+ [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
+
+ # Ensure proper ordering of :hour, :minute and :second
+ [:hour, :minute, :second].each { |o| order.delete(o); order.push(o) }
+
+ date_or_time_select = ''
+ order.reverse.each do |param|
+ # Send hidden fields for discarded elements once output has started
+ # This ensures AR can reconstruct valid dates using ParseDate
+ next if discard[param] && date_or_time_select.empty?
+
+ date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param]))))
+ date_or_time_select.insert(0,
+ case param
+ when :hour then (discard[:year] && discard[:day] ? "" : " &mdash; ")
+ when :minute then " : "
+ when :second then options[:include_seconds] ? " : " : ""
+ else ""
+ end)
+
+ end
+
+ date_or_time_select
+ end
+
+ def options_with_prefix(position, options)
+ prefix = "#{@object_name}"
+ if options[:index]
+ prefix << "[#{options[:index]}]"
+ elsif @auto_index
+ prefix << "[#{@auto_index}]"
+ end
+ options.merge(:prefix => "#{prefix}[#{@method_name}(#{position}i)]")
+ end
+
+ def default_time_from_options(default)
+ case default
+ when nil
+ Time.now
+ when Date, Time
+ default
+ else
+ # Rename :minute and :second to :min and :sec
+ default[:min] ||= default[:minute]
+ default[:sec] ||= default[:second]
+
+ [:year, :month, :day, :hour, :min, :sec].each do |key|
+ default[key] ||= Time.now.send(key)
+ end
+
+ Time.mktime(default[:year], default[:month], default[:day],
+ default[:hour], default[:min], default[:sec])
+ end
+ end
+ end
+
+ class FormBuilder
+ def date_select(method, options = {})
+ @template.date_select(@object_name, method, options.merge(:object => @object))
+ end
+
+ def time_select(method, options = {})
+ @template.time_select(@object_name, method, options.merge(:object => @object))
+ end
+
+ def datetime_select(method, options = {})
+ @template.datetime_select(@object_name, method, options.merge(:object => @object))
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/debug_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/debug_helper.rb
new file mode 100644
index 000000000..20de7e465
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/debug_helper.rb
@@ -0,0 +1,31 @@
+module ActionView
+ module Helpers
+ # Provides a set of methods for making it easier to debug Rails objects.
+ module DebugHelper
+ # Returns a <pre>-tag that has +object+ dumped by YAML. This creates a very
+ # readable way to inspect an object.
+ #
+ # ==== Example
+ # my_hash = {'first' => 1, 'second' => 'two', 'third' => [1,2,3]}
+ # debug(my_hash)
+ #
+ # => <pre class='debug_dump'>---
+ # first: 1
+ # second: two
+ # third:
+ # - 1
+ # - 2
+ # - 3
+ # </pre>
+ def debug(object)
+ begin
+ Marshal::dump(object)
+ "<pre class='debug_dump'>#{h(object.to_yaml).gsub(" ", "&nbsp; ")}</pre>"
+ rescue Exception => e # errors from Marshal or YAML
+ # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
+ "<code class='debug_dump'>#{h(object.inspect)}</code>"
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/form_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/form_helper.rb
new file mode 100644
index 000000000..2bfb89663
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/form_helper.rb
@@ -0,0 +1,689 @@
+require 'cgi'
+require 'action_view/helpers/date_helper'
+require 'action_view/helpers/tag_helper'
+
+module ActionView
+ module Helpers
+ # Form helpers are designed to make working with models much easier compared to using just standard HTML
+ # elements by providing a set of methods for creating forms based on your models. This helper generates the HTML
+ # for forms, providing a method for each sort of input (e.g., text, password, select, and so on). When the form
+ # is submitted (i.e., when the user hits the submit button or <tt>form.submit</tt> is called via JavaScript), the form inputs will be bundled into the <tt>params</tt> object and passed back to the controller.
+ #
+ # There are two types of form helpers: those that specifically work with model attributes and those that don't.
+ # This helper deals with those that work with model attributes; to see an example of form helpers that don't work
+ # with model attributes, check the ActionView::Helpers::FormTagHelper documentation.
+ #
+ # The core method of this helper, form_for, gives you the ability to create a form for a model instance;
+ # for example, let's say that you have a model <tt>Person</tt> and want to create a new instance of it:
+ #
+ # # Note: a @person variable will have been created in the controller.
+ # # For example: @person = Person.new
+ # <% form_for :person, @person, :url => { :action => "create" } do |f| %>
+ # <%= f.text_field :first_name %>
+ # <%= f.text_field :last_name %>
+ # <%= submit_tag 'Create' %>
+ # <% end %>
+ #
+ # The HTML generated for this would be:
+ #
+ # <form action="/persons/create" method="post">
+ # <input id="person_first_name" name="person[first_name]" size="30" type="text" />
+ # <input id="person_last_name" name="person[last_name]" size="30" type="text" />
+ # <input name="commit" type="submit" value="Create" />
+ # </form>
+ #
+ # The <tt>params</tt> object created when this form is submitted would look like:
+ #
+ # {"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}}
+ #
+ # The params hash has a nested <tt>person</tt> value, which can therefore be accessed with <tt>params[:person]</tt> in the controller.
+ # If were editing/updating an instance (e.g., <tt>Person.find(1)</tt> rather than <tt>Person.new</tt> in the controller), the objects
+ # attribute values are filled into the form (e.g., the <tt>person_first_name</tt> field would have that person's first name in it).
+ #
+ # If the object name contains square brackets the id for the object will be inserted. For example:
+ #
+ # <%= text_field "person[]", "name" %>
+ #
+ # ...will generate the following ERb.
+ #
+ # <input type="text" id="person_<%= @person.id %>_name" name="person[<%= @person.id %>][name]" value="<%= @person.name %>" />
+ #
+ # If the helper is being used to generate a repetitive sequence of similar form elements, for example in a partial
+ # used by <tt>render_collection_of_partials</tt>, the <tt>index</tt> option may come in handy. Example:
+ #
+ # <%= text_field "person", "name", "index" => 1 %>
+ #
+ # ...becomes...
+ #
+ # <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" />
+ #
+ # There are also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html,
+ # link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html
+ module FormHelper
+ # Creates a form and a scope around a specific model object that is used as a base for questioning about
+ # values for the fields.
+ #
+ # <% form_for :person, @person, :url => { :action => "update" } do |f| %>
+ # First name: <%= f.text_field :first_name %>
+ # Last name : <%= f.text_field :last_name %>
+ # Biography : <%= f.text_area :biography %>
+ # Admin? : <%= f.check_box :admin %>
+ # <% end %>
+ #
+ # Worth noting is that the form_for tag is called in a ERb evaluation block, not an ERb output block. So that's <tt><% %></tt>,
+ # not <tt><%= %></tt>. Also worth noting is that form_for yields a <tt>form_builder</tt> object, in this example as <tt>f</tt>, which emulates
+ # the API for the stand-alone FormHelper methods, but without the object name. So instead of <tt>text_field :person, :name</tt>,
+ # you get away with <tt>f.text_field :name</tt>.
+ #
+ # Even further, the form_for method allows you to more easily escape the instance variable convention. So while the stand-alone
+ # approach would require <tt>text_field :person, :name, :object => person</tt>
+ # to work with local variables instead of instance ones, the form_for calls remain the same. You simply declare once with
+ # <tt>:person, person</tt> and all subsequent field calls save <tt>:person</tt> and <tt>:object => person</tt>.
+ #
+ # Also note that form_for doesn't create an exclusive scope. It's still possible to use both the stand-alone FormHelper methods
+ # and methods from FormTagHelper. For example:
+ #
+ # <% form_for :person, @person, :url => { :action => "update" } do |f| %>
+ # First name: <%= f.text_field :first_name %>
+ # Last name : <%= f.text_field :last_name %>
+ # Biography : <%= text_area :person, :biography %>
+ # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
+ # <% end %>
+ #
+ # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base,
+ # like FormOptionHelper#collection_select and DateHelper#datetime_select.
+ #
+ # HTML attributes for the form tag can be given as :html => {...}. For example:
+ #
+ # <% form_for :person, @person, :html => {:id => 'person_form'} do |f| %>
+ # ...
+ # <% end %>
+ #
+ # The above form will then have the <tt>id</tt> attribute with the value </tt>person_form</tt>, which you can then
+ # style with CSS or manipulate with JavaScript.
+ #
+ # === Relying on record identification
+ #
+ # In addition to manually configuring the form_for call, you can also rely on record identification, which will use
+ # the conventions and named routes of that approach. Examples:
+ #
+ # <% form_for(@post) do |f| %>
+ # ...
+ # <% end %>
+ #
+ # This will expand to be the same as:
+ #
+ # <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
+ # ...
+ # <% end %>
+ #
+ # And for new records:
+ #
+ # <% form_for(Post.new) do |f| %>
+ # ...
+ # <% end %>
+ #
+ # This will expand to be the same as:
+ #
+ # <% form_for :post, @post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
+ # ...
+ # <% end %>
+ #
+ # You can also overwrite the individual conventions, like this:
+ #
+ # <% form_for(@post, :url => super_post_path(@post)) do |f| %>
+ # ...
+ # <% end %>
+ #
+ # And for namespaced routes, like admin_post_url:
+ #
+ # <% form_for([:admin, @post]) do |f| %>
+ # ...
+ # <% end %>
+ #
+ # === Customized form builders
+ #
+ # You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers,
+ # then use your custom builder. For example, let's say you made a helper to automatically add labels to form inputs.
+ #
+ # <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
+ # <%= f.text_field :first_name %>
+ # <%= f.text_field :last_name %>
+ # <%= text_area :person, :biography %>
+ # <%= check_box_tag "person[admin]", @person.company.admin? %>
+ # <% end %>
+ #
+ # In many cases you will want to wrap the above in another helper, so you could do something like the following:
+ #
+ # def labelled_form_for(name, object, options, &proc)
+ # form_for(name, object, options.merge(:builder => LabellingFormBuiler), &proc)
+ # end
+ #
+ # If you don't need to attach a form to a model instance, then check out FormTagHelper#form_tag.
+ def form_for(record_or_name_or_array, *args, &proc)
+ raise ArgumentError, "Missing block" unless block_given?
+
+ options = args.extract_options!
+
+ case record_or_name_or_array
+ when String, Symbol
+ object_name = record_or_name_or_array
+ when Array
+ object = record_or_name_or_array.last
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
+ apply_form_for_options!(record_or_name_or_array, options)
+ args.unshift object
+ else
+ object = record_or_name_or_array
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
+ apply_form_for_options!([object], options)
+ args.unshift object
+ end
+
+ concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding)
+ fields_for(object_name, *(args << options), &proc)
+ concat('</form>', proc.binding)
+ end
+
+ def apply_form_for_options!(object_or_array, options) #:nodoc:
+ object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
+
+ html_options =
+ if object.respond_to?(:new_record?) && object.new_record?
+ { :class => dom_class(object, :new), :id => dom_id(object), :method => :post }
+ else
+ { :class => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put }
+ end
+
+ options[:html] ||= {}
+ options[:html].reverse_merge!(html_options)
+ options[:url] ||= polymorphic_path(object_or_array)
+ end
+
+ # Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes
+ # fields_for suitable for specifying additional model objects in the same form:
+ #
+ # ==== Examples
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
+ # First name: <%= person_form.text_field :first_name %>
+ # Last name : <%= person_form.text_field :last_name %>
+ #
+ # <% fields_for @person.permission do |permission_fields| %>
+ # Admin? : <%= permission_fields.check_box :admin %>
+ # <% end %>
+ # <% end %>
+ #
+ # ...or if you have an object that needs to be represented as a different parameter, like a Client that acts as a Person:
+ #
+ # <% fields_for :person, @client do |permission_fields| %>
+ # Admin?: <%= permission_fields.check_box :admin %>
+ # <% end %>
+ #
+ # ...or if you don't have an object, just a name of the parameter
+ #
+ # <% fields_for :person do |permission_fields| %>
+ # Admin?: <%= permission_fields.check_box :admin %>
+ # <% end %>
+ #
+ # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base,
+ # like FormOptionHelper#collection_select and DateHelper#datetime_select.
+ def fields_for(record_or_name_or_array, *args, &block)
+ raise ArgumentError, "Missing block" unless block_given?
+ options = args.extract_options!
+
+ case record_or_name_or_array
+ when String, Symbol
+ object_name = record_or_name_or_array
+ object = args.first
+ when Array
+ object = record_or_name_or_array.last
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
+ apply_form_for_options!(record_or_name_or_array, options)
+ else
+ object = record_or_name_or_array
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
+ end
+
+ builder = options[:builder] || ActionView::Base.default_form_builder
+ yield builder.new(object_name, object, self, options, block)
+ end
+
+ # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). The text of label will default to the attribute name unless you specify
+ # it explicitly. Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
+ # onto the HTML as an HTML element attribute as in the example shown.
+ #
+ # ==== Examples
+ # label(:post, :title)
+ # #=> <label for="post_title">Title</label>
+ #
+ # label(:post, :title, "A short title")
+ # #=> <label for="post_title">A short title</label>
+ #
+ # label(:post, :title, "A short title", :class => "title_label")
+ # #=> <label for="post_title" class="title_label">A short title</label>
+ #
+ def label(object_name, method, text = nil, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_label_tag(text, options)
+ end
+
+ # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
+ # shown.
+ #
+ # ==== Examples
+ # text_field(:post, :title, :size => 20)
+ # # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
+ #
+ # text_field(:post, :title, :class => "create_input")
+ # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
+ #
+ # text_field(:session, :user, :onchange => "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
+ # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }"/>
+ #
+ # text_field(:snippet, :code, :size => 20, :class => 'code_input')
+ # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
+ #
+ def text_field(object_name, method, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("text", options)
+ end
+
+ # Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
+ # shown.
+ #
+ # ==== Examples
+ # password_field(:login, :pass, :size => 20)
+ # # => <input type="text" id="login_pass" name="login[pass]" size="20" value="#{@login.pass}" />
+ #
+ # password_field(:account, :secret, :class => "form_input")
+ # # => <input type="text" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
+ #
+ # password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
+ # # => <input type="text" id="user_password" name="user[password]" value="#{@user.password}" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/>
+ #
+ # password_field(:account, :pin, :size => 20, :class => 'form_input')
+ # # => <input type="text" id="account_pin" name="account[pin]" size="20" value="#{@account.pin}" class="form_input" />
+ #
+ def password_field(object_name, method, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("password", options)
+ end
+
+ # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
+ # shown.
+ #
+ # ==== Examples
+ # hidden_field(:signup, :pass_confirm)
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
+ #
+ # hidden_field(:post, :tag_list)
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
+ #
+ # hidden_field(:user, :token)
+ # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
+ def hidden_field(object_name, method, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("hidden", options)
+ end
+
+ # Returns an file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
+ # shown.
+ #
+ # ==== Examples
+ # file_field(:user, :avatar)
+ # # => <input type="file" id="user_avatar" name="user[avatar]" />
+ #
+ # file_field(:post, :attached, :accept => 'text/html')
+ # # => <input type="file" id="post_attached" name="post[attached]" />
+ #
+ # file_field(:attachment, :file, :class => 'file_input')
+ # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
+ #
+ def file_field(object_name, method, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("file", options)
+ end
+
+ # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
+ # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
+ # hash with +options+.
+ #
+ # ==== Examples
+ # text_area(:post, :body, :cols => 20, :rows => 40)
+ # # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
+ # # #{@post.body}
+ # # </textarea>
+ #
+ # text_area(:comment, :text, :size => "20x30")
+ # # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
+ # # #{@comment.text}
+ # # </textarea>
+ #
+ # text_area(:application, :notes, :cols => 40, :rows => 15, :class => 'app_input')
+ # # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
+ # # #{@application.notes}
+ # # </textarea>
+ #
+ # text_area(:entry, :body, :size => "20x20", :disabled => 'disabled')
+ # # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
+ # # #{@entry.body}
+ # # </textarea>
+ def text_area(object_name, method, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_text_area_tag(options)
+ end
+
+ # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). It's intended that +method+ returns an integer and if that
+ # integer is above zero, then the checkbox is checked. Additional options on the input tag can be passed as a
+ # hash with +options+. The +checked_value+ defaults to 1 while the default +unchecked_value+
+ # is set to 0 which is convenient for boolean values. Since HTTP standards say that unchecked checkboxes don't post anything,
+ # we add a hidden value with the same name as the checkbox as a work around.
+ #
+ # ==== Examples
+ # # Let's say that @post.validated? is 1:
+ # check_box("post", "validated")
+ # # => <input type="checkbox" id="post_validate" name="post[validated]" value="1" checked="checked" />
+ # # <input name="post[validated]" type="hidden" value="0" />
+ #
+ # # Let's say that @puppy.gooddog is "no":
+ # check_box("puppy", "gooddog", {}, "yes", "no")
+ # # => <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
+ # # <input name="puppy[gooddog]" type="hidden" value="no" />
+ #
+ # check_box("eula", "accepted", {}, "yes", "no", :class => 'eula_check')
+ # # => <input type="checkbox" id="eula_accepted" name="eula[accepted]" value="no" />
+ # # <input name="eula[accepted]" type="hidden" value="no" />
+ #
+ def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
+ end
+
+ # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
+ # radio button will be checked. Additional options on the input tag can be passed as a
+ # hash with +options+.
+ #
+ # ==== Examples
+ # # Let's say that @post.category returns "rails":
+ # radio_button("post", "category", "rails")
+ # radio_button("post", "category", "java")
+ # # => <input type="radio" id="post_category" name="post[category]" value="rails" checked="checked" />
+ # # <input type="radio" id="post_category" name="post[category]" value="java" />
+ #
+ # radio_button("user", "receive_newsletter", "yes")
+ # radio_button("user", "receive_newsletter", "no")
+ # # => <input type="radio" id="user_receive_newsletter" name="user[receive_newsletter]" value="yes" />
+ # # <input type="radio" id="user_receive_newsletter" name="user[receive_newsletter]" value="no" checked="checked" />
+ def radio_button(object_name, method, tag_value, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_radio_button_tag(tag_value, options)
+ end
+ end
+
+ class InstanceTag #:nodoc:
+ include Helpers::TagHelper
+
+ attr_reader :method_name, :object_name
+
+ DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS)
+ DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
+ DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
+ DEFAULT_DATE_OPTIONS = { :discard_type => true }.freeze unless const_defined?(:DEFAULT_DATE_OPTIONS)
+
+ def initialize(object_name, method_name, template_object, local_binding = nil, object = nil)
+ @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
+ @template_object, @local_binding = template_object, local_binding
+ @object = object
+ if @object_name.sub!(/\[\]$/,"")
+ if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
+ @auto_index = object.to_param
+ else
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
+ end
+ end
+ end
+
+ def to_label_tag(text = nil, options = {})
+ name_and_id = options.dup
+ add_default_name_and_id(name_and_id)
+ options["for"] = name_and_id["id"]
+ content = (text.blank? ? nil : text.to_s) || method_name.humanize
+ content_tag("label", content, options)
+ end
+
+ def to_input_field_tag(field_type, options = {})
+ options = options.stringify_keys
+ options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
+ options = DEFAULT_FIELD_OPTIONS.merge(options)
+ if field_type == "hidden"
+ options.delete("size")
+ end
+ options["type"] = field_type
+ options["value"] ||= value_before_type_cast(object) unless field_type == "file"
+ add_default_name_and_id(options)
+ tag("input", options)
+ end
+
+ def to_radio_button_tag(tag_value, options = {})
+ options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
+ options["type"] = "radio"
+ options["value"] = tag_value
+ 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"] ||= 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)
+ tag("input", options)
+ end
+
+ def to_text_area_tag(options = {})
+ options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
+ add_default_name_and_id(options)
+
+ if size = options.delete("size")
+ options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
+ 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
+ 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" => options['disabled'] && checked ? checked_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 = "<select"
+ tag_text << tag_options(options)
+ tag_text << "><option value=\"false\""
+ tag_text << " selected" if value == false
+ tag_text << ">False</option><option value=\"true\""
+ tag_text << " selected" if value
+ tag_text << ">True</option></select>"
+ 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}") rescue nil)
+ 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
+ false
+ when Integer
+ value != 0
+ when String
+ value == checked_value
+ else
+ value.to_i != 0
+ end
+ end
+
+ def radio_button_checked?(value, checked_value)
+ value.to_s == checked_value.to_s
+ end
+ end
+
+ private
+ def add_default_name_and_id(options)
+ if options.has_key?("index")
+ options["name"] ||= tag_name_with_index(options["index"])
+ options["id"] ||= tag_id_with_index(options["index"])
+ options.delete("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.has_key?('multiple') ? '[]' : '')
+ options["id"] ||= tag_id
+ end
+ end
+
+ def tag_name
+ "#{@object_name}[#{@method_name}]"
+ end
+
+ def tag_name_with_index(index)
+ "#{@object_name}[#{index}][#{@method_name}]"
+ end
+
+ def tag_id
+ "#{sanitized_object_name}_#{@method_name}"
+ end
+
+ def tag_id_with_index(index)
+ "#{sanitized_object_name}_#{index}_#{@method_name}"
+ end
+
+ def sanitized_object_name
+ @object_name.gsub(/[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
+ end
+ end
+
+ class FormBuilder #:nodoc:
+ # The methods which wrap a form helper call.
+ class_inheritable_accessor :field_helpers
+ self.field_helpers = (FormHelper.instance_methods - ['form_for'])
+
+ 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
+ end
+
+ (field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
+ src = <<-end_src
+ def #{selector}(method, options = {})
+ @template.send(#{selector.inspect}, @object_name, method, options.merge(:object => @object))
+ end
+ end_src
+ class_eval src, __FILE__, __LINE__
+ end
+
+ def fields_for(record_or_name_or_array, *args, &block)
+ case record_or_name_or_array
+ when String, Symbol
+ name = "#{object_name}[#{record_or_name_or_array}]"
+ when Array
+ object = record_or_name_or_array.last
+ name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
+ args.unshift(object)
+ else
+ object = record_or_name_or_array
+ name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
+ args.unshift(object)
+ end
+
+ @template.fields_for(name, *args, &block)
+ end
+
+ def label(method, text = nil, options = {})
+ @template.label(@object_name, method, text, options.merge(:object => @object))
+ end
+
+ def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
+ @template.check_box(@object_name, method, options.merge(:object => @object), checked_value, unchecked_value)
+ end
+
+ def radio_button(method, tag_value, options = {})
+ @template.radio_button(@object_name, method, tag_value, options.merge(:object => @object))
+ end
+
+ def error_message_on(method, prepend_text = "", append_text = "", css_class = "formError")
+ @template.error_message_on(@object, method, prepend_text, append_text, css_class)
+ end
+
+ def error_messages(options = {})
+ @template.error_messages_for(@object_name, options.merge(:object => @object))
+ end
+
+ def submit(value = "Save changes", options = {})
+ @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
+ end
+ end
+ end
+
+ class Base
+ cattr_accessor :default_form_builder
+ self.default_form_builder = ::ActionView::Helpers::FormBuilder
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/form_options_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/form_options_helper.rb
new file mode 100644
index 000000000..0f1d2b02b
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -0,0 +1,425 @@
+require 'cgi'
+require 'erb'
+require 'action_view/helpers/form_helper'
+
+module ActionView
+ module Helpers
+ # Provides a number of methods for turning different kinds of containers into a set of option tags.
+ # == Options
+ # The <tt>collection_select</tt>, <tt>country_select</tt>, <tt>select</tt>,
+ # and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter,
+ # a hash.
+ #
+ # * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
+ #
+ # For example,
+ #
+ # select("post", "category", Post::CATEGORIES, {:include_blank => true})
+ #
+ # could become:
+ #
+ # <select name="post[category]">
+ # <option></option>
+ # <option>joke</option>
+ # <option>poem</option>
+ # </select>
+ #
+ # Another common case is a select tag for an <tt>belongs_to</tt>-associated object.
+ #
+ # Example with @post.person_id => 2:
+ #
+ # select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, {:include_blank => 'None'})
+ #
+ # could become:
+ #
+ # <select name="post[person_id]">
+ # <option value="">None</option>
+ # <option value="1">David</option>
+ # <option value="2" selected="selected">Sam</option>
+ # <option value="3">Tobias</option>
+ # </select>
+ #
+ # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
+ #
+ # Example:
+ #
+ # select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, {:prompt => 'Select Person'})
+ #
+ # could become:
+ #
+ # <select name="post[person_id]">
+ # <option value="">Select Person</option>
+ # <option value="1">David</option>
+ # <option value="2">Sam</option>
+ # <option value="3">Tobias</option>
+ # </select>
+ module FormOptionsHelper
+ include ERB::Util
+
+ # Create a select tag and a series of contained option tags for the provided object and method.
+ # The option currently held by the object will be selected, provided that the object is available.
+ # 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 })
+ #
+ # could become:
+ #
+ # <select name="post[person_id]">
+ # <option value=""></option>
+ # <option value="1" selected="selected">David</option>
+ # <option value="2">Sam</option>
+ # <option value="3">Tobias</option>
+ # </select>
+ #
+ # This can be used to provide a default set of options in the standard way: before rendering the create form, a
+ # new model instance is assigned the default options and bound to @model_name. Usually this model is not saved
+ # to the database. Instead, a second model object is created when the create request is received.
+ # This allows the user to submit a form page more than once with the expected results of creating multiple records.
+ # In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
+ #
+ # By default, post.person_id is the selected option. Specify :selected => value to use a different selection
+ # or :selected => nil to leave all options unselected.
+ def select(object, method, choices, options = {}, html_options = {})
+ InstanceTag.new(object, method, self, nil, options.delete(:object)).to_select_tag(choices, options, html_options)
+ end
+
+ # Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
+ # +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
+ # be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
+ # or <tt>:include_blank</tt> in the +options+ hash.
+ #
+ # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are methods to be called on each member
+ # of +collection+. The return values are used as the +value+ attribute and contents of each
+ # <tt><option></tt> tag, respectively.
+ #
+ # Example object structure for use with this method:
+ # class Post < ActiveRecord::Base
+ # belongs_to :author
+ # end
+ # class Author < ActiveRecord::Base
+ # has_many :posts
+ # def name_with_initial
+ # "#{first_name.first}. #{last_name}"
+ # end
+ # end
+ #
+ # Sample usage (selecting the associated +Author+ for an instance of +Post+, <tt>@post</tt>):
+ # collection_select(:post, :author_id, Author.find(:all), :id, :name_with_initial, {:prompt => true})
+ #
+ # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
+ # <select name="post[author_id]">
+ # <option value="">Please select</option>
+ # <option value="1" selected="selected">D. Heinemeier Hansson</option>
+ # <option value="2">D. Thomas</option>
+ # <option value="3">M. Clark</option>
+ # </select>
+ def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
+ InstanceTag.new(object, method, self, nil, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
+ end
+
+ # Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
+ def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
+ InstanceTag.new(object, method, self, nil, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
+ end
+
+ # Return select and option tags for the given object and method, using
+ # #time_zone_options_for_select to generate the list of option tags.
+ #
+ # In addition to the <tt>:include_blank</tt> option documented above,
+ # this method also supports a <tt>:model</tt> option, which defaults
+ # to TimeZone. This may be used by users to specify a different time
+ # zone model object. (See #time_zone_options_for_select for more
+ # information.)
+ def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
+ InstanceTag.new(object, method, self, nil, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
+ end
+
+ # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
+ # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
+ # the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values
+ # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +Selected+
+ # may also be an array of values to be selected when using a multiple select.
+ #
+ # Examples (call, result):
+ # options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
+ # <option value="$">Dollar</option>\n<option value="DKK">Kroner</option>
+ #
+ # options_for_select([ "VISA", "MasterCard" ], "MasterCard")
+ # <option>VISA</option>\n<option selected="selected">MasterCard</option>
+ #
+ # options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
+ # <option value="$20">Basic</option>\n<option value="$40" selected="selected">Plus</option>
+ #
+ # options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
+ # <option selected="selected">VISA</option>\n<option>MasterCard</option>\n<option selected="selected">Discover</option>
+ #
+ # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
+ def options_for_select(container, selected = nil)
+ container = container.to_a if Hash === container
+
+ options_for_select = container.inject([]) do |options, element|
+ text, value = option_text_and_value(element)
+ selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
+ options << %(<option value="#{html_escape(value.to_s)}"#{selected_attribute}>#{html_escape(text.to_s)}</option>)
+ end
+
+ options_for_select.join("\n")
+ end
+
+ # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the
+ # the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
+ # If +selected+ is specified, the element returning a match on +value_method+ will get the selected option tag.
+ #
+ # Example (call, result). Imagine a loop iterating over each +person+ in <tt>@project.people</tt> to generate an input tag:
+ # options_from_collection_for_select(@project.people, "id", "name")
+ # <option value="#{person.id}">#{person.name}</option>
+ #
+ # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
+ def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
+ options = collection.map do |element|
+ [element.send(text_method), element.send(value_method)]
+ end
+ options_for_select(options, selected)
+ end
+
+ # Returns a string of <tt><option></tt> tags, like <tt>#options_from_collection_for_select</tt>, but
+ # groups them by <tt><optgroup></tt> tags based on the object relationships of the arguments.
+ #
+ # Parameters:
+ # +collection+:: An array of objects representing the <tt><optgroup></tt> tags
+ # +group_method+:: The name of a method which, when called on a member of +collection+, returns an
+ # array of child objects representing the <tt><option></tt> tags
+ # +group_label_method+:: The name of a method which, when called on a member of +collection+, returns a
+ # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag
+ # +option_key_method+:: The name of a method which, when called on a child object of a member of
+ # +collection+, returns a value to be used as the +value+ attribute for its
+ # <tt><option></tt> tag
+ # +option_value_method+:: The name of a method which, when called on a child object of a member of
+ # +collection+, returns a value to be used as the contents of its
+ # <tt><option></tt> tag
+ # +selected_key+:: A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
+ # which will have the +selected+ attribute set. Corresponds to the return value
+ # of one of the calls to +option_key_method+. If +nil+, no selection is made.
+ #
+ # Example object structure for use with this method:
+ # class Continent < ActiveRecord::Base
+ # has_many :countries
+ # # attribs: id, name
+ # end
+ # class Country < ActiveRecord::Base
+ # belongs_to :continent
+ # # attribs: id, name, continent_id
+ # end
+ #
+ # Sample usage:
+ # option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3)
+ #
+ # Possible output:
+ # <optgroup label="Africa">
+ # <option value="1">Egypt</option>
+ # <option value="4">Rwanda</option>
+ # ...
+ # </optgroup>
+ # <optgroup label="Asia">
+ # <option value="3" selected="selected">China</option>
+ # <option value="12">India</option>
+ # <option value="5">Japan</option>
+ # ...
+ # </optgroup>
+ #
+ # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
+ # wrap the output in an appropriate <tt><select></tt> tag.
+ def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
+ collection.inject("") do |options_for_select, group|
+ group_label_string = eval("group.#{group_label_method}")
+ options_for_select += "<optgroup label=\"#{html_escape(group_label_string)}\">"
+ options_for_select += options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key)
+ options_for_select += '</optgroup>'
+ end
+ end
+
+ # Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to
+ # have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so
+ # that they will be listed above the rest of the (long) list.
+ #
+ # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
+ def country_options_for_select(selected = nil, priority_countries = nil)
+ country_options = ""
+
+ if priority_countries
+ country_options += options_for_select(priority_countries, selected)
+ country_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
+ end
+
+ return country_options + options_for_select(COUNTRIES, selected)
+ end
+
+ # Returns a string of option tags for pretty much any time zone in the
+ # world. Supply a TimeZone name as +selected+ to have it marked as the
+ # selected option tag. You can also supply an array of TimeZone objects
+ # as +priority_zones+, so that they will be listed above the rest of the
+ # (long) list. (You can use TimeZone.us_zones as a convenience for
+ # obtaining a list of the US time zones.)
+ #
+ # The +selected+ parameter must be either +nil+, or a string that names
+ # a TimeZone.
+ #
+ # By default, +model+ is the TimeZone constant (which can be obtained
+ # in ActiveRecord as a value object). The only requirement is that the
+ # +model+ parameter be an object that responds to #all, and returns
+ # an array of objects that represent time zones.
+ #
+ # NOTE: Only the option tags are returned, you have to wrap this call in
+ # a regular HTML select tag.
+ def time_zone_options_for_select(selected = nil, priority_zones = nil, model = TimeZone)
+ zone_options = ""
+
+ zones = model.all
+ convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
+
+ if priority_zones
+ zone_options += options_for_select(convert_zones[priority_zones], selected)
+ zone_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
+
+ zones = zones.reject { |z| priority_zones.include?( z ) }
+ end
+
+ zone_options += options_for_select(convert_zones[zones], selected)
+ zone_options
+ end
+
+ private
+ def option_text_and_value(option)
+ # Options are [text, value] pairs or strings used for both.
+ if !option.is_a?(String) and option.respond_to?(:first) and option.respond_to?(:last)
+ [option.first, option.last]
+ else
+ [option, option]
+ end
+ end
+
+ def option_value_selected?(value, selected)
+ if selected.respond_to?(:include?) && !selected.is_a?(String)
+ selected.include? value
+ else
+ value == selected
+ end
+ end
+
+ # All the countries included in the country_options output.
+ COUNTRIES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
+ "Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
+ "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
+ "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
+ "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
+ "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
+ "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
+ "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
+ "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
+ "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
+ "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
+ "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
+ "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
+ "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
+ "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
+ "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
+ "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
+ "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
+ "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
+ "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
+ "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
+ "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
+ "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
+ "Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
+ "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
+ "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
+ "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
+ "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
+ "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
+ "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
+ "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
+ "Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
+ "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
+ "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
+ "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
+ "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
+ "Yemen", "Zambia", "Zimbabwe"] unless const_defined?("COUNTRIES")
+ end
+
+ class InstanceTag #:nodoc:
+ include FormOptionsHelper
+
+ 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, 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
+ )
+ end
+
+ 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),
+ options, value
+ ), html_options
+ )
+ end
+
+ private
+ def add_options(option_tags, options, value = nil)
+ if options[:include_blank]
+ option_tags = "<option value=\"\">#{options[:include_blank] if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
+ end
+ if value.blank? && options[:prompt]
+ ("<option value=\"\">#{options[:prompt].kind_of?(String) ? options[:prompt] : 'Please select'}</option>\n") + option_tags
+ else
+ option_tags
+ end
+ end
+ end
+
+ class FormBuilder
+ def select(method, choices, options = {}, html_options = {})
+ @template.select(@object_name, method, choices, options.merge(:object => @object), html_options)
+ end
+
+ def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
+ @template.collection_select(@object_name, method, collection, value_method, text_method, options.merge(:object => @object), html_options)
+ end
+
+ def country_select(method, priority_countries = nil, options = {}, html_options = {})
+ @template.country_select(@object_name, method, priority_countries, options.merge(:object => @object), html_options)
+ end
+
+ def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
+ @template.time_zone_select(@object_name, method, priority_zones, options.merge(:object => @object), html_options)
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/form_tag_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/form_tag_helper.rb
new file mode 100644
index 000000000..077465086
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -0,0 +1,432 @@
+require 'cgi'
+require 'action_view/helpers/tag_helper'
+
+module ActionView
+ module Helpers
+ # Provides a number of methods for creating form tags that doesn't rely on an ActiveRecord object assigned to the template like
+ # FormHelper does. Instead, you provide the names and values manually.
+ #
+ # NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying
+ # <tt>:disabled => true</tt> will give <tt>disabled="disabled"</tt>.
+ module FormTagHelper
+ # Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like
+ # ActionController::Base#url_for. The method for the form defaults to POST.
+ #
+ # ==== Options
+ # * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data".
+ # * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".
+ # If "put", "delete", or another verb is used, a hidden input with name _method
+ # is added to simulate the verb over post.
+ # * A list of parameters to feed to the URL the form will be posted to.
+ #
+ # ==== Examples
+ # form_tag('/posts')
+ # # => <form action="/posts" method="post">
+ #
+ # form_tag('/posts/1', :method => :put)
+ # # => <form action="/posts/1" method="put">
+ #
+ # form_tag('/upload', :multipart => true)
+ # # => <form action="/upload" method="post" enctype="multipart/form-data">
+ #
+ # <% form_tag '/posts' do -%>
+ # <div><%= submit_tag 'Save' %></div>
+ # <% end -%>
+ # # => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
+ def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
+ html_options = html_options_for_form(url_for_options, options, *parameters_for_url)
+ if block_given?
+ form_tag_in_block(html_options, &block)
+ else
+ form_tag_html(html_options)
+ end
+ end
+
+ # Creates a dropdown selection box, or if the <tt>:multiple</tt> option is set to true, a multiple
+ # choice selection box.
+ #
+ # Helpers::FormOptions can be used to create common select boxes such as countries, time zones, or
+ # associated records. <tt>option_tags</tt> is a string containing the option tags for the select box.
+ #
+ # ==== Options
+ # * <tt>:multiple</tt> - If set to true the selection will allow multiple choices.
+ # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
+ # * Any other key creates standard HTML attributes for the tag.
+ #
+ # ==== Examples
+ # select_tag "people", "<option>David</option>"
+ # # => <select id="people" name="people"><option>David</option></select>
+ #
+ # select_tag "count", "<option>1</option><option>2</option><option>3</option><option>4</option>"
+ # # => <select id="count" name="count"><option>1</option><option>2</option>
+ # # <option>3</option><option>4</option></select>
+ #
+ # select_tag "colors", "<option>Red</option><option>Green</option><option>Blue</option>", :multiple => true
+ # # => <select id="colors" multiple="multiple" name="colors"><option>Red</option>
+ # # <option>Green</option><option>Blue</option></select>
+ #
+ # select_tag "locations", "<option>Home</option><option selected="selected">Work</option><option>Out</option>"
+ # # => <select id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option>
+ # # <option>Out</option></select>
+ #
+ # select_tag "access", "<option>Read</option><option>Write</option>", :multiple => true, :class => 'form_input'
+ # # => <select class="form_input" id="access" multiple="multiple" name="access"><option>Read</option>
+ # # <option>Write</option></select>
+ #
+ # select_tag "destination", "<option>NYC</option><option>Paris</option><option>Rome</option>", :disabled => true
+ # # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option>
+ # # <option>Paris</option><option>Rome</option></select>
+ def select_tag(name, option_tags = nil, options = {})
+ content_tag :select, option_tags, { "name" => name, "id" => name }.update(options.stringify_keys)
+ end
+
+ # Creates a standard text field; use these text fields to input smaller chunks of text like a username
+ # or a search query.
+ #
+ # ==== Options
+ # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
+ # * <tt>:size</tt> - The number of visible characters that will fit in the input.
+ # * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
+ # * Any other key creates standard HTML attributes for the tag.
+ #
+ # ==== Examples
+ # text_field_tag 'name'
+ # # => <input id="name" name="name" type="text" />
+ #
+ # text_field_tag 'query', 'Enter your search query here'
+ # # => <input id="query" name="query" type="text" value="Enter your search query here" />
+ #
+ # text_field_tag 'request', nil, :class => 'special_input'
+ # # => <input class="special_input" id="request" name="request" type="text" />
+ #
+ # text_field_tag 'address', '', :size => 75
+ # # => <input id="address" name="address" size="75" type="text" value="" />
+ #
+ # text_field_tag 'zip', nil, :maxlength => 5
+ # # => <input id="zip" maxlength="5" name="zip" type="text" />
+ #
+ # text_field_tag 'payment_amount', '$0.00', :disabled => true
+ # # => <input disabled="disabled" id="payment_amount" name="payment_amount" type="text" value="$0.00" />
+ #
+ # text_field_tag 'ip', '0.0.0.0', :maxlength => 15, :size => 20, :class => "ip-input"
+ # # => <input class="ip-input" id="ip" maxlength="15" name="ip" size="20" type="text" value="0.0.0.0" />
+ def text_field_tag(name, value = nil, options = {})
+ tag :input, { "type" => "text", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys)
+ end
+
+ # Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or
+ # data that should be hidden from the user.
+ #
+ # ==== Options
+ # * Creates standard HTML attributes for the tag.
+ #
+ # ==== Examples
+ # hidden_field_tag 'tags_list'
+ # # => <input id="tags_list" name="tags_list" type="hidden" />
+ #
+ # hidden_field_tag 'token', 'VUBJKB23UIVI1UU1VOBVI@'
+ # # => <input id="token" name="token" type="hidden" value="VUBJKB23UIVI1UU1VOBVI@" />
+ #
+ # hidden_field_tag 'collected_input', '', :onchange => "alert('Input collected!')"
+ # # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
+ # # type="hidden" value="" />
+ def hidden_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "hidden"))
+ end
+
+ # Creates a file upload field. If you are using file uploads then you will also need
+ # to set the multipart option for the form tag:
+ #
+ # <%= form_tag { :action => "post" }, { :multipart => true } %>
+ # <label for="file">File to Upload</label> <%= file_field_tag "file" %>
+ # <%= submit_tag %>
+ # <%= end_form_tag %>
+ #
+ # The specified URL will then be passed a File object containing the selected file, or if the field
+ # was left blank, a StringIO object.
+ #
+ # ==== Options
+ # * Creates standard HTML attributes for the tag.
+ # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
+ #
+ # ==== Examples
+ # file_field_tag 'attachment'
+ # # => <input id="attachment" name="attachment" type="file" />
+ #
+ # file_field_tag 'avatar', :class => 'profile-input'
+ # # => <input class="profile-input" id="avatar" name="avatar" type="file" />
+ #
+ # file_field_tag 'picture', :disabled => true
+ # # => <input disabled="disabled" id="picture" name="picture" type="file" />
+ #
+ # file_field_tag 'resume', :value => '~/resume.doc'
+ # # => <input id="resume" name="resume" type="file" value="~/resume.doc" />
+ #
+ # file_field_tag 'user_pic', :accept => 'image/png,image/gif,image/jpeg'
+ # # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" />
+ #
+ # file_field_tag 'file', :accept => 'text/html', :class => 'upload', :value => 'index.html'
+ # # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" />
+ def file_field_tag(name, options = {})
+ text_field_tag(name, nil, options.update("type" => "file"))
+ end
+
+ # Creates a password field, a masked text field that will hide the users input behind a mask character.
+ #
+ # ==== Options
+ # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
+ # * <tt>:size</tt> - The number of visible characters that will fit in the input.
+ # * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
+ # * Any other key creates standard HTML attributes for the tag.
+ #
+ # ==== Examples
+ # password_field_tag 'pass'
+ # # => <input id="pass" name="pass" type="password" />
+ #
+ # password_field_tag 'secret', 'Your secret here'
+ # # => <input id="secret" name="secret" type="password" value="Your secret here" />
+ #
+ # password_field_tag 'masked', nil, :class => 'masked_input_field'
+ # # => <input class="masked_input_field" id="masked" name="masked" type="password" />
+ #
+ # password_field_tag 'token', '', :size => 15
+ # # => <input id="token" name="token" size="15" type="password" value="" />
+ #
+ # password_field_tag 'key', nil, :maxlength => 16
+ # # => <input id="key" maxlength="16" name="key" type="password" />
+ #
+ # password_field_tag 'confirm_pass', nil, :disabled => true
+ # # => <input disabled="disabled" id="confirm_pass" name="confirm_pass" type="password" />
+ #
+ # password_field_tag 'pin', '1234', :maxlength => 4, :size => 6, :class => "pin-input"
+ # # => <input class="pin-input" id="pin" maxlength="4" name="pin" size="6" type="password" value="1234" />
+ def password_field_tag(name = "password", value = nil, options = {})
+ text_field_tag(name, value, options.update("type" => "password"))
+ end
+
+ # Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions.
+ #
+ # ==== Options
+ # * <tt>:size</tt> - A string specifying the dimensions (columns by rows) of the textarea (e.g., "25x10").
+ # * <tt>:rows</tt> - Specify the number of rows in the textarea
+ # * <tt>:cols</tt> - Specify the number of columns in the textarea
+ # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
+ # * Any other key creates standard HTML attributes for the tag.
+ #
+ # ==== Examples
+ # text_area_tag 'post'
+ # # => <textarea id="post" name="post"></textarea>
+ #
+ # text_area_tag 'bio', @user.bio
+ # # => <textarea id="bio" name="bio">This is my biography.</textarea>
+ #
+ # text_area_tag 'body', nil, :rows => 10, :cols => 25
+ # # => <textarea cols="25" id="body" name="body" rows="10"></textarea>
+ #
+ # text_area_tag 'body', nil, :size => "25x10"
+ # # => <textarea name="body" id="body" cols="25" rows="10"></textarea>
+ #
+ # text_area_tag 'description', "Description goes here.", :disabled => true
+ # # => <textarea disabled="disabled" id="description" name="description">Description goes here.</textarea>
+ #
+ # text_area_tag 'comment', nil, :class => 'comment_input'
+ # # => <textarea class="comment_input" id="comment" name="comment"></textarea>
+ def text_area_tag(name, content = nil, options = {})
+ options.stringify_keys!
+
+ if size = options.delete("size")
+ options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
+ end
+
+ content_tag :textarea, content, { "name" => name, "id" => name }.update(options.stringify_keys)
+ end
+
+ # Creates a check box form input tag.
+ #
+ # ==== Options
+ # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
+ # * Any other key creates standard HTML options for the tag.
+ #
+ # ==== Examples
+ # check_box_tag 'accept'
+ # # => <input id="accept" name="accept" type="checkbox" value="1" />
+ #
+ # check_box_tag 'rock', 'rock music'
+ # # => <input id="rock" name="rock" type="checkbox" value="rock music" />
+ #
+ # check_box_tag 'receive_email', 'yes', true
+ # # => <input checked="checked" id="receive_email" name="receive_email" type="checkbox" value="yes" />
+ #
+ # check_box_tag 'tos', 'yes', false, :class => 'accept_tos'
+ # # => <input class="accept_tos" id="tos" name="tos" type="checkbox" value="yes" />
+ #
+ # check_box_tag 'eula', 'accepted', false, :disabled => true
+ # # => <input disabled="disabled" id="eula" name="eula" type="checkbox" value="accepted" />
+ def check_box_tag(name, value = "1", checked = false, options = {})
+ html_options = { "type" => "checkbox", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys)
+ html_options["checked"] = "checked" if checked
+ tag :input, html_options
+ end
+
+ # Creates a radio button; use groups of radio buttons named the same to allow users to
+ # select from a group of options.
+ #
+ # ==== Options
+ # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
+ # * Any other key creates standard HTML options for the tag.
+ #
+ # ==== Examples
+ # radio_button_tag 'gender', 'male'
+ # # => <input id="gender_male" name="gender" type="radio" value="male" />
+ #
+ # radio_button_tag 'receive_updates', 'no', true
+ # # => <input checked="checked" id="receive_updates_no" name="receive_updates" type="radio" value="no" />
+ #
+ # radio_button_tag 'time_slot', "3:00 p.m.", false, :disabled => true
+ # # => <input disabled="disabled" id="time_slot_300_pm" name="time_slot" type="radio" value="3:00 p.m." />
+ #
+ # radio_button_tag 'color', "green", true, :class => "color_input"
+ # # => <input checked="checked" class="color_input" id="color_green" name="color" type="radio" value="green" />
+ def radio_button_tag(name, value, checked = false, options = {})
+ pretty_tag_value = value.to_s.gsub(/\s/, "_").gsub(/(?!-)\W/, "").downcase
+ pretty_name = name.to_s.gsub(/\[/, "_").gsub(/\]/, "")
+ html_options = { "type" => "radio", "name" => name, "id" => "#{pretty_name}_#{pretty_tag_value}", "value" => value }.update(options.stringify_keys)
+ html_options["checked"] = "checked" if checked
+ tag :input, html_options
+ end
+
+ # Creates a submit button with the text <tt>value</tt> as the caption.
+ #
+ # ==== Options
+ # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
+ # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a disabled version
+ # of the submit button when the form is submitted.
+ # * Any other key creates standard HTML options for the tag.
+ #
+ # ==== Examples
+ # submit_tag
+ # # => <input name="commit" type="submit" value="Save changes" />
+ #
+ # submit_tag "Edit this article"
+ # # => <input name="commit" type="submit" value="Edit this article" />
+ #
+ # submit_tag "Save edits", :disabled => true
+ # # => <input disabled="disabled" name="commit" type="submit" value="Save edits" />
+ #
+ # submit_tag "Complete sale", :disable_with => "Please wait..."
+ # # => <input name="commit" onclick="this.disabled=true;this.value='Please wait...';this.form.submit();"
+ # # type="submit" value="Complete sale" />
+ #
+ # submit_tag nil, :class => "form_submit"
+ # # => <input class="form_submit" name="commit" type="submit" />
+ #
+ # submit_tag "Edit", :disable_with => "Editing...", :class => 'edit-button'
+ # # => <input class="edit-button" disable_with="Editing..." name="commit" type="submit" value="Edit" />
+ def submit_tag(value = "Save changes", options = {})
+ options.stringify_keys!
+
+ if disable_with = options.delete("disable_with")
+ options["onclick"] = [
+ "this.setAttribute('originalValue', this.value)",
+ "this.disabled=true",
+ "this.value='#{disable_with}'",
+ "#{options["onclick"]}",
+ "result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit())",
+ "if (result == false) { this.value = this.getAttribute('originalValue'); this.disabled = false }",
+ "return result",
+ ].join(";")
+ end
+
+ tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
+ end
+
+ # Displays an image which when clicked will submit the form.
+ #
+ # <tt>source</tt> is passed to AssetTagHelper#image_path
+ #
+ # ==== Options
+ # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
+ # * Any other key creates standard HTML options for the tag.
+ #
+ # ==== Examples
+ # image_submit_tag("login.png")
+ # # => <input src="/images/login.png" type="image" />
+ #
+ # image_submit_tag("purchase.png"), :disabled => true
+ # # => <input disabled="disabled" src="/images/purchase.png" type="image" />
+ #
+ # image_submit_tag("search.png"), :class => 'search-button'
+ # # => <input class="search-button" src="/images/search.png" type="image" />
+ #
+ # image_submit_tag("agree.png"), :disabled => true, :class => "agree-disagree-button"
+ # # => <input class="agree-disagree-button" disabled="disabled" src="/images/agree.png" type="image" />
+ def image_submit_tag(source, options = {})
+ tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options.stringify_keys)
+ end
+
+ # Creates a field set for grouping HTML form elements.
+ #
+ # <tt>legend</tt> will become the fieldset's title (optional as per W3C).
+ #
+ # === Examples
+ # <% field_set_tag do %>
+ # <p><%= text_field_tag 'name' %></p>
+ # <% end %>
+ # # => <fieldset><p><input id="name" name="name" type="text" /></p></fieldset>
+ #
+ # <% field_set_tag 'Your details' do %>
+ # <p><%= text_field_tag 'name' %></p>
+ # <% end %>
+ # # => <fieldset><legend>Your details</legend><p><input id="name" name="name" type="text" /></p></fieldset>
+ def field_set_tag(legend = nil, &block)
+ content = capture(&block)
+ concat(tag(:fieldset, {}, true), block.binding)
+ concat(content_tag(:legend, legend), block.binding) unless legend.blank?
+ concat(content, block.binding)
+ concat("</fieldset>", block.binding)
+ end
+
+ private
+ def html_options_for_form(url_for_options, options, *parameters_for_url)
+ returning options.stringify_keys do |html_options|
+ html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
+ html_options["action"] = url_for(url_for_options, *parameters_for_url)
+ end
+ end
+
+ def extra_tags_for_form(html_options)
+ case method = html_options.delete("method").to_s
+ when /^get$/i # must be case-insentive, but can't use downcase as might be nil
+ html_options["method"] = "get"
+ ''
+ when /^post$/i, "", nil
+ html_options["method"] = "post"
+ protect_against_forgery? ? content_tag(:div, token_tag, :style => 'margin:0;padding:0') : ''
+ else
+ html_options["method"] = "post"
+ content_tag(:div, tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag, :style => 'margin:0;padding:0')
+ end
+ end
+
+ def form_tag_html(html_options)
+ extra_tags = extra_tags_for_form(html_options)
+ tag(:form, html_options, true) + extra_tags
+ end
+
+ def form_tag_in_block(html_options, &block)
+ content = capture(&block)
+ concat(form_tag_html(html_options), block.binding)
+ concat(content, block.binding)
+ concat("</form>", block.binding)
+ end
+
+ def token_tag
+ unless protect_against_forgery?
+ ''
+ else
+ tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascript_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascript_helper.rb
new file mode 100644
index 000000000..1ea3cbd74
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -0,0 +1,217 @@
+require 'action_view/helpers/tag_helper'
+require 'action_view/helpers/prototype_helper'
+
+module ActionView
+ module Helpers
+ # Provides functionality for working with JavaScript in your views.
+ #
+ # == Ajax, controls and visual effects
+ #
+ # * For information on using Ajax, see
+ # ActionView::Helpers::PrototypeHelper.
+ # * For information on using controls and visual effects, see
+ # ActionView::Helpers::ScriptaculousHelper.
+ #
+ # == Including the JavaScript libraries into your pages
+ #
+ # Rails includes the Prototype JavaScript framework and the Scriptaculous
+ # JavaScript controls and visual effects library. If you wish to use
+ # these libraries and their helpers (ActionView::Helpers::PrototypeHelper
+ # and ActionView::Helpers::ScriptaculousHelper), you must do one of the
+ # following:
+ #
+ # * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD
+ # section of your page (recommended): This function will return
+ # references to the JavaScript files created by the +rails+ command in
+ # your <tt>public/javascripts</tt> directory. Using it is recommended as
+ # the browser can then cache the libraries instead of fetching all the
+ # functions anew on every request.
+ # * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but
+ # will only include the Prototype core library, which means you are able
+ # to use all basic AJAX functionality. For the Scriptaculous-based
+ # JavaScript helpers, like visual effects, autocompletion, drag and drop
+ # and so on, you should use the method described above.
+ # * Use <tt><%= define_javascript_functions %></tt>: this will copy all the
+ # JavaScript support functions within a single script block. Not
+ # recommended.
+ #
+ # For documentation on +javascript_include_tag+ see
+ # ActionView::Helpers::AssetTagHelper.
+ module JavaScriptHelper
+ unless const_defined? :JAVASCRIPT_PATH
+ JAVASCRIPT_PATH = File.join(File.dirname(__FILE__), 'javascripts')
+ end
+
+ include PrototypeHelper
+
+ # Returns a link that will trigger a JavaScript +function+ using the
+ # onclick handler and return false after the fact.
+ #
+ # The +function+ argument can be omitted in favor of an +update_page+
+ # block, which evaluates to a string when the template is rendered
+ # (instead of making an Ajax request first).
+ #
+ # Examples:
+ # link_to_function "Greeting", "alert('Hello world!')"
+ # Produces:
+ # <a onclick="alert('Hello world!'); return false;" href="#">Greeting</a>
+ #
+ # link_to_function(image_tag("delete"), "if (confirm('Really?')) do_delete()")
+ # Produces:
+ # <a onclick="if (confirm('Really?')) do_delete(); return false;" href="#">
+ # <img src="/images/delete.png?" alt="Delete"/>
+ # </a>
+ #
+ # link_to_function("Show me more", nil, :id => "more_link") do |page|
+ # page[:details].visual_effect :toggle_blind
+ # page[:more_link].replace_html "Show me less"
+ # end
+ # Produces:
+ # <a href="#" id="more_link" onclick="try {
+ # $(&quot;details&quot;).visualEffect(&quot;toggle_blind&quot;);
+ # $(&quot;more_link&quot;).update(&quot;Show me less&quot;);
+ # }
+ # catch (e) {
+ # alert('RJS error:\n\n' + e.toString());
+ # alert('$(\&quot;details\&quot;).visualEffect(\&quot;toggle_blind\&quot;);
+ # \n$(\&quot;more_link\&quot;).update(\&quot;Show me less\&quot;);');
+ # throw e
+ # };
+ # return false;">Show me more</a>
+ #
+ def link_to_function(name, *args, &block)
+ html_options = args.extract_options!
+ function = args[0] || ''
+
+ html_options.symbolize_keys!
+ function = update_page(&block) if block_given?
+ content_tag(
+ "a", name,
+ html_options.merge({
+ :href => html_options[:href] || "#",
+ :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function}; return false;"
+ })
+ )
+ end
+
+ # Returns a button that'll trigger a JavaScript +function+ using the
+ # onclick handler.
+ #
+ # The +function+ argument can be omitted in favor of an +update_page+
+ # block, which evaluates to a string when the template is rendered
+ # (instead of making an Ajax request first).
+ #
+ # Examples:
+ # button_to_function "Greeting", "alert('Hello world!')"
+ # button_to_function "Delete", "if (confirm('Really?')) do_delete()"
+ # button_to_function "Details" do |page|
+ # page[:details].visual_effect :toggle_slide
+ # end
+ # button_to_function "Details", :class => "details_button" do |page|
+ # page[:details].visual_effect :toggle_slide
+ # end
+ def button_to_function(name, *args, &block)
+ html_options = args.extract_options!
+ function = args[0] || ''
+
+ html_options.symbolize_keys!
+ function = update_page(&block) if block_given?
+ tag(:input, html_options.merge({
+ :type => "button", :value => name,
+ :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
+ }))
+ end
+
+ # Includes the Action Pack JavaScript libraries inside a single <script>
+ # tag. The function first includes prototype.js and then its core extensions,
+ # (determined by filenames starting with "prototype").
+ # Afterwards, any additional scripts will be included in undefined order.
+ #
+ # Note: The recommended approach is to copy the contents of
+ # lib/action_view/helpers/javascripts/ into your application's
+ # public/javascripts/ directory, and use +javascript_include_tag+ to
+ # create remote <script> links.
+ def define_javascript_functions
+ javascript = "<script type=\"#{Mime::JS}\">"
+
+ # load prototype.js and its extensions first
+ prototype_libs = Dir.glob(File.join(JAVASCRIPT_PATH, 'prototype*')).sort.reverse
+ prototype_libs.each do |filename|
+ javascript << "\n" << IO.read(filename)
+ end
+
+ # load other libraries
+ (Dir.glob(File.join(JAVASCRIPT_PATH, '*')) - prototype_libs).each do |filename|
+ javascript << "\n" << IO.read(filename)
+ end
+ javascript << '</script>'
+ end
+
+ # Escape carrier returns and single and double quotes for JavaScript segments.
+ def escape_javascript(javascript)
+ (javascript || '').gsub('\\','\0\0').gsub('</','<\/').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }
+ end
+
+ # Returns a JavaScript tag with the +content+ inside. Example:
+ # javascript_tag "alert('All is good')"
+ #
+ # Returns:
+ # <script type="text/javascript">
+ # //<![CDATA[
+ # alert('All is good')
+ # //]]>
+ # </script>
+ #
+ # +html_options+ may be a hash of attributes for the <script> tag. Example:
+ # javascript_tag "alert('All is good')", :defer => 'defer'
+ # # => <script defer="defer" type="text/javascript">alert('All is good')</script>
+ #
+ # Instead of passing the content as an argument, you can also use a block
+ # in which case, you pass your +html_options+ as the first parameter.
+ # <% javascript_tag :defer => 'defer' do -%>
+ # alert('All is good')
+ # <% end -%>
+ def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
+ if block_given?
+ html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
+ content = capture(&block)
+ else
+ content = content_or_options_with_block
+ end
+
+ javascript_tag = content_tag("script", javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
+
+ if block_given? && block_is_within_action_view?(block)
+ concat(javascript_tag, block.binding)
+ else
+ javascript_tag
+ end
+ end
+
+ def javascript_cdata_section(content) #:nodoc:
+ "\n//#{cdata_section("\n#{content}\n//")}\n"
+ end
+
+ protected
+ def options_for_javascript(options)
+ '{' + options.map {|k, v| "#{k}:#{v}"}.sort.join(', ') + '}'
+ end
+
+ def array_or_string_for_javascript(option)
+ js_option = if option.kind_of?(Array)
+ "['#{option.join('\',\'')}']"
+ elsif !option.nil?
+ "'#{option}'"
+ end
+ js_option
+ end
+
+ private
+ def block_is_within_action_view?(block)
+ eval("defined? _erbout", block.binding)
+ end
+ end
+
+ JavascriptHelper = JavaScriptHelper unless const_defined? :JavascriptHelper
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/controls.js b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/controls.js
new file mode 100644
index 000000000..fbc4418b8
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/controls.js
@@ -0,0 +1,963 @@
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+// Richard Livsey
+// Rahul Bhargava
+// Rob Wills
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// Autocompleter.Base handles all the autocompletion functionality
+// that's independent of the data source for autocompletion. This
+// includes drawing the autocompletion menu, observing keyboard
+// and mouse events, and similar.
+//
+// Specific autocompleters need to provide, at the very least,
+// a getUpdatedChoices function that will be invoked every time
+// the text inside the monitored textbox changes. This method
+// should get the text for which to provide autocompletion by
+// invoking this.getToken(), NOT by directly accessing
+// this.element.value. This is to allow incremental tokenized
+// autocompletion. Specific auto-completion logic (AJAX, etc)
+// belongs in getUpdatedChoices.
+//
+// Tokenized incremental autocompletion is enabled automatically
+// when an autocompleter is instantiated with the 'tokens' option
+// in the options parameter, e.g.:
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
+// will incrementally autocomplete with a comma as the token.
+// Additionally, ',' in the above example can be replaced with
+// a token array, e.g. { tokens: [',', '\n'] } which
+// enables autocompletion on multiple tokens. This is most
+// useful when one of the tokens is \n (a newline), as it
+// allows smart autocompletion after linebreaks.
+
+if(typeof Effect == 'undefined')
+ throw("controls.js requires including script.aculo.us' effects.js library");
+
+var Autocompleter = { }
+Autocompleter.Base = Class.create({
+ baseInitialize: function(element, update, options) {
+ element = $(element)
+ this.element = element;
+ this.update = $(update);
+ this.hasFocus = false;
+ this.changed = false;
+ this.active = false;
+ this.index = 0;
+ this.entryCount = 0;
+ this.oldElementValue = this.element.value;
+
+ if(this.setOptions)
+ this.setOptions(options);
+ else
+ this.options = options || { };
+
+ this.options.paramName = this.options.paramName || this.element.name;
+ this.options.tokens = this.options.tokens || [];
+ this.options.frequency = this.options.frequency || 0.4;
+ this.options.minChars = this.options.minChars || 1;
+ this.options.onShow = this.options.onShow ||
+ function(element, update){
+ if(!update.style.position || update.style.position=='absolute') {
+ update.style.position = 'absolute';
+ Position.clone(element, update, {
+ setHeight: false,
+ offsetTop: element.offsetHeight
+ });
+ }
+ Effect.Appear(update,{duration:0.15});
+ };
+ this.options.onHide = this.options.onHide ||
+ function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+
+ if(typeof(this.options.tokens) == 'string')
+ this.options.tokens = new Array(this.options.tokens);
+ // Force carriage returns as token delimiters anyway
+ if (!this.options.tokens.include('\n'))
+ this.options.tokens.push('\n');
+
+ this.observer = null;
+
+ this.element.setAttribute('autocomplete','off');
+
+ Element.hide(this.update);
+
+ Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
+ Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
+ },
+
+ show: function() {
+ if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+ if(!this.iefix &&
+ (Prototype.Browser.IE) &&
+ (Element.getStyle(this.update, 'position')=='absolute')) {
+ new Insertion.After(this.update,
+ '<iframe id="' + this.update.id + '_iefix" '+
+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+ 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+ this.iefix = $(this.update.id+'_iefix');
+ }
+ if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+ },
+
+ fixIEOverlapping: function() {
+ Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
+ this.iefix.style.zIndex = 1;
+ this.update.style.zIndex = 2;
+ Element.show(this.iefix);
+ },
+
+ hide: function() {
+ this.stopIndicator();
+ if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
+ if(this.iefix) Element.hide(this.iefix);
+ },
+
+ startIndicator: function() {
+ if(this.options.indicator) Element.show(this.options.indicator);
+ },
+
+ stopIndicator: function() {
+ if(this.options.indicator) Element.hide(this.options.indicator);
+ },
+
+ onKeyPress: function(event) {
+ if(this.active)
+ switch(event.keyCode) {
+ case Event.KEY_TAB:
+ case Event.KEY_RETURN:
+ this.selectEntry();
+ Event.stop(event);
+ case Event.KEY_ESC:
+ this.hide();
+ this.active = false;
+ Event.stop(event);
+ return;
+ case Event.KEY_LEFT:
+ case Event.KEY_RIGHT:
+ return;
+ case Event.KEY_UP:
+ this.markPrevious();
+ this.render();
+ Event.stop(event);
+ return;
+ case Event.KEY_DOWN:
+ this.markNext();
+ this.render();
+ Event.stop(event);
+ return;
+ }
+ else
+ if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
+ (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
+
+ this.changed = true;
+ this.hasFocus = true;
+
+ if(this.observer) clearTimeout(this.observer);
+ this.observer =
+ setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
+ },
+
+ activate: function() {
+ this.changed = false;
+ this.hasFocus = true;
+ this.getUpdatedChoices();
+ },
+
+ onHover: function(event) {
+ var element = Event.findElement(event, 'LI');
+ if(this.index != element.autocompleteIndex)
+ {
+ this.index = element.autocompleteIndex;
+ this.render();
+ }
+ Event.stop(event);
+ },
+
+ onClick: function(event) {
+ var element = Event.findElement(event, 'LI');
+ this.index = element.autocompleteIndex;
+ this.selectEntry();
+ this.hide();
+ },
+
+ onBlur: function(event) {
+ // needed to make click events working
+ setTimeout(this.hide.bind(this), 250);
+ this.hasFocus = false;
+ this.active = false;
+ },
+
+ render: function() {
+ if(this.entryCount > 0) {
+ for (var i = 0; i < this.entryCount; i++)
+ this.index==i ?
+ Element.addClassName(this.getEntry(i),"selected") :
+ Element.removeClassName(this.getEntry(i),"selected");
+ if(this.hasFocus) {
+ this.show();
+ this.active = true;
+ }
+ } else {
+ this.active = false;
+ this.hide();
+ }
+ },
+
+ markPrevious: function() {
+ if(this.index > 0) this.index--
+ else this.index = this.entryCount-1;
+ this.getEntry(this.index).scrollIntoView(true);
+ },
+
+ markNext: function() {
+ if(this.index < this.entryCount-1) this.index++
+ else this.index = 0;
+ this.getEntry(this.index).scrollIntoView(false);
+ },
+
+ getEntry: function(index) {
+ return this.update.firstChild.childNodes[index];
+ },
+
+ getCurrentEntry: function() {
+ return this.getEntry(this.index);
+ },
+
+ selectEntry: function() {
+ this.active = false;
+ this.updateElement(this.getCurrentEntry());
+ },
+
+ updateElement: function(selectedElement) {
+ if (this.options.updateElement) {
+ this.options.updateElement(selectedElement);
+ return;
+ }
+ var value = '';
+ if (this.options.select) {
+ var nodes = $(selectedElement).select('.' + this.options.select) || [];
+ if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
+ } else
+ value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
+
+ var bounds = this.getTokenBounds();
+ if (bounds[0] != -1) {
+ var newValue = this.element.value.substr(0, bounds[0]);
+ var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
+ if (whitespace)
+ newValue += whitespace[0];
+ this.element.value = newValue + value + this.element.value.substr(bounds[1]);
+ } else {
+ this.element.value = value;
+ }
+ this.oldElementValue = this.element.value;
+ this.element.focus();
+
+ if (this.options.afterUpdateElement)
+ this.options.afterUpdateElement(this.element, selectedElement);
+ },
+
+ updateChoices: function(choices) {
+ if(!this.changed && this.hasFocus) {
+ this.update.innerHTML = choices;
+ Element.cleanWhitespace(this.update);
+ Element.cleanWhitespace(this.update.down());
+
+ if(this.update.firstChild && this.update.down().childNodes) {
+ this.entryCount =
+ this.update.down().childNodes.length;
+ for (var i = 0; i < this.entryCount; i++) {
+ var entry = this.getEntry(i);
+ entry.autocompleteIndex = i;
+ this.addObservers(entry);
+ }
+ } else {
+ this.entryCount = 0;
+ }
+
+ this.stopIndicator();
+ this.index = 0;
+
+ if(this.entryCount==1 && this.options.autoSelect) {
+ this.selectEntry();
+ this.hide();
+ } else {
+ this.render();
+ }
+ }
+ },
+
+ addObservers: function(element) {
+ Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
+ Event.observe(element, "click", this.onClick.bindAsEventListener(this));
+ },
+
+ onObserverEvent: function() {
+ this.changed = false;
+ this.tokenBounds = null;
+ if(this.getToken().length>=this.options.minChars) {
+ this.getUpdatedChoices();
+ } else {
+ this.active = false;
+ this.hide();
+ }
+ this.oldElementValue = this.element.value;
+ },
+
+ getToken: function() {
+ var bounds = this.getTokenBounds();
+ return this.element.value.substring(bounds[0], bounds[1]).strip();
+ },
+
+ getTokenBounds: function() {
+ if (null != this.tokenBounds) return this.tokenBounds;
+ var value = this.element.value;
+ if (value.strip().empty()) return [-1, 0];
+ var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
+ var offset = (diff == this.oldElementValue.length ? 1 : 0);
+ var prevTokenPos = -1, nextTokenPos = value.length;
+ var tp;
+ for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
+ tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
+ if (tp > prevTokenPos) prevTokenPos = tp;
+ tp = value.indexOf(this.options.tokens[index], diff + offset);
+ if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
+ }
+ return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
+ }
+});
+
+Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
+ var boundary = Math.min(newS.length, oldS.length);
+ for (var index = 0; index < boundary; ++index)
+ if (newS[index] != oldS[index])
+ return index;
+ return boundary;
+};
+
+Ajax.Autocompleter = Class.create(Autocompleter.Base, {
+ initialize: function(element, update, url, options) {
+ this.baseInitialize(element, update, options);
+ this.options.asynchronous = true;
+ this.options.onComplete = this.onComplete.bind(this);
+ this.options.defaultParams = this.options.parameters || null;
+ this.url = url;
+ },
+
+ getUpdatedChoices: function() {
+ this.startIndicator();
+
+ var entry = encodeURIComponent(this.options.paramName) + '=' +
+ encodeURIComponent(this.getToken());
+
+ this.options.parameters = this.options.callback ?
+ this.options.callback(this.element, entry) : entry;
+
+ if(this.options.defaultParams)
+ this.options.parameters += '&' + this.options.defaultParams;
+
+ new Ajax.Request(this.url, this.options);
+ },
+
+ onComplete: function(request) {
+ this.updateChoices(request.responseText);
+ }
+});
+
+// The local array autocompleter. Used when you'd prefer to
+// inject an array of autocompletion options into the page, rather
+// than sending out Ajax queries, which can be quite slow sometimes.
+//
+// The constructor takes four parameters. The first two are, as usual,
+// the id of the monitored textbox, and id of the autocompletion menu.
+// The third is the array you want to autocomplete from, and the fourth
+// is the options block.
+//
+// Extra local autocompletion options:
+// - choices - How many autocompletion choices to offer
+//
+// - partialSearch - If false, the autocompleter will match entered
+// text only at the beginning of strings in the
+// autocomplete array. Defaults to true, which will
+// match text at the beginning of any *word* in the
+// strings in the autocomplete array. If you want to
+// search anywhere in the string, additionally set
+// the option fullSearch to true (default: off).
+//
+// - fullSsearch - Search anywhere in autocomplete array strings.
+//
+// - partialChars - How many characters to enter before triggering
+// a partial match (unlike minChars, which defines
+// how many characters are required to do any match
+// at all). Defaults to 2.
+//
+// - ignoreCase - Whether to ignore case when autocompleting.
+// Defaults to true.
+//
+// It's possible to pass in a custom function as the 'selector'
+// option, if you prefer to write your own autocompletion logic.
+// In that case, the other options above will not apply unless
+// you support them.
+
+Autocompleter.Local = Class.create(Autocompleter.Base, {
+ initialize: function(element, update, array, options) {
+ this.baseInitialize(element, update, options);
+ this.options.array = array;
+ },
+
+ getUpdatedChoices: function() {
+ this.updateChoices(this.options.selector(this));
+ },
+
+ setOptions: function(options) {
+ this.options = Object.extend({
+ choices: 10,
+ partialSearch: true,
+ partialChars: 2,
+ ignoreCase: true,
+ fullSearch: false,
+ selector: function(instance) {
+ var ret = []; // Beginning matches
+ var partial = []; // Inside matches
+ var entry = instance.getToken();
+ var count = 0;
+
+ for (var i = 0; i < instance.options.array.length &&
+ ret.length < instance.options.choices ; i++) {
+
+ var elem = instance.options.array[i];
+ var foundPos = instance.options.ignoreCase ?
+ elem.toLowerCase().indexOf(entry.toLowerCase()) :
+ elem.indexOf(entry);
+
+ while (foundPos != -1) {
+ if (foundPos == 0 && elem.length != entry.length) {
+ ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
+ elem.substr(entry.length) + "</li>");
+ break;
+ } else if (entry.length >= instance.options.partialChars &&
+ instance.options.partialSearch && foundPos != -1) {
+ if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
+ partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
+ elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
+ foundPos + entry.length) + "</li>");
+ break;
+ }
+ }
+
+ foundPos = instance.options.ignoreCase ?
+ elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
+ elem.indexOf(entry, foundPos + 1);
+
+ }
+ }
+ if (partial.length)
+ ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
+ return "<ul>" + ret.join('') + "</ul>";
+ }
+ }, options || { });
+ }
+});
+
+// AJAX in-place editor and collection editor
+// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
+
+// Use this if you notice weird scrolling problems on some browsers,
+// the DOM might be a bit confused when this gets called so do this
+// waits 1 ms (with setTimeout) until it does the activation
+Field.scrollFreeActivate = function(field) {
+ setTimeout(function() {
+ Field.activate(field);
+ }, 1);
+}
+
+Ajax.InPlaceEditor = Class.create({
+ initialize: function(element, url, options) {
+ this.url = url;
+ this.element = element = $(element);
+ this.prepareOptions();
+ this._controls = { };
+ arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
+ Object.extend(this.options, options || { });
+ if (!this.options.formId && this.element.id) {
+ this.options.formId = this.element.id + '-inplaceeditor';
+ if ($(this.options.formId))
+ this.options.formId = '';
+ }
+ if (this.options.externalControl)
+ this.options.externalControl = $(this.options.externalControl);
+ if (!this.options.externalControl)
+ this.options.externalControlOnly = false;
+ this._originalBackground = this.element.getStyle('background-color') || 'transparent';
+ this.element.title = this.options.clickToEditText;
+ this._boundCancelHandler = this.handleFormCancellation.bind(this);
+ this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
+ this._boundFailureHandler = this.handleAJAXFailure.bind(this);
+ this._boundSubmitHandler = this.handleFormSubmission.bind(this);
+ this._boundWrapperHandler = this.wrapUp.bind(this);
+ this.registerListeners();
+ },
+ checkForEscapeOrReturn: function(e) {
+ if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
+ if (Event.KEY_ESC == e.keyCode)
+ this.handleFormCancellation(e);
+ else if (Event.KEY_RETURN == e.keyCode)
+ this.handleFormSubmission(e);
+ },
+ createControl: function(mode, handler, extraClasses) {
+ var control = this.options[mode + 'Control'];
+ var text = this.options[mode + 'Text'];
+ if ('button' == control) {
+ var btn = document.createElement('input');
+ btn.type = 'submit';
+ btn.value = text;
+ btn.className = 'editor_' + mode + '_button';
+ if ('cancel' == mode)
+ btn.onclick = this._boundCancelHandler;
+ this._form.appendChild(btn);
+ this._controls[mode] = btn;
+ } else if ('link' == control) {
+ var link = document.createElement('a');
+ link.href = '#';
+ link.appendChild(document.createTextNode(text));
+ link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
+ link.className = 'editor_' + mode + '_link';
+ if (extraClasses)
+ link.className += ' ' + extraClasses;
+ this._form.appendChild(link);
+ this._controls[mode] = link;
+ }
+ },
+ createEditField: function() {
+ var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
+ var fld;
+ if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
+ fld = document.createElement('input');
+ fld.type = 'text';
+ var size = this.options.size || this.options.cols || 0;
+ if (0 < size) fld.size = size;
+ } else {
+ fld = document.createElement('textarea');
+ fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
+ fld.cols = this.options.cols || 40;
+ }
+ fld.name = this.options.paramName;
+ fld.value = text; // No HTML breaks conversion anymore
+ fld.className = 'editor_field';
+ if (this.options.submitOnBlur)
+ fld.onblur = this._boundSubmitHandler;
+ this._controls.editor = fld;
+ if (this.options.loadTextURL)
+ this.loadExternalText();
+ this._form.appendChild(this._controls.editor);
+ },
+ createForm: function() {
+ var ipe = this;
+ function addText(mode, condition) {
+ var text = ipe.options['text' + mode + 'Controls'];
+ if (!text || condition === false) return;
+ ipe._form.appendChild(document.createTextNode(text));
+ };
+ this._form = $(document.createElement('form'));
+ this._form.id = this.options.formId;
+ this._form.addClassName(this.options.formClassName);
+ this._form.onsubmit = this._boundSubmitHandler;
+ this.createEditField();
+ if ('textarea' == this._controls.editor.tagName.toLowerCase())
+ this._form.appendChild(document.createElement('br'));
+ if (this.options.onFormCustomization)
+ this.options.onFormCustomization(this, this._form);
+ addText('Before', this.options.okControl || this.options.cancelControl);
+ this.createControl('ok', this._boundSubmitHandler);
+ addText('Between', this.options.okControl && this.options.cancelControl);
+ this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
+ addText('After', this.options.okControl || this.options.cancelControl);
+ },
+ destroy: function() {
+ if (this._oldInnerHTML)
+ this.element.innerHTML = this._oldInnerHTML;
+ this.leaveEditMode();
+ this.unregisterListeners();
+ },
+ enterEditMode: function(e) {
+ if (this._saving || this._editing) return;
+ this._editing = true;
+ this.triggerCallback('onEnterEditMode');
+ if (this.options.externalControl)
+ this.options.externalControl.hide();
+ this.element.hide();
+ this.createForm();
+ this.element.parentNode.insertBefore(this._form, this.element);
+ if (!this.options.loadTextURL)
+ this.postProcessEditField();
+ if (e) Event.stop(e);
+ },
+ enterHover: function(e) {
+ if (this.options.hoverClassName)
+ this.element.addClassName(this.options.hoverClassName);
+ if (this._saving) return;
+ this.triggerCallback('onEnterHover');
+ },
+ getText: function() {
+ return this.element.innerHTML;
+ },
+ handleAJAXFailure: function(transport) {
+ this.triggerCallback('onFailure', transport);
+ if (this._oldInnerHTML) {
+ this.element.innerHTML = this._oldInnerHTML;
+ this._oldInnerHTML = null;
+ }
+ },
+ handleFormCancellation: function(e) {
+ this.wrapUp();
+ if (e) Event.stop(e);
+ },
+ handleFormSubmission: function(e) {
+ var form = this._form;
+ var value = $F(this._controls.editor);
+ this.prepareSubmission();
+ var params = this.options.callback(form, value) || '';
+ if (Object.isString(params))
+ params = params.toQueryParams();
+ params.editorId = this.element.id;
+ if (this.options.htmlResponse) {
+ var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: params,
+ onComplete: this._boundWrapperHandler,
+ onFailure: this._boundFailureHandler
+ });
+ new Ajax.Updater({ success: this.element }, this.url, options);
+ } else {
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: params,
+ onComplete: this._boundWrapperHandler,
+ onFailure: this._boundFailureHandler
+ });
+ new Ajax.Request(this.url, options);
+ }
+ if (e) Event.stop(e);
+ },
+ leaveEditMode: function() {
+ this.element.removeClassName(this.options.savingClassName);
+ this.removeForm();
+ this.leaveHover();
+ this.element.style.backgroundColor = this._originalBackground;
+ this.element.show();
+ if (this.options.externalControl)
+ this.options.externalControl.show();
+ this._saving = false;
+ this._editing = false;
+ this._oldInnerHTML = null;
+ this.triggerCallback('onLeaveEditMode');
+ },
+ leaveHover: function(e) {
+ if (this.options.hoverClassName)
+ this.element.removeClassName(this.options.hoverClassName);
+ if (this._saving) return;
+ this.triggerCallback('onLeaveHover');
+ },
+ loadExternalText: function() {
+ this._form.addClassName(this.options.loadingClassName);
+ this._controls.editor.disabled = true;
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
+ onComplete: Prototype.emptyFunction,
+ onSuccess: function(transport) {
+ this._form.removeClassName(this.options.loadingClassName);
+ var text = transport.responseText;
+ if (this.options.stripLoadedTextTags)
+ text = text.stripTags();
+ this._controls.editor.value = text;
+ this._controls.editor.disabled = false;
+ this.postProcessEditField();
+ }.bind(this),
+ onFailure: this._boundFailureHandler
+ });
+ new Ajax.Request(this.options.loadTextURL, options);
+ },
+ postProcessEditField: function() {
+ var fpc = this.options.fieldPostCreation;
+ if (fpc)
+ $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
+ },
+ prepareOptions: function() {
+ this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
+ Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
+ [this._extraDefaultOptions].flatten().compact().each(function(defs) {
+ Object.extend(this.options, defs);
+ }.bind(this));
+ },
+ prepareSubmission: function() {
+ this._saving = true;
+ this.removeForm();
+ this.leaveHover();
+ this.showSaving();
+ },
+ registerListeners: function() {
+ this._listeners = { };
+ var listener;
+ $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
+ listener = this[pair.value].bind(this);
+ this._listeners[pair.key] = listener;
+ if (!this.options.externalControlOnly)
+ this.element.observe(pair.key, listener);
+ if (this.options.externalControl)
+ this.options.externalControl.observe(pair.key, listener);
+ }.bind(this));
+ },
+ removeForm: function() {
+ if (!this._form) return;
+ this._form.remove();
+ this._form = null;
+ this._controls = { };
+ },
+ showSaving: function() {
+ this._oldInnerHTML = this.element.innerHTML;
+ this.element.innerHTML = this.options.savingText;
+ this.element.addClassName(this.options.savingClassName);
+ this.element.style.backgroundColor = this._originalBackground;
+ this.element.show();
+ },
+ triggerCallback: function(cbName, arg) {
+ if ('function' == typeof this.options[cbName]) {
+ this.options[cbName](this, arg);
+ }
+ },
+ unregisterListeners: function() {
+ $H(this._listeners).each(function(pair) {
+ if (!this.options.externalControlOnly)
+ this.element.stopObserving(pair.key, pair.value);
+ if (this.options.externalControl)
+ this.options.externalControl.stopObserving(pair.key, pair.value);
+ }.bind(this));
+ },
+ wrapUp: function(transport) {
+ this.leaveEditMode();
+ // Can't use triggerCallback due to backward compatibility: requires
+ // binding + direct element
+ this._boundComplete(transport, this.element);
+ }
+});
+
+Object.extend(Ajax.InPlaceEditor.prototype, {
+ dispose: Ajax.InPlaceEditor.prototype.destroy
+});
+
+Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
+ initialize: function($super, element, url, options) {
+ this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
+ $super(element, url, options);
+ },
+
+ createEditField: function() {
+ var list = document.createElement('select');
+ list.name = this.options.paramName;
+ list.size = 1;
+ this._controls.editor = list;
+ this._collection = this.options.collection || [];
+ if (this.options.loadCollectionURL)
+ this.loadCollection();
+ else
+ this.checkForExternalText();
+ this._form.appendChild(this._controls.editor);
+ },
+
+ loadCollection: function() {
+ this._form.addClassName(this.options.loadingClassName);
+ this.showLoadingText(this.options.loadingCollectionText);
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
+ onComplete: Prototype.emptyFunction,
+ onSuccess: function(transport) {
+ var js = transport.responseText.strip();
+ if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
+ throw 'Server returned an invalid collection representation.';
+ this._collection = eval(js);
+ this.checkForExternalText();
+ }.bind(this),
+ onFailure: this.onFailure
+ });
+ new Ajax.Request(this.options.loadCollectionURL, options);
+ },
+
+ showLoadingText: function(text) {
+ this._controls.editor.disabled = true;
+ var tempOption = this._controls.editor.firstChild;
+ if (!tempOption) {
+ tempOption = document.createElement('option');
+ tempOption.value = '';
+ this._controls.editor.appendChild(tempOption);
+ tempOption.selected = true;
+ }
+ tempOption.update((text || '').stripScripts().stripTags());
+ },
+
+ checkForExternalText: function() {
+ this._text = this.getText();
+ if (this.options.loadTextURL)
+ this.loadExternalText();
+ else
+ this.buildOptionList();
+ },
+
+ loadExternalText: function() {
+ this.showLoadingText(this.options.loadingText);
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
+ onComplete: Prototype.emptyFunction,
+ onSuccess: function(transport) {
+ this._text = transport.responseText.strip();
+ this.buildOptionList();
+ }.bind(this),
+ onFailure: this.onFailure
+ });
+ new Ajax.Request(this.options.loadTextURL, options);
+ },
+
+ buildOptionList: function() {
+ this._form.removeClassName(this.options.loadingClassName);
+ this._collection = this._collection.map(function(entry) {
+ return 2 === entry.length ? entry : [entry, entry].flatten();
+ });
+ var marker = ('value' in this.options) ? this.options.value : this._text;
+ var textFound = this._collection.any(function(entry) {
+ return entry[0] == marker;
+ }.bind(this));
+ this._controls.editor.update('');
+ var option;
+ this._collection.each(function(entry, index) {
+ option = document.createElement('option');
+ option.value = entry[0];
+ option.selected = textFound ? entry[0] == marker : 0 == index;
+ option.appendChild(document.createTextNode(entry[1]));
+ this._controls.editor.appendChild(option);
+ }.bind(this));
+ this._controls.editor.disabled = false;
+ Field.scrollFreeActivate(this._controls.editor);
+ }
+});
+
+//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
+//**** This only exists for a while, in order to let ****
+//**** users adapt to the new API. Read up on the new ****
+//**** API and convert your code to it ASAP! ****
+
+Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
+ if (!options) return;
+ function fallback(name, expr) {
+ if (name in options || expr === undefined) return;
+ options[name] = expr;
+ };
+ fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
+ options.cancelLink == options.cancelButton == false ? false : undefined)));
+ fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
+ options.okLink == options.okButton == false ? false : undefined)));
+ fallback('highlightColor', options.highlightcolor);
+ fallback('highlightEndColor', options.highlightendcolor);
+};
+
+Object.extend(Ajax.InPlaceEditor, {
+ DefaultOptions: {
+ ajaxOptions: { },
+ autoRows: 3, // Use when multi-line w/ rows == 1
+ cancelControl: 'link', // 'link'|'button'|false
+ cancelText: 'cancel',
+ clickToEditText: 'Click to edit',
+ externalControl: null, // id|elt
+ externalControlOnly: false,
+ fieldPostCreation: 'activate', // 'activate'|'focus'|false
+ formClassName: 'inplaceeditor-form',
+ formId: null, // id|elt
+ highlightColor: '#ffff99',
+ highlightEndColor: '#ffffff',
+ hoverClassName: '',
+ htmlResponse: true,
+ loadingClassName: 'inplaceeditor-loading',
+ loadingText: 'Loading...',
+ okControl: 'button', // 'link'|'button'|false
+ okText: 'ok',
+ paramName: 'value',
+ rows: 1, // If 1 and multi-line, uses autoRows
+ savingClassName: 'inplaceeditor-saving',
+ savingText: 'Saving...',
+ size: 0,
+ stripLoadedTextTags: false,
+ submitOnBlur: false,
+ textAfterControls: '',
+ textBeforeControls: '',
+ textBetweenControls: ''
+ },
+ DefaultCallbacks: {
+ callback: function(form) {
+ return Form.serialize(form);
+ },
+ onComplete: function(transport, element) {
+ // For backward compatibility, this one is bound to the IPE, and passes
+ // the element directly. It was too often customized, so we don't break it.
+ new Effect.Highlight(element, {
+ startcolor: this.options.highlightColor, keepBackgroundImage: true });
+ },
+ onEnterEditMode: null,
+ onEnterHover: function(ipe) {
+ ipe.element.style.backgroundColor = ipe.options.highlightColor;
+ if (ipe._effect)
+ ipe._effect.cancel();
+ },
+ onFailure: function(transport, ipe) {
+ alert('Error communication with the server: ' + transport.responseText.stripTags());
+ },
+ onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
+ onLeaveEditMode: null,
+ onLeaveHover: function(ipe) {
+ ipe._effect = new Effect.Highlight(ipe.element, {
+ startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
+ restorecolor: ipe._originalBackground, keepBackgroundImage: true
+ });
+ }
+ },
+ Listeners: {
+ click: 'enterEditMode',
+ keydown: 'checkForEscapeOrReturn',
+ mouseover: 'enterHover',
+ mouseout: 'leaveHover'
+ }
+});
+
+Ajax.InPlaceCollectionEditor.DefaultOptions = {
+ loadingCollectionText: 'Loading options...'
+};
+
+// Delayed observer, like Form.Element.Observer,
+// but waits for delay after last key input
+// Ideal for live-search fields
+
+Form.Element.DelayedObserver = Class.create({
+ initialize: function(element, delay, callback) {
+ this.delay = delay || 0.5;
+ this.element = $(element);
+ this.callback = callback;
+ this.timer = null;
+ this.lastValue = $F(this.element);
+ Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
+ },
+ delayedListener: function(event) {
+ if(this.lastValue == $F(this.element)) return;
+ if(this.timer) clearTimeout(this.timer);
+ this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
+ this.lastValue = $F(this.element);
+ },
+ onTimerEvent: function() {
+ this.timer = null;
+ this.callback(this.element, $F(this.element));
+ }
+});
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/dragdrop.js b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/dragdrop.js
new file mode 100644
index 000000000..ccf4a1e45
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/dragdrop.js
@@ -0,0 +1,972 @@
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+if(Object.isUndefined(Effect))
+ throw("dragdrop.js requires including script.aculo.us' effects.js library");
+
+var Droppables = {
+ drops: [],
+
+ remove: function(element) {
+ this.drops = this.drops.reject(function(d) { return d.element==$(element) });
+ },
+
+ add: function(element) {
+ element = $(element);
+ var options = Object.extend({
+ greedy: true,
+ hoverclass: null,
+ tree: false
+ }, arguments[1] || { });
+
+ // cache containers
+ if(options.containment) {
+ options._containers = [];
+ var containment = options.containment;
+ if(Object.isArray(containment)) {
+ containment.each( function(c) { options._containers.push($(c)) });
+ } else {
+ options._containers.push($(containment));
+ }
+ }
+
+ if(options.accept) options.accept = [options.accept].flatten();
+
+ Element.makePositioned(element); // fix IE
+ options.element = element;
+
+ this.drops.push(options);
+ },
+
+ findDeepestChild: function(drops) {
+ deepest = drops[0];
+
+ for (i = 1; i < drops.length; ++i)
+ if (Element.isParent(drops[i].element, deepest.element))
+ deepest = drops[i];
+
+ return deepest;
+ },
+
+ isContained: function(element, drop) {
+ var containmentNode;
+ if(drop.tree) {
+ containmentNode = element.treeNode;
+ } else {
+ containmentNode = element.parentNode;
+ }
+ return drop._containers.detect(function(c) { return containmentNode == c });
+ },
+
+ isAffected: function(point, element, drop) {
+ return (
+ (drop.element!=element) &&
+ ((!drop._containers) ||
+ this.isContained(element, drop)) &&
+ ((!drop.accept) ||
+ (Element.classNames(element).detect(
+ function(v) { return drop.accept.include(v) } ) )) &&
+ Position.within(drop.element, point[0], point[1]) );
+ },
+
+ deactivate: function(drop) {
+ if(drop.hoverclass)
+ Element.removeClassName(drop.element, drop.hoverclass);
+ this.last_active = null;
+ },
+
+ activate: function(drop) {
+ if(drop.hoverclass)
+ Element.addClassName(drop.element, drop.hoverclass);
+ this.last_active = drop;
+ },
+
+ show: function(point, element) {
+ if(!this.drops.length) return;
+ var drop, affected = [];
+
+ this.drops.each( function(drop) {
+ if(Droppables.isAffected(point, element, drop))
+ affected.push(drop);
+ });
+
+ if(affected.length>0)
+ drop = Droppables.findDeepestChild(affected);
+
+ if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
+ if (drop) {
+ Position.within(drop.element, point[0], point[1]);
+ if(drop.onHover)
+ drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
+
+ if (drop != this.last_active) Droppables.activate(drop);
+ }
+ },
+
+ fire: function(event, element) {
+ if(!this.last_active) return;
+ Position.prepare();
+
+ if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
+ if (this.last_active.onDrop) {
+ this.last_active.onDrop(element, this.last_active.element, event);
+ return true;
+ }
+ },
+
+ reset: function() {
+ if(this.last_active)
+ this.deactivate(this.last_active);
+ }
+}
+
+var Draggables = {
+ drags: [],
+ observers: [],
+
+ register: function(draggable) {
+ if(this.drags.length == 0) {
+ this.eventMouseUp = this.endDrag.bindAsEventListener(this);
+ this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
+ this.eventKeypress = this.keyPress.bindAsEventListener(this);
+
+ Event.observe(document, "mouseup", this.eventMouseUp);
+ Event.observe(document, "mousemove", this.eventMouseMove);
+ Event.observe(document, "keypress", this.eventKeypress);
+ }
+ this.drags.push(draggable);
+ },
+
+ unregister: function(draggable) {
+ this.drags = this.drags.reject(function(d) { return d==draggable });
+ if(this.drags.length == 0) {
+ Event.stopObserving(document, "mouseup", this.eventMouseUp);
+ Event.stopObserving(document, "mousemove", this.eventMouseMove);
+ Event.stopObserving(document, "keypress", this.eventKeypress);
+ }
+ },
+
+ activate: function(draggable) {
+ if(draggable.options.delay) {
+ this._timeout = setTimeout(function() {
+ Draggables._timeout = null;
+ window.focus();
+ Draggables.activeDraggable = draggable;
+ }.bind(this), draggable.options.delay);
+ } else {
+ window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
+ this.activeDraggable = draggable;
+ }
+ },
+
+ deactivate: function() {
+ this.activeDraggable = null;
+ },
+
+ updateDrag: function(event) {
+ if(!this.activeDraggable) return;
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ // Mozilla-based browsers fire successive mousemove events with
+ // the same coordinates, prevent needless redrawing (moz bug?)
+ if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
+ this._lastPointer = pointer;
+
+ this.activeDraggable.updateDrag(event, pointer);
+ },
+
+ endDrag: function(event) {
+ if(this._timeout) {
+ clearTimeout(this._timeout);
+ this._timeout = null;
+ }
+ if(!this.activeDraggable) return;
+ this._lastPointer = null;
+ this.activeDraggable.endDrag(event);
+ this.activeDraggable = null;
+ },
+
+ keyPress: function(event) {
+ if(this.activeDraggable)
+ this.activeDraggable.keyPress(event);
+ },
+
+ addObserver: function(observer) {
+ this.observers.push(observer);
+ this._cacheObserverCallbacks();
+ },
+
+ removeObserver: function(element) { // element instead of observer fixes mem leaks
+ this.observers = this.observers.reject( function(o) { return o.element==element });
+ this._cacheObserverCallbacks();
+ },
+
+ notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
+ if(this[eventName+'Count'] > 0)
+ this.observers.each( function(o) {
+ if(o[eventName]) o[eventName](eventName, draggable, event);
+ });
+ if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
+ },
+
+ _cacheObserverCallbacks: function() {
+ ['onStart','onEnd','onDrag'].each( function(eventName) {
+ Draggables[eventName+'Count'] = Draggables.observers.select(
+ function(o) { return o[eventName]; }
+ ).length;
+ });
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Draggable = Class.create({
+ initialize: function(element) {
+ var defaults = {
+ handle: false,
+ reverteffect: function(element, top_offset, left_offset) {
+ var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
+ new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
+ queue: {scope:'_draggable', position:'end'}
+ });
+ },
+ endeffect: function(element) {
+ var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
+ new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
+ queue: {scope:'_draggable', position:'end'},
+ afterFinish: function(){
+ Draggable._dragging[element] = false
+ }
+ });
+ },
+ zindex: 1000,
+ revert: false,
+ quiet: false,
+ scroll: false,
+ scrollSensitivity: 20,
+ scrollSpeed: 15,
+ snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
+ delay: 0
+ };
+
+ if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
+ Object.extend(defaults, {
+ starteffect: function(element) {
+ element._opacity = Element.getOpacity(element);
+ Draggable._dragging[element] = true;
+ new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
+ }
+ });
+
+ var options = Object.extend(defaults, arguments[1] || { });
+
+ this.element = $(element);
+
+ if(options.handle && Object.isString(options.handle))
+ this.handle = this.element.down('.'+options.handle, 0);
+
+ if(!this.handle) this.handle = $(options.handle);
+ if(!this.handle) this.handle = this.element;
+
+ if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
+ options.scroll = $(options.scroll);
+ this._isScrollChild = Element.childOf(this.element, options.scroll);
+ }
+
+ Element.makePositioned(this.element); // fix IE
+
+ this.options = options;
+ this.dragging = false;
+
+ this.eventMouseDown = this.initDrag.bindAsEventListener(this);
+ Event.observe(this.handle, "mousedown", this.eventMouseDown);
+
+ Draggables.register(this);
+ },
+
+ destroy: function() {
+ Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
+ Draggables.unregister(this);
+ },
+
+ currentDelta: function() {
+ return([
+ parseInt(Element.getStyle(this.element,'left') || '0'),
+ parseInt(Element.getStyle(this.element,'top') || '0')]);
+ },
+
+ initDrag: function(event) {
+ if(!Object.isUndefined(Draggable._dragging[this.element]) &&
+ Draggable._dragging[this.element]) return;
+ if(Event.isLeftClick(event)) {
+ // abort on form elements, fixes a Firefox issue
+ var src = Event.element(event);
+ if((tag_name = src.tagName.toUpperCase()) && (
+ tag_name=='INPUT' ||
+ tag_name=='SELECT' ||
+ tag_name=='OPTION' ||
+ tag_name=='BUTTON' ||
+ tag_name=='TEXTAREA')) return;
+
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ var pos = Position.cumulativeOffset(this.element);
+ this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
+
+ Draggables.activate(this);
+ Event.stop(event);
+ }
+ },
+
+ startDrag: function(event) {
+ this.dragging = true;
+ if(!this.delta)
+ this.delta = this.currentDelta();
+
+ if(this.options.zindex) {
+ this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
+ this.element.style.zIndex = this.options.zindex;
+ }
+
+ if(this.options.ghosting) {
+ this._clone = this.element.cloneNode(true);
+ this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
+ if (!this.element._originallyAbsolute)
+ Position.absolutize(this.element);
+ this.element.parentNode.insertBefore(this._clone, this.element);
+ }
+
+ if(this.options.scroll) {
+ if (this.options.scroll == window) {
+ var where = this._getWindowScroll(this.options.scroll);
+ this.originalScrollLeft = where.left;
+ this.originalScrollTop = where.top;
+ } else {
+ this.originalScrollLeft = this.options.scroll.scrollLeft;
+ this.originalScrollTop = this.options.scroll.scrollTop;
+ }
+ }
+
+ Draggables.notify('onStart', this, event);
+
+ if(this.options.starteffect) this.options.starteffect(this.element);
+ },
+
+ updateDrag: function(event, pointer) {
+ if(!this.dragging) this.startDrag(event);
+
+ if(!this.options.quiet){
+ Position.prepare();
+ Droppables.show(pointer, this.element);
+ }
+
+ Draggables.notify('onDrag', this, event);
+
+ this.draw(pointer);
+ if(this.options.change) this.options.change(this);
+
+ if(this.options.scroll) {
+ this.stopScrolling();
+
+ var p;
+ if (this.options.scroll == window) {
+ with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
+ } else {
+ p = Position.page(this.options.scroll);
+ p[0] += this.options.scroll.scrollLeft + Position.deltaX;
+ p[1] += this.options.scroll.scrollTop + Position.deltaY;
+ p.push(p[0]+this.options.scroll.offsetWidth);
+ p.push(p[1]+this.options.scroll.offsetHeight);
+ }
+ var speed = [0,0];
+ if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
+ if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
+ if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
+ if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
+ this.startScrolling(speed);
+ }
+
+ // fix AppleWebKit rendering
+ if(Prototype.Browser.WebKit) window.scrollBy(0,0);
+
+ Event.stop(event);
+ },
+
+ finishDrag: function(event, success) {
+ this.dragging = false;
+
+ if(this.options.quiet){
+ Position.prepare();
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ Droppables.show(pointer, this.element);
+ }
+
+ if(this.options.ghosting) {
+ if (!this.element._originallyAbsolute)
+ Position.relativize(this.element);
+ delete this.element._originallyAbsolute;
+ Element.remove(this._clone);
+ this._clone = null;
+ }
+
+ var dropped = false;
+ if(success) {
+ dropped = Droppables.fire(event, this.element);
+ if (!dropped) dropped = false;
+ }
+ if(dropped && this.options.onDropped) this.options.onDropped(this.element);
+ Draggables.notify('onEnd', this, event);
+
+ var revert = this.options.revert;
+ if(revert && Object.isFunction(revert)) revert = revert(this.element);
+
+ var d = this.currentDelta();
+ if(revert && this.options.reverteffect) {
+ if (dropped == 0 || revert != 'failure')
+ this.options.reverteffect(this.element,
+ d[1]-this.delta[1], d[0]-this.delta[0]);
+ } else {
+ this.delta = d;
+ }
+
+ if(this.options.zindex)
+ this.element.style.zIndex = this.originalZ;
+
+ if(this.options.endeffect)
+ this.options.endeffect(this.element);
+
+ Draggables.deactivate(this);
+ Droppables.reset();
+ },
+
+ keyPress: function(event) {
+ if(event.keyCode!=Event.KEY_ESC) return;
+ this.finishDrag(event, false);
+ Event.stop(event);
+ },
+
+ endDrag: function(event) {
+ if(!this.dragging) return;
+ this.stopScrolling();
+ this.finishDrag(event, true);
+ Event.stop(event);
+ },
+
+ draw: function(point) {
+ var pos = Position.cumulativeOffset(this.element);
+ if(this.options.ghosting) {
+ var r = Position.realOffset(this.element);
+ pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
+ }
+
+ var d = this.currentDelta();
+ pos[0] -= d[0]; pos[1] -= d[1];
+
+ if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
+ pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
+ pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
+ }
+
+ var p = [0,1].map(function(i){
+ return (point[i]-pos[i]-this.offset[i])
+ }.bind(this));
+
+ if(this.options.snap) {
+ if(Object.isFunction(this.options.snap)) {
+ p = this.options.snap(p[0],p[1],this);
+ } else {
+ if(Object.isArray(this.options.snap)) {
+ p = p.map( function(v, i) {
+ return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
+ } else {
+ p = p.map( function(v) {
+ return (v/this.options.snap).round()*this.options.snap }.bind(this))
+ }
+ }}
+
+ var style = this.element.style;
+ if((!this.options.constraint) || (this.options.constraint=='horizontal'))
+ style.left = p[0] + "px";
+ if((!this.options.constraint) || (this.options.constraint=='vertical'))
+ style.top = p[1] + "px";
+
+ if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
+ },
+
+ stopScrolling: function() {
+ if(this.scrollInterval) {
+ clearInterval(this.scrollInterval);
+ this.scrollInterval = null;
+ Draggables._lastScrollPointer = null;
+ }
+ },
+
+ startScrolling: function(speed) {
+ if(!(speed[0] || speed[1])) return;
+ this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
+ this.lastScrolled = new Date();
+ this.scrollInterval = setInterval(this.scroll.bind(this), 10);
+ },
+
+ scroll: function() {
+ var current = new Date();
+ var delta = current - this.lastScrolled;
+ this.lastScrolled = current;
+ if(this.options.scroll == window) {
+ with (this._getWindowScroll(this.options.scroll)) {
+ if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
+ var d = delta / 1000;
+ this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
+ }
+ }
+ } else {
+ this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
+ this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
+ }
+
+ Position.prepare();
+ Droppables.show(Draggables._lastPointer, this.element);
+ Draggables.notify('onDrag', this);
+ if (this._isScrollChild) {
+ Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
+ Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
+ Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
+ if (Draggables._lastScrollPointer[0] < 0)
+ Draggables._lastScrollPointer[0] = 0;
+ if (Draggables._lastScrollPointer[1] < 0)
+ Draggables._lastScrollPointer[1] = 0;
+ this.draw(Draggables._lastScrollPointer);
+ }
+
+ if(this.options.change) this.options.change(this);
+ },
+
+ _getWindowScroll: function(w) {
+ var T, L, W, H;
+ with (w.document) {
+ if (w.document.documentElement && documentElement.scrollTop) {
+ T = documentElement.scrollTop;
+ L = documentElement.scrollLeft;
+ } else if (w.document.body) {
+ T = body.scrollTop;
+ L = body.scrollLeft;
+ }
+ if (w.innerWidth) {
+ W = w.innerWidth;
+ H = w.innerHeight;
+ } else if (w.document.documentElement && documentElement.clientWidth) {
+ W = documentElement.clientWidth;
+ H = documentElement.clientHeight;
+ } else {
+ W = body.offsetWidth;
+ H = body.offsetHeight
+ }
+ }
+ return { top: T, left: L, width: W, height: H };
+ }
+});
+
+Draggable._dragging = { };
+
+/*--------------------------------------------------------------------------*/
+
+var SortableObserver = Class.create({
+ initialize: function(element, observer) {
+ this.element = $(element);
+ this.observer = observer;
+ this.lastValue = Sortable.serialize(this.element);
+ },
+
+ onStart: function() {
+ this.lastValue = Sortable.serialize(this.element);
+ },
+
+ onEnd: function() {
+ Sortable.unmark();
+ if(this.lastValue != Sortable.serialize(this.element))
+ this.observer(this.element)
+ }
+});
+
+var Sortable = {
+ SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
+
+ sortables: { },
+
+ _findRootElement: function(element) {
+ while (element.tagName.toUpperCase() != "BODY") {
+ if(element.id && Sortable.sortables[element.id]) return element;
+ element = element.parentNode;
+ }
+ },
+
+ options: function(element) {
+ element = Sortable._findRootElement($(element));
+ if(!element) return;
+ return Sortable.sortables[element.id];
+ },
+
+ destroy: function(element){
+ var s = Sortable.options(element);
+
+ if(s) {
+ Draggables.removeObserver(s.element);
+ s.droppables.each(function(d){ Droppables.remove(d) });
+ s.draggables.invoke('destroy');
+
+ delete Sortable.sortables[s.element.id];
+ }
+ },
+
+ create: function(element) {
+ element = $(element);
+ var options = Object.extend({
+ element: element,
+ tag: 'li', // assumes li children, override with tag: 'tagname'
+ dropOnEmpty: false,
+ tree: false,
+ treeTag: 'ul',
+ overlap: 'vertical', // one of 'vertical', 'horizontal'
+ constraint: 'vertical', // one of 'vertical', 'horizontal', false
+ containment: element, // also takes array of elements (or id's); or false
+ handle: false, // or a CSS class
+ only: false,
+ delay: 0,
+ hoverclass: null,
+ ghosting: false,
+ quiet: false,
+ scroll: false,
+ scrollSensitivity: 20,
+ scrollSpeed: 15,
+ format: this.SERIALIZE_RULE,
+
+ // these take arrays of elements or ids and can be
+ // used for better initialization performance
+ elements: false,
+ handles: false,
+
+ onChange: Prototype.emptyFunction,
+ onUpdate: Prototype.emptyFunction
+ }, arguments[1] || { });
+
+ // clear any old sortable with same element
+ this.destroy(element);
+
+ // build options for the draggables
+ var options_for_draggable = {
+ revert: true,
+ quiet: options.quiet,
+ scroll: options.scroll,
+ scrollSpeed: options.scrollSpeed,
+ scrollSensitivity: options.scrollSensitivity,
+ delay: options.delay,
+ ghosting: options.ghosting,
+ constraint: options.constraint,
+ handle: options.handle };
+
+ if(options.starteffect)
+ options_for_draggable.starteffect = options.starteffect;
+
+ if(options.reverteffect)
+ options_for_draggable.reverteffect = options.reverteffect;
+ else
+ if(options.ghosting) options_for_draggable.reverteffect = function(element) {
+ element.style.top = 0;
+ element.style.left = 0;
+ };
+
+ if(options.endeffect)
+ options_for_draggable.endeffect = options.endeffect;
+
+ if(options.zindex)
+ options_for_draggable.zindex = options.zindex;
+
+ // build options for the droppables
+ var options_for_droppable = {
+ overlap: options.overlap,
+ containment: options.containment,
+ tree: options.tree,
+ hoverclass: options.hoverclass,
+ onHover: Sortable.onHover
+ }
+
+ var options_for_tree = {
+ onHover: Sortable.onEmptyHover,
+ overlap: options.overlap,
+ containment: options.containment,
+ hoverclass: options.hoverclass
+ }
+
+ // fix for gecko engine
+ Element.cleanWhitespace(element);
+
+ options.draggables = [];
+ options.droppables = [];
+
+ // drop on empty handling
+ if(options.dropOnEmpty || options.tree) {
+ Droppables.add(element, options_for_tree);
+ options.droppables.push(element);
+ }
+
+ (options.elements || this.findElements(element, options) || []).each( function(e,i) {
+ var handle = options.handles ? $(options.handles[i]) :
+ (options.handle ? $(e).select('.' + options.handle)[0] : e);
+ options.draggables.push(
+ new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
+ Droppables.add(e, options_for_droppable);
+ if(options.tree) e.treeNode = element;
+ options.droppables.push(e);
+ });
+
+ if(options.tree) {
+ (Sortable.findTreeElements(element, options) || []).each( function(e) {
+ Droppables.add(e, options_for_tree);
+ e.treeNode = element;
+ options.droppables.push(e);
+ });
+ }
+
+ // keep reference
+ this.sortables[element.id] = options;
+
+ // for onupdate
+ Draggables.addObserver(new SortableObserver(element, options.onUpdate));
+
+ },
+
+ // return all suitable-for-sortable elements in a guaranteed order
+ findElements: function(element, options) {
+ return Element.findChildren(
+ element, options.only, options.tree ? true : false, options.tag);
+ },
+
+ findTreeElements: function(element, options) {
+ return Element.findChildren(
+ element, options.only, options.tree ? true : false, options.treeTag);
+ },
+
+ onHover: function(element, dropon, overlap) {
+ if(Element.isParent(dropon, element)) return;
+
+ if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
+ return;
+ } else if(overlap>0.5) {
+ Sortable.mark(dropon, 'before');
+ if(dropon.previousSibling != element) {
+ var oldParentNode = element.parentNode;
+ element.style.visibility = "hidden"; // fix gecko rendering
+ dropon.parentNode.insertBefore(element, dropon);
+ if(dropon.parentNode!=oldParentNode)
+ Sortable.options(oldParentNode).onChange(element);
+ Sortable.options(dropon.parentNode).onChange(element);
+ }
+ } else {
+ Sortable.mark(dropon, 'after');
+ var nextElement = dropon.nextSibling || null;
+ if(nextElement != element) {
+ var oldParentNode = element.parentNode;
+ element.style.visibility = "hidden"; // fix gecko rendering
+ dropon.parentNode.insertBefore(element, nextElement);
+ if(dropon.parentNode!=oldParentNode)
+ Sortable.options(oldParentNode).onChange(element);
+ Sortable.options(dropon.parentNode).onChange(element);
+ }
+ }
+ },
+
+ onEmptyHover: function(element, dropon, overlap) {
+ var oldParentNode = element.parentNode;
+ var droponOptions = Sortable.options(dropon);
+
+ if(!Element.isParent(dropon, element)) {
+ var index;
+
+ var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
+ var child = null;
+
+ if(children) {
+ var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
+
+ for (index = 0; index < children.length; index += 1) {
+ if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
+ offset -= Element.offsetSize (children[index], droponOptions.overlap);
+ } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
+ child = index + 1 < children.length ? children[index + 1] : null;
+ break;
+ } else {
+ child = children[index];
+ break;
+ }
+ }
+ }
+
+ dropon.insertBefore(element, child);
+
+ Sortable.options(oldParentNode).onChange(element);
+ droponOptions.onChange(element);
+ }
+ },
+
+ unmark: function() {
+ if(Sortable._marker) Sortable._marker.hide();
+ },
+
+ mark: function(dropon, position) {
+ // mark on ghosting only
+ var sortable = Sortable.options(dropon.parentNode);
+ if(sortable && !sortable.ghosting) return;
+
+ if(!Sortable._marker) {
+ Sortable._marker =
+ ($('dropmarker') || Element.extend(document.createElement('DIV'))).
+ hide().addClassName('dropmarker').setStyle({position:'absolute'});
+ document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
+ }
+ var offsets = Position.cumulativeOffset(dropon);
+ Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
+
+ if(position=='after')
+ if(sortable.overlap == 'horizontal')
+ Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
+ else
+ Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
+
+ Sortable._marker.show();
+ },
+
+ _tree: function(element, options, parent) {
+ var children = Sortable.findElements(element, options) || [];
+
+ for (var i = 0; i < children.length; ++i) {
+ var match = children[i].id.match(options.format);
+
+ if (!match) continue;
+
+ var child = {
+ id: encodeURIComponent(match ? match[1] : null),
+ element: element,
+ parent: parent,
+ children: [],
+ position: parent.children.length,
+ container: $(children[i]).down(options.treeTag)
+ }
+
+ /* Get the element containing the children and recurse over it */
+ if (child.container)
+ this._tree(child.container, options, child)
+
+ parent.children.push (child);
+ }
+
+ return parent;
+ },
+
+ tree: function(element) {
+ element = $(element);
+ var sortableOptions = this.options(element);
+ var options = Object.extend({
+ tag: sortableOptions.tag,
+ treeTag: sortableOptions.treeTag,
+ only: sortableOptions.only,
+ name: element.id,
+ format: sortableOptions.format
+ }, arguments[1] || { });
+
+ var root = {
+ id: null,
+ parent: null,
+ children: [],
+ container: element,
+ position: 0
+ }
+
+ return Sortable._tree(element, options, root);
+ },
+
+ /* Construct a [i] index for a particular node */
+ _constructIndex: function(node) {
+ var index = '';
+ do {
+ if (node.id) index = '[' + node.position + ']' + index;
+ } while ((node = node.parent) != null);
+ return index;
+ },
+
+ sequence: function(element) {
+ element = $(element);
+ var options = Object.extend(this.options(element), arguments[1] || { });
+
+ return $(this.findElements(element, options) || []).map( function(item) {
+ return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
+ });
+ },
+
+ setSequence: function(element, new_sequence) {
+ element = $(element);
+ var options = Object.extend(this.options(element), arguments[2] || { });
+
+ var nodeMap = { };
+ this.findElements(element, options).each( function(n) {
+ if (n.id.match(options.format))
+ nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
+ n.parentNode.removeChild(n);
+ });
+
+ new_sequence.each(function(ident) {
+ var n = nodeMap[ident];
+ if (n) {
+ n[1].appendChild(n[0]);
+ delete nodeMap[ident];
+ }
+ });
+ },
+
+ serialize: function(element) {
+ element = $(element);
+ var options = Object.extend(Sortable.options(element), arguments[1] || { });
+ var name = encodeURIComponent(
+ (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
+
+ if (options.tree) {
+ return Sortable.tree(element, arguments[1]).children.map( function (item) {
+ return [name + Sortable._constructIndex(item) + "[id]=" +
+ encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
+ }).flatten().join('&');
+ } else {
+ return Sortable.sequence(element, arguments[1]).map( function(item) {
+ return name + "[]=" + encodeURIComponent(item);
+ }).join('&');
+ }
+ }
+}
+
+// Returns true if child is contained within element
+Element.isParent = function(child, element) {
+ if (!child.parentNode || child == element) return false;
+ if (child.parentNode == element) return true;
+ return Element.isParent(child.parentNode, element);
+}
+
+Element.findChildren = function(element, only, recursive, tagName) {
+ if(!element.hasChildNodes()) return null;
+ tagName = tagName.toUpperCase();
+ if(only) only = [only].flatten();
+ var elements = [];
+ $A(element.childNodes).each( function(e) {
+ if(e.tagName && e.tagName.toUpperCase()==tagName &&
+ (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
+ elements.push(e);
+ if(recursive) {
+ var grandchildren = Element.findChildren(e, only, recursive, tagName);
+ if(grandchildren) elements.push(grandchildren);
+ }
+ });
+
+ return (elements.length>0 ? elements.flatten() : []);
+}
+
+Element.offsetSize = function (element, type) {
+ return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
+}
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/effects.js b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/effects.js
new file mode 100644
index 000000000..65aed2395
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/effects.js
@@ -0,0 +1,1120 @@
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Contributors:
+// Justin Palmer (http://encytemedia.com/)
+// Mark Pilgrim (http://diveintomark.org/)
+// Martin Bialasinki
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// converts rgb() and #xxx to #xxxxxx format,
+// returns self (or first argument) if not convertable
+String.prototype.parseColor = function() {
+ var color = '#';
+ if (this.slice(0,4) == 'rgb(') {
+ var cols = this.slice(4,this.length-1).split(',');
+ var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
+ } else {
+ if (this.slice(0,1) == '#') {
+ if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
+ if (this.length==7) color = this.toLowerCase();
+ }
+ }
+ return (color.length==7 ? color : (arguments[0] || this));
+};
+
+/*--------------------------------------------------------------------------*/
+
+Element.collectTextNodes = function(element) {
+ return $A($(element).childNodes).collect( function(node) {
+ return (node.nodeType==3 ? node.nodeValue :
+ (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
+ }).flatten().join('');
+};
+
+Element.collectTextNodesIgnoreClass = function(element, className) {
+ return $A($(element).childNodes).collect( function(node) {
+ return (node.nodeType==3 ? node.nodeValue :
+ ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
+ Element.collectTextNodesIgnoreClass(node, className) : ''));
+ }).flatten().join('');
+};
+
+Element.setContentZoom = function(element, percent) {
+ element = $(element);
+ element.setStyle({fontSize: (percent/100) + 'em'});
+ if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+ return element;
+};
+
+Element.getInlineOpacity = function(element){
+ return $(element).style.opacity || '';
+};
+
+Element.forceRerendering = function(element) {
+ try {
+ element = $(element);
+ var n = document.createTextNode(' ');
+ element.appendChild(n);
+ element.removeChild(n);
+ } catch(e) { }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Effect = {
+ _elementDoesNotExistError: {
+ name: 'ElementDoesNotExistError',
+ message: 'The specified DOM element does not exist, but is required for this effect to operate'
+ },
+ Transitions: {
+ linear: Prototype.K,
+ sinoidal: function(pos) {
+ return (-Math.cos(pos*Math.PI)/2) + 0.5;
+ },
+ reverse: function(pos) {
+ return 1-pos;
+ },
+ flicker: function(pos) {
+ var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
+ return pos > 1 ? 1 : pos;
+ },
+ wobble: function(pos) {
+ return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
+ },
+ pulse: function(pos, pulses) {
+ pulses = pulses || 5;
+ return (
+ ((pos % (1/pulses)) * pulses).round() == 0 ?
+ ((pos * pulses * 2) - (pos * pulses * 2).floor()) :
+ 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
+ );
+ },
+ spring: function(pos) {
+ return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
+ },
+ none: function(pos) {
+ return 0;
+ },
+ full: function(pos) {
+ return 1;
+ }
+ },
+ DefaultOptions: {
+ duration: 1.0, // seconds
+ fps: 100, // 100= assume 66fps max.
+ sync: false, // true for combining
+ from: 0.0,
+ to: 1.0,
+ delay: 0.0,
+ queue: 'parallel'
+ },
+ tagifyText: function(element) {
+ var tagifyStyle = 'position:relative';
+ if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
+
+ element = $(element);
+ $A(element.childNodes).each( function(child) {
+ if (child.nodeType==3) {
+ child.nodeValue.toArray().each( function(character) {
+ element.insertBefore(
+ new Element('span', {style: tagifyStyle}).update(
+ character == ' ' ? String.fromCharCode(160) : character),
+ child);
+ });
+ Element.remove(child);
+ }
+ });
+ },
+ multiple: function(element, effect) {
+ var elements;
+ if (((typeof element == 'object') ||
+ Object.isFunction(element)) &&
+ (element.length))
+ elements = element;
+ else
+ elements = $(element).childNodes;
+
+ var options = Object.extend({
+ speed: 0.1,
+ delay: 0.0
+ }, arguments[2] || { });
+ var masterDelay = options.delay;
+
+ $A(elements).each( function(element, index) {
+ new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
+ });
+ },
+ PAIRS: {
+ 'slide': ['SlideDown','SlideUp'],
+ 'blind': ['BlindDown','BlindUp'],
+ 'appear': ['Appear','Fade']
+ },
+ toggle: function(element, effect) {
+ element = $(element);
+ effect = (effect || 'appear').toLowerCase();
+ var options = Object.extend({
+ queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
+ }, arguments[2] || { });
+ Effect[element.visible() ?
+ Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
+ }
+};
+
+Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
+
+/* ------------- core effects ------------- */
+
+Effect.ScopedQueue = Class.create(Enumerable, {
+ initialize: function() {
+ this.effects = [];
+ this.interval = null;
+ },
+ _each: function(iterator) {
+ this.effects._each(iterator);
+ },
+ add: function(effect) {
+ var timestamp = new Date().getTime();
+
+ var position = Object.isString(effect.options.queue) ?
+ effect.options.queue : effect.options.queue.position;
+
+ switch(position) {
+ case 'front':
+ // move unstarted effects after this effect
+ this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
+ e.startOn += effect.finishOn;
+ e.finishOn += effect.finishOn;
+ });
+ break;
+ case 'with-last':
+ timestamp = this.effects.pluck('startOn').max() || timestamp;
+ break;
+ case 'end':
+ // start effect after last queued effect has finished
+ timestamp = this.effects.pluck('finishOn').max() || timestamp;
+ break;
+ }
+
+ effect.startOn += timestamp;
+ effect.finishOn += timestamp;
+
+ if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
+ this.effects.push(effect);
+
+ if (!this.interval)
+ this.interval = setInterval(this.loop.bind(this), 15);
+ },
+ remove: function(effect) {
+ this.effects = this.effects.reject(function(e) { return e==effect });
+ if (this.effects.length == 0) {
+ clearInterval(this.interval);
+ this.interval = null;
+ }
+ },
+ loop: function() {
+ var timePos = new Date().getTime();
+ for(var i=0, len=this.effects.length;i<len;i++)
+ this.effects[i] && this.effects[i].loop(timePos);
+ }
+});
+
+Effect.Queues = {
+ instances: $H(),
+ get: function(queueName) {
+ if (!Object.isString(queueName)) return queueName;
+
+ return this.instances.get(queueName) ||
+ this.instances.set(queueName, new Effect.ScopedQueue());
+ }
+};
+Effect.Queue = Effect.Queues.get('global');
+
+Effect.Base = Class.create({
+ position: null,
+ start: function(options) {
+ function codeForEvent(options,eventName){
+ return (
+ (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
+ (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
+ );
+ }
+ if (options && options.transition === false) options.transition = Effect.Transitions.linear;
+ this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
+ this.currentFrame = 0;
+ this.state = 'idle';
+ this.startOn = this.options.delay*1000;
+ this.finishOn = this.startOn+(this.options.duration*1000);
+ this.fromToDelta = this.options.to-this.options.from;
+ this.totalTime = this.finishOn-this.startOn;
+ this.totalFrames = this.options.fps*this.options.duration;
+
+ eval('this.render = function(pos){ '+
+ 'if (this.state=="idle"){this.state="running";'+
+ codeForEvent(this.options,'beforeSetup')+
+ (this.setup ? 'this.setup();':'')+
+ codeForEvent(this.options,'afterSetup')+
+ '};if (this.state=="running"){'+
+ 'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
+ 'this.position=pos;'+
+ codeForEvent(this.options,'beforeUpdate')+
+ (this.update ? 'this.update(pos);':'')+
+ codeForEvent(this.options,'afterUpdate')+
+ '}}');
+
+ this.event('beforeStart');
+ if (!this.options.sync)
+ Effect.Queues.get(Object.isString(this.options.queue) ?
+ 'global' : this.options.queue.scope).add(this);
+ },
+ loop: function(timePos) {
+ if (timePos >= this.startOn) {
+ if (timePos >= this.finishOn) {
+ this.render(1.0);
+ this.cancel();
+ this.event('beforeFinish');
+ if (this.finish) this.finish();
+ this.event('afterFinish');
+ return;
+ }
+ var pos = (timePos - this.startOn) / this.totalTime,
+ frame = (pos * this.totalFrames).round();
+ if (frame > this.currentFrame) {
+ this.render(pos);
+ this.currentFrame = frame;
+ }
+ }
+ },
+ cancel: function() {
+ if (!this.options.sync)
+ Effect.Queues.get(Object.isString(this.options.queue) ?
+ 'global' : this.options.queue.scope).remove(this);
+ this.state = 'finished';
+ },
+ event: function(eventName) {
+ if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
+ if (this.options[eventName]) this.options[eventName](this);
+ },
+ inspect: function() {
+ var data = $H();
+ for(property in this)
+ if (!Object.isFunction(this[property])) data.set(property, this[property]);
+ return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
+ }
+});
+
+Effect.Parallel = Class.create(Effect.Base, {
+ initialize: function(effects) {
+ this.effects = effects || [];
+ this.start(arguments[1]);
+ },
+ update: function(position) {
+ this.effects.invoke('render', position);
+ },
+ finish: function(position) {
+ this.effects.each( function(effect) {
+ effect.render(1.0);
+ effect.cancel();
+ effect.event('beforeFinish');
+ if (effect.finish) effect.finish(position);
+ effect.event('afterFinish');
+ });
+ }
+});
+
+Effect.Tween = Class.create(Effect.Base, {
+ initialize: function(object, from, to) {
+ object = Object.isString(object) ? $(object) : object;
+ var args = $A(arguments), method = args.last(),
+ options = args.length == 5 ? args[3] : null;
+ this.method = Object.isFunction(method) ? method.bind(object) :
+ Object.isFunction(object[method]) ? object[method].bind(object) :
+ function(value) { object[method] = value };
+ this.start(Object.extend({ from: from, to: to }, options || { }));
+ },
+ update: function(position) {
+ this.method(position);
+ }
+});
+
+Effect.Event = Class.create(Effect.Base, {
+ initialize: function() {
+ this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
+ },
+ update: Prototype.emptyFunction
+});
+
+Effect.Opacity = Class.create(Effect.Base, {
+ initialize: function(element) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ // make this work on IE on elements without 'layout'
+ if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+ this.element.setStyle({zoom: 1});
+ var options = Object.extend({
+ from: this.element.getOpacity() || 0.0,
+ to: 1.0
+ }, arguments[1] || { });
+ this.start(options);
+ },
+ update: function(position) {
+ this.element.setOpacity(position);
+ }
+});
+
+Effect.Move = Class.create(Effect.Base, {
+ initialize: function(element) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({
+ x: 0,
+ y: 0,
+ mode: 'relative'
+ }, arguments[1] || { });
+ this.start(options);
+ },
+ setup: function() {
+ this.element.makePositioned();
+ this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
+ this.originalTop = parseFloat(this.element.getStyle('top') || '0');
+ if (this.options.mode == 'absolute') {
+ this.options.x = this.options.x - this.originalLeft;
+ this.options.y = this.options.y - this.originalTop;
+ }
+ },
+ update: function(position) {
+ this.element.setStyle({
+ left: (this.options.x * position + this.originalLeft).round() + 'px',
+ top: (this.options.y * position + this.originalTop).round() + 'px'
+ });
+ }
+});
+
+// for backwards compatibility
+Effect.MoveBy = function(element, toTop, toLeft) {
+ return new Effect.Move(element,
+ Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
+};
+
+Effect.Scale = Class.create(Effect.Base, {
+ initialize: function(element, percent) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({
+ scaleX: true,
+ scaleY: true,
+ scaleContent: true,
+ scaleFromCenter: false,
+ scaleMode: 'box', // 'box' or 'contents' or { } with provided values
+ scaleFrom: 100.0,
+ scaleTo: percent
+ }, arguments[2] || { });
+ this.start(options);
+ },
+ setup: function() {
+ this.restoreAfterFinish = this.options.restoreAfterFinish || false;
+ this.elementPositioning = this.element.getStyle('position');
+
+ this.originalStyle = { };
+ ['top','left','width','height','fontSize'].each( function(k) {
+ this.originalStyle[k] = this.element.style[k];
+ }.bind(this));
+
+ this.originalTop = this.element.offsetTop;
+ this.originalLeft = this.element.offsetLeft;
+
+ var fontSize = this.element.getStyle('font-size') || '100%';
+ ['em','px','%','pt'].each( function(fontSizeType) {
+ if (fontSize.indexOf(fontSizeType)>0) {
+ this.fontSize = parseFloat(fontSize);
+ this.fontSizeType = fontSizeType;
+ }
+ }.bind(this));
+
+ this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
+
+ this.dims = null;
+ if (this.options.scaleMode=='box')
+ this.dims = [this.element.offsetHeight, this.element.offsetWidth];
+ if (/^content/.test(this.options.scaleMode))
+ this.dims = [this.element.scrollHeight, this.element.scrollWidth];
+ if (!this.dims)
+ this.dims = [this.options.scaleMode.originalHeight,
+ this.options.scaleMode.originalWidth];
+ },
+ update: function(position) {
+ var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
+ if (this.options.scaleContent && this.fontSize)
+ this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
+ this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
+ },
+ finish: function(position) {
+ if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
+ },
+ setDimensions: function(height, width) {
+ var d = { };
+ if (this.options.scaleX) d.width = width.round() + 'px';
+ if (this.options.scaleY) d.height = height.round() + 'px';
+ if (this.options.scaleFromCenter) {
+ var topd = (height - this.dims[0])/2;
+ var leftd = (width - this.dims[1])/2;
+ if (this.elementPositioning == 'absolute') {
+ if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
+ if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
+ } else {
+ if (this.options.scaleY) d.top = -topd + 'px';
+ if (this.options.scaleX) d.left = -leftd + 'px';
+ }
+ }
+ this.element.setStyle(d);
+ }
+});
+
+Effect.Highlight = Class.create(Effect.Base, {
+ initialize: function(element) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
+ this.start(options);
+ },
+ setup: function() {
+ // Prevent executing on elements not in the layout flow
+ if (this.element.getStyle('display')=='none') { this.cancel(); return; }
+ // Disable background image during the effect
+ this.oldStyle = { };
+ if (!this.options.keepBackgroundImage) {
+ this.oldStyle.backgroundImage = this.element.getStyle('background-image');
+ this.element.setStyle({backgroundImage: 'none'});
+ }
+ if (!this.options.endcolor)
+ this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
+ if (!this.options.restorecolor)
+ this.options.restorecolor = this.element.getStyle('background-color');
+ // init color calculations
+ this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
+ this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
+ },
+ update: function(position) {
+ this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
+ return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
+ },
+ finish: function() {
+ this.element.setStyle(Object.extend(this.oldStyle, {
+ backgroundColor: this.options.restorecolor
+ }));
+ }
+});
+
+Effect.ScrollTo = function(element) {
+ var options = arguments[1] || { },
+ scrollOffsets = document.viewport.getScrollOffsets(),
+ elementOffsets = $(element).cumulativeOffset(),
+ max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();
+
+ if (options.offset) elementOffsets[1] += options.offset;
+
+ return new Effect.Tween(null,
+ scrollOffsets.top,
+ elementOffsets[1] > max ? max : elementOffsets[1],
+ options,
+ function(p){ scrollTo(scrollOffsets.left, p.round()) }
+ );
+};
+
+/* ------------- combination effects ------------- */
+
+Effect.Fade = function(element) {
+ element = $(element);
+ var oldOpacity = element.getInlineOpacity();
+ var options = Object.extend({
+ from: element.getOpacity() || 1.0,
+ to: 0.0,
+ afterFinishInternal: function(effect) {
+ if (effect.options.to!=0) return;
+ effect.element.hide().setStyle({opacity: oldOpacity});
+ }
+ }, arguments[1] || { });
+ return new Effect.Opacity(element,options);
+};
+
+Effect.Appear = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
+ to: 1.0,
+ // force Safari to render floated elements properly
+ afterFinishInternal: function(effect) {
+ effect.element.forceRerendering();
+ },
+ beforeSetup: function(effect) {
+ effect.element.setOpacity(effect.options.from).show();
+ }}, arguments[1] || { });
+ return new Effect.Opacity(element,options);
+};
+
+Effect.Puff = function(element) {
+ element = $(element);
+ var oldStyle = {
+ opacity: element.getInlineOpacity(),
+ position: element.getStyle('position'),
+ top: element.style.top,
+ left: element.style.left,
+ width: element.style.width,
+ height: element.style.height
+ };
+ return new Effect.Parallel(
+ [ new Effect.Scale(element, 200,
+ { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
+ new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
+ Object.extend({ duration: 1.0,
+ beforeSetupInternal: function(effect) {
+ Position.absolutize(effect.effects[0].element)
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide().setStyle(oldStyle); }
+ }, arguments[1] || { })
+ );
+};
+
+Effect.BlindUp = function(element) {
+ element = $(element);
+ element.makeClipping();
+ return new Effect.Scale(element, 0,
+ Object.extend({ scaleContent: false,
+ scaleX: false,
+ restoreAfterFinish: true,
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping();
+ }
+ }, arguments[1] || { })
+ );
+};
+
+Effect.BlindDown = function(element) {
+ element = $(element);
+ var elementDimensions = element.getDimensions();
+ return new Effect.Scale(element, 100, Object.extend({
+ scaleContent: false,
+ scaleX: false,
+ scaleFrom: 0,
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+ restoreAfterFinish: true,
+ afterSetup: function(effect) {
+ effect.element.makeClipping().setStyle({height: '0px'}).show();
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.undoClipping();
+ }
+ }, arguments[1] || { }));
+};
+
+Effect.SwitchOff = function(element) {
+ element = $(element);
+ var oldOpacity = element.getInlineOpacity();
+ return new Effect.Appear(element, Object.extend({
+ duration: 0.4,
+ from: 0,
+ transition: Effect.Transitions.flicker,
+ afterFinishInternal: function(effect) {
+ new Effect.Scale(effect.element, 1, {
+ duration: 0.3, scaleFromCenter: true,
+ scaleX: false, scaleContent: false, restoreAfterFinish: true,
+ beforeSetup: function(effect) {
+ effect.element.makePositioned().makeClipping();
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
+ }
+ })
+ }
+ }, arguments[1] || { }));
+};
+
+Effect.DropOut = function(element) {
+ element = $(element);
+ var oldStyle = {
+ top: element.getStyle('top'),
+ left: element.getStyle('left'),
+ opacity: element.getInlineOpacity() };
+ return new Effect.Parallel(
+ [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
+ new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
+ Object.extend(
+ { duration: 0.5,
+ beforeSetup: function(effect) {
+ effect.effects[0].element.makePositioned();
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
+ }
+ }, arguments[1] || { }));
+};
+
+Effect.Shake = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ distance: 20,
+ duration: 0.5
+ }, arguments[1] || {});
+ var distance = parseFloat(options.distance);
+ var split = parseFloat(options.duration) / 10.0;
+ var oldStyle = {
+ top: element.getStyle('top'),
+ left: element.getStyle('left') };
+ return new Effect.Move(element,
+ { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+ effect.element.undoPositioned().setStyle(oldStyle);
+ }}) }}) }}) }}) }}) }});
+};
+
+Effect.SlideDown = function(element) {
+ element = $(element).cleanWhitespace();
+ // SlideDown need to have the content of the element wrapped in a container element with fixed height!
+ var oldInnerBottom = element.down().getStyle('bottom');
+ var elementDimensions = element.getDimensions();
+ return new Effect.Scale(element, 100, Object.extend({
+ scaleContent: false,
+ scaleX: false,
+ scaleFrom: window.opera ? 0 : 1,
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+ restoreAfterFinish: true,
+ afterSetup: function(effect) {
+ effect.element.makePositioned();
+ effect.element.down().makePositioned();
+ if (window.opera) effect.element.setStyle({top: ''});
+ effect.element.makeClipping().setStyle({height: '0px'}).show();
+ },
+ afterUpdateInternal: function(effect) {
+ effect.element.down().setStyle({bottom:
+ (effect.dims[0] - effect.element.clientHeight) + 'px' });
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.undoClipping().undoPositioned();
+ effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
+ }, arguments[1] || { })
+ );
+};
+
+Effect.SlideUp = function(element) {
+ element = $(element).cleanWhitespace();
+ var oldInnerBottom = element.down().getStyle('bottom');
+ var elementDimensions = element.getDimensions();
+ return new Effect.Scale(element, window.opera ? 0 : 1,
+ Object.extend({ scaleContent: false,
+ scaleX: false,
+ scaleMode: 'box',
+ scaleFrom: 100,
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+ restoreAfterFinish: true,
+ afterSetup: function(effect) {
+ effect.element.makePositioned();
+ effect.element.down().makePositioned();
+ if (window.opera) effect.element.setStyle({top: ''});
+ effect.element.makeClipping().show();
+ },
+ afterUpdateInternal: function(effect) {
+ effect.element.down().setStyle({bottom:
+ (effect.dims[0] - effect.element.clientHeight) + 'px' });
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping().undoPositioned();
+ effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
+ }
+ }, arguments[1] || { })
+ );
+};
+
+// Bug in opera makes the TD containing this element expand for a instance after finish
+Effect.Squish = function(element) {
+ return new Effect.Scale(element, window.opera ? 1 : 0, {
+ restoreAfterFinish: true,
+ beforeSetup: function(effect) {
+ effect.element.makeClipping();
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping();
+ }
+ });
+};
+
+Effect.Grow = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ direction: 'center',
+ moveTransition: Effect.Transitions.sinoidal,
+ scaleTransition: Effect.Transitions.sinoidal,
+ opacityTransition: Effect.Transitions.full
+ }, arguments[1] || { });
+ var oldStyle = {
+ top: element.style.top,
+ left: element.style.left,
+ height: element.style.height,
+ width: element.style.width,
+ opacity: element.getInlineOpacity() };
+
+ var dims = element.getDimensions();
+ var initialMoveX, initialMoveY;
+ var moveX, moveY;
+
+ switch (options.direction) {
+ case 'top-left':
+ initialMoveX = initialMoveY = moveX = moveY = 0;
+ break;
+ case 'top-right':
+ initialMoveX = dims.width;
+ initialMoveY = moveY = 0;
+ moveX = -dims.width;
+ break;
+ case 'bottom-left':
+ initialMoveX = moveX = 0;
+ initialMoveY = dims.height;
+ moveY = -dims.height;
+ break;
+ case 'bottom-right':
+ initialMoveX = dims.width;
+ initialMoveY = dims.height;
+ moveX = -dims.width;
+ moveY = -dims.height;
+ break;
+ case 'center':
+ initialMoveX = dims.width / 2;
+ initialMoveY = dims.height / 2;
+ moveX = -dims.width / 2;
+ moveY = -dims.height / 2;
+ break;
+ }
+
+ return new Effect.Move(element, {
+ x: initialMoveX,
+ y: initialMoveY,
+ duration: 0.01,
+ beforeSetup: function(effect) {
+ effect.element.hide().makeClipping().makePositioned();
+ },
+ afterFinishInternal: function(effect) {
+ new Effect.Parallel(
+ [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
+ new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
+ new Effect.Scale(effect.element, 100, {
+ scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
+ sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
+ ], Object.extend({
+ beforeSetup: function(effect) {
+ effect.effects[0].element.setStyle({height: '0px'}).show();
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
+ }
+ }, options)
+ )
+ }
+ });
+};
+
+Effect.Shrink = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ direction: 'center',
+ moveTransition: Effect.Transitions.sinoidal,
+ scaleTransition: Effect.Transitions.sinoidal,
+ opacityTransition: Effect.Transitions.none
+ }, arguments[1] || { });
+ var oldStyle = {
+ top: element.style.top,
+ left: element.style.left,
+ height: element.style.height,
+ width: element.style.width,
+ opacity: element.getInlineOpacity() };
+
+ var dims = element.getDimensions();
+ var moveX, moveY;
+
+ switch (options.direction) {
+ case 'top-left':
+ moveX = moveY = 0;
+ break;
+ case 'top-right':
+ moveX = dims.width;
+ moveY = 0;
+ break;
+ case 'bottom-left':
+ moveX = 0;
+ moveY = dims.height;
+ break;
+ case 'bottom-right':
+ moveX = dims.width;
+ moveY = dims.height;
+ break;
+ case 'center':
+ moveX = dims.width / 2;
+ moveY = dims.height / 2;
+ break;
+ }
+
+ return new Effect.Parallel(
+ [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
+ new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
+ new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
+ ], Object.extend({
+ beforeStartInternal: function(effect) {
+ effect.effects[0].element.makePositioned().makeClipping();
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
+ }, options)
+ );
+};
+
+Effect.Pulsate = function(element) {
+ element = $(element);
+ var options = arguments[1] || { };
+ var oldOpacity = element.getInlineOpacity();
+ var transition = options.transition || Effect.Transitions.sinoidal;
+ var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
+ reverser.bind(transition);
+ return new Effect.Opacity(element,
+ Object.extend(Object.extend({ duration: 2.0, from: 0,
+ afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
+ }, options), {transition: reverser}));
+};
+
+Effect.Fold = function(element) {
+ element = $(element);
+ var oldStyle = {
+ top: element.style.top,
+ left: element.style.left,
+ width: element.style.width,
+ height: element.style.height };
+ element.makeClipping();
+ return new Effect.Scale(element, 5, Object.extend({
+ scaleContent: false,
+ scaleX: false,
+ afterFinishInternal: function(effect) {
+ new Effect.Scale(element, 1, {
+ scaleContent: false,
+ scaleY: false,
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping().setStyle(oldStyle);
+ } });
+ }}, arguments[1] || { }));
+};
+
+Effect.Morph = Class.create(Effect.Base, {
+ initialize: function(element) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({
+ style: { }
+ }, arguments[1] || { });
+
+ if (!Object.isString(options.style)) this.style = $H(options.style);
+ else {
+ if (options.style.include(':'))
+ this.style = options.style.parseStyle();
+ else {
+ this.element.addClassName(options.style);
+ this.style = $H(this.element.getStyles());
+ this.element.removeClassName(options.style);
+ var css = this.element.getStyles();
+ this.style = this.style.reject(function(style) {
+ return style.value == css[style.key];
+ });
+ options.afterFinishInternal = function(effect) {
+ effect.element.addClassName(effect.options.style);
+ effect.transforms.each(function(transform) {
+ effect.element.style[transform.style] = '';
+ });
+ }
+ }
+ }
+ this.start(options);
+ },
+
+ setup: function(){
+ function parseColor(color){
+ if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
+ color = color.parseColor();
+ return $R(0,2).map(function(i){
+ return parseInt( color.slice(i*2+1,i*2+3), 16 )
+ });
+ }
+ this.transforms = this.style.map(function(pair){
+ var property = pair[0], value = pair[1], unit = null;
+
+ if (value.parseColor('#zzzzzz') != '#zzzzzz') {
+ value = value.parseColor();
+ unit = 'color';
+ } else if (property == 'opacity') {
+ value = parseFloat(value);
+ if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+ this.element.setStyle({zoom: 1});
+ } else if (Element.CSS_LENGTH.test(value)) {
+ var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
+ value = parseFloat(components[1]);
+ unit = (components.length == 3) ? components[2] : null;
+ }
+
+ var originalValue = this.element.getStyle(property);
+ return {
+ style: property.camelize(),
+ originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
+ targetValue: unit=='color' ? parseColor(value) : value,
+ unit: unit
+ };
+ }.bind(this)).reject(function(transform){
+ return (
+ (transform.originalValue == transform.targetValue) ||
+ (
+ transform.unit != 'color' &&
+ (isNaN(transform.originalValue) || isNaN(transform.targetValue))
+ )
+ )
+ });
+ },
+ update: function(position) {
+ var style = { }, transform, i = this.transforms.length;
+ while(i--)
+ style[(transform = this.transforms[i]).style] =
+ transform.unit=='color' ? '#'+
+ (Math.round(transform.originalValue[0]+
+ (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
+ (Math.round(transform.originalValue[1]+
+ (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
+ (Math.round(transform.originalValue[2]+
+ (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
+ (transform.originalValue +
+ (transform.targetValue - transform.originalValue) * position).toFixed(3) +
+ (transform.unit === null ? '' : transform.unit);
+ this.element.setStyle(style, true);
+ }
+});
+
+Effect.Transform = Class.create({
+ initialize: function(tracks){
+ this.tracks = [];
+ this.options = arguments[1] || { };
+ this.addTracks(tracks);
+ },
+ addTracks: function(tracks){
+ tracks.each(function(track){
+ track = $H(track);
+ var data = track.values().first();
+ this.tracks.push($H({
+ ids: track.keys().first(),
+ effect: Effect.Morph,
+ options: { style: data }
+ }));
+ }.bind(this));
+ return this;
+ },
+ play: function(){
+ return new Effect.Parallel(
+ this.tracks.map(function(track){
+ var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
+ var elements = [$(ids) || $$(ids)].flatten();
+ return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
+ }).flatten(),
+ this.options
+ );
+ }
+});
+
+Element.CSS_PROPERTIES = $w(
+ 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
+ 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
+ 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
+ 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
+ 'fontSize fontWeight height left letterSpacing lineHeight ' +
+ 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
+ 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
+ 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
+ 'right textIndent top width wordSpacing zIndex');
+
+Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
+
+String.__parseStyleElement = document.createElement('div');
+String.prototype.parseStyle = function(){
+ var style, styleRules = $H();
+ if (Prototype.Browser.WebKit)
+ style = new Element('div',{style:this}).style;
+ else {
+ String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
+ style = String.__parseStyleElement.childNodes[0].style;
+ }
+
+ Element.CSS_PROPERTIES.each(function(property){
+ if (style[property]) styleRules.set(property, style[property]);
+ });
+
+ if (Prototype.Browser.IE && this.include('opacity'))
+ styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
+
+ return styleRules;
+};
+
+if (document.defaultView && document.defaultView.getComputedStyle) {
+ Element.getStyles = function(element) {
+ var css = document.defaultView.getComputedStyle($(element), null);
+ return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
+ styles[property] = css[property];
+ return styles;
+ });
+ };
+} else {
+ Element.getStyles = function(element) {
+ element = $(element);
+ var css = element.currentStyle, styles;
+ styles = Element.CSS_PROPERTIES.inject({ }, function(hash, property) {
+ hash.set(property, css[property]);
+ return hash;
+ });
+ if (!styles.opacity) styles.set('opacity', element.getOpacity());
+ return styles;
+ };
+};
+
+Effect.Methods = {
+ morph: function(element, style) {
+ element = $(element);
+ new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
+ return element;
+ },
+ visualEffect: function(element, effect, options) {
+ element = $(element)
+ var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
+ new Effect[klass](element, options);
+ return element;
+ },
+ highlight: function(element, options) {
+ element = $(element);
+ new Effect.Highlight(element, options);
+ return element;
+ }
+};
+
+$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
+ 'pulsate shake puff squish switchOff dropOut').each(
+ function(effect) {
+ Effect.Methods[effect] = function(element, options){
+ element = $(element);
+ Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
+ return element;
+ }
+ }
+);
+
+$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
+ function(f) { Effect.Methods[f] = Element[f]; }
+);
+
+Element.addMethods(Effect.Methods);
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/prototype.js b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/prototype.js
new file mode 100644
index 000000000..546f9fe44
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/javascripts/prototype.js
@@ -0,0 +1,4225 @@
+/* Prototype JavaScript framework, version 1.6.0.1
+ * (c) 2005-2007 Sam Stephenson
+ *
+ * Prototype is freely distributable under the terms of an MIT-style license.
+ * For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+ *--------------------------------------------------------------------------*/
+
+var Prototype = {
+ Version: '1.6.0.1',
+
+ Browser: {
+ IE: !!(window.attachEvent && !window.opera),
+ Opera: !!window.opera,
+ WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+ Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
+ MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
+ },
+
+ BrowserFeatures: {
+ XPath: !!document.evaluate,
+ ElementExtensions: !!window.HTMLElement,
+ SpecificElementExtensions:
+ document.createElement('div').__proto__ &&
+ document.createElement('div').__proto__ !==
+ document.createElement('form').__proto__
+ },
+
+ ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+ JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+ emptyFunction: function() { },
+ K: function(x) { return x }
+};
+
+if (Prototype.Browser.MobileSafari)
+ Prototype.BrowserFeatures.SpecificElementExtensions = false;
+
+
+/* Based on Alex Arnell's inheritance implementation. */
+var Class = {
+ create: function() {
+ var parent = null, properties = $A(arguments);
+ if (Object.isFunction(properties[0]))
+ parent = properties.shift();
+
+ function klass() {
+ this.initialize.apply(this, arguments);
+ }
+
+ Object.extend(klass, Class.Methods);
+ klass.superclass = parent;
+ klass.subclasses = [];
+
+ if (parent) {
+ var subclass = function() { };
+ subclass.prototype = parent.prototype;
+ klass.prototype = new subclass;
+ parent.subclasses.push(klass);
+ }
+
+ for (var i = 0; i < properties.length; i++)
+ klass.addMethods(properties[i]);
+
+ if (!klass.prototype.initialize)
+ klass.prototype.initialize = Prototype.emptyFunction;
+
+ klass.prototype.constructor = klass;
+
+ return klass;
+ }
+};
+
+Class.Methods = {
+ addMethods: function(source) {
+ var ancestor = this.superclass && this.superclass.prototype;
+ var properties = Object.keys(source);
+
+ if (!Object.keys({ toString: true }).length)
+ properties.push("toString", "valueOf");
+
+ for (var i = 0, length = properties.length; i < length; i++) {
+ var property = properties[i], value = source[property];
+ if (ancestor && Object.isFunction(value) &&
+ value.argumentNames().first() == "$super") {
+ var method = value, value = Object.extend((function(m) {
+ return function() { return ancestor[m].apply(this, arguments) };
+ })(property).wrap(method), {
+ valueOf: function() { return method },
+ toString: function() { return method.toString() }
+ });
+ }
+ this.prototype[property] = value;
+ }
+
+ return this;
+ }
+};
+
+var Abstract = { };
+
+Object.extend = function(destination, source) {
+ for (var property in source)
+ destination[property] = source[property];
+ return destination;
+};
+
+Object.extend(Object, {
+ inspect: function(object) {
+ try {
+ if (Object.isUndefined(object)) return 'undefined';
+ if (object === null) return 'null';
+ return object.inspect ? object.inspect() : object.toString();
+ } catch (e) {
+ if (e instanceof RangeError) return '...';
+ throw e;
+ }
+ },
+
+ toJSON: function(object) {
+ var type = typeof object;
+ switch (type) {
+ case 'undefined':
+ case 'function':
+ case 'unknown': return;
+ case 'boolean': return object.toString();
+ }
+
+ if (object === null) return 'null';
+ if (object.toJSON) return object.toJSON();
+ if (Object.isElement(object)) return;
+
+ var results = [];
+ for (var property in object) {
+ var value = Object.toJSON(object[property]);
+ if (!Object.isUndefined(value))
+ results.push(property.toJSON() + ': ' + value);
+ }
+
+ return '{' + results.join(', ') + '}';
+ },
+
+ toQueryString: function(object) {
+ return $H(object).toQueryString();
+ },
+
+ toHTML: function(object) {
+ return object && object.toHTML ? object.toHTML() : String.interpret(object);
+ },
+
+ keys: function(object) {
+ var keys = [];
+ for (var property in object)
+ keys.push(property);
+ return keys;
+ },
+
+ values: function(object) {
+ var values = [];
+ for (var property in object)
+ values.push(object[property]);
+ return values;
+ },
+
+ clone: function(object) {
+ return Object.extend({ }, object);
+ },
+
+ isElement: function(object) {
+ return object && object.nodeType == 1;
+ },
+
+ isArray: function(object) {
+ return object && object.constructor === Array;
+ },
+
+ isHash: function(object) {
+ return object instanceof Hash;
+ },
+
+ isFunction: function(object) {
+ return typeof object == "function";
+ },
+
+ isString: function(object) {
+ return typeof object == "string";
+ },
+
+ isNumber: function(object) {
+ return typeof object == "number";
+ },
+
+ isUndefined: function(object) {
+ return typeof object == "undefined";
+ }
+});
+
+Object.extend(Function.prototype, {
+ argumentNames: function() {
+ var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
+ return names.length == 1 && !names[0] ? [] : names;
+ },
+
+ bind: function() {
+ if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
+ var __method = this, args = $A(arguments), object = args.shift();
+ return function() {
+ return __method.apply(object, args.concat($A(arguments)));
+ }
+ },
+
+ bindAsEventListener: function() {
+ var __method = this, args = $A(arguments), object = args.shift();
+ return function(event) {
+ return __method.apply(object, [event || window.event].concat(args));
+ }
+ },
+
+ curry: function() {
+ if (!arguments.length) return this;
+ var __method = this, args = $A(arguments);
+ return function() {
+ return __method.apply(this, args.concat($A(arguments)));
+ }
+ },
+
+ delay: function() {
+ var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
+ return window.setTimeout(function() {
+ return __method.apply(__method, args);
+ }, timeout);
+ },
+
+ wrap: function(wrapper) {
+ var __method = this;
+ return function() {
+ return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
+ }
+ },
+
+ methodize: function() {
+ if (this._methodized) return this._methodized;
+ var __method = this;
+ return this._methodized = function() {
+ return __method.apply(null, [this].concat($A(arguments)));
+ };
+ }
+});
+
+Function.prototype.defer = Function.prototype.delay.curry(0.01);
+
+Date.prototype.toJSON = function() {
+ return '"' + this.getUTCFullYear() + '-' +
+ (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+ this.getUTCDate().toPaddedString(2) + 'T' +
+ this.getUTCHours().toPaddedString(2) + ':' +
+ this.getUTCMinutes().toPaddedString(2) + ':' +
+ this.getUTCSeconds().toPaddedString(2) + 'Z"';
+};
+
+var Try = {
+ these: function() {
+ var returnValue;
+
+ for (var i = 0, length = arguments.length; i < length; i++) {
+ var lambda = arguments[i];
+ try {
+ returnValue = lambda();
+ break;
+ } catch (e) { }
+ }
+
+ return returnValue;
+ }
+};
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+ return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
+
+/*--------------------------------------------------------------------------*/
+
+var PeriodicalExecuter = Class.create({
+ initialize: function(callback, frequency) {
+ this.callback = callback;
+ this.frequency = frequency;
+ this.currentlyExecuting = false;
+
+ this.registerCallback();
+ },
+
+ registerCallback: function() {
+ this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+ },
+
+ execute: function() {
+ this.callback(this);
+ },
+
+ stop: function() {
+ if (!this.timer) return;
+ clearInterval(this.timer);
+ this.timer = null;
+ },
+
+ onTimerEvent: function() {
+ if (!this.currentlyExecuting) {
+ try {
+ this.currentlyExecuting = true;
+ this.execute();
+ } finally {
+ this.currentlyExecuting = false;
+ }
+ }
+ }
+});
+Object.extend(String, {
+ interpret: function(value) {
+ return value == null ? '' : String(value);
+ },
+ specialChar: {
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '\\': '\\\\'
+ }
+});
+
+Object.extend(String.prototype, {
+ gsub: function(pattern, replacement) {
+ var result = '', source = this, match;
+ replacement = arguments.callee.prepareReplacement(replacement);
+
+ while (source.length > 0) {
+ if (match = source.match(pattern)) {
+ result += source.slice(0, match.index);
+ result += String.interpret(replacement(match));
+ source = source.slice(match.index + match[0].length);
+ } else {
+ result += source, source = '';
+ }
+ }
+ return result;
+ },
+
+ sub: function(pattern, replacement, count) {
+ replacement = this.gsub.prepareReplacement(replacement);
+ count = Object.isUndefined(count) ? 1 : count;
+
+ return this.gsub(pattern, function(match) {
+ if (--count < 0) return match[0];
+ return replacement(match);
+ });
+ },
+
+ scan: function(pattern, iterator) {
+ this.gsub(pattern, iterator);
+ return String(this);
+ },
+
+ truncate: function(length, truncation) {
+ length = length || 30;
+ truncation = Object.isUndefined(truncation) ? '...' : truncation;
+ return this.length > length ?
+ this.slice(0, length - truncation.length) + truncation : String(this);
+ },
+
+ strip: function() {
+ return this.replace(/^\s+/, '').replace(/\s+$/, '');
+ },
+
+ stripTags: function() {
+ return this.replace(/<\/?[^>]+>/gi, '');
+ },
+
+ stripScripts: function() {
+ return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+ },
+
+ extractScripts: function() {
+ var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
+ var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+ return (this.match(matchAll) || []).map(function(scriptTag) {
+ return (scriptTag.match(matchOne) || ['', ''])[1];
+ });
+ },
+
+ evalScripts: function() {
+ return this.extractScripts().map(function(script) { return eval(script) });
+ },
+
+ escapeHTML: function() {
+ var self = arguments.callee;
+ self.text.data = this;
+ return self.div.innerHTML;
+ },
+
+ unescapeHTML: function() {
+ var div = new Element('div');
+ div.innerHTML = this.stripTags();
+ return div.childNodes[0] ? (div.childNodes.length > 1 ?
+ $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
+ div.childNodes[0].nodeValue) : '';
+ },
+
+ toQueryParams: function(separator) {
+ var match = this.strip().match(/([^?#]*)(#.*)?$/);
+ if (!match) return { };
+
+ return match[1].split(separator || '&').inject({ }, function(hash, pair) {
+ if ((pair = pair.split('='))[0]) {
+ var key = decodeURIComponent(pair.shift());
+ var value = pair.length > 1 ? pair.join('=') : pair[0];
+ if (value != undefined) value = decodeURIComponent(value);
+
+ if (key in hash) {
+ if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
+ hash[key].push(value);
+ }
+ else hash[key] = value;
+ }
+ return hash;
+ });
+ },
+
+ toArray: function() {
+ return this.split('');
+ },
+
+ succ: function() {
+ return this.slice(0, this.length - 1) +
+ String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+ },
+
+ times: function(count) {
+ return count < 1 ? '' : new Array(count + 1).join(this);
+ },
+
+ camelize: function() {
+ var parts = this.split('-'), len = parts.length;
+ if (len == 1) return parts[0];
+
+ var camelized = this.charAt(0) == '-'
+ ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
+ : parts[0];
+
+ for (var i = 1; i < len; i++)
+ camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
+
+ return camelized;
+ },
+
+ capitalize: function() {
+ return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+ },
+
+ underscore: function() {
+ return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
+ },
+
+ dasherize: function() {
+ return this.gsub(/_/,'-');
+ },
+
+ inspect: function(useDoubleQuotes) {
+ var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
+ var character = String.specialChar[match[0]];
+ return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
+ });
+ if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+ return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+ },
+
+ toJSON: function() {
+ return this.inspect(true);
+ },
+
+ unfilterJSON: function(filter) {
+ return this.sub(filter || Prototype.JSONFilter, '#{1}');
+ },
+
+ isJSON: function() {
+ var str = this;
+ if (str.blank()) return false;
+ str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
+ return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+ },
+
+ evalJSON: function(sanitize) {
+ var json = this.unfilterJSON();
+ try {
+ if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+ } catch (e) { }
+ throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+ },
+
+ include: function(pattern) {
+ return this.indexOf(pattern) > -1;
+ },
+
+ startsWith: function(pattern) {
+ return this.indexOf(pattern) === 0;
+ },
+
+ endsWith: function(pattern) {
+ var d = this.length - pattern.length;
+ return d >= 0 && this.lastIndexOf(pattern) === d;
+ },
+
+ empty: function() {
+ return this == '';
+ },
+
+ blank: function() {
+ return /^\s*$/.test(this);
+ },
+
+ interpolate: function(object, pattern) {
+ return new Template(this, pattern).evaluate(object);
+ }
+});
+
+if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
+ escapeHTML: function() {
+ return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+ },
+ unescapeHTML: function() {
+ return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
+ }
+});
+
+String.prototype.gsub.prepareReplacement = function(replacement) {
+ if (Object.isFunction(replacement)) return replacement;
+ var template = new Template(replacement);
+ return function(match) { return template.evaluate(match) };
+};
+
+String.prototype.parseQuery = String.prototype.toQueryParams;
+
+Object.extend(String.prototype.escapeHTML, {
+ div: document.createElement('div'),
+ text: document.createTextNode('')
+});
+
+with (String.prototype.escapeHTML) div.appendChild(text);
+
+var Template = Class.create({
+ initialize: function(template, pattern) {
+ this.template = template.toString();
+ this.pattern = pattern || Template.Pattern;
+ },
+
+ evaluate: function(object) {
+ if (Object.isFunction(object.toTemplateReplacements))
+ object = object.toTemplateReplacements();
+
+ return this.template.gsub(this.pattern, function(match) {
+ if (object == null) return '';
+
+ var before = match[1] || '';
+ if (before == '\\') return match[2];
+
+ var ctx = object, expr = match[3];
+ var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+ match = pattern.exec(expr);
+ if (match == null) return before;
+
+ while (match != null) {
+ var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
+ ctx = ctx[comp];
+ if (null == ctx || '' == match[3]) break;
+ expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+ match = pattern.exec(expr);
+ }
+
+ return before + String.interpret(ctx);
+ }.bind(this));
+ }
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+
+var $break = { };
+
+var Enumerable = {
+ each: function(iterator, context) {
+ var index = 0;
+ iterator = iterator.bind(context);
+ try {
+ this._each(function(value) {
+ iterator(value, index++);
+ });
+ } catch (e) {
+ if (e != $break) throw e;
+ }
+ return this;
+ },
+
+ eachSlice: function(number, iterator, context) {
+ iterator = iterator ? iterator.bind(context) : Prototype.K;
+ var index = -number, slices = [], array = this.toArray();
+ while ((index += number) < array.length)
+ slices.push(array.slice(index, index+number));
+ return slices.collect(iterator, context);
+ },
+
+ all: function(iterator, context) {
+ iterator = iterator ? iterator.bind(context) : Prototype.K;
+ var result = true;
+ this.each(function(value, index) {
+ result = result && !!iterator(value, index);
+ if (!result) throw $break;
+ });
+ return result;
+ },
+
+ any: function(iterator, context) {
+ iterator = iterator ? iterator.bind(context) : Prototype.K;
+ var result = false;
+ this.each(function(value, index) {
+ if (result = !!iterator(value, index))
+ throw $break;
+ });
+ return result;
+ },
+
+ collect: function(iterator, context) {
+ iterator = iterator ? iterator.bind(context) : Prototype.K;
+ var results = [];
+ this.each(function(value, index) {
+ results.push(iterator(value, index));
+ });
+ return results;
+ },
+
+ detect: function(iterator, context) {
+ iterator = iterator.bind(context);
+ var result;
+ this.each(function(value, index) {
+ if (iterator(value, index)) {
+ result = value;
+ throw $break;
+ }
+ });
+ return result;
+ },
+
+ findAll: function(iterator, context) {
+ iterator = iterator.bind(context);
+ var results = [];
+ this.each(function(value, index) {
+ if (iterator(value, index))
+ results.push(value);
+ });
+ return results;
+ },
+
+ grep: function(filter, iterator, context) {
+ iterator = iterator ? iterator.bind(context) : Prototype.K;
+ var results = [];
+
+ if (Object.isString(filter))
+ filter = new RegExp(filter);
+
+ this.each(function(value, index) {
+ if (filter.match(value))
+ results.push(iterator(value, index));
+ });
+ return results;
+ },
+
+ include: function(object) {
+ if (Object.isFunction(this.indexOf))
+ if (this.indexOf(object) != -1) return true;
+
+ var found = false;
+ this.each(function(value) {
+ if (value == object) {
+ found = true;
+ throw $break;
+ }
+ });
+ return found;
+ },
+
+ inGroupsOf: function(number, fillWith) {
+ fillWith = Object.isUndefined(fillWith) ? null : fillWith;
+ return this.eachSlice(number, function(slice) {
+ while(slice.length < number) slice.push(fillWith);
+ return slice;
+ });
+ },
+
+ inject: function(memo, iterator, context) {
+ iterator = iterator.bind(context);
+ this.each(function(value, index) {
+ memo = iterator(memo, value, index);
+ });
+ return memo;
+ },
+
+ invoke: function(method) {
+ var args = $A(arguments).slice(1);
+ return this.map(function(value) {
+ return value[method].apply(value, args);
+ });
+ },
+
+ max: function(iterator, context) {
+ iterator = iterator ? iterator.bind(context) : Prototype.K;
+ var result;
+ this.each(function(value, index) {
+ value = iterator(value, index);
+ if (result == null || value >= result)
+ result = value;
+ });
+ return result;
+ },
+
+ min: function(iterator, context) {
+ iterator = iterator ? iterator.bind(context) : Prototype.K;
+ var result;
+ this.each(function(value, index) {
+ value = iterator(value, index);
+ if (result == null || value < result)
+ result = value;
+ });
+ return result;
+ },
+
+ partition: function(iterator, context) {
+ iterator = iterator ? iterator.bind(context) : Prototype.K;
+ var trues = [], falses = [];
+ this.each(function(value, index) {
+ (iterator(value, index) ?
+ trues : falses).push(value);
+ });
+ return [trues, falses];
+ },
+
+ pluck: function(property) {
+ var results = [];
+ this.each(function(value) {
+ results.push(value[property]);
+ });
+ return results;
+ },
+
+ reject: function(iterator, context) {
+ iterator = iterator.bind(context);
+ var results = [];
+ this.each(function(value, index) {
+ if (!iterator(value, index))
+ results.push(value);
+ });
+ return results;
+ },
+
+ sortBy: function(iterator, context) {
+ iterator = iterator.bind(context);
+ return this.map(function(value, index) {
+ return {value: value, criteria: iterator(value, index)};
+ }).sort(function(left, right) {
+ var a = left.criteria, b = right.criteria;
+ return a < b ? -1 : a > b ? 1 : 0;
+ }).pluck('value');
+ },
+
+ toArray: function() {
+ return this.map();
+ },
+
+ zip: function() {
+ var iterator = Prototype.K, args = $A(arguments);
+ if (Object.isFunction(args.last()))
+ iterator = args.pop();
+
+ var collections = [this].concat(args).map($A);
+ return this.map(function(value, index) {
+ return iterator(collections.pluck(index));
+ });
+ },
+
+ size: function() {
+ return this.toArray().length;
+ },
+
+ inspect: function() {
+ return '#<Enumerable:' + this.toArray().inspect() + '>';
+ }
+};
+
+Object.extend(Enumerable, {
+ map: Enumerable.collect,
+ find: Enumerable.detect,
+ select: Enumerable.findAll,
+ filter: Enumerable.findAll,
+ member: Enumerable.include,
+ entries: Enumerable.toArray,
+ every: Enumerable.all,
+ some: Enumerable.any
+});
+function $A(iterable) {
+ if (!iterable) return [];
+ if (iterable.toArray) return iterable.toArray();
+ var length = iterable.length, results = new Array(length);
+ while (length--) results[length] = iterable[length];
+ return results;
+}
+
+if (Prototype.Browser.WebKit) {
+ function $A(iterable) {
+ if (!iterable) return [];
+ if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
+ iterable.toArray) return iterable.toArray();
+ var length = iterable.length, results = new Array(length);
+ while (length--) results[length] = iterable[length];
+ return results;
+ }
+}
+
+Array.from = $A;
+
+Object.extend(Array.prototype, Enumerable);
+
+if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
+
+Object.extend(Array.prototype, {
+ _each: function(iterator) {
+ for (var i = 0, length = this.length; i < length; i++)
+ iterator(this[i]);
+ },
+
+ clear: function() {
+ this.length = 0;
+ return this;
+ },
+
+ first: function() {
+ return this[0];
+ },
+
+ last: function() {
+ return this[this.length - 1];
+ },
+
+ compact: function() {
+ return this.select(function(value) {
+ return value != null;
+ });
+ },
+
+ flatten: function() {
+ return this.inject([], function(array, value) {
+ return array.concat(Object.isArray(value) ?
+ value.flatten() : [value]);
+ });
+ },
+
+ without: function() {
+ var values = $A(arguments);
+ return this.select(function(value) {
+ return !values.include(value);
+ });
+ },
+
+ reverse: function(inline) {
+ return (inline !== false ? this : this.toArray())._reverse();
+ },
+
+ reduce: function() {
+ return this.length > 1 ? this : this[0];
+ },
+
+ uniq: function(sorted) {
+ return this.inject([], function(array, value, index) {
+ if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+ array.push(value);
+ return array;
+ });
+ },
+
+ intersect: function(array) {
+ return this.uniq().findAll(function(item) {
+ return array.detect(function(value) { return item === value });
+ });
+ },
+
+ clone: function() {
+ return [].concat(this);
+ },
+
+ size: function() {
+ return this.length;
+ },
+
+ inspect: function() {
+ return '[' + this.map(Object.inspect).join(', ') + ']';
+ },
+
+ toJSON: function() {
+ var results = [];
+ this.each(function(object) {
+ var value = Object.toJSON(object);
+ if (!Object.isUndefined(value)) results.push(value);
+ });
+ return '[' + results.join(', ') + ']';
+ }
+});
+
+// use native browser JS 1.6 implementation if available
+if (Object.isFunction(Array.prototype.forEach))
+ Array.prototype._each = Array.prototype.forEach;
+
+if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
+ i || (i = 0);
+ var length = this.length;
+ if (i < 0) i = length + i;
+ for (; i < length; i++)
+ if (this[i] === item) return i;
+ return -1;
+};
+
+if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
+ i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
+ var n = this.slice(0, i).reverse().indexOf(item);
+ return (n < 0) ? n : i - n - 1;
+};
+
+Array.prototype.toArray = Array.prototype.clone;
+
+function $w(string) {
+ if (!Object.isString(string)) return [];
+ string = string.strip();
+ return string ? string.split(/\s+/) : [];
+}
+
+if (Prototype.Browser.Opera){
+ Array.prototype.concat = function() {
+ var array = [];
+ for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
+ for (var i = 0, length = arguments.length; i < length; i++) {
+ if (Object.isArray(arguments[i])) {
+ for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
+ array.push(arguments[i][j]);
+ } else {
+ array.push(arguments[i]);
+ }
+ }
+ return array;
+ };
+}
+Object.extend(Number.prototype, {
+ toColorPart: function() {
+ return this.toPaddedString(2, 16);
+ },
+
+ succ: function() {
+ return this + 1;
+ },
+
+ times: function(iterator) {
+ $R(0, this, true).each(iterator);
+ return this;
+ },
+
+ toPaddedString: function(length, radix) {
+ var string = this.toString(radix || 10);
+ return '0'.times(length - string.length) + string;
+ },
+
+ toJSON: function() {
+ return isFinite(this) ? this.toString() : 'null';
+ }
+});
+
+$w('abs round ceil floor').each(function(method){
+ Number.prototype[method] = Math[method].methodize();
+});
+function $H(object) {
+ return new Hash(object);
+};
+
+var Hash = Class.create(Enumerable, (function() {
+
+ function toQueryPair(key, value) {
+ if (Object.isUndefined(value)) return key;
+ return key + '=' + encodeURIComponent(String.interpret(value));
+ }
+
+ return {
+ initialize: function(object) {
+ this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+ },
+
+ _each: function(iterator) {
+ for (var key in this._object) {
+ var value = this._object[key], pair = [key, value];
+ pair.key = key;
+ pair.value = value;
+ iterator(pair);
+ }
+ },
+
+ set: function(key, value) {
+ return this._object[key] = value;
+ },
+
+ get: function(key) {
+ return this._object[key];
+ },
+
+ unset: function(key) {
+ var value = this._object[key];
+ delete this._object[key];
+ return value;
+ },
+
+ toObject: function() {
+ return Object.clone(this._object);
+ },
+
+ keys: function() {
+ return this.pluck('key');
+ },
+
+ values: function() {
+ return this.pluck('value');
+ },
+
+ index: function(value) {
+ var match = this.detect(function(pair) {
+ return pair.value === value;
+ });
+ return match && match.key;
+ },
+
+ merge: function(object) {
+ return this.clone().update(object);
+ },
+
+ update: function(object) {
+ return new Hash(object).inject(this, function(result, pair) {
+ result.set(pair.key, pair.value);
+ return result;
+ });
+ },
+
+ toQueryString: function() {
+ return this.map(function(pair) {
+ var key = encodeURIComponent(pair.key), values = pair.value;
+
+ if (values && typeof values == 'object') {
+ if (Object.isArray(values))
+ return values.map(toQueryPair.curry(key)).join('&');
+ }
+ return toQueryPair(key, values);
+ }).join('&');
+ },
+
+ inspect: function() {
+ return '#<Hash:{' + this.map(function(pair) {
+ return pair.map(Object.inspect).join(': ');
+ }).join(', ') + '}>';
+ },
+
+ toJSON: function() {
+ return Object.toJSON(this.toObject());
+ },
+
+ clone: function() {
+ return new Hash(this);
+ }
+ }
+})());
+
+Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
+Hash.from = $H;
+var ObjectRange = Class.create(Enumerable, {
+ initialize: function(start, end, exclusive) {
+ this.start = start;
+ this.end = end;
+ this.exclusive = exclusive;
+ },
+
+ _each: function(iterator) {
+ var value = this.start;
+ while (this.include(value)) {
+ iterator(value);
+ value = value.succ();
+ }
+ },
+
+ include: function(value) {
+ if (value < this.start)
+ return false;
+ if (this.exclusive)
+ return value < this.end;
+ return value <= this.end;
+ }
+});
+
+var $R = function(start, end, exclusive) {
+ return new ObjectRange(start, end, exclusive);
+};
+
+var Ajax = {
+ getTransport: function() {
+ return Try.these(
+ function() {return new XMLHttpRequest()},
+ function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+ function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+ ) || false;
+ },
+
+ activeRequestCount: 0
+};
+
+Ajax.Responders = {
+ responders: [],
+
+ _each: function(iterator) {
+ this.responders._each(iterator);
+ },
+
+ register: function(responder) {
+ if (!this.include(responder))
+ this.responders.push(responder);
+ },
+
+ unregister: function(responder) {
+ this.responders = this.responders.without(responder);
+ },
+
+ dispatch: function(callback, request, transport, json) {
+ this.each(function(responder) {
+ if (Object.isFunction(responder[callback])) {
+ try {
+ responder[callback].apply(responder, [request, transport, json]);
+ } catch (e) { }
+ }
+ });
+ }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+ onCreate: function() { Ajax.activeRequestCount++ },
+ onComplete: function() { Ajax.activeRequestCount-- }
+});
+
+Ajax.Base = Class.create({
+ initialize: function(options) {
+ this.options = {
+ method: 'post',
+ asynchronous: true,
+ contentType: 'application/x-www-form-urlencoded',
+ encoding: 'UTF-8',
+ parameters: '',
+ evalJSON: true,
+ evalJS: true
+ };
+ Object.extend(this.options, options || { });
+
+ this.options.method = this.options.method.toLowerCase();
+
+ if (Object.isString(this.options.parameters))
+ this.options.parameters = this.options.parameters.toQueryParams();
+ else if (Object.isHash(this.options.parameters))
+ this.options.parameters = this.options.parameters.toObject();
+ }
+});
+
+Ajax.Request = Class.create(Ajax.Base, {
+ _complete: false,
+
+ initialize: function($super, url, options) {
+ $super(options);
+ this.transport = Ajax.getTransport();
+ this.request(url);
+ },
+
+ request: function(url) {
+ this.url = url;
+ this.method = this.options.method;
+ var params = Object.clone(this.options.parameters);
+
+ if (!['get', 'post'].include(this.method)) {
+ // simulate other verbs over post
+ params['_method'] = this.method;
+ this.method = 'post';
+ }
+
+ this.parameters = params;
+
+ if (params = Object.toQueryString(params)) {
+ // when GET, append parameters to URL
+ if (this.method == 'get')
+ this.url += (this.url.include('?') ? '&' : '?') + params;
+ else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
+ params += '&_=';
+ }
+
+ try {
+ var response = new Ajax.Response(this);
+ if (this.options.onCreate) this.options.onCreate(response);
+ Ajax.Responders.dispatch('onCreate', this, response);
+
+ this.transport.open(this.method.toUpperCase(), this.url,
+ this.options.asynchronous);
+
+ if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
+
+ this.transport.onreadystatechange = this.onStateChange.bind(this);
+ this.setRequestHeaders();
+
+ this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+ this.transport.send(this.body);
+
+ /* Force Firefox to handle ready state 4 for synchronous requests */
+ if (!this.options.asynchronous && this.transport.overrideMimeType)
+ this.onStateChange();
+
+ }
+ catch (e) {
+ this.dispatchException(e);
+ }
+ },
+
+ onStateChange: function() {
+ var readyState = this.transport.readyState;
+ if (readyState > 1 && !((readyState == 4) && this._complete))
+ this.respondToReadyState(this.transport.readyState);
+ },
+
+ setRequestHeaders: function() {
+ var headers = {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'X-Prototype-Version': Prototype.Version,
+ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+ };
+
+ if (this.method == 'post') {
+ headers['Content-type'] = this.options.contentType +
+ (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+ /* Force "Connection: close" for older Mozilla browsers to work
+ * around a bug where XMLHttpRequest sends an incorrect
+ * Content-length header. See Mozilla Bugzilla #246651.
+ */
+ if (this.transport.overrideMimeType &&
+ (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+ headers['Connection'] = 'close';
+ }
+
+ // user-defined headers
+ if (typeof this.options.requestHeaders == 'object') {
+ var extras = this.options.requestHeaders;
+
+ if (Object.isFunction(extras.push))
+ for (var i = 0, length = extras.length; i < length; i += 2)
+ headers[extras[i]] = extras[i+1];
+ else
+ $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+ }
+
+ for (var name in headers)
+ this.transport.setRequestHeader(name, headers[name]);
+ },
+
+ success: function() {
+ var status = this.getStatus();
+ return !status || (status >= 200 && status < 300);
+ },
+
+ getStatus: function() {
+ try {
+ return this.transport.status || 0;
+ } catch (e) { return 0 }
+ },
+
+ respondToReadyState: function(readyState) {
+ var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
+
+ if (state == 'Complete') {
+ try {
+ this._complete = true;
+ (this.options['on' + response.status]
+ || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+ || Prototype.emptyFunction)(response, response.headerJSON);
+ } catch (e) {
+ this.dispatchException(e);
+ }
+
+ var contentType = response.getHeader('Content-type');
+ if (this.options.evalJS == 'force'
+ || (this.options.evalJS && contentType
+ && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
+ this.evalResponse();
+ }
+
+ try {
+ (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
+ Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
+ } catch (e) {
+ this.dispatchException(e);
+ }
+
+ if (state == 'Complete') {
+ // avoid memory leak in MSIE: clean up
+ this.transport.onreadystatechange = Prototype.emptyFunction;
+ }
+ },
+
+ getHeader: function(name) {
+ try {
+ return this.transport.getResponseHeader(name);
+ } catch (e) { return null }
+ },
+
+ evalResponse: function() {
+ try {
+ return eval((this.transport.responseText || '').unfilterJSON());
+ } catch (e) {
+ this.dispatchException(e);
+ }
+ },
+
+ dispatchException: function(exception) {
+ (this.options.onException || Prototype.emptyFunction)(this, exception);
+ Ajax.Responders.dispatch('onException', this, exception);
+ }
+});
+
+Ajax.Request.Events =
+ ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Response = Class.create({
+ initialize: function(request){
+ this.request = request;
+ var transport = this.transport = request.transport,
+ readyState = this.readyState = transport.readyState;
+
+ if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+ this.status = this.getStatus();
+ this.statusText = this.getStatusText();
+ this.responseText = String.interpret(transport.responseText);
+ this.headerJSON = this._getHeaderJSON();
+ }
+
+ if(readyState == 4) {
+ var xml = transport.responseXML;
+ this.responseXML = Object.isUndefined(xml) ? null : xml;
+ this.responseJSON = this._getResponseJSON();
+ }
+ },
+
+ status: 0,
+ statusText: '',
+
+ getStatus: Ajax.Request.prototype.getStatus,
+
+ getStatusText: function() {
+ try {
+ return this.transport.statusText || '';
+ } catch (e) { return '' }
+ },
+
+ getHeader: Ajax.Request.prototype.getHeader,
+
+ getAllHeaders: function() {
+ try {
+ return this.getAllResponseHeaders();
+ } catch (e) { return null }
+ },
+
+ getResponseHeader: function(name) {
+ return this.transport.getResponseHeader(name);
+ },
+
+ getAllResponseHeaders: function() {
+ return this.transport.getAllResponseHeaders();
+ },
+
+ _getHeaderJSON: function() {
+ var json = this.getHeader('X-JSON');
+ if (!json) return null;
+ json = decodeURIComponent(escape(json));
+ try {
+ return json.evalJSON(this.request.options.sanitizeJSON);
+ } catch (e) {
+ this.request.dispatchException(e);
+ }
+ },
+
+ _getResponseJSON: function() {
+ var options = this.request.options;
+ if (!options.evalJSON || (options.evalJSON != 'force' &&
+ !(this.getHeader('Content-type') || '').include('application/json')) ||
+ this.responseText.blank())
+ return null;
+ try {
+ return this.responseText.evalJSON(options.sanitizeJSON);
+ } catch (e) {
+ this.request.dispatchException(e);
+ }
+ }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+ initialize: function($super, container, url, options) {
+ this.container = {
+ success: (container.success || container),
+ failure: (container.failure || (container.success ? null : container))
+ };
+
+ options = Object.clone(options);
+ var onComplete = options.onComplete;
+ options.onComplete = (function(response, json) {
+ this.updateContent(response.responseText);
+ if (Object.isFunction(onComplete)) onComplete(response, json);
+ }).bind(this);
+
+ $super(url, options);
+ },
+
+ updateContent: function(responseText) {
+ var receiver = this.container[this.success() ? 'success' : 'failure'],
+ options = this.options;
+
+ if (!options.evalScripts) responseText = responseText.stripScripts();
+
+ if (receiver = $(receiver)) {
+ if (options.insertion) {
+ if (Object.isString(options.insertion)) {
+ var insertion = { }; insertion[options.insertion] = responseText;
+ receiver.insert(insertion);
+ }
+ else options.insertion(receiver, responseText);
+ }
+ else receiver.update(responseText);
+ }
+ }
+});
+
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+ initialize: function($super, container, url, options) {
+ $super(options);
+ this.onComplete = this.options.onComplete;
+
+ this.frequency = (this.options.frequency || 2);
+ this.decay = (this.options.decay || 1);
+
+ this.updater = { };
+ this.container = container;
+ this.url = url;
+
+ this.start();
+ },
+
+ start: function() {
+ this.options.onComplete = this.updateComplete.bind(this);
+ this.onTimerEvent();
+ },
+
+ stop: function() {
+ this.updater.options.onComplete = undefined;
+ clearTimeout(this.timer);
+ (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+ },
+
+ updateComplete: function(response) {
+ if (this.options.decay) {
+ this.decay = (response.responseText == this.lastText ?
+ this.decay * this.options.decay : 1);
+
+ this.lastText = response.responseText;
+ }
+ this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
+ },
+
+ onTimerEvent: function() {
+ this.updater = new Ajax.Updater(this.container, this.url, this.options);
+ }
+});
+function $(element) {
+ if (arguments.length > 1) {
+ for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+ elements.push($(arguments[i]));
+ return elements;
+ }
+ if (Object.isString(element))
+ element = document.getElementById(element);
+ return Element.extend(element);
+}
+
+if (Prototype.BrowserFeatures.XPath) {
+ document._getElementsByXPath = function(expression, parentElement) {
+ var results = [];
+ var query = document.evaluate(expression, $(parentElement) || document,
+ null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+ for (var i = 0, length = query.snapshotLength; i < length; i++)
+ results.push(Element.extend(query.snapshotItem(i)));
+ return results;
+ };
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Node) var Node = { };
+
+if (!Node.ELEMENT_NODE) {
+ // DOM level 2 ECMAScript Language Binding
+ Object.extend(Node, {
+ ELEMENT_NODE: 1,
+ ATTRIBUTE_NODE: 2,
+ TEXT_NODE: 3,
+ CDATA_SECTION_NODE: 4,
+ ENTITY_REFERENCE_NODE: 5,
+ ENTITY_NODE: 6,
+ PROCESSING_INSTRUCTION_NODE: 7,
+ COMMENT_NODE: 8,
+ DOCUMENT_NODE: 9,
+ DOCUMENT_TYPE_NODE: 10,
+ DOCUMENT_FRAGMENT_NODE: 11,
+ NOTATION_NODE: 12
+ });
+}
+
+(function() {
+ var element = this.Element;
+ this.Element = function(tagName, attributes) {
+ attributes = attributes || { };
+ tagName = tagName.toLowerCase();
+ var cache = Element.cache;
+ if (Prototype.Browser.IE && attributes.name) {
+ tagName = '<' + tagName + ' name="' + attributes.name + '">';
+ delete attributes.name;
+ return Element.writeAttribute(document.createElement(tagName), attributes);
+ }
+ if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
+ return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
+ };
+ Object.extend(this.Element, element || { });
+}).call(window);
+
+Element.cache = { };
+
+Element.Methods = {
+ visible: function(element) {
+ return $(element).style.display != 'none';
+ },
+
+ toggle: function(element) {
+ element = $(element);
+ Element[Element.visible(element) ? 'hide' : 'show'](element);
+ return element;
+ },
+
+ hide: function(element) {
+ $(element).style.display = 'none';
+ return element;
+ },
+
+ show: function(element) {
+ $(element).style.display = '';
+ return element;
+ },
+
+ remove: function(element) {
+ element = $(element);
+ element.parentNode.removeChild(element);
+ return element;
+ },
+
+ update: function(element, content) {
+ element = $(element);
+ if (content && content.toElement) content = content.toElement();
+ if (Object.isElement(content)) return element.update().insert(content);
+ content = Object.toHTML(content);
+ element.innerHTML = content.stripScripts();
+ content.evalScripts.bind(content).defer();
+ return element;
+ },
+
+ replace: function(element, content) {
+ element = $(element);
+ if (content && content.toElement) content = content.toElement();
+ else if (!Object.isElement(content)) {
+ content = Object.toHTML(content);
+ var range = element.ownerDocument.createRange();
+ range.selectNode(element);
+ content.evalScripts.bind(content).defer();
+ content = range.createContextualFragment(content.stripScripts());
+ }
+ element.parentNode.replaceChild(content, element);
+ return element;
+ },
+
+ insert: function(element, insertions) {
+ element = $(element);
+
+ if (Object.isString(insertions) || Object.isNumber(insertions) ||
+ Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+ insertions = {bottom:insertions};
+
+ var content, t, range;
+
+ for (position in insertions) {
+ content = insertions[position];
+ position = position.toLowerCase();
+ t = Element._insertionTranslations[position];
+
+ if (content && content.toElement) content = content.toElement();
+ if (Object.isElement(content)) {
+ t.insert(element, content);
+ continue;
+ }
+
+ content = Object.toHTML(content);
+
+ range = element.ownerDocument.createRange();
+ t.initializeRange(element, range);
+ t.insert(element, range.createContextualFragment(content.stripScripts()));
+
+ content.evalScripts.bind(content).defer();
+ }
+
+ return element;
+ },
+
+ wrap: function(element, wrapper, attributes) {
+ element = $(element);
+ if (Object.isElement(wrapper))
+ $(wrapper).writeAttribute(attributes || { });
+ else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
+ else wrapper = new Element('div', wrapper);
+ if (element.parentNode)
+ element.parentNode.replaceChild(wrapper, element);
+ wrapper.appendChild(element);
+ return wrapper;
+ },
+
+ inspect: function(element) {
+ element = $(element);
+ var result = '<' + element.tagName.toLowerCase();
+ $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+ var property = pair.first(), attribute = pair.last();
+ var value = (element[property] || '').toString();
+ if (value) result += ' ' + attribute + '=' + value.inspect(true);
+ });
+ return result + '>';
+ },
+
+ recursivelyCollect: function(element, property) {
+ element = $(element);
+ var elements = [];
+ while (element = element[property])
+ if (element.nodeType == 1)
+ elements.push(Element.extend(element));
+ return elements;
+ },
+
+ ancestors: function(element) {
+ return $(element).recursivelyCollect('parentNode');
+ },
+
+ descendants: function(element) {
+ return $(element).getElementsBySelector("*");
+ },
+
+ firstDescendant: function(element) {
+ element = $(element).firstChild;
+ while (element && element.nodeType != 1) element = element.nextSibling;
+ return $(element);
+ },
+
+ immediateDescendants: function(element) {
+ if (!(element = $(element).firstChild)) return [];
+ while (element && element.nodeType != 1) element = element.nextSibling;
+ if (element) return [element].concat($(element).nextSiblings());
+ return [];
+ },
+
+ previousSiblings: function(element) {
+ return $(element).recursivelyCollect('previousSibling');
+ },
+
+ nextSiblings: function(element) {
+ return $(element).recursivelyCollect('nextSibling');
+ },
+
+ siblings: function(element) {
+ element = $(element);
+ return element.previousSiblings().reverse().concat(element.nextSiblings());
+ },
+
+ match: function(element, selector) {
+ if (Object.isString(selector))
+ selector = new Selector(selector);
+ return selector.match($(element));
+ },
+
+ up: function(element, expression, index) {
+ element = $(element);
+ if (arguments.length == 1) return $(element.parentNode);
+ var ancestors = element.ancestors();
+ return expression ? Selector.findElement(ancestors, expression, index) :
+ ancestors[index || 0];
+ },
+
+ down: function(element, expression, index) {
+ element = $(element);
+ if (arguments.length == 1) return element.firstDescendant();
+ var descendants = element.descendants();
+ return expression ? Selector.findElement(descendants, expression, index) :
+ descendants[index || 0];
+ },
+
+ previous: function(element, expression, index) {
+ element = $(element);
+ if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
+ var previousSiblings = element.previousSiblings();
+ return expression ? Selector.findElement(previousSiblings, expression, index) :
+ previousSiblings[index || 0];
+ },
+
+ next: function(element, expression, index) {
+ element = $(element);
+ if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
+ var nextSiblings = element.nextSiblings();
+ return expression ? Selector.findElement(nextSiblings, expression, index) :
+ nextSiblings[index || 0];
+ },
+
+ select: function() {
+ var args = $A(arguments), element = $(args.shift());
+ return Selector.findChildElements(element, args);
+ },
+
+ adjacent: function() {
+ var args = $A(arguments), element = $(args.shift());
+ return Selector.findChildElements(element.parentNode, args).without(element);
+ },
+
+ identify: function(element) {
+ element = $(element);
+ var id = element.readAttribute('id'), self = arguments.callee;
+ if (id) return id;
+ do { id = 'anonymous_element_' + self.counter++ } while ($(id));
+ element.writeAttribute('id', id);
+ return id;
+ },
+
+ readAttribute: function(element, name) {
+ element = $(element);
+ if (Prototype.Browser.IE) {
+ var t = Element._attributeTranslations.read;
+ if (t.values[name]) return t.values[name](element, name);
+ if (t.names[name]) name = t.names[name];
+ if (name.include(':')) {
+ return (!element.attributes || !element.attributes[name]) ? null :
+ element.attributes[name].value;
+ }
+ }
+ return element.getAttribute(name);
+ },
+
+ writeAttribute: function(element, name, value) {
+ element = $(element);
+ var attributes = { }, t = Element._attributeTranslations.write;
+
+ if (typeof name == 'object') attributes = name;
+ else attributes[name] = Object.isUndefined(value) ? true : value;
+
+ for (var attr in attributes) {
+ name = t.names[attr] || attr;
+ value = attributes[attr];
+ if (t.values[attr]) name = t.values[attr](element, value);
+ if (value === false || value === null)
+ element.removeAttribute(name);
+ else if (value === true)
+ element.setAttribute(name, name);
+ else element.setAttribute(name, value);
+ }
+ return element;
+ },
+
+ getHeight: function(element) {
+ return $(element).getDimensions().height;
+ },
+
+ getWidth: function(element) {
+ return $(element).getDimensions().width;
+ },
+
+ classNames: function(element) {
+ return new Element.ClassNames(element);
+ },
+
+ hasClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ var elementClassName = element.className;
+ return (elementClassName.length > 0 && (elementClassName == className ||
+ new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+ },
+
+ addClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ if (!element.hasClassName(className))
+ element.className += (element.className ? ' ' : '') + className;
+ return element;
+ },
+
+ removeClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ element.className = element.className.replace(
+ new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
+ return element;
+ },
+
+ toggleClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ return element[element.hasClassName(className) ?
+ 'removeClassName' : 'addClassName'](className);
+ },
+
+ // removes whitespace-only text node children
+ cleanWhitespace: function(element) {
+ element = $(element);
+ var node = element.firstChild;
+ while (node) {
+ var nextNode = node.nextSibling;
+ if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+ element.removeChild(node);
+ node = nextNode;
+ }
+ return element;
+ },
+
+ empty: function(element) {
+ return $(element).innerHTML.blank();
+ },
+
+ descendantOf: function(element, ancestor) {
+ element = $(element), ancestor = $(ancestor);
+ var originalAncestor = ancestor;
+
+ if (element.compareDocumentPosition)
+ return (element.compareDocumentPosition(ancestor) & 8) === 8;
+
+ if (element.sourceIndex && !Prototype.Browser.Opera) {
+ var e = element.sourceIndex, a = ancestor.sourceIndex,
+ nextAncestor = ancestor.nextSibling;
+ if (!nextAncestor) {
+ do { ancestor = ancestor.parentNode; }
+ while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
+ }
+ if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex);
+ }
+
+ while (element = element.parentNode)
+ if (element == originalAncestor) return true;
+ return false;
+ },
+
+ scrollTo: function(element) {
+ element = $(element);
+ var pos = element.cumulativeOffset();
+ window.scrollTo(pos[0], pos[1]);
+ return element;
+ },
+
+ getStyle: function(element, style) {
+ element = $(element);
+ style = style == 'float' ? 'cssFloat' : style.camelize();
+ var value = element.style[style];
+ if (!value) {
+ var css = document.defaultView.getComputedStyle(element, null);
+ value = css ? css[style] : null;
+ }
+ if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+ return value == 'auto' ? null : value;
+ },
+
+ getOpacity: function(element) {
+ return $(element).getStyle('opacity');
+ },
+
+ setStyle: function(element, styles) {
+ element = $(element);
+ var elementStyle = element.style, match;
+ if (Object.isString(styles)) {
+ element.style.cssText += ';' + styles;
+ return styles.include('opacity') ?
+ element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+ }
+ for (var property in styles)
+ if (property == 'opacity') element.setOpacity(styles[property]);
+ else
+ elementStyle[(property == 'float' || property == 'cssFloat') ?
+ (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
+ property] = styles[property];
+
+ return element;
+ },
+
+ setOpacity: function(element, value) {
+ element = $(element);
+ element.style.opacity = (value == 1 || value === '') ? '' :
+ (value < 0.00001) ? 0 : value;
+ return element;
+ },
+
+ getDimensions: function(element) {
+ element = $(element);
+ var display = $(element).getStyle('display');
+ if (display != 'none' && display != null) // Safari bug
+ return {width: element.offsetWidth, height: element.offsetHeight};
+
+ // All *Width and *Height properties give 0 on elements with display none,
+ // so enable the element temporarily
+ var els = element.style;
+ var originalVisibility = els.visibility;
+ var originalPosition = els.position;
+ var originalDisplay = els.display;
+ els.visibility = 'hidden';
+ els.position = 'absolute';
+ els.display = 'block';
+ var originalWidth = element.clientWidth;
+ var originalHeight = element.clientHeight;
+ els.display = originalDisplay;
+ els.position = originalPosition;
+ els.visibility = originalVisibility;
+ return {width: originalWidth, height: originalHeight};
+ },
+
+ makePositioned: function(element) {
+ element = $(element);
+ var pos = Element.getStyle(element, 'position');
+ if (pos == 'static' || !pos) {
+ element._madePositioned = true;
+ element.style.position = 'relative';
+ // Opera returns the offset relative to the positioning context, when an
+ // element is position relative but top and left have not been defined
+ if (window.opera) {
+ element.style.top = 0;
+ element.style.left = 0;
+ }
+ }
+ return element;
+ },
+
+ undoPositioned: function(element) {
+ element = $(element);
+ if (element._madePositioned) {
+ element._madePositioned = undefined;
+ element.style.position =
+ element.style.top =
+ element.style.left =
+ element.style.bottom =
+ element.style.right = '';
+ }
+ return element;
+ },
+
+ makeClipping: function(element) {
+ element = $(element);
+ if (element._overflow) return element;
+ element._overflow = Element.getStyle(element, 'overflow') || 'auto';
+ if (element._overflow !== 'hidden')
+ element.style.overflow = 'hidden';
+ return element;
+ },
+
+ undoClipping: function(element) {
+ element = $(element);
+ if (!element._overflow) return element;
+ element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
+ element._overflow = null;
+ return element;
+ },
+
+ cumulativeOffset: function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ } while (element);
+ return Element._returnOffset(valueL, valueT);
+ },
+
+ positionedOffset: function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ if (element) {
+ if (element.tagName == 'BODY') break;
+ var p = Element.getStyle(element, 'position');
+ if (p == 'relative' || p == 'absolute') break;
+ }
+ } while (element);
+ return Element._returnOffset(valueL, valueT);
+ },
+
+ absolutize: function(element) {
+ element = $(element);
+ if (element.getStyle('position') == 'absolute') return;
+ // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+ var offsets = element.positionedOffset();
+ var top = offsets[1];
+ var left = offsets[0];
+ var width = element.clientWidth;
+ var height = element.clientHeight;
+
+ element._originalLeft = left - parseFloat(element.style.left || 0);
+ element._originalTop = top - parseFloat(element.style.top || 0);
+ element._originalWidth = element.style.width;
+ element._originalHeight = element.style.height;
+
+ element.style.position = 'absolute';
+ element.style.top = top + 'px';
+ element.style.left = left + 'px';
+ element.style.width = width + 'px';
+ element.style.height = height + 'px';
+ return element;
+ },
+
+ relativize: function(element) {
+ element = $(element);
+ if (element.getStyle('position') == 'relative') return;
+ // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+ element.style.position = 'relative';
+ var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
+ var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+ element.style.top = top + 'px';
+ element.style.left = left + 'px';
+ element.style.height = element._originalHeight;
+ element.style.width = element._originalWidth;
+ return element;
+ },
+
+ cumulativeScrollOffset: function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.scrollTop || 0;
+ valueL += element.scrollLeft || 0;
+ element = element.parentNode;
+ } while (element);
+ return Element._returnOffset(valueL, valueT);
+ },
+
+ getOffsetParent: function(element) {
+ if (element.offsetParent) return $(element.offsetParent);
+ if (element == document.body) return $(element);
+
+ while ((element = element.parentNode) && element != document.body)
+ if (Element.getStyle(element, 'position') != 'static')
+ return $(element);
+
+ return $(document.body);
+ },
+
+ viewportOffset: function(forElement) {
+ var valueT = 0, valueL = 0;
+
+ var element = forElement;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+
+ // Safari fix
+ if (element.offsetParent == document.body &&
+ Element.getStyle(element, 'position') == 'absolute') break;
+
+ } while (element = element.offsetParent);
+
+ element = forElement;
+ do {
+ if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
+ valueT -= element.scrollTop || 0;
+ valueL -= element.scrollLeft || 0;
+ }
+ } while (element = element.parentNode);
+
+ return Element._returnOffset(valueL, valueT);
+ },
+
+ clonePosition: function(element, source) {
+ var options = Object.extend({
+ setLeft: true,
+ setTop: true,
+ setWidth: true,
+ setHeight: true,
+ offsetTop: 0,
+ offsetLeft: 0
+ }, arguments[2] || { });
+
+ // find page position of source
+ source = $(source);
+ var p = source.viewportOffset();
+
+ // find coordinate system to use
+ element = $(element);
+ var delta = [0, 0];
+ var parent = null;
+ // delta [0,0] will do fine with position: fixed elements,
+ // position:absolute needs offsetParent deltas
+ if (Element.getStyle(element, 'position') == 'absolute') {
+ parent = element.getOffsetParent();
+ delta = parent.viewportOffset();
+ }
+
+ // correct by body offsets (fixes Safari)
+ if (parent == document.body) {
+ delta[0] -= document.body.offsetLeft;
+ delta[1] -= document.body.offsetTop;
+ }
+
+ // set position
+ if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
+ if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
+ if (options.setWidth) element.style.width = source.offsetWidth + 'px';
+ if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+ return element;
+ }
+};
+
+Element.Methods.identify.counter = 1;
+
+Object.extend(Element.Methods, {
+ getElementsBySelector: Element.Methods.select,
+ childElements: Element.Methods.immediateDescendants
+});
+
+Element._attributeTranslations = {
+ write: {
+ names: {
+ className: 'class',
+ htmlFor: 'for'
+ },
+ values: { }
+ }
+};
+
+
+if (!document.createRange || Prototype.Browser.Opera) {
+ Element.Methods.insert = function(element, insertions) {
+ element = $(element);
+
+ if (Object.isString(insertions) || Object.isNumber(insertions) ||
+ Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+ insertions = { bottom: insertions };
+
+ var t = Element._insertionTranslations, content, position, pos, tagName;
+
+ for (position in insertions) {
+ content = insertions[position];
+ position = position.toLowerCase();
+ pos = t[position];
+
+ if (content && content.toElement) content = content.toElement();
+ if (Object.isElement(content)) {
+ pos.insert(element, content);
+ continue;
+ }
+
+ content = Object.toHTML(content);
+ tagName = ((position == 'before' || position == 'after')
+ ? element.parentNode : element).tagName.toUpperCase();
+
+ if (t.tags[tagName]) {
+ var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+ if (position == 'top' || position == 'after') fragments.reverse();
+ fragments.each(pos.insert.curry(element));
+ }
+ else element.insertAdjacentHTML(pos.adjacency, content.stripScripts());
+
+ content.evalScripts.bind(content).defer();
+ }
+
+ return element;
+ };
+}
+
+if (Prototype.Browser.Opera) {
+ Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+ function(proceed, element, style) {
+ switch (style) {
+ case 'left': case 'top': case 'right': case 'bottom':
+ if (proceed(element, 'position') === 'static') return null;
+ case 'height': case 'width':
+ // returns '0px' for hidden elements; we want it to return null
+ if (!Element.visible(element)) return null;
+
+ // returns the border-box dimensions rather than the content-box
+ // dimensions, so we subtract padding and borders from the value
+ var dim = parseInt(proceed(element, style), 10);
+
+ if (dim !== element['offset' + style.capitalize()])
+ return dim + 'px';
+
+ var properties;
+ if (style === 'height') {
+ properties = ['border-top-width', 'padding-top',
+ 'padding-bottom', 'border-bottom-width'];
+ }
+ else {
+ properties = ['border-left-width', 'padding-left',
+ 'padding-right', 'border-right-width'];
+ }
+ return properties.inject(dim, function(memo, property) {
+ var val = proceed(element, property);
+ return val === null ? memo : memo - parseInt(val, 10);
+ }) + 'px';
+ default: return proceed(element, style);
+ }
+ }
+ );
+
+ Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+ function(proceed, element, attribute) {
+ if (attribute === 'title') return element.title;
+ return proceed(element, attribute);
+ }
+ );
+}
+
+else if (Prototype.Browser.IE) {
+ $w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
+ Element.Methods[method] = Element.Methods[method].wrap(
+ function(proceed, element) {
+ element = $(element);
+ var position = element.getStyle('position');
+ if (position != 'static') return proceed(element);
+ element.setStyle({ position: 'relative' });
+ var value = proceed(element);
+ element.setStyle({ position: position });
+ return value;
+ }
+ );
+ });
+
+ Element.Methods.getStyle = function(element, style) {
+ element = $(element);
+ style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+ var value = element.style[style];
+ if (!value && element.currentStyle) value = element.currentStyle[style];
+
+ if (style == 'opacity') {
+ if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+ if (value[1]) return parseFloat(value[1]) / 100;
+ return 1.0;
+ }
+
+ if (value == 'auto') {
+ if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
+ return element['offset' + style.capitalize()] + 'px';
+ return null;
+ }
+ return value;
+ };
+
+ Element.Methods.setOpacity = function(element, value) {
+ function stripAlpha(filter){
+ return filter.replace(/alpha\([^\)]*\)/gi,'');
+ }
+ element = $(element);
+ var currentStyle = element.currentStyle;
+ if ((currentStyle && !currentStyle.hasLayout) ||
+ (!currentStyle && element.style.zoom == 'normal'))
+ element.style.zoom = 1;
+
+ var filter = element.getStyle('filter'), style = element.style;
+ if (value == 1 || value === '') {
+ (filter = stripAlpha(filter)) ?
+ style.filter = filter : style.removeAttribute('filter');
+ return element;
+ } else if (value < 0.00001) value = 0;
+ style.filter = stripAlpha(filter) +
+ 'alpha(opacity=' + (value * 100) + ')';
+ return element;
+ };
+
+ Element._attributeTranslations = {
+ read: {
+ names: {
+ 'class': 'className',
+ 'for': 'htmlFor'
+ },
+ values: {
+ _getAttr: function(element, attribute) {
+ return element.getAttribute(attribute, 2);
+ },
+ _getAttrNode: function(element, attribute) {
+ var node = element.getAttributeNode(attribute);
+ return node ? node.value : "";
+ },
+ _getEv: function(element, attribute) {
+ attribute = element.getAttribute(attribute);
+ return attribute ? attribute.toString().slice(23, -2) : null;
+ },
+ _flag: function(element, attribute) {
+ return $(element).hasAttribute(attribute) ? attribute : null;
+ },
+ style: function(element) {
+ return element.style.cssText.toLowerCase();
+ },
+ title: function(element) {
+ return element.title;
+ }
+ }
+ }
+ };
+
+ Element._attributeTranslations.write = {
+ names: Object.clone(Element._attributeTranslations.read.names),
+ values: {
+ checked: function(element, value) {
+ element.checked = !!value;
+ },
+
+ style: function(element, value) {
+ element.style.cssText = value ? value : '';
+ }
+ }
+ };
+
+ Element._attributeTranslations.has = {};
+
+ $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+ 'encType maxLength readOnly longDesc').each(function(attr) {
+ Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+ Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+ });
+
+ (function(v) {
+ Object.extend(v, {
+ href: v._getAttr,
+ src: v._getAttr,
+ type: v._getAttr,
+ action: v._getAttrNode,
+ disabled: v._flag,
+ checked: v._flag,
+ readonly: v._flag,
+ multiple: v._flag,
+ onload: v._getEv,
+ onunload: v._getEv,
+ onclick: v._getEv,
+ ondblclick: v._getEv,
+ onmousedown: v._getEv,
+ onmouseup: v._getEv,
+ onmouseover: v._getEv,
+ onmousemove: v._getEv,
+ onmouseout: v._getEv,
+ onfocus: v._getEv,
+ onblur: v._getEv,
+ onkeypress: v._getEv,
+ onkeydown: v._getEv,
+ onkeyup: v._getEv,
+ onsubmit: v._getEv,
+ onreset: v._getEv,
+ onselect: v._getEv,
+ onchange: v._getEv
+ });
+ })(Element._attributeTranslations.read.values);
+}
+
+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
+ Element.Methods.setOpacity = function(element, value) {
+ element = $(element);
+ element.style.opacity = (value == 1) ? 0.999999 :
+ (value === '') ? '' : (value < 0.00001) ? 0 : value;
+ return element;
+ };
+}
+
+else if (Prototype.Browser.WebKit) {
+ Element.Methods.setOpacity = function(element, value) {
+ element = $(element);
+ element.style.opacity = (value == 1 || value === '') ? '' :
+ (value < 0.00001) ? 0 : value;
+
+ if (value == 1)
+ if(element.tagName == 'IMG' && element.width) {
+ element.width++; element.width--;
+ } else try {
+ var n = document.createTextNode(' ');
+ element.appendChild(n);
+ element.removeChild(n);
+ } catch (e) { }
+
+ return element;
+ };
+
+ // Safari returns margins on body which is incorrect if the child is absolutely
+ // positioned. For performance reasons, redefine Element#cumulativeOffset for
+ // KHTML/WebKit only.
+ Element.Methods.cumulativeOffset = function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ if (element.offsetParent == document.body)
+ if (Element.getStyle(element, 'position') == 'absolute') break;
+
+ element = element.offsetParent;
+ } while (element);
+
+ return Element._returnOffset(valueL, valueT);
+ };
+}
+
+if (Prototype.Browser.IE || Prototype.Browser.Opera) {
+ // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
+ Element.Methods.update = function(element, content) {
+ element = $(element);
+
+ if (content && content.toElement) content = content.toElement();
+ if (Object.isElement(content)) return element.update().insert(content);
+
+ content = Object.toHTML(content);
+ var tagName = element.tagName.toUpperCase();
+
+ if (tagName in Element._insertionTranslations.tags) {
+ $A(element.childNodes).each(function(node) { element.removeChild(node) });
+ Element._getContentFromAnonymousElement(tagName, content.stripScripts())
+ .each(function(node) { element.appendChild(node) });
+ }
+ else element.innerHTML = content.stripScripts();
+
+ content.evalScripts.bind(content).defer();
+ return element;
+ };
+}
+
+if (document.createElement('div').outerHTML) {
+ Element.Methods.replace = function(element, content) {
+ element = $(element);
+
+ if (content && content.toElement) content = content.toElement();
+ if (Object.isElement(content)) {
+ element.parentNode.replaceChild(content, element);
+ return element;
+ }
+
+ content = Object.toHTML(content);
+ var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+ if (Element._insertionTranslations.tags[tagName]) {
+ var nextSibling = element.next();
+ var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+ parent.removeChild(element);
+ if (nextSibling)
+ fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
+ else
+ fragments.each(function(node) { parent.appendChild(node) });
+ }
+ else element.outerHTML = content.stripScripts();
+
+ content.evalScripts.bind(content).defer();
+ return element;
+ };
+}
+
+Element._returnOffset = function(l, t) {
+ var result = [l, t];
+ result.left = l;
+ result.top = t;
+ return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html) {
+ var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
+ div.innerHTML = t[0] + html + t[1];
+ t[2].times(function() { div = div.firstChild });
+ return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+ before: {
+ adjacency: 'beforeBegin',
+ insert: function(element, node) {
+ element.parentNode.insertBefore(node, element);
+ },
+ initializeRange: function(element, range) {
+ range.setStartBefore(element);
+ }
+ },
+ top: {
+ adjacency: 'afterBegin',
+ insert: function(element, node) {
+ element.insertBefore(node, element.firstChild);
+ },
+ initializeRange: function(element, range) {
+ range.selectNodeContents(element);
+ range.collapse(true);
+ }
+ },
+ bottom: {
+ adjacency: 'beforeEnd',
+ insert: function(element, node) {
+ element.appendChild(node);
+ }
+ },
+ after: {
+ adjacency: 'afterEnd',
+ insert: function(element, node) {
+ element.parentNode.insertBefore(node, element.nextSibling);
+ },
+ initializeRange: function(element, range) {
+ range.setStartAfter(element);
+ }
+ },
+ tags: {
+ TABLE: ['<table>', '</table>', 1],
+ TBODY: ['<table><tbody>', '</tbody></table>', 2],
+ TR: ['<table><tbody><tr>', '</tr></tbody></table>', 3],
+ TD: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+ SELECT: ['<select>', '</select>', 1]
+ }
+};
+
+(function() {
+ this.bottom.initializeRange = this.top.initializeRange;
+ Object.extend(this.tags, {
+ THEAD: this.tags.TBODY,
+ TFOOT: this.tags.TBODY,
+ TH: this.tags.TD
+ });
+}).call(Element._insertionTranslations);
+
+Element.Methods.Simulated = {
+ hasAttribute: function(element, attribute) {
+ attribute = Element._attributeTranslations.has[attribute] || attribute;
+ var node = $(element).getAttributeNode(attribute);
+ return node && node.specified;
+ }
+};
+
+Element.Methods.ByTag = { };
+
+Object.extend(Element, Element.Methods);
+
+if (!Prototype.BrowserFeatures.ElementExtensions &&
+ document.createElement('div').__proto__) {
+ window.HTMLElement = { };
+ window.HTMLElement.prototype = document.createElement('div').__proto__;
+ Prototype.BrowserFeatures.ElementExtensions = true;
+}
+
+Element.extend = (function() {
+ if (Prototype.BrowserFeatures.SpecificElementExtensions)
+ return Prototype.K;
+
+ var Methods = { }, ByTag = Element.Methods.ByTag;
+
+ var extend = Object.extend(function(element) {
+ if (!element || element._extendedByPrototype ||
+ element.nodeType != 1 || element == window) return element;
+
+ var methods = Object.clone(Methods),
+ tagName = element.tagName, property, value;
+
+ // extend methods for specific tags
+ if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+ for (property in methods) {
+ value = methods[property];
+ if (Object.isFunction(value) && !(property in element))
+ element[property] = value.methodize();
+ }
+
+ element._extendedByPrototype = Prototype.emptyFunction;
+ return element;
+
+ }, {
+ refresh: function() {
+ // extend methods for all tags (Safari doesn't need this)
+ if (!Prototype.BrowserFeatures.ElementExtensions) {
+ Object.extend(Methods, Element.Methods);
+ Object.extend(Methods, Element.Methods.Simulated);
+ }
+ }
+ });
+
+ extend.refresh();
+ return extend;
+})();
+
+Element.hasAttribute = function(element, attribute) {
+ if (element.hasAttribute) return element.hasAttribute(attribute);
+ return Element.Methods.Simulated.hasAttribute(element, attribute);
+};
+
+Element.addMethods = function(methods) {
+ var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+ if (!methods) {
+ Object.extend(Form, Form.Methods);
+ Object.extend(Form.Element, Form.Element.Methods);
+ Object.extend(Element.Methods.ByTag, {
+ "FORM": Object.clone(Form.Methods),
+ "INPUT": Object.clone(Form.Element.Methods),
+ "SELECT": Object.clone(Form.Element.Methods),
+ "TEXTAREA": Object.clone(Form.Element.Methods)
+ });
+ }
+
+ if (arguments.length == 2) {
+ var tagName = methods;
+ methods = arguments[1];
+ }
+
+ if (!tagName) Object.extend(Element.Methods, methods || { });
+ else {
+ if (Object.isArray(tagName)) tagName.each(extend);
+ else extend(tagName);
+ }
+
+ function extend(tagName) {
+ tagName = tagName.toUpperCase();
+ if (!Element.Methods.ByTag[tagName])
+ Element.Methods.ByTag[tagName] = { };
+ Object.extend(Element.Methods.ByTag[tagName], methods);
+ }
+
+ function copy(methods, destination, onlyIfAbsent) {
+ onlyIfAbsent = onlyIfAbsent || false;
+ for (var property in methods) {
+ var value = methods[property];
+ if (!Object.isFunction(value)) continue;
+ if (!onlyIfAbsent || !(property in destination))
+ destination[property] = value.methodize();
+ }
+ }
+
+ function findDOMClass(tagName) {
+ var klass;
+ var trans = {
+ "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+ "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+ "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+ "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+ "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+ "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+ "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+ "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+ "FrameSet", "IFRAME": "IFrame"
+ };
+ if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+ if (window[klass]) return window[klass];
+ klass = 'HTML' + tagName + 'Element';
+ if (window[klass]) return window[klass];
+ klass = 'HTML' + tagName.capitalize() + 'Element';
+ if (window[klass]) return window[klass];
+
+ window[klass] = { };
+ window[klass].prototype = document.createElement(tagName).__proto__;
+ return window[klass];
+ }
+
+ if (F.ElementExtensions) {
+ copy(Element.Methods, HTMLElement.prototype);
+ copy(Element.Methods.Simulated, HTMLElement.prototype, true);
+ }
+
+ if (F.SpecificElementExtensions) {
+ for (var tag in Element.Methods.ByTag) {
+ var klass = findDOMClass(tag);
+ if (Object.isUndefined(klass)) continue;
+ copy(T[tag], klass.prototype);
+ }
+ }
+
+ Object.extend(Element, Element.Methods);
+ delete Element.ByTag;
+
+ if (Element.extend.refresh) Element.extend.refresh();
+ Element.cache = { };
+};
+
+document.viewport = {
+ getDimensions: function() {
+ var dimensions = { };
+ var B = Prototype.Browser;
+ $w('width height').each(function(d) {
+ var D = d.capitalize();
+ dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
+ (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];
+ });
+ return dimensions;
+ },
+
+ getWidth: function() {
+ return this.getDimensions().width;
+ },
+
+ getHeight: function() {
+ return this.getDimensions().height;
+ },
+
+ getScrollOffsets: function() {
+ return Element._returnOffset(
+ window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
+ window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
+ }
+};
+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
+ * license. Please see http://www.yui-ext.com/ for more information. */
+
+var Selector = Class.create({
+ initialize: function(expression) {
+ this.expression = expression.strip();
+ this.compileMatcher();
+ },
+
+ shouldUseXPath: function() {
+ if (!Prototype.BrowserFeatures.XPath) return false;
+
+ var e = this.expression;
+
+ // Safari 3 chokes on :*-of-type and :empty
+ if (Prototype.Browser.WebKit &&
+ (e.include("-of-type") || e.include(":empty")))
+ return false;
+
+ // XPath can't do namespaced attributes, nor can it read
+ // the "checked" property from DOM nodes
+ if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
+ return false;
+
+ return true;
+ },
+
+ compileMatcher: function() {
+ if (this.shouldUseXPath())
+ return this.compileXPathMatcher();
+
+ var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
+ c = Selector.criteria, le, p, m;
+
+ if (Selector._cache[e]) {
+ this.matcher = Selector._cache[e];
+ return;
+ }
+
+ this.matcher = ["this.matcher = function(root) {",
+ "var r = root, h = Selector.handlers, c = false, n;"];
+
+ while (e && le != e && (/\S/).test(e)) {
+ le = e;
+ for (var i in ps) {
+ p = ps[i];
+ if (m = e.match(p)) {
+ this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
+ new Template(c[i]).evaluate(m));
+ e = e.replace(m[0], '');
+ break;
+ }
+ }
+ }
+
+ this.matcher.push("return h.unique(n);\n}");
+ eval(this.matcher.join('\n'));
+ Selector._cache[this.expression] = this.matcher;
+ },
+
+ compileXPathMatcher: function() {
+ var e = this.expression, ps = Selector.patterns,
+ x = Selector.xpath, le, m;
+
+ if (Selector._cache[e]) {
+ this.xpath = Selector._cache[e]; return;
+ }
+
+ this.matcher = ['.//*'];
+ while (e && le != e && (/\S/).test(e)) {
+ le = e;
+ for (var i in ps) {
+ if (m = e.match(ps[i])) {
+ this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
+ new Template(x[i]).evaluate(m));
+ e = e.replace(m[0], '');
+ break;
+ }
+ }
+ }
+
+ this.xpath = this.matcher.join('');
+ Selector._cache[this.expression] = this.xpath;
+ },
+
+ findElements: function(root) {
+ root = root || document;
+ if (this.xpath) return document._getElementsByXPath(this.xpath, root);
+ return this.matcher(root);
+ },
+
+ match: function(element) {
+ this.tokens = [];
+
+ var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
+ var le, p, m;
+
+ while (e && le !== e && (/\S/).test(e)) {
+ le = e;
+ for (var i in ps) {
+ p = ps[i];
+ if (m = e.match(p)) {
+ // use the Selector.assertions methods unless the selector
+ // is too complex.
+ if (as[i]) {
+ this.tokens.push([i, Object.clone(m)]);
+ e = e.replace(m[0], '');
+ } else {
+ // reluctantly do a document-wide search
+ // and look for a match in the array
+ return this.findElements(document).include(element);
+ }
+ }
+ }
+ }
+
+ var match = true, name, matches;
+ for (var i = 0, token; token = this.tokens[i]; i++) {
+ name = token[0], matches = token[1];
+ if (!Selector.assertions[name](element, matches)) {
+ match = false; break;
+ }
+ }
+
+ return match;
+ },
+
+ toString: function() {
+ return this.expression;
+ },
+
+ inspect: function() {
+ return "#<Selector:" + this.expression.inspect() + ">";
+ }
+});
+
+Object.extend(Selector, {
+ _cache: { },
+
+ xpath: {
+ descendant: "//*",
+ child: "/*",
+ adjacent: "/following-sibling::*[1]",
+ laterSibling: '/following-sibling::*',
+ tagName: function(m) {
+ if (m[1] == '*') return '';
+ return "[local-name()='" + m[1].toLowerCase() +
+ "' or local-name()='" + m[1].toUpperCase() + "']";
+ },
+ className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
+ id: "[@id='#{1}']",
+ attrPresence: function(m) {
+ m[1] = m[1].toLowerCase();
+ return new Template("[@#{1}]").evaluate(m);
+ },
+ attr: function(m) {
+ m[1] = m[1].toLowerCase();
+ m[3] = m[5] || m[6];
+ return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
+ },
+ pseudo: function(m) {
+ var h = Selector.xpath.pseudos[m[1]];
+ if (!h) return '';
+ if (Object.isFunction(h)) return h(m);
+ return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
+ },
+ operators: {
+ '=': "[@#{1}='#{3}']",
+ '!=': "[@#{1}!='#{3}']",
+ '^=': "[starts-with(@#{1}, '#{3}')]",
+ '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
+ '*=': "[contains(@#{1}, '#{3}')]",
+ '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
+ '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
+ },
+ pseudos: {
+ 'first-child': '[not(preceding-sibling::*)]',
+ 'last-child': '[not(following-sibling::*)]',
+ 'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
+ 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
+ 'checked': "[@checked]",
+ 'disabled': "[@disabled]",
+ 'enabled': "[not(@disabled)]",
+ 'not': function(m) {
+ var e = m[6], p = Selector.patterns,
+ x = Selector.xpath, le, v;
+
+ var exclusion = [];
+ while (e && le != e && (/\S/).test(e)) {
+ le = e;
+ for (var i in p) {
+ if (m = e.match(p[i])) {
+ v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
+ exclusion.push("(" + v.substring(1, v.length - 1) + ")");
+ e = e.replace(m[0], '');
+ break;
+ }
+ }
+ }
+ return "[not(" + exclusion.join(" and ") + ")]";
+ },
+ 'nth-child': function(m) {
+ return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
+ },
+ 'nth-last-child': function(m) {
+ return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
+ },
+ 'nth-of-type': function(m) {
+ return Selector.xpath.pseudos.nth("position() ", m);
+ },
+ 'nth-last-of-type': function(m) {
+ return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
+ },
+ 'first-of-type': function(m) {
+ m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
+ },
+ 'last-of-type': function(m) {
+ m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
+ },
+ 'only-of-type': function(m) {
+ var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+ },
+ nth: function(fragment, m) {
+ var mm, formula = m[6], predicate;
+ if (formula == 'even') formula = '2n+0';
+ if (formula == 'odd') formula = '2n+1';
+ if (mm = formula.match(/^(\d+)$/)) // digit only
+ return '[' + fragment + "= " + mm[1] + ']';
+ if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+ if (mm[1] == "-") mm[1] = -1;
+ var a = mm[1] ? Number(mm[1]) : 1;
+ var b = mm[2] ? Number(mm[2]) : 0;
+ predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
+ "((#{fragment} - #{b}) div #{a} >= 0)]";
+ return new Template(predicate).evaluate({
+ fragment: fragment, a: a, b: b });
+ }
+ }
+ }
+ },
+
+ criteria: {
+ tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
+ className: 'n = h.className(n, r, "#{1}", c); c = false;',
+ id: 'n = h.id(n, r, "#{1}", c); c = false;',
+ attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
+ attr: function(m) {
+ m[3] = (m[5] || m[6]);
+ return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
+ },
+ pseudo: function(m) {
+ if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
+ return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
+ },
+ descendant: 'c = "descendant";',
+ child: 'c = "child";',
+ adjacent: 'c = "adjacent";',
+ laterSibling: 'c = "laterSibling";'
+ },
+
+ patterns: {
+ // combinators must be listed first
+ // (and descendant needs to be last combinator)
+ laterSibling: /^\s*~\s*/,
+ child: /^\s*>\s*/,
+ adjacent: /^\s*\+\s*/,
+ descendant: /^\s/,
+
+ // selectors follow
+ tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
+ id: /^#([\w\-\*]+)(\b|$)/,
+ className: /^\.([\w\-\*]+)(\b|$)/,
+ pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
+ attrPresence: /^\[([\w]+)\]/,
+ attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
+ },
+
+ // for Selector.match and Element#match
+ assertions: {
+ tagName: function(element, matches) {
+ return matches[1].toUpperCase() == element.tagName.toUpperCase();
+ },
+
+ className: function(element, matches) {
+ return Element.hasClassName(element, matches[1]);
+ },
+
+ id: function(element, matches) {
+ return element.id === matches[1];
+ },
+
+ attrPresence: function(element, matches) {
+ return Element.hasAttribute(element, matches[1]);
+ },
+
+ attr: function(element, matches) {
+ var nodeValue = Element.readAttribute(element, matches[1]);
+ return Selector.operators[matches[2]](nodeValue, matches[3]);
+ }
+ },
+
+ handlers: {
+ // UTILITY FUNCTIONS
+ // joins two collections
+ concat: function(a, b) {
+ for (var i = 0, node; node = b[i]; i++)
+ a.push(node);
+ return a;
+ },
+
+ // marks an array of nodes for counting
+ mark: function(nodes) {
+ for (var i = 0, node; node = nodes[i]; i++)
+ node._counted = true;
+ return nodes;
+ },
+
+ unmark: function(nodes) {
+ for (var i = 0, node; node = nodes[i]; i++)
+ node._counted = undefined;
+ return nodes;
+ },
+
+ // mark each child node with its position (for nth calls)
+ // "ofType" flag indicates whether we're indexing for nth-of-type
+ // rather than nth-child
+ index: function(parentNode, reverse, ofType) {
+ parentNode._counted = true;
+ if (reverse) {
+ for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
+ var node = nodes[i];
+ if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+ }
+ } else {
+ for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
+ if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+ }
+ },
+
+ // filters out duplicates and extends all nodes
+ unique: function(nodes) {
+ if (nodes.length == 0) return nodes;
+ var results = [], n;
+ for (var i = 0, l = nodes.length; i < l; i++)
+ if (!(n = nodes[i])._counted) {
+ n._counted = true;
+ results.push(Element.extend(n));
+ }
+ return Selector.handlers.unmark(results);
+ },
+
+ // COMBINATOR FUNCTIONS
+ descendant: function(nodes) {
+ var h = Selector.handlers;
+ for (var i = 0, results = [], node; node = nodes[i]; i++)
+ h.concat(results, node.getElementsByTagName('*'));
+ return results;
+ },
+
+ child: function(nodes) {
+ var h = Selector.handlers;
+ for (var i = 0, results = [], node; node = nodes[i]; i++) {
+ for (var j = 0, child; child = node.childNodes[j]; j++)
+ if (child.nodeType == 1 && child.tagName != '!') results.push(child);
+ }
+ return results;
+ },
+
+ adjacent: function(nodes) {
+ for (var i = 0, results = [], node; node = nodes[i]; i++) {
+ var next = this.nextElementSibling(node);
+ if (next) results.push(next);
+ }
+ return results;
+ },
+
+ laterSibling: function(nodes) {
+ var h = Selector.handlers;
+ for (var i = 0, results = [], node; node = nodes[i]; i++)
+ h.concat(results, Element.nextSiblings(node));
+ return results;
+ },
+
+ nextElementSibling: function(node) {
+ while (node = node.nextSibling)
+ if (node.nodeType == 1) return node;
+ return null;
+ },
+
+ previousElementSibling: function(node) {
+ while (node = node.previousSibling)
+ if (node.nodeType == 1) return node;
+ return null;
+ },
+
+ // TOKEN FUNCTIONS
+ tagName: function(nodes, root, tagName, combinator) {
+ tagName = tagName.toUpperCase();
+ var results = [], h = Selector.handlers;
+ if (nodes) {
+ if (combinator) {
+ // fastlane for ordinary descendant combinators
+ if (combinator == "descendant") {
+ for (var i = 0, node; node = nodes[i]; i++)
+ h.concat(results, node.getElementsByTagName(tagName));
+ return results;
+ } else nodes = this[combinator](nodes);
+ if (tagName == "*") return nodes;
+ }
+ for (var i = 0, node; node = nodes[i]; i++)
+ if (node.tagName.toUpperCase() == tagName) results.push(node);
+ return results;
+ } else return root.getElementsByTagName(tagName);
+ },
+
+ id: function(nodes, root, id, combinator) {
+ var targetNode = $(id), h = Selector.handlers;
+ if (!targetNode) return [];
+ if (!nodes && root == document) return [targetNode];
+ if (nodes) {
+ if (combinator) {
+ if (combinator == 'child') {
+ for (var i = 0, node; node = nodes[i]; i++)
+ if (targetNode.parentNode == node) return [targetNode];
+ } else if (combinator == 'descendant') {
+ for (var i = 0, node; node = nodes[i]; i++)
+ if (Element.descendantOf(targetNode, node)) return [targetNode];
+ } else if (combinator == 'adjacent') {
+ for (var i = 0, node; node = nodes[i]; i++)
+ if (Selector.handlers.previousElementSibling(targetNode) == node)
+ return [targetNode];
+ } else nodes = h[combinator](nodes);
+ }
+ for (var i = 0, node; node = nodes[i]; i++)
+ if (node == targetNode) return [targetNode];
+ return [];
+ }
+ return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
+ },
+
+ className: function(nodes, root, className, combinator) {
+ if (nodes && combinator) nodes = this[combinator](nodes);
+ return Selector.handlers.byClassName(nodes, root, className);
+ },
+
+ byClassName: function(nodes, root, className) {
+ if (!nodes) nodes = Selector.handlers.descendant([root]);
+ var needle = ' ' + className + ' ';
+ for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
+ nodeClassName = node.className;
+ if (nodeClassName.length == 0) continue;
+ if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
+ results.push(node);
+ }
+ return results;
+ },
+
+ attrPresence: function(nodes, root, attr) {
+ if (!nodes) nodes = root.getElementsByTagName("*");
+ var results = [];
+ for (var i = 0, node; node = nodes[i]; i++)
+ if (Element.hasAttribute(node, attr)) results.push(node);
+ return results;
+ },
+
+ attr: function(nodes, root, attr, value, operator) {
+ if (!nodes) nodes = root.getElementsByTagName("*");
+ var handler = Selector.operators[operator], results = [];
+ for (var i = 0, node; node = nodes[i]; i++) {
+ var nodeValue = Element.readAttribute(node, attr);
+ if (nodeValue === null) continue;
+ if (handler(nodeValue, value)) results.push(node);
+ }
+ return results;
+ },
+
+ pseudo: function(nodes, name, value, root, combinator) {
+ if (nodes && combinator) nodes = this[combinator](nodes);
+ if (!nodes) nodes = root.getElementsByTagName("*");
+ return Selector.pseudos[name](nodes, value, root);
+ }
+ },
+
+ pseudos: {
+ 'first-child': function(nodes, value, root) {
+ for (var i = 0, results = [], node; node = nodes[i]; i++) {
+ if (Selector.handlers.previousElementSibling(node)) continue;
+ results.push(node);
+ }
+ return results;
+ },
+ 'last-child': function(nodes, value, root) {
+ for (var i = 0, results = [], node; node = nodes[i]; i++) {
+ if (Selector.handlers.nextElementSibling(node)) continue;
+ results.push(node);
+ }
+ return results;
+ },
+ 'only-child': function(nodes, value, root) {
+ var h = Selector.handlers;
+ for (var i = 0, results = [], node; node = nodes[i]; i++)
+ if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
+ results.push(node);
+ return results;
+ },
+ 'nth-child': function(nodes, formula, root) {
+ return Selector.pseudos.nth(nodes, formula, root);
+ },
+ 'nth-last-child': function(nodes, formula, root) {
+ return Selector.pseudos.nth(nodes, formula, root, true);
+ },
+ 'nth-of-type': function(nodes, formula, root) {
+ return Selector.pseudos.nth(nodes, formula, root, false, true);
+ },
+ 'nth-last-of-type': function(nodes, formula, root) {
+ return Selector.pseudos.nth(nodes, formula, root, true, true);
+ },
+ 'first-of-type': function(nodes, formula, root) {
+ return Selector.pseudos.nth(nodes, "1", root, false, true);
+ },
+ 'last-of-type': function(nodes, formula, root) {
+ return Selector.pseudos.nth(nodes, "1", root, true, true);
+ },
+ 'only-of-type': function(nodes, formula, root) {
+ var p = Selector.pseudos;
+ return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
+ },
+
+ // handles the an+b logic
+ getIndices: function(a, b, total) {
+ if (a == 0) return b > 0 ? [b] : [];
+ return $R(1, total).inject([], function(memo, i) {
+ if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
+ return memo;
+ });
+ },
+
+ // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
+ nth: function(nodes, formula, root, reverse, ofType) {
+ if (nodes.length == 0) return [];
+ if (formula == 'even') formula = '2n+0';
+ if (formula == 'odd') formula = '2n+1';
+ var h = Selector.handlers, results = [], indexed = [], m;
+ h.mark(nodes);
+ for (var i = 0, node; node = nodes[i]; i++) {
+ if (!node.parentNode._counted) {
+ h.index(node.parentNode, reverse, ofType);
+ indexed.push(node.parentNode);
+ }
+ }
+ if (formula.match(/^\d+$/)) { // just a number
+ formula = Number(formula);
+ for (var i = 0, node; node = nodes[i]; i++)
+ if (node.nodeIndex == formula) results.push(node);
+ } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+ if (m[1] == "-") m[1] = -1;
+ var a = m[1] ? Number(m[1]) : 1;
+ var b = m[2] ? Number(m[2]) : 0;
+ var indices = Selector.pseudos.getIndices(a, b, nodes.length);
+ for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
+ for (var j = 0; j < l; j++)
+ if (node.nodeIndex == indices[j]) results.push(node);
+ }
+ }
+ h.unmark(nodes);
+ h.unmark(indexed);
+ return results;
+ },
+
+ 'empty': function(nodes, value, root) {
+ for (var i = 0, results = [], node; node = nodes[i]; i++) {
+ // IE treats comments as element nodes
+ if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
+ results.push(node);
+ }
+ return results;
+ },
+
+ 'not': function(nodes, selector, root) {
+ var h = Selector.handlers, selectorType, m;
+ var exclusions = new Selector(selector).findElements(root);
+ h.mark(exclusions);
+ for (var i = 0, results = [], node; node = nodes[i]; i++)
+ if (!node._counted) results.push(node);
+ h.unmark(exclusions);
+ return results;
+ },
+
+ 'enabled': function(nodes, value, root) {
+ for (var i = 0, results = [], node; node = nodes[i]; i++)
+ if (!node.disabled) results.push(node);
+ return results;
+ },
+
+ 'disabled': function(nodes, value, root) {
+ for (var i = 0, results = [], node; node = nodes[i]; i++)
+ if (node.disabled) results.push(node);
+ return results;
+ },
+
+ 'checked': function(nodes, value, root) {
+ for (var i = 0, results = [], node; node = nodes[i]; i++)
+ if (node.checked) results.push(node);
+ return results;
+ }
+ },
+
+ operators: {
+ '=': function(nv, v) { return nv == v; },
+ '!=': function(nv, v) { return nv != v; },
+ '^=': function(nv, v) { return nv.startsWith(v); },
+ '$=': function(nv, v) { return nv.endsWith(v); },
+ '*=': function(nv, v) { return nv.include(v); },
+ '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
+ '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
+ },
+
+ matchElements: function(elements, expression) {
+ var matches = new Selector(expression).findElements(), h = Selector.handlers;
+ h.mark(matches);
+ for (var i = 0, results = [], element; element = elements[i]; i++)
+ if (element._counted) results.push(element);
+ h.unmark(matches);
+ return results;
+ },
+
+ findElement: function(elements, expression, index) {
+ if (Object.isNumber(expression)) {
+ index = expression; expression = false;
+ }
+ return Selector.matchElements(elements, expression || '*')[index || 0];
+ },
+
+ findChildElements: function(element, expressions) {
+ var exprs = expressions.join(',');
+ expressions = [];
+ exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+ expressions.push(m[1].strip());
+ });
+ var results = [], h = Selector.handlers;
+ for (var i = 0, l = expressions.length, selector; i < l; i++) {
+ selector = new Selector(expressions[i].strip());
+ h.concat(results, selector.findElements(element));
+ }
+ return (l > 1) ? h.unique(results) : results;
+ }
+});
+
+if (Prototype.Browser.IE) {
+ // IE returns comment nodes on getElementsByTagName("*").
+ // Filter them out.
+ Selector.handlers.concat = function(a, b) {
+ for (var i = 0, node; node = b[i]; i++)
+ if (node.tagName !== "!") a.push(node);
+ return a;
+ };
+}
+
+function $$() {
+ return Selector.findChildElements(document, $A(arguments));
+}
+var Form = {
+ reset: function(form) {
+ $(form).reset();
+ return form;
+ },
+
+ serializeElements: function(elements, options) {
+ if (typeof options != 'object') options = { hash: !!options };
+ else if (Object.isUndefined(options.hash)) options.hash = true;
+ var key, value, submitted = false, submit = options.submit;
+
+ var data = elements.inject({ }, function(result, element) {
+ if (!element.disabled && element.name) {
+ key = element.name; value = $(element).getValue();
+ if (value != null && (element.type != 'submit' || (!submitted &&
+ submit !== false && (!submit || key == submit) && (submitted = true)))) {
+ if (key in result) {
+ // a key is already present; construct an array of values
+ if (!Object.isArray(result[key])) result[key] = [result[key]];
+ result[key].push(value);
+ }
+ else result[key] = value;
+ }
+ }
+ return result;
+ });
+
+ return options.hash ? data : Object.toQueryString(data);
+ }
+};
+
+Form.Methods = {
+ serialize: function(form, options) {
+ return Form.serializeElements(Form.getElements(form), options);
+ },
+
+ getElements: function(form) {
+ return $A($(form).getElementsByTagName('*')).inject([],
+ function(elements, child) {
+ if (Form.Element.Serializers[child.tagName.toLowerCase()])
+ elements.push(Element.extend(child));
+ return elements;
+ }
+ );
+ },
+
+ getInputs: function(form, typeName, name) {
+ form = $(form);
+ var inputs = form.getElementsByTagName('input');
+
+ if (!typeName && !name) return $A(inputs).map(Element.extend);
+
+ for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
+ var input = inputs[i];
+ if ((typeName && input.type != typeName) || (name && input.name != name))
+ continue;
+ matchingInputs.push(Element.extend(input));
+ }
+
+ return matchingInputs;
+ },
+
+ disable: function(form) {
+ form = $(form);
+ Form.getElements(form).invoke('disable');
+ return form;
+ },
+
+ enable: function(form) {
+ form = $(form);
+ Form.getElements(form).invoke('enable');
+ return form;
+ },
+
+ findFirstElement: function(form) {
+ var elements = $(form).getElements().findAll(function(element) {
+ return 'hidden' != element.type && !element.disabled;
+ });
+ var firstByIndex = elements.findAll(function(element) {
+ return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
+ }).sortBy(function(element) { return element.tabIndex }).first();
+
+ return firstByIndex ? firstByIndex : elements.find(function(element) {
+ return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+ });
+ },
+
+ focusFirstElement: function(form) {
+ form = $(form);
+ form.findFirstElement().activate();
+ return form;
+ },
+
+ request: function(form, options) {
+ form = $(form), options = Object.clone(options || { });
+
+ var params = options.parameters, action = form.readAttribute('action') || '';
+ if (action.blank()) action = window.location.href;
+ options.parameters = form.serialize(true);
+
+ if (params) {
+ if (Object.isString(params)) params = params.toQueryParams();
+ Object.extend(options.parameters, params);
+ }
+
+ if (form.hasAttribute('method') && !options.method)
+ options.method = form.method;
+
+ return new Ajax.Request(action, options);
+ }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element = {
+ focus: function(element) {
+ $(element).focus();
+ return element;
+ },
+
+ select: function(element) {
+ $(element).select();
+ return element;
+ }
+};
+
+Form.Element.Methods = {
+ serialize: function(element) {
+ element = $(element);
+ if (!element.disabled && element.name) {
+ var value = element.getValue();
+ if (value != undefined) {
+ var pair = { };
+ pair[element.name] = value;
+ return Object.toQueryString(pair);
+ }
+ }
+ return '';
+ },
+
+ getValue: function(element) {
+ element = $(element);
+ var method = element.tagName.toLowerCase();
+ return Form.Element.Serializers[method](element);
+ },
+
+ setValue: function(element, value) {
+ element = $(element);
+ var method = element.tagName.toLowerCase();
+ Form.Element.Serializers[method](element, value);
+ return element;
+ },
+
+ clear: function(element) {
+ $(element).value = '';
+ return element;
+ },
+
+ present: function(element) {
+ return $(element).value != '';
+ },
+
+ activate: function(element) {
+ element = $(element);
+ try {
+ element.focus();
+ if (element.select && (element.tagName.toLowerCase() != 'input' ||
+ !['button', 'reset', 'submit'].include(element.type)))
+ element.select();
+ } catch (e) { }
+ return element;
+ },
+
+ disable: function(element) {
+ element = $(element);
+ element.blur();
+ element.disabled = true;
+ return element;
+ },
+
+ enable: function(element) {
+ element = $(element);
+ element.disabled = false;
+ return element;
+ }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element.Serializers = {
+ input: function(element, value) {
+ switch (element.type.toLowerCase()) {
+ case 'checkbox':
+ case 'radio':
+ return Form.Element.Serializers.inputSelector(element, value);
+ default:
+ return Form.Element.Serializers.textarea(element, value);
+ }
+ },
+
+ inputSelector: function(element, value) {
+ if (Object.isUndefined(value)) return element.checked ? element.value : null;
+ else element.checked = !!value;
+ },
+
+ textarea: function(element, value) {
+ if (Object.isUndefined(value)) return element.value;
+ else element.value = value;
+ },
+
+ select: function(element, index) {
+ if (Object.isUndefined(index))
+ return this[element.type == 'select-one' ?
+ 'selectOne' : 'selectMany'](element);
+ else {
+ var opt, value, single = !Object.isArray(index);
+ for (var i = 0, length = element.length; i < length; i++) {
+ opt = element.options[i];
+ value = this.optionValue(opt);
+ if (single) {
+ if (value == index) {
+ opt.selected = true;
+ return;
+ }
+ }
+ else opt.selected = index.include(value);
+ }
+ }
+ },
+
+ selectOne: function(element) {
+ var index = element.selectedIndex;
+ return index >= 0 ? this.optionValue(element.options[index]) : null;
+ },
+
+ selectMany: function(element) {
+ var values, length = element.length;
+ if (!length) return null;
+
+ for (var i = 0, values = []; i < length; i++) {
+ var opt = element.options[i];
+ if (opt.selected) values.push(this.optionValue(opt));
+ }
+ return values;
+ },
+
+ optionValue: function(opt) {
+ // extend element because hasAttribute may not be native
+ return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
+ }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+ initialize: function($super, element, frequency, callback) {
+ $super(callback, frequency);
+ this.element = $(element);
+ this.lastValue = this.getValue();
+ },
+
+ execute: function() {
+ var value = this.getValue();
+ if (Object.isString(this.lastValue) && Object.isString(value) ?
+ this.lastValue != value : String(this.lastValue) != String(value)) {
+ this.callback(this.element, value);
+ this.lastValue = value;
+ }
+ }
+});
+
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
+ getValue: function() {
+ return Form.Element.getValue(this.element);
+ }
+});
+
+Form.Observer = Class.create(Abstract.TimedObserver, {
+ getValue: function() {
+ return Form.serialize(this.element);
+ }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = Class.create({
+ initialize: function(element, callback) {
+ this.element = $(element);
+ this.callback = callback;
+
+ this.lastValue = this.getValue();
+ if (this.element.tagName.toLowerCase() == 'form')
+ this.registerFormCallbacks();
+ else
+ this.registerCallback(this.element);
+ },
+
+ onElementEvent: function() {
+ var value = this.getValue();
+ if (this.lastValue != value) {
+ this.callback(this.element, value);
+ this.lastValue = value;
+ }
+ },
+
+ registerFormCallbacks: function() {
+ Form.getElements(this.element).each(this.registerCallback, this);
+ },
+
+ registerCallback: function(element) {
+ if (element.type) {
+ switch (element.type.toLowerCase()) {
+ case 'checkbox':
+ case 'radio':
+ Event.observe(element, 'click', this.onElementEvent.bind(this));
+ break;
+ default:
+ Event.observe(element, 'change', this.onElementEvent.bind(this));
+ break;
+ }
+ }
+ }
+});
+
+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
+ getValue: function() {
+ return Form.Element.getValue(this.element);
+ }
+});
+
+Form.EventObserver = Class.create(Abstract.EventObserver, {
+ getValue: function() {
+ return Form.serialize(this.element);
+ }
+});
+if (!window.Event) var Event = { };
+
+Object.extend(Event, {
+ KEY_BACKSPACE: 8,
+ KEY_TAB: 9,
+ KEY_RETURN: 13,
+ KEY_ESC: 27,
+ KEY_LEFT: 37,
+ KEY_UP: 38,
+ KEY_RIGHT: 39,
+ KEY_DOWN: 40,
+ KEY_DELETE: 46,
+ KEY_HOME: 36,
+ KEY_END: 35,
+ KEY_PAGEUP: 33,
+ KEY_PAGEDOWN: 34,
+ KEY_INSERT: 45,
+
+ cache: { },
+
+ relatedTarget: function(event) {
+ var element;
+ switch(event.type) {
+ case 'mouseover': element = event.fromElement; break;
+ case 'mouseout': element = event.toElement; break;
+ default: return null;
+ }
+ return Element.extend(element);
+ }
+});
+
+Event.Methods = (function() {
+ var isButton;
+
+ if (Prototype.Browser.IE) {
+ var buttonMap = { 0: 1, 1: 4, 2: 2 };
+ isButton = function(event, code) {
+ return event.button == buttonMap[code];
+ };
+
+ } else if (Prototype.Browser.WebKit) {
+ isButton = function(event, code) {
+ switch (code) {
+ case 0: return event.which == 1 && !event.metaKey;
+ case 1: return event.which == 1 && event.metaKey;
+ default: return false;
+ }
+ };
+
+ } else {
+ isButton = function(event, code) {
+ return event.which ? (event.which === code + 1) : (event.button === code);
+ };
+ }
+
+ return {
+ isLeftClick: function(event) { return isButton(event, 0) },
+ isMiddleClick: function(event) { return isButton(event, 1) },
+ isRightClick: function(event) { return isButton(event, 2) },
+
+ element: function(event) {
+ var node = Event.extend(event).target;
+ return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
+ },
+
+ findElement: function(event, expression) {
+ var element = Event.element(event);
+ if (!expression) return element;
+ var elements = [element].concat(element.ancestors());
+ return Selector.findElement(elements, expression, 0);
+ },
+
+ pointer: function(event) {
+ return {
+ x: event.pageX || (event.clientX +
+ (document.documentElement.scrollLeft || document.body.scrollLeft)),
+ y: event.pageY || (event.clientY +
+ (document.documentElement.scrollTop || document.body.scrollTop))
+ };
+ },
+
+ pointerX: function(event) { return Event.pointer(event).x },
+ pointerY: function(event) { return Event.pointer(event).y },
+
+ stop: function(event) {
+ Event.extend(event);
+ event.preventDefault();
+ event.stopPropagation();
+ event.stopped = true;
+ }
+ };
+})();
+
+Event.extend = (function() {
+ var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+ m[name] = Event.Methods[name].methodize();
+ return m;
+ });
+
+ if (Prototype.Browser.IE) {
+ Object.extend(methods, {
+ stopPropagation: function() { this.cancelBubble = true },
+ preventDefault: function() { this.returnValue = false },
+ inspect: function() { return "[object Event]" }
+ });
+
+ return function(event) {
+ if (!event) return false;
+ if (event._extendedByPrototype) return event;
+
+ event._extendedByPrototype = Prototype.emptyFunction;
+ var pointer = Event.pointer(event);
+ Object.extend(event, {
+ target: event.srcElement,
+ relatedTarget: Event.relatedTarget(event),
+ pageX: pointer.x,
+ pageY: pointer.y
+ });
+ return Object.extend(event, methods);
+ };
+
+ } else {
+ Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
+ Object.extend(Event.prototype, methods);
+ return Prototype.K;
+ }
+})();
+
+Object.extend(Event, (function() {
+ var cache = Event.cache;
+
+ function getEventID(element) {
+ if (element._eventID) return element._eventID;
+ arguments.callee.id = arguments.callee.id || 1;
+ return element._eventID = ++arguments.callee.id;
+ }
+
+ function getDOMEventName(eventName) {
+ if (eventName && eventName.include(':')) return "dataavailable";
+ return eventName;
+ }
+
+ function getCacheForID(id) {
+ return cache[id] = cache[id] || { };
+ }
+
+ function getWrappersForEventName(id, eventName) {
+ var c = getCacheForID(id);
+ return c[eventName] = c[eventName] || [];
+ }
+
+ function createWrapper(element, eventName, handler) {
+ var id = getEventID(element);
+ var c = getWrappersForEventName(id, eventName);
+ if (c.pluck("handler").include(handler)) return false;
+
+ var wrapper = function(event) {
+ if (!Event || !Event.extend ||
+ (event.eventName && event.eventName != eventName))
+ return false;
+
+ Event.extend(event);
+ handler.call(element, event)
+ };
+
+ wrapper.handler = handler;
+ c.push(wrapper);
+ return wrapper;
+ }
+
+ function findWrapper(id, eventName, handler) {
+ var c = getWrappersForEventName(id, eventName);
+ return c.find(function(wrapper) { return wrapper.handler == handler });
+ }
+
+ function destroyWrapper(id, eventName, handler) {
+ var c = getCacheForID(id);
+ if (!c[eventName]) return false;
+ c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
+ }
+
+ function destroyCache() {
+ for (var id in cache)
+ for (var eventName in cache[id])
+ cache[id][eventName] = null;
+ }
+
+ if (window.attachEvent) {
+ window.attachEvent("onunload", destroyCache);
+ }
+
+ return {
+ observe: function(element, eventName, handler) {
+ element = $(element);
+ var name = getDOMEventName(eventName);
+
+ var wrapper = createWrapper(element, eventName, handler);
+ if (!wrapper) return element;
+
+ if (element.addEventListener) {
+ element.addEventListener(name, wrapper, false);
+ } else {
+ element.attachEvent("on" + name, wrapper);
+ }
+
+ return element;
+ },
+
+ stopObserving: function(element, eventName, handler) {
+ element = $(element);
+ var id = getEventID(element), name = getDOMEventName(eventName);
+
+ if (!handler && eventName) {
+ getWrappersForEventName(id, eventName).each(function(wrapper) {
+ element.stopObserving(eventName, wrapper.handler);
+ });
+ return element;
+
+ } else if (!eventName) {
+ Object.keys(getCacheForID(id)).each(function(eventName) {
+ element.stopObserving(eventName);
+ });
+ return element;
+ }
+
+ var wrapper = findWrapper(id, eventName, handler);
+ if (!wrapper) return element;
+
+ if (element.removeEventListener) {
+ element.removeEventListener(name, wrapper, false);
+ } else {
+ element.detachEvent("on" + name, wrapper);
+ }
+
+ destroyWrapper(id, eventName, handler);
+
+ return element;
+ },
+
+ fire: function(element, eventName, memo) {
+ element = $(element);
+ if (element == document && document.createEvent && !element.dispatchEvent)
+ element = document.documentElement;
+
+ if (document.createEvent) {
+ var event = document.createEvent("HTMLEvents");
+ event.initEvent("dataavailable", true, true);
+ } else {
+ var event = document.createEventObject();
+ event.eventType = "ondataavailable";
+ }
+
+ event.eventName = eventName;
+ event.memo = memo || { };
+
+ if (document.createEvent) {
+ element.dispatchEvent(event);
+ } else {
+ element.fireEvent(event.eventType, event);
+ }
+
+ return Event.extend(event);
+ }
+ };
+})());
+
+Object.extend(Event, Event.Methods);
+
+Element.addMethods({
+ fire: Event.fire,
+ observe: Event.observe,
+ stopObserving: Event.stopObserving
+});
+
+Object.extend(document, {
+ fire: Element.Methods.fire.methodize(),
+ observe: Element.Methods.observe.methodize(),
+ stopObserving: Element.Methods.stopObserving.methodize()
+});
+
+(function() {
+ /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+ Matthias Miller, Dean Edwards and John Resig. */
+
+ var timer, fired = false;
+
+ function fireContentLoadedEvent() {
+ if (fired) return;
+ if (timer) window.clearInterval(timer);
+ document.fire("dom:loaded");
+ fired = true;
+ }
+
+ if (document.addEventListener) {
+ if (Prototype.Browser.WebKit) {
+ timer = window.setInterval(function() {
+ if (/loaded|complete/.test(document.readyState))
+ fireContentLoadedEvent();
+ }, 0);
+
+ Event.observe(window, "load", fireContentLoadedEvent);
+
+ } else {
+ document.addEventListener("DOMContentLoaded",
+ fireContentLoadedEvent, false);
+ }
+
+ } else {
+ document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
+ $("__onDOMContentLoaded").onreadystatechange = function() {
+ if (this.readyState == "complete") {
+ this.onreadystatechange = null;
+ fireContentLoadedEvent();
+ }
+ };
+ }
+})();
+/*------------------------------- DEPRECATED -------------------------------*/
+
+Hash.toQueryString = Object.toQueryString;
+
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+ Before: function(element, content) {
+ return Element.insert(element, {before:content});
+ },
+
+ Top: function(element, content) {
+ return Element.insert(element, {top:content});
+ },
+
+ Bottom: function(element, content) {
+ return Element.insert(element, {bottom:content});
+ },
+
+ After: function(element, content) {
+ return Element.insert(element, {after:content});
+ }
+};
+
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+// This should be moved to script.aculo.us; notice the deprecated methods
+// further below, that map to the newer Element methods.
+var Position = {
+ // set to true if needed, warning: firefox performance problems
+ // NOT neeeded for page scrolling, only if draggable contained in
+ // scrollable elements
+ includeScrollOffsets: false,
+
+ // must be called before calling withinIncludingScrolloffset, every time the
+ // page is scrolled
+ prepare: function() {
+ this.deltaX = window.pageXOffset
+ || document.documentElement.scrollLeft
+ || document.body.scrollLeft
+ || 0;
+ this.deltaY = window.pageYOffset
+ || document.documentElement.scrollTop
+ || document.body.scrollTop
+ || 0;
+ },
+
+ // caches x/y coordinate pair to use with overlap
+ within: function(element, x, y) {
+ if (this.includeScrollOffsets)
+ return this.withinIncludingScrolloffsets(element, x, y);
+ this.xcomp = x;
+ this.ycomp = y;
+ this.offset = Element.cumulativeOffset(element);
+
+ return (y >= this.offset[1] &&
+ y < this.offset[1] + element.offsetHeight &&
+ x >= this.offset[0] &&
+ x < this.offset[0] + element.offsetWidth);
+ },
+
+ withinIncludingScrolloffsets: function(element, x, y) {
+ var offsetcache = Element.cumulativeScrollOffset(element);
+
+ this.xcomp = x + offsetcache[0] - this.deltaX;
+ this.ycomp = y + offsetcache[1] - this.deltaY;
+ this.offset = Element.cumulativeOffset(element);
+
+ return (this.ycomp >= this.offset[1] &&
+ this.ycomp < this.offset[1] + element.offsetHeight &&
+ this.xcomp >= this.offset[0] &&
+ this.xcomp < this.offset[0] + element.offsetWidth);
+ },
+
+ // within must be called directly before
+ overlap: function(mode, element) {
+ if (!mode) return 0;
+ if (mode == 'vertical')
+ return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+ element.offsetHeight;
+ if (mode == 'horizontal')
+ return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+ element.offsetWidth;
+ },
+
+ // Deprecation layer -- use newer Element methods now (1.5.2).
+
+ cumulativeOffset: Element.Methods.cumulativeOffset,
+
+ positionedOffset: Element.Methods.positionedOffset,
+
+ absolutize: function(element) {
+ Position.prepare();
+ return Element.absolutize(element);
+ },
+
+ relativize: function(element) {
+ Position.prepare();
+ return Element.relativize(element);
+ },
+
+ realOffset: Element.Methods.cumulativeScrollOffset,
+
+ offsetParent: Element.Methods.getOffsetParent,
+
+ page: Element.Methods.viewportOffset,
+
+ clone: function(source, target, options) {
+ options = options || { };
+ return Element.clonePosition(target, source, options);
+ }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+ function iter(name) {
+ return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
+ }
+
+ instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+ function(element, className) {
+ className = className.toString().strip();
+ var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+ return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+ } : function(element, className) {
+ className = className.toString().strip();
+ var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+ if (!classNames && !className) return elements;
+
+ var nodes = $(element).getElementsByTagName('*');
+ className = ' ' + className + ' ';
+
+ for (var i = 0, child, cn; child = nodes[i]; i++) {
+ if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
+ (classNames && classNames.all(function(name) {
+ return !name.toString().blank() && cn.include(' ' + name + ' ');
+ }))))
+ elements.push(Element.extend(child));
+ }
+ return elements;
+ };
+
+ return function(className, parentElement) {
+ return $(parentElement || document.body).getElementsByClassName(className);
+ };
+}(Element.Methods);
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+ initialize: function(element) {
+ this.element = $(element);
+ },
+
+ _each: function(iterator) {
+ this.element.className.split(/\s+/).select(function(name) {
+ return name.length > 0;
+ })._each(iterator);
+ },
+
+ set: function(className) {
+ this.element.className = className;
+ },
+
+ add: function(classNameToAdd) {
+ if (this.include(classNameToAdd)) return;
+ this.set($A(this).concat(classNameToAdd).join(' '));
+ },
+
+ remove: function(classNameToRemove) {
+ if (!this.include(classNameToRemove)) return;
+ this.set($A(this).without(classNameToRemove).join(' '));
+ },
+
+ toString: function() {
+ return $A(this).join(' ');
+ }
+};
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+
+/*--------------------------------------------------------------------------*/
+
+Element.addMethods(); \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/number_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/number_helper.rb
new file mode 100644
index 000000000..eae230ad9
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/number_helper.rb
@@ -0,0 +1,179 @@
+module ActionView
+ module Helpers #:nodoc:
+ # Provides methods for converting numbers into formatted strings.
+ # Methods are provided for phone numbers, currency, percentage,
+ # precision, positional notation, and file size.
+ module NumberHelper
+ # Formats a +number+ into a US phone number (e.g., (555) 123-9876). You can customize the format
+ # in the +options+ hash.
+ #
+ # ==== Options
+ # * <tt>:area_code</tt> - Adds parentheses around the area code.
+ # * <tt>:delimiter</tt> - Specifies the delimiter to use (defaults to "-").
+ # * <tt>:extension</tt> - Specifies an extension to add to the end of the
+ # generated number.
+ # * <tt>:country_code</tt> - Sets the country code for the phone number.
+ #
+ # ==== Examples
+ # number_to_phone(1235551234) # => 123-555-1234
+ # number_to_phone(1235551234, :area_code => true) # => (123) 555-1234
+ # number_to_phone(1235551234, :delimiter => " ") # => 123 555 1234
+ # number_to_phone(1235551234, :area_code => true, :extension => 555) # => (123) 555-1234 x 555
+ # number_to_phone(1235551234, :country_code => 1) # => +1-123-555-1234
+ #
+ # number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".")
+ # => +1.123.555.1234 x 1343
+ def number_to_phone(number, options = {})
+ number = number.to_s.strip unless number.nil?
+ options = options.stringify_keys
+ area_code = options["area_code"] || nil
+ delimiter = options["delimiter"] || "-"
+ extension = options["extension"].to_s.strip || nil
+ country_code = options["country_code"] || nil
+
+ begin
+ str = ""
+ str << "+#{country_code}#{delimiter}" unless country_code.blank?
+ str << if area_code
+ number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4}$)/,"(\\1) \\2#{delimiter}\\3")
+ else
+ number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
+ end
+ str << " x #{extension}" unless extension.blank?
+ str
+ rescue
+ number
+ end
+ end
+
+ # Formats a +number+ into a currency string (e.g., $13.65). You can customize the format
+ # in the +options+ hash.
+ #
+ # ==== Options
+ # * <tt>:precision</tt> - Sets the level of precision (defaults to 2).
+ # * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$").
+ # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
+ #
+ # ==== Examples
+ # number_to_currency(1234567890.50) # => $1,234,567,890.50
+ # number_to_currency(1234567890.506) # => $1,234,567,890.51
+ # number_to_currency(1234567890.506, :precision => 3) # => $1,234,567,890.506
+ #
+ # number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "")
+ # # => &pound;1234567890,50
+ def number_to_currency(number, options = {})
+ options = options.stringify_keys
+ precision = options["precision"] || 2
+ unit = options["unit"] || "$"
+ separator = precision > 0 ? options["separator"] || "." : ""
+ delimiter = options["delimiter"] || ","
+
+ begin
+ parts = number_with_precision(number, precision).split('.')
+ unit + number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s
+ rescue
+ number
+ end
+ end
+
+ # Formats a +number+ as a percentage string (e.g., 65%). You can customize the
+ # format in the +options+ hash.
+ #
+ # ==== Options
+ # * <tt>:precision</tt> - Sets the level of precision (defaults to 3).
+ # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
+ #
+ # ==== Examples
+ # number_to_percentage(100) # => 100.000%
+ # number_to_percentage(100, :precision => 0) # => 100%
+ #
+ # number_to_percentage(302.24398923423, :precision => 5)
+ # # => 302.24399%
+ def number_to_percentage(number, options = {})
+ options = options.stringify_keys
+ precision = options["precision"] || 3
+ separator = options["separator"] || "."
+
+ begin
+ number = number_with_precision(number, precision)
+ parts = number.split('.')
+ if parts.at(1).nil?
+ parts[0] + "%"
+ else
+ parts[0] + separator + parts[1].to_s + "%"
+ end
+ rescue
+ number
+ end
+ end
+
+ # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You
+ # can customize the format using optional <em>delimiter</em> and <em>separator</em> parameters.
+ #
+ # ==== Options
+ # * <tt>delimiter</tt> - Sets the thousands delimiter (defaults to ",").
+ # * <tt>separator</tt> - Sets the separator between the units (defaults to ".").
+ #
+ # ==== Examples
+ # number_with_delimiter(12345678) # => 12,345,678
+ # number_with_delimiter(12345678.05) # => 12,345,678.05
+ # number_with_delimiter(12345678, ".") # => 12.345.678
+ #
+ # number_with_delimiter(98765432.98, " ", ",")
+ # # => 98 765 432,98
+ def number_with_delimiter(number, delimiter=",", separator=".")
+ begin
+ parts = number.to_s.split('.')
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
+ parts.join separator
+ rescue
+ number
+ end
+ end
+
+ # Formats a +number+ with the specified level of +precision+ (e.g., 112.32 has a precision of 2). The default
+ # level of precision is 3.
+ #
+ # ==== Examples
+ # number_with_precision(111.2345) # => 111.235
+ # number_with_precision(111.2345, 2) # => 111.24
+ # number_with_precision(13, 5) # => 13.00000
+ # number_with_precision(389.32314, 0) # => 389
+ def number_with_precision(number, precision=3)
+ "%01.#{precision}f" % number
+ rescue
+ number
+ end
+
+ # Formats the bytes in +size+ into a more understandable representation
+ # (e.g., giving it 1500 yields 1.5 KB). This method is useful for
+ # reporting file sizes to users. This method returns nil if
+ # +size+ cannot be converted into a number. You can change the default
+ # precision of 1 using the precision parameter +precision+.
+ #
+ # ==== Examples
+ # number_to_human_size(123) # => 123 Bytes
+ # number_to_human_size(1234) # => 1.2 KB
+ # number_to_human_size(12345) # => 12.1 KB
+ # number_to_human_size(1234567) # => 1.2 MB
+ # number_to_human_size(1234567890) # => 1.1 GB
+ # number_to_human_size(1234567890123) # => 1.1 TB
+ # number_to_human_size(1234567, 2) # => 1.18 MB
+ # number_to_human_size(483989, 0) # => 4 MB
+ def number_to_human_size(size, precision=1)
+ size = Kernel.Float(size)
+ case
+ when size.to_i == 1; "1 Byte"
+ when size < 1.kilobyte; "%d Bytes" % size
+ when size < 1.megabyte; "%.#{precision}f KB" % (size / 1.0.kilobyte)
+ when size < 1.gigabyte; "%.#{precision}f MB" % (size / 1.0.megabyte)
+ when size < 1.terabyte; "%.#{precision}f GB" % (size / 1.0.gigabyte)
+ else "%.#{precision}f TB" % (size / 1.0.terabyte)
+ end.sub(/([0-9])\.?0+ /, '\1 ' )
+ rescue
+ nil
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/prototype_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/prototype_helper.rb
new file mode 100644
index 000000000..0435f5c42
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -0,0 +1,1268 @@
+require 'set'
+
+module ActionView
+ module Helpers
+ # Prototype[http://www.prototypejs.org/] is a JavaScript library that provides
+ # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
+ # Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php]
+ # functionality, and more traditional object-oriented facilities for JavaScript.
+ # This module provides a set of helpers to make it more convenient to call
+ # functions from Prototype using Rails, including functionality to call remote
+ # Rails methods (that is, making a background request to a Rails action) using Ajax.
+ # This means that you can call actions in your controllers without
+ # reloading the page, but still update certain parts of it using
+ # injections into the DOM. A common use case is having a form that adds
+ # a new element to a list without reloading the page or updating a shopping
+ # cart total when a new item is added.
+ #
+ # == Usage
+ # To be able to use these helpers, you must first include the Prototype
+ # JavaScript framework in your pages.
+ #
+ # javascript_include_tag 'prototype'
+ #
+ # (See the documentation for
+ # ActionView::Helpers::JavaScriptHelper for more information on including
+ # this and other JavaScript files in your Rails templates.)
+ #
+ # Now you're ready to call a remote action either through a link...
+ #
+ # link_to_remote "Add to cart",
+ # :url => { :action => "add", :id => product.id },
+ # :update => { :success => "cart", :failure => "error" }
+ #
+ # ...through a form...
+ #
+ # <% form_remote_tag :url => '/shipping' do -%>
+ # <div><%= submit_tag 'Recalculate Shipping' %></div>
+ # <% end -%>
+ #
+ # ...periodically...
+ #
+ # periodically_call_remote(:url => 'update', :frequency => '5', :update => 'ticker')
+ #
+ # ...or through an observer (i.e., a form or field that is observed and calls a remote
+ # action when changed).
+ #
+ # <%= observe_field(:searchbox,
+ # :url => { :action => :live_search }),
+ # :frequency => 0.5,
+ # :update => :hits,
+ # :with => 'query'
+ # %>
+ #
+ # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
+ # are listed here); check out the documentation for each method to find out more about its usage and options.
+ #
+ # === Common Options
+ # See link_to_remote for documentation of options common to all Ajax
+ # helpers; any of the options specified by link_to_remote can be used
+ # by the other helpers.
+ #
+ # == Designing your Rails actions for Ajax
+ # When building your action handlers (that is, the Rails actions that receive your background requests), it's
+ # important to remember a few things. First, whatever your action would normall return to the browser, it will
+ # return to the Ajax call. As such, you typically don't want to render with a layout. This call will cause
+ # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up.
+ # You can turn the layout off on particular actions by doing the following:
+ #
+ # class SiteController < ActionController::Base
+ # layout "standard", :except => [:ajax_method, :more_ajax, :another_ajax]
+ # end
+ #
+ # Optionally, you could do this in the method you wish to lack a layout:
+ #
+ # render :layout => false
+ #
+ # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the
+ # method that Ajax uses to make background requests) method.
+ # def name
+ # # Is this an XmlHttpRequest request?
+ # if (request.xhr?)
+ # render :text => @name.to_s
+ # else
+ # # No? Then render an action.
+ # render :action => 'view_attribute', :attr => @name
+ # end
+ # end
+ #
+ # The else clause can be left off and the current action will render with full layout and template. An extension
+ # to this solution was posted to Ryan Heneise's blog at ArtOfMission["http://www.artofmission.com/"].
+ #
+ # layout proc{ |c| c.request.xhr? ? false : "application" }
+ #
+ # Dropping this in your ApplicationController turns the layout off for every request that is an "xhr" request.
+ #
+ # If you are just returning a little data or don't want to build a template for your output, you may opt to simply
+ # render text output, like this:
+ #
+ # render :text => 'Return this from my method!'
+ #
+ # Since whatever the method returns is injected into the DOM, this will simply inject some text (or HTML, if you
+ # tell it to). This is usually how small updates, such updating a cart total or a file count, are handled.
+ #
+ # == Updating multiple elements
+ # See JavaScriptGenerator for information on updating multiple elements
+ # on the page in an Ajax response.
+ module PrototypeHelper
+ unless const_defined? :CALLBACKS
+ CALLBACKS = Set.new([ :uninitialized, :loading, :loaded,
+ :interactive, :complete, :failure, :success ] +
+ (100..599).to_a)
+ AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url,
+ :asynchronous, :method, :insertion, :position,
+ :form, :with, :update, :script ]).merge(CALLBACKS)
+ end
+
+ # Returns a link to a remote action defined by <tt>options[:url]</tt>
+ # (using the url_for format) that's called in the background using
+ # XMLHttpRequest. The result of that request can then be inserted into a
+ # DOM object whose id can be specified with <tt>options[:update]</tt>.
+ # Usually, the result would be a partial prepared by the controller with
+ # render :partial.
+ #
+ # Examples:
+ # # Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true});
+ # # return false;">Delete this post</a>
+ # link_to_remote "Delete this post", :update => "posts",
+ # :url => { :action => "destroy", :id => post.id }
+ #
+ # # Generates: <a href="#" onclick="new Ajax.Updater('emails', '/mail/list_emails', {asynchronous:true, evalScripts:true});
+ # # return false;"><img alt="Refresh" src="/images/refresh.png?" /></a>
+ # link_to_remote(image_tag("refresh"), :update => "emails",
+ # :url => { :action => "list_emails" })
+ #
+ # You can override the generated HTML options by specifying a hash in
+ # <tt>options[:html]</tt>.
+ #
+ # link_to_remote "Delete this post", :update => "posts",
+ # :url => post_url(@post), :method => :delete,
+ # :html => { :class => "destructive" }
+ #
+ # You can also specify a hash for <tt>options[:update]</tt> to allow for
+ # easy redirection of output to an other DOM element if a server-side
+ # error occurs:
+ #
+ # Example:
+ # # Generates: <a href="#" onclick="new Ajax.Updater({success:'posts',failure:'error'}, '/blog/destroy/5',
+ # # {asynchronous:true, evalScripts:true}); return false;">Delete this post</a>
+ # link_to_remote "Delete this post",
+ # :url => { :action => "destroy", :id => post.id },
+ # :update => { :success => "posts", :failure => "error" }
+ #
+ # Optionally, you can use the <tt>options[:position]</tt> parameter to
+ # influence how the target DOM element is updated. It must be one of
+ # <tt>:before</tt>, <tt>:top</tt>, <tt>:bottom</tt>, or <tt>:after</tt>.
+ #
+ # The method used is by default POST. You can also specify GET or you
+ # can simulate PUT or DELETE over POST. All specified with <tt>options[:method]</tt>
+ #
+ # Example:
+ # # Generates: <a href="#" onclick="new Ajax.Request('/person/4', {asynchronous:true, evalScripts:true, method:'delete'});
+ # # return false;">Destroy</a>
+ # link_to_remote "Destroy", :url => person_url(:id => person), :method => :delete
+ #
+ # By default, these remote requests are processed asynchronous during
+ # which various JavaScript callbacks can be triggered (for progress
+ # indicators and the likes). All callbacks get access to the
+ # <tt>request</tt> object, which holds the underlying XMLHttpRequest.
+ #
+ # To access the server response, use <tt>request.responseText</tt>, to
+ # find out the HTTP status, use <tt>request.status</tt>.
+ #
+ # Example:
+ # # Generates: <a href="#" onclick="new Ajax.Request('/words/undo?n=33', {asynchronous:true, evalScripts:true,
+ # # onComplete:function(request){undoRequestCompleted(request)}}); return false;">hello</a>
+ # word = 'hello'
+ # link_to_remote word,
+ # :url => { :action => "undo", :n => word_counter },
+ # :complete => "undoRequestCompleted(request)"
+ #
+ # The callbacks that may be specified are (in order):
+ #
+ # <tt>:loading</tt>:: Called when the remote document is being
+ # loaded with data by the browser.
+ # <tt>:loaded</tt>:: Called when the browser has finished loading
+ # the remote document.
+ # <tt>:interactive</tt>:: Called when the user can interact with the
+ # remote document, even though it has not
+ # finished loading.
+ # <tt>:success</tt>:: Called when the XMLHttpRequest is completed,
+ # and the HTTP status code is in the 2XX range.
+ # <tt>:failure</tt>:: Called when the XMLHttpRequest is completed,
+ # and the HTTP status code is not in the 2XX
+ # range.
+ # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete
+ # (fires after success/failure if they are
+ # present).
+ #
+ # You can further refine <tt>:success</tt> and <tt>:failure</tt> by
+ # adding additional callbacks for specific status codes.
+ #
+ # Example:
+ # # Generates: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true,
+ # # on404:function(request){alert('Not found...? Wrong URL...?')},
+ # # onFailure:function(request){alert('HTTP Error ' + request.status + '!')}}); return false;">hello</a>
+ # link_to_remote word,
+ # :url => { :action => "action" },
+ # 404 => "alert('Not found...? Wrong URL...?')",
+ # :failure => "alert('HTTP Error ' + request.status + '!')"
+ #
+ # A status code callback overrides the success/failure handlers if
+ # present.
+ #
+ # If you for some reason or another need synchronous processing (that'll
+ # block the browser while the request is happening), you can specify
+ # <tt>options[:type] = :synchronous</tt>.
+ #
+ # You can customize further browser side call logic by passing in
+ # JavaScript code snippets via some optional parameters. In their order
+ # of use these are:
+ #
+ # <tt>:confirm</tt>:: Adds confirmation dialog.
+ # <tt>:condition</tt>:: Perform remote request conditionally
+ # by this expression. Use this to
+ # describe browser-side conditions when
+ # request should not be initiated.
+ # <tt>:before</tt>:: Called before request is initiated.
+ # <tt>:after</tt>:: Called immediately after request was
+ # initiated and before <tt>:loading</tt>.
+ # <tt>:submit</tt>:: Specifies the DOM element ID that's used
+ # as the parent of the form elements. By
+ # default this is the current form, but
+ # it could just as well be the ID of a
+ # table row or any other DOM element.
+ # <tt>:with</tt>:: A JavaScript expression specifying
+ # the parameters for the XMLHttpRequest.
+ # Any expressions should return a valid
+ # URL query string.
+ #
+ # Example:
+ #
+ # :with => "'name=' + $('name').value"
+ #
+ # You can generate a link that uses AJAX in the general case, while
+ # degrading gracefully to plain link behavior in the absence of
+ # JavaScript by setting <tt>html_options[:href]</tt> to an alternate URL.
+ # Note the extra curly braces around the <tt>options</tt> hash separate
+ # it as the second parameter from <tt>html_options</tt>, the third.
+ #
+ # Example:
+ # link_to_remote "Delete this post",
+ # { :update => "posts", :url => { :action => "destroy", :id => post.id } },
+ # :href => url_for(:action => "destroy", :id => post.id)
+ def link_to_remote(name, options = {}, html_options = nil)
+ link_to_function(name, remote_function(options), html_options || options.delete(:html))
+ end
+
+ # Periodically calls the specified url (<tt>options[:url]</tt>) every
+ # <tt>options[:frequency]</tt> seconds (default is 10). Usually used to
+ # update a specified div (<tt>options[:update]</tt>) with the results
+ # of the remote call. The options for specifying the target with :url
+ # and defining callbacks is the same as link_to_remote.
+ # Examples:
+ # # Call get_averages and put its results in 'avg' every 10 seconds
+ # # Generates:
+ # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages',
+ # # {asynchronous:true, evalScripts:true})}, 10)
+ # periodically_call_remote(:url => { :action => 'get_averages' }, :update => 'avg')
+ #
+ # # Call invoice every 10 seconds with the id of the customer
+ # # If it succeeds, update the invoice DIV; if it fails, update the error DIV
+ # # Generates:
+ # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'},
+ # # '/testing/invoice/16', {asynchronous:true, evalScripts:true})}, 10)
+ # periodically_call_remote(:url => { :action => 'invoice', :id => customer.id },
+ # :update => { :success => "invoice", :failure => "error" }
+ #
+ # # Call update every 20 seconds and update the new_block DIV
+ # # Generates:
+ # # new PeriodicalExecuter(function() {new Ajax.Updater('news_block', 'update', {asynchronous:true, evalScripts:true})}, 20)
+ # periodically_call_remote(:url => 'update', :frequency => '20', :update => 'news_block')
+ #
+ def periodically_call_remote(options = {})
+ frequency = options[:frequency] || 10 # every ten seconds by default
+ code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})"
+ javascript_tag(code)
+ end
+
+ # Returns a form tag that will submit using XMLHttpRequest in the
+ # background instead of the regular reloading POST arrangement. Even
+ # though it's using JavaScript to serialize the form elements, the form
+ # submission will work just like a regular submission as viewed by the
+ # receiving side (all elements available in <tt>params</tt>). The options for
+ # specifying the target with :url and defining callbacks is the same as
+ # link_to_remote.
+ #
+ # A "fall-through" target for browsers that doesn't do JavaScript can be
+ # specified with the :action/:method options on :html.
+ #
+ # Example:
+ # # Generates:
+ # # <form action="/some/place" method="post" onsubmit="new Ajax.Request('',
+ # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">
+ # form_remote_tag :html => { :action =>
+ # url_for(:controller => "some", :action => "place") }
+ #
+ # The Hash passed to the :html key is equivalent to the options (2nd)
+ # argument in the FormTagHelper.form_tag method.
+ #
+ # By default the fall-through action is the same as the one specified in
+ # the :url (and the default method is :post).
+ #
+ # form_remote_tag also takes a block, like form_tag:
+ # # Generates:
+ # # <form action="/" method="post" onsubmit="new Ajax.Request('/',
+ # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)});
+ # # return false;"> <div><input name="commit" type="submit" value="Save" /></div>
+ # # </form>
+ # <% form_remote_tag :url => '/posts' do -%>
+ # <div><%= submit_tag 'Save' %></div>
+ # <% end -%>
+ def form_remote_tag(options = {}, &block)
+ options[:form] = true
+
+ options[:html] ||= {}
+ options[:html][:onsubmit] =
+ (options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") +
+ "#{remote_function(options)}; return false;"
+
+ form_tag(options[:html].delete(:action) || url_for(options[:url]), options[:html], &block)
+ end
+
+ # Creates a form that will submit using XMLHttpRequest in the background
+ # instead of the regular reloading POST arrangement and a scope around a
+ # specific resource that is used as a base for questioning about
+ # values for the fields.
+ #
+ # === Resource
+ #
+ # Example:
+ # <% remote_form_for(@post) do |f| %>
+ # ...
+ # <% end %>
+ #
+ # This will expand to be the same as:
+ #
+ # <% remote_form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
+ # ...
+ # <% end %>
+ #
+ # === Nested Resource
+ #
+ # Example:
+ # <% remote_form_for([@post, @comment]) do |f| %>
+ # ...
+ # <% end %>
+ #
+ # This will expand to be the same as:
+ #
+ # <% remote_form_for :comment, @comment, :url => post_comment_path(@post, @comment), :html => { :method => :put, :class => "edit_comment", :id => "edit_comment_45" } do |f| %>
+ # ...
+ # <% end %>
+ #
+ # If you don't need to attach a form to a resource, then check out form_remote_tag.
+ #
+ # See FormHelper#form_for for additional semantics.
+ def remote_form_for(record_or_name_or_array, *args, &proc)
+ options = args.extract_options!
+
+ case record_or_name_or_array
+ when String, Symbol
+ object_name = record_or_name_or_array
+ when Array
+ object = record_or_name_or_array.last
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
+ apply_form_for_options!(record_or_name_or_array, options)
+ args.unshift object
+ else
+ object = record_or_name_or_array
+ object_name = ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array)
+ apply_form_for_options!(object, options)
+ args.unshift object
+ end
+
+ concat(form_remote_tag(options), proc.binding)
+ fields_for(object_name, *(args << options), &proc)
+ concat('</form>', proc.binding)
+ end
+ alias_method :form_remote_for, :remote_form_for
+
+ # Returns a button input tag with the element name of +name+ and a value (i.e., display text) of +value+
+ # that will submit form using XMLHttpRequest in the background instead of a regular POST request that
+ # reloads the page.
+ #
+ # # Create a button that submits to the create action
+ # #
+ # # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create',
+ # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
+ # # return false;" type="button" value="Create" />
+ # <%= submit_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %>
+ #
+ # # Submit to the remote action update and update the DIV succeed or fail based
+ # # on the success or failure of the request
+ # #
+ # # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'},
+ # # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
+ # # return false;" type="button" value="Update" />
+ # <%= submit_to_remote 'update_btn', 'Update', :url => { :action => 'update' },
+ # :update => { :success => "succeed", :failure => "fail" }
+ #
+ # <tt>options</tt> argument is the same as in form_remote_tag.
+ def submit_to_remote(name, value, options = {})
+ options[:with] ||= 'Form.serialize(this.form)'
+
+ options[:html] ||= {}
+ options[:html][:type] = 'button'
+ options[:html][:onclick] = "#{remote_function(options)}; return false;"
+ options[:html][:name] = name
+ options[:html][:value] = value
+
+ tag("input", options[:html], false)
+ end
+
+ # Returns '<tt>eval(request.responseText)</tt>' 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.
+ def evaluate_remote_response
+ "eval(request.responseText)"
+ end
+
+ # Returns the JavaScript needed for a remote function.
+ # Takes the same arguments as link_to_remote.
+ #
+ # Example:
+ # # Generates: <select id="options" onchange="new Ajax.Updater('options',
+ # # '/testing/update_options', {asynchronous:true, evalScripts:true})">
+ # <select id="options" onchange="<%= remote_function(:update => "options",
+ # :url => { :action => :update_options }) %>">
+ # <option value="0">Hello</option>
+ # <option value="1">World</option>
+ # </select>
+ def remote_function(options)
+ javascript_options = options_for_ajax(options)
+
+ update = ''
+ 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]
+ update = '{' + update.join(',') + '}'
+ elsif options[:update]
+ update << "'#{options[:update]}'"
+ end
+
+ function = update.empty? ?
+ "new Ajax.Request(" :
+ "new Ajax.Updater(#{update}, "
+
+ url_options = options[:url]
+ url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash)
+ function << "'#{url_for(url_options)}'"
+ function << ", #{javascript_options})"
+
+ function = "#{options[:before]}; #{function}" if options[:before]
+ function = "#{function}; #{options[:after]}" if options[:after]
+ function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]
+ function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm]
+
+ return function
+ end
+
+ # Observes the field with the DOM ID specified by +field_id+ and calls a
+ # callback when its contents have changed. The default callback is an
+ # Ajax call. By default the value of the observed field is sent as a
+ # parameter with the Ajax call.
+ #
+ # Example:
+ # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest',
+ # # '/testing/find_suggestion', {asynchronous:true, evalScripts:true, parameters:'q=' + value})})
+ # <%= observe_field :suggest, :url => { :action => :find_suggestion },
+ # :frequency => 0.25,
+ # :update => :suggest,
+ # :with => 'q'
+ # %>
+ #
+ # Required +options+ are either of:
+ # <tt>:url</tt>:: +url_for+-style options for the action to call
+ # when the field has changed.
+ # <tt>:function</tt>:: Instead of making a remote call to a URL, you
+ # can specify javascript code to be called instead.
+ # Note that the value of this option is used as the
+ # *body* of the javascript function, a function definition
+ # with parameters named element and value will be generated for you
+ # for example:
+ # observe_field("glass", :frequency => 1, :function => "alert('Element changed')")
+ # will generate:
+ # new Form.Element.Observer('glass', 1, function(element, value) {alert('Element changed')})
+ # The element parameter is the DOM element being observed, and the value is its value at the
+ # time the observer is triggered.
+ #
+ # Additional options are:
+ # <tt>:frequency</tt>:: The frequency (in seconds) at which changes to
+ # this field will be detected. Not setting this
+ # option at all or to a value equal to or less than
+ # zero will use event based observation instead of
+ # time based observation.
+ # <tt>:update</tt>:: Specifies the DOM ID of the element whose
+ # innerHTML should be updated with the
+ # XMLHttpRequest response text.
+ # <tt>:with</tt>:: A JavaScript expression specifying the parameters
+ # for the XMLHttpRequest. The default is to send the
+ # key and value of the observed field. Any custom
+ # expressions should return a valid URL query string.
+ # The value of the field is stored in the JavaScript
+ # variable +value+.
+ #
+ # Examples
+ #
+ # :with => "'my_custom_key=' + value"
+ # :with => "'person[name]=' + prompt('New name')"
+ # :with => "Form.Element.serialize('other-field')"
+ #
+ # Finally
+ # :with => 'name'
+ # is shorthand for
+ # :with => "'name=' + value"
+ # This essentially just changes the key of the parameter.
+ # <tt>:on</tt>:: Specifies which event handler to observe. By default,
+ # it's set to "changed" for text fields and areas and
+ # "click" for radio buttons and checkboxes. With this,
+ # you can specify it instead to be "blur" or "focus" or
+ # any other event.
+ #
+ # Additionally, you may specify any of the options documented in the
+ # <em>Common options</em> section at the top of this document.
+ #
+ # Example:
+ #
+ # # Sends params: {:title => 'Title of the book'} when the book_title input
+ # # field is changed.
+ # observe_field 'book_title',
+ # :url => 'http://example.com/books/edit/1',
+ # :with => 'title'
+ #
+ # # Sends params: {:book_title => 'Title of the book'} when the focus leaves
+ # # the input field.
+ # observe_field 'book_title',
+ # :url => 'http://example.com/books/edit/1',
+ # :on => 'blur'
+ #
+ def observe_field(field_id, options = {})
+ if options[:frequency] && options[:frequency] > 0
+ build_observer('Form.Element.Observer', field_id, options)
+ else
+ build_observer('Form.Element.EventObserver', field_id, options)
+ end
+ end
+
+ # Observes the form with the DOM ID specified by +form_id+ and calls a
+ # callback when its contents have changed. The default callback is an
+ # Ajax call. By default all fields of the observed field are sent as
+ # parameters with the Ajax call.
+ #
+ # The +options+ for +observe_form+ are the same as the options for
+ # +observe_field+. The JavaScript variable +value+ available to the
+ # <tt>:with</tt> option is set to the serialized form by default.
+ def observe_form(form_id, options = {})
+ if options[:frequency]
+ build_observer('Form.Observer', form_id, options)
+ else
+ build_observer('Form.EventObserver', form_id, options)
+ end
+ end
+
+ # All the methods were moved to GeneratorMethods so that
+ # #include_helpers_from_context has nothing to overwrite.
+ class JavaScriptGenerator #:nodoc:
+ def initialize(context, &block) #:nodoc:
+ @context, @lines = context, []
+ include_helpers_from_context
+ @context.instance_exec(self, &block)
+ end
+
+ private
+ def include_helpers_from_context
+ @context.extended_by.each do |mod|
+ extend mod unless mod.name =~ /^ActionView::Helpers/
+ end
+ extend GeneratorMethods
+ end
+
+ # JavaScriptGenerator generates blocks of JavaScript code that allow you
+ # to change the content and presentation of multiple DOM elements. Use
+ # this in your Ajax response bodies, either in a <script> tag or as plain
+ # JavaScript sent with a Content-type of "text/javascript".
+ #
+ # Create new instances with PrototypeHelper#update_page or with
+ # ActionController::Base#render, then call #insert_html, #replace_html,
+ # #remove, #show, #hide, #visual_effect, or any other of the built-in
+ # methods on the yielded generator in any order you like to modify the
+ # content and appearance of the current page.
+ #
+ # Example:
+ #
+ # # Generates:
+ # # new Insertion.Bottom("list", "<li>Some item</li>");
+ # # new Effect.Highlight("list");
+ # # ["status-indicator", "cancel-link"].each(Element.hide);
+ # update_page do |page|
+ # page.insert_html :bottom, 'list', "<li>#{@item.name}</li>"
+ # page.visual_effect :highlight, 'list'
+ # page.hide 'status-indicator', 'cancel-link'
+ # end
+ #
+ #
+ # Helper methods can be used in conjunction with JavaScriptGenerator.
+ # When a helper method is called inside an update block on the +page+
+ # object, that method will also have access to a +page+ object.
+ #
+ # Example:
+ #
+ # module ApplicationHelper
+ # def update_time
+ # page.replace_html 'time', Time.now.to_s(:db)
+ # page.visual_effect :highlight, 'time'
+ # end
+ # end
+ #
+ # # Controller action
+ # def poll
+ # render(:update) { |page| page.update_time }
+ # end
+ #
+ # You can also use PrototypeHelper#update_page_tag instead of
+ # PrototypeHelper#update_page to wrap the generated JavaScript in a
+ # <script> tag.
+ module GeneratorMethods
+ def to_s #:nodoc:
+ returning javascript = @lines * $/ do
+ 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('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}'); throw e }"
+ end
+ end
+ end
+
+ # Returns a element reference by finding it through +id+ in the DOM. This element can then be
+ # used for further method calls. Examples:
+ #
+ # page['blank_slate'] # => $('blank_slate');
+ # page['blank_slate'].show # => $('blank_slate').show();
+ # page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
+ #
+ # You can also pass in a record, which will use ActionController::RecordIdentifier.dom_id to lookup
+ # the correct id:
+ #
+ # page[@post] # => $('post_45')
+ # page[Post.new] # => $('new_post')
+ def [](id)
+ case id
+ when String, Symbol, NilClass
+ JavaScriptElementProxy.new(self, id)
+ else
+ JavaScriptElementProxy.new(self, ActionController::RecordIdentifier.dom_id(id))
+ end
+ end
+
+ # Returns an object whose <tt>#to_json</tt> 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:
+ #
+ # page.select('p') # => $$('p');
+ # page.select('p.welcome b').first # => $$('p.welcome b').first();
+ # page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
+ #
+ # You can also use prototype enumerations with the collection. Observe:
+ #
+ # # Generates: $$('#items li').each(function(value) { value.hide(); });
+ # page.select('#items li').each do |value|
+ # value.hide
+ # end
+ #
+ # Though you can call the block param anything you want, they are always rendered in the
+ # javascript as 'value, index.' Other enumerations, like collect() return the last statement:
+ #
+ # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
+ # page.select('#items li').collect('hidden') do |item|
+ # item.hide
+ # end
+ #
+ def select(pattern)
+ JavaScriptElementCollectionProxy.new(self, pattern)
+ end
+
+ # Inserts HTML at the specified +position+ relative to the DOM element
+ # identified by the given +id+.
+ #
+ # +position+ may be one of:
+ #
+ # <tt>:top</tt>:: HTML is inserted inside the element, before the
+ # element's existing content.
+ # <tt>:bottom</tt>:: HTML is inserted inside the element, after the
+ # element's existing content.
+ # <tt>:before</tt>:: HTML is inserted immediately preceding the element.
+ # <tt>:after</tt>:: HTML is inserted immediately following the element.
+ #
+ # +options_for_render+ may be either a string of HTML to insert, or a hash
+ # of options to be passed to ActionView::Base#render. For example:
+ #
+ # # Insert the rendered 'navigation' partial just before the DOM
+ # # element with ID 'content'.
+ # # Generates: new Insertion.Before("content", "-- Contents of 'navigation' partial --");
+ # insert_html :before, 'content', :partial => 'navigation'
+ #
+ # # Add a list item to the bottom of the <ul> with ID 'list'.
+ # # Generates: new Insertion.Bottom("list", "<li>Last item</li>");
+ # insert_html :bottom, 'list', '<li>Last item</li>'
+ #
+ def insert_html(position, id, *options_for_render)
+ insertion = position.to_s.camelize
+ call "new Insertion.#{insertion}", id, render(*options_for_render)
+ end
+
+ # Replaces the inner HTML of the DOM element with the given +id+.
+ #
+ # +options_for_render+ may be either a string of HTML to insert, or a hash
+ # of options to be passed to ActionView::Base#render. For example:
+ #
+ # # Replace the HTML of the DOM element having ID 'person-45' with the
+ # # 'person' partial for the appropriate object.
+ # # Generates: Element.update("person-45", "-- Contents of 'person' partial --");
+ # replace_html 'person-45', :partial => 'person', :object => @person
+ #
+ def replace_html(id, *options_for_render)
+ call 'Element.update', id, render(*options_for_render)
+ end
+
+ # Replaces the "outer HTML" (i.e., the entire element, not just its
+ # contents) of the DOM element with the given +id+.
+ #
+ # +options_for_render+ may be either a string of HTML to insert, or a hash
+ # of options to be passed to ActionView::Base#render. For example:
+ #
+ # # Replace the DOM element having ID 'person-45' with the
+ # # 'person' partial for the appropriate object.
+ # 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
+ # the use of wrapper elements.
+ #
+ # Examples:
+ #
+ # <div id="people">
+ # <%= render :partial => 'person', :collection => @people %>
+ # </div>
+ #
+ # # Insert a new person
+ # #
+ # # Generates: new Insertion.Bottom({object: "Matz", partial: "person"}, "");
+ # page.insert_html :bottom, :partial => 'person', :object => @person
+ #
+ # # Replace an existing person
+ #
+ # # Generates: Element.replace("person_45", "-- Contents of partial --");
+ # page.replace 'person_45', :partial => 'person', :object => @person
+ #
+ def replace(id, *options_for_render)
+ call 'Element.replace', id, render(*options_for_render)
+ end
+
+ # Removes the DOM elements with the given +ids+ from the page.
+ #
+ # Example:
+ #
+ # # Remove a few people
+ # # Generates: ["person_23", "person_9", "person_2"].each(Element.remove);
+ # page.remove 'person_23', 'person_9', 'person_2'
+ #
+ def remove(*ids)
+ loop_on_multiple_args 'Element.remove', ids
+ end
+
+ # Shows hidden DOM elements with the given +ids+.
+ #
+ # Example:
+ #
+ # # Show a few people
+ # # Generates: ["person_6", "person_13", "person_223"].each(Element.show);
+ # page.show 'person_6', 'person_13', 'person_223'
+ #
+ def show(*ids)
+ loop_on_multiple_args 'Element.show', ids
+ end
+
+ # Hides the visible DOM elements with the given +ids+.
+ #
+ # Example:
+ #
+ # # Hide a few people
+ # # Generates: ["person_29", "person_9", "person_0"].each(Element.hide);
+ # page.hide 'person_29', 'person_9', 'person_0'
+ #
+ def hide(*ids)
+ loop_on_multiple_args 'Element.hide', ids
+ end
+
+ # Toggles the visibility of the DOM elements with the given +ids+.
+ # Example:
+ #
+ # # Show a few people
+ # # Generates: ["person_14", "person_12", "person_23"].each(Element.toggle);
+ # page.toggle 'person_14', 'person_12', 'person_23' # Hides the elements
+ # page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements
+ #
+ def toggle(*ids)
+ loop_on_multiple_args 'Element.toggle', ids
+ end
+
+ # Displays an alert dialog with the given +message+.
+ #
+ # Example:
+ #
+ # # Generates: alert('This message is from Rails!')
+ # page.alert('This message is from Rails!')
+ def alert(message)
+ call 'alert', message
+ end
+
+ # Redirects the browser to the given +location+ using JavaScript, in the same form as +url_for+.
+ #
+ # Examples:
+ #
+ # # Generates: window.location.href = "/mycontroller";
+ # page.redirect_to(:action => 'index')
+ #
+ # # Generates: window.location.href = "/account/signup";
+ # page.redirect_to(:controller => 'account', :action => 'signup')
+ def redirect_to(location)
+ assign 'window.location.href', @context.url_for(location)
+ end
+
+ # 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 <tt>function() { ... }</tt>
+ # and passed as the called function's final argument.
+ #
+ # Examples:
+ #
+ # # Generates: Element.replace(my_element, "My content to replace with.")
+ # page.call 'Element.replace', 'my_element', "My content to replace with."
+ #
+ # # Generates: alert('My message!')
+ # page.call 'alert', 'My message!'
+ #
+ def call(function, *arguments, &block)
+ record "#{function}(#{arguments_for_call(arguments, block)})"
+ end
+
+ # Assigns the JavaScript +variable+ the given +value+.
+ #
+ # Examples:
+ #
+ # # Generates: my_string = "This is mine!";
+ # page.assign 'my_string', 'This is mine!'
+ #
+ # # Generates: record_count = 33;
+ # page.assign 'record_count', 33
+ #
+ # # Generates: tabulated_total = 47
+ # page.assign 'tabulated_total', @total_from_cart
+ #
+ def assign(variable, value)
+ record "#{variable} = #{javascript_object_for(value)}"
+ end
+
+ # Writes raw JavaScript to the page.
+ #
+ # Example:
+ #
+ # page << "alert('JavaScript with Prototype.');"
+ def <<(javascript)
+ @lines << javascript
+ end
+
+ # Executes the content of the block after a delay of +seconds+. Example:
+ #
+ # # Generates:
+ # # setTimeout(function() {
+ # # ;
+ # # new Effect.Fade("notice",{});
+ # # }, 20000);
+ # page.delay(20) do
+ # page.visual_effect :fade, 'notice'
+ # end
+ def delay(seconds = 1)
+ record "setTimeout(function() {\n\n"
+ yield
+ record "}, #{(seconds * 1000).to_i})"
+ end
+
+ # Starts a script.aculo.us visual effect. See
+ # ActionView::Helpers::ScriptaculousHelper for more information.
+ def visual_effect(name, id = nil, options = {})
+ record @context.send(:visual_effect, name, id, options)
+ end
+
+ # Creates a script.aculo.us sortable element. Useful
+ # to recreate sortable elements after items get added
+ # or deleted.
+ # See ActionView::Helpers::ScriptaculousHelper for more information.
+ def sortable(id, options = {})
+ record @context.send(:sortable_element_js, id, options)
+ end
+
+ # Creates a script.aculo.us draggable element.
+ # See ActionView::Helpers::ScriptaculousHelper for more information.
+ def draggable(id, options = {})
+ record @context.send(:draggable_element_js, id, options)
+ end
+
+ # Creates a script.aculo.us drop receiving element.
+ # See ActionView::Helpers::ScriptaculousHelper for more information.
+ def drop_receiving(id, options = {})
+ record @context.send(:drop_receiving_element_js, id, options)
+ 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(/\;\z/, '')};" do
+ self << line
+ end
+ end
+
+ def render(*options_for_render)
+ old_format = @context && @context.template_format
+ @context.template_format = :html if @context
+ Hash === options_for_render.first ?
+ @context.render(*options_for_render) :
+ options_for_render.first.to_s
+ ensure
+ @context.template_format = old_format if @context
+ end
+
+ def javascript_object_for(object)
+ object.respond_to?(:to_json) ? object.to_json : object.inspect
+ end
+
+ 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
+ end
+ end
+
+ # Yields a JavaScriptGenerator and returns the generated JavaScript code.
+ # Use this to update multiple elements on a page in an Ajax response.
+ # See JavaScriptGenerator for more information.
+ #
+ # Example:
+ #
+ # update_page do |page|
+ # page.hide 'spinner'
+ # end
+ def update_page(&block)
+ JavaScriptGenerator.new(@template, &block).to_s
+ end
+
+ # Works like update_page but wraps the generated JavaScript in a <script>
+ # tag. Use this to include generated JavaScript in an ERb template.
+ # See JavaScriptGenerator for more information.
+ #
+ # +html_options+ may be a hash of <script> attributes to be passed
+ # to ActionView::Helpers::JavaScriptHelper#javascript_tag.
+ def update_page_tag(html_options = {}, &block)
+ javascript_tag update_page(&block), html_options
+ end
+
+ protected
+ def options_for_ajax(options)
+ js_options = build_callbacks(options)
+
+ js_options['asynchronous'] = options[:type] != :synchronous
+ js_options['method'] = method_option_to_s(options[:method]) if options[:method]
+ js_options['insertion'] = "Insertion.#{options[:position].to_s.camelize}" if options[:position]
+ js_options['evalScripts'] = options[:script].nil? || options[:script]
+
+ if options[:form]
+ js_options['parameters'] = 'Form.serialize(this)'
+ elsif options[:submit]
+ js_options['parameters'] = "Form.serialize('#{options[:submit]}')"
+ elsif options[:with]
+ js_options['parameters'] = options[:with]
+ end
+
+ if protect_against_forgery?
+ if js_options['parameters']
+ js_options['parameters'] << " + '&"
+ else
+ js_options['parameters'] = "'"
+ end
+ js_options['parameters'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')"
+ end
+
+ options_for_javascript(js_options)
+ end
+
+ def method_option_to_s(method)
+ (method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'"
+ end
+
+ def build_observer(klass, name, options = {})
+ if options[:with] && (options[:with] !~ /[\{=(.]/)
+ options[:with] = "'#{options[:with]}=' + value"
+ else
+ options[:with] ||= 'value' unless options[:function]
+ end
+
+ callback = options[:function] || remote_function(options)
+ javascript = "new #{klass}('#{name}', "
+ javascript << "#{options[:frequency]}, " if options[:frequency]
+ javascript << "function(element, value) {"
+ javascript << "#{callback}}"
+ javascript << ", '#{options[:on]}'" if options[:on]
+ javascript << ")"
+ javascript_tag(javascript)
+ end
+
+ def build_callbacks(options)
+ callbacks = {}
+ options.each do |callback, code|
+ if CALLBACKS.include?(callback)
+ name = 'on' + callback.to_s.capitalize
+ callbacks[name] = "function(request){#{code}}"
+ end
+ end
+ callbacks
+ end
+ end
+
+ # Converts chained method calls on DOM proxy elements into JavaScript chains
+ class JavaScriptProxy < BasicObject #:nodoc:
+ def initialize(generator, root = nil)
+ @generator = generator
+ @generator << root if root
+ end
+
+ private
+ def method_missing(method, *arguments, &block)
+ if method.to_s =~ /(.*)=$/
+ assign($1, arguments.first)
+ else
+ call("#{method.to_s.camelize(:lower)}", *arguments, &block)
+ end
+ end
+
+ def call(function, *arguments, &block)
+ append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})")
+ self
+ end
+
+ def assign(variable, value)
+ append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}")
+ end
+
+ def function_chain
+ @function_chain ||= @generator.instance_variable_get(:@lines)
+ end
+
+ def append_to_function_chain!(call)
+ function_chain[-1].chomp!(';')
+ function_chain[-1] += ".#{call};"
+ end
+ end
+
+ class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
+ def initialize(generator, id)
+ @id = id
+ super(generator, "$(#{id.to_json})")
+ end
+
+ # Allows access of element attributes through +attribute+. Examples:
+ #
+ # page['foo']['style'] # => $('foo').style;
+ # page['foo']['style']['color'] # => $('blank_slate').style.color;
+ # page['foo']['style']['color'] = 'red' # => $('blank_slate').style.color = 'red';
+ # page['foo']['style'].color = 'red' # => $('blank_slate').style.color = 'red';
+ def [](attribute)
+ append_to_function_chain!(attribute)
+ self
+ end
+
+ def []=(variable, value)
+ assign(variable, value)
+ end
+
+ def replace_html(*options_for_render)
+ call 'update', @generator.send(:render, *options_for_render)
+ end
+
+ def replace(*options_for_render)
+ call 'replace', @generator.send(:render, *options_for_render)
+ end
+
+ def reload(options_for_replace = {})
+ replace(options_for_replace.merge({ :partial => @id.to_s }))
+ end
+
+ end
+
+ class JavaScriptVariableProxy < JavaScriptProxy #:nodoc:
+ def initialize(generator, variable)
+ @variable = variable
+ @empty = true # only record lines if we have to. gets rid of unnecessary linebreaks
+ super(generator)
+ end
+
+ # The JSON Encoder calls this to check for the #to_json method
+ # Since it's a blank slate object, I suppose it responds to anything.
+ def respond_to?(method)
+ true
+ end
+
+ def to_json(options = nil)
+ @variable
+ end
+
+ private
+ def append_to_function_chain!(call)
+ @generator << @variable if @empty
+ @empty = false
+ super
+ end
+ end
+
+ class JavaScriptCollectionProxy < JavaScriptProxy #:nodoc:
+ ENUMERABLE_METHODS_WITH_RETURN = [:all, :any, :collect, :map, :detect, :find, :find_all, :select, :max, :min, :partition, :reject, :sort_by, :in_groups_of, :each_slice] unless defined? ENUMERABLE_METHODS_WITH_RETURN
+ ENUMERABLE_METHODS = ENUMERABLE_METHODS_WITH_RETURN + [:each] unless defined? ENUMERABLE_METHODS
+ attr_reader :generator
+ delegate :arguments_for_call, :to => :generator
+
+ def initialize(generator, pattern)
+ super(generator, @pattern = pattern)
+ end
+
+ def each_slice(variable, number, &block)
+ if block
+ enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block
+ else
+ add_variable_assignment!(variable)
+ append_enumerable_function!("eachSlice(#{number.to_json});")
+ end
+ end
+
+ def grep(variable, pattern, &block)
+ enumerate :grep, :variable => variable, :return => true, :method_args => [pattern], :yield_args => %w(value index), &block
+ end
+
+ def in_groups_of(variable, number, fill_with = nil)
+ arguments = [number]
+ arguments << fill_with unless fill_with.nil?
+ add_variable_assignment!(variable)
+ append_enumerable_function!("inGroupsOf(#{arguments_for_call arguments});")
+ end
+
+ def inject(variable, memo, &block)
+ enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block
+ end
+
+ def pluck(variable, property)
+ add_variable_assignment!(variable)
+ append_enumerable_function!("pluck(#{property.to_json});")
+ end
+
+ def zip(variable, *arguments, &block)
+ add_variable_assignment!(variable)
+ append_enumerable_function!("zip(#{arguments_for_call arguments}")
+ if block
+ function_chain[-1] += ", function(array) {"
+ yield ActiveSupport::JSON::Variable.new('array')
+ add_return_statement!
+ @generator << '});'
+ else
+ function_chain[-1] += ');'
+ end
+ end
+
+ private
+ def method_missing(method, *arguments, &block)
+ if ENUMERABLE_METHODS.include?(method)
+ returnable = ENUMERABLE_METHODS_WITH_RETURN.include?(method)
+ variable = arguments.first if returnable
+ enumerate(method, {:variable => (arguments.first if returnable), :return => returnable, :yield_args => %w(value index)}, &block)
+ else
+ super
+ end
+ end
+
+ # Options
+ # * variable - name of the variable to set the result of the enumeration to
+ # * method_args - array of the javascript enumeration method args that occur before the function
+ # * yield_args - array of the javascript yield args
+ # * return - true if the enumeration should return the last statement
+ def enumerate(enumerable, options = {}, &block)
+ options[:method_args] ||= []
+ options[:yield_args] ||= []
+ yield_args = options[:yield_args] * ', '
+ method_args = arguments_for_call options[:method_args] # foo, bar, function
+ method_args << ', ' unless method_args.blank?
+ add_variable_assignment!(options[:variable]) if options[:variable]
+ append_enumerable_function!("#{enumerable.to_s.camelize(:lower)}(#{method_args}function(#{yield_args}) {")
+ # only yield as many params as were passed in the block
+ yield(*options[:yield_args].collect { |p| JavaScriptVariableProxy.new(@generator, p) }[0..block.arity-1])
+ add_return_statement! if options[:return]
+ @generator << '});'
+ end
+
+ def add_variable_assignment!(variable)
+ function_chain.push("var #{variable} = #{function_chain.pop}")
+ end
+
+ def add_return_statement!
+ unless function_chain.last =~ /return/
+ function_chain.push("return #{function_chain.pop.chomp(';')};")
+ end
+ end
+
+ def append_enumerable_function!(call)
+ function_chain[-1].chomp!(';')
+ function_chain[-1] += ".#{call}"
+ end
+ end
+
+ class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
+ def initialize(generator, pattern)
+ super(generator, "$$(#{pattern.to_json})")
+ end
+ end
+ end
+end
+
+require 'action_view/helpers/javascript_helper'
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/record_identification_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/record_identification_helper.rb
new file mode 100644
index 000000000..6c235bff3
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/record_identification_helper.rb
@@ -0,0 +1,20 @@
+module ActionView
+ module Helpers
+ module RecordIdentificationHelper
+ # See ActionController::RecordIdentifier.partial_path -- this is just a delegate to that for convenient access in the view.
+ def partial_path(*args, &block)
+ ActionController::RecordIdentifier.partial_path(*args, &block)
+ end
+
+ # See ActionController::RecordIdentifier.dom_class -- this is just a delegate to that for convenient access in the view.
+ def dom_class(*args, &block)
+ ActionController::RecordIdentifier.dom_class(*args, &block)
+ end
+
+ # See ActionController::RecordIdentifier.dom_id -- this is just a delegate to that for convenient access in the view.
+ def dom_id(*args, &block)
+ ActionController::RecordIdentifier.dom_id(*args, &block)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/record_tag_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/record_tag_helper.rb
new file mode 100644
index 000000000..7b82c38a0
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/record_tag_helper.rb
@@ -0,0 +1,59 @@
+module ActionView
+ module Helpers
+ module RecordTagHelper
+ # Produces a wrapper DIV element with id and class parameters that
+ # relate to the specified ActiveRecord object. Usage example:
+ #
+ # <% div_for(@person, :class => "foo") do %>
+ # <%=h @person.name %>
+ # <% end %>
+ #
+ # produces:
+ #
+ # <div id="person_123" class="person foo"> Joe Bloggs </div>
+ #
+ def div_for(record, *args, &block)
+ content_tag_for(:div, record, *args, &block)
+ end
+
+ # content_tag_for creates an HTML element with id and class parameters
+ # that relate to the specified ActiveRecord object. For example:
+ #
+ # <% content_tag_for(:tr, @person) do %>
+ # <td><%=h @person.first_name %></td>
+ # <td><%=h @person.last_name %></td>
+ # <% end %>
+ #
+ # would produce hthe following HTML (assuming @person is an instance of
+ # a Person object, with an id value of 123):
+ #
+ # <tr id="person_123" class="person">....</tr>
+ #
+ # If you require the HTML id attribute to have a prefix, you can specify it:
+ #
+ # <% content_tag_for(:tr, @person, :foo) do %> ...
+ #
+ # produces:
+ #
+ # <tr id="foo_person_123" class="person">...
+ #
+ # content_tag_for also accepts a hash of options, which will be converted to
+ # additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined
+ # with the default class name for your object. For example:
+ #
+ # <% content_tag_for(:li, @person, :class => "bar") %>...
+ #
+ # produces:
+ #
+ # <li id="person_123" class="person bar">...
+ #
+ def content_tag_for(tag_name, record, *args, &block)
+ prefix = args.first.is_a?(Hash) ? nil : args.shift
+ options = args.first.is_a?(Hash) ? args.shift : {}
+ concat content_tag(tag_name, capture(&block),
+ options.merge({ :class => "#{dom_class(record)} #{options[:class]}".strip, :id => dom_id(record, prefix) })),
+ block.binding
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/sanitize_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/sanitize_helper.rb
new file mode 100644
index 000000000..47fbe3a27
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/sanitize_helper.rb
@@ -0,0 +1,223 @@
+require 'action_view/helpers/tag_helper'
+require 'html/document'
+
+module ActionView
+ module Helpers #:nodoc:
+ # The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
+ # These helper methods extend ActionView making them callable within your template files.
+ module SanitizeHelper
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ # This #sanitize helper will html encode all tags and strip all attributes that aren't specifically allowed.
+ # It also strips href/src tags with invalid protocols, like javascript: especially. It does its best to counter any
+ # tricks that hackers may use, like throwing in unicode/ascii/hex values to get past the javascript: filters. Check out
+ # the extensive test suite.
+ #
+ # <%= sanitize @article.body %>
+ #
+ # You can add or remove tags/attributes if you want to customize it a bit. See ActionView::Base for full docs on the
+ # available options. You can add tags/attributes for single uses of #sanitize by passing either the :attributes or :tags options:
+ #
+ # Normal Use
+ #
+ # <%= sanitize @article.body %>
+ #
+ # Custom Use (only the mentioned tags and attributes are allowed, nothing else)
+ #
+ # <%= sanitize @article.body, :tags => %w(table tr td), :attributes => %w(id class style)
+ #
+ # Add table tags to the default allowed tags
+ #
+ # Rails::Initializer.run do |config|
+ # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
+ # end
+ #
+ # Remove tags to the default allowed tags
+ #
+ # Rails::Initializer.run do |config|
+ # config.after_initialize do
+ # ActionView::Base.sanitized_allowed_tags.delete 'div'
+ # end
+ # end
+ #
+ # Change allowed default attributes
+ #
+ # Rails::Initializer.run do |config|
+ # config.action_view.sanitized_allowed_attributes = 'id', 'class', 'style'
+ # end
+ #
+ def sanitize(html, options = {})
+ self.class.white_list_sanitizer.sanitize(html, options)
+ end
+
+ # Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute
+ def sanitize_css(style)
+ self.class.white_list_sanitizer.sanitize_css(style)
+ end
+
+ # 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.
+ #
+ # ==== Examples
+ #
+ # strip_tags("Strip <i>these</i> tags!")
+ # # => Strip these tags!
+ #
+ # strip_tags("<b>Bold</b> no more! <a href='more.html'>See more here</a>...")
+ # # => Bold no more! See more here...
+ #
+ # strip_tags("<div id='top-bar'>Welcome to my website!</div>")
+ # # => Welcome to my website!
+ def strip_tags(html)
+ self.class.full_sanitizer.sanitize(html)
+ end
+
+ # Strips all link tags from +text+ leaving just the link text.
+ #
+ # ==== Examples
+ # strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>')
+ # # => Ruby on Rails
+ #
+ # strip_links('Please e-mail me at <a href="mailto:me@email.com">me@email.com</a>.')
+ # # => Please e-mail me at me@email.com.
+ #
+ # strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.')
+ # # => Blog: Visit
+ def strip_links(html)
+ self.class.link_sanitizer.sanitize(html)
+ end
+
+ module ClassMethods #:nodoc:
+ def self.extended(base)
+ class << base
+ attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
+
+ # we want these to be class methods on ActionView::Base, they'll get mattr_readers for these below.
+ helper_def = [:sanitized_protocol_separator, :sanitized_uri_attributes, :sanitized_bad_tags, :sanitized_allowed_tags,
+ :sanitized_allowed_attributes, :sanitized_allowed_css_properties, :sanitized_allowed_css_keywords,
+ :sanitized_shorthand_css_properties, :sanitized_allowed_protocols, :sanitized_protocol_separator=].collect! do |prop|
+ prop = prop.to_s
+ "def #{prop}(#{:value if prop =~ /=$/}) white_list_sanitizer.#{prop.sub /sanitized_/, ''} #{:value if prop =~ /=$/} end"
+ end.join("\n")
+ eval helper_def
+ end
+ end
+
+ # Gets the HTML::FullSanitizer instance used by strip_tags. Replace with
+ # any object that responds to #sanitize
+ #
+ # Rails::Initializer.run do |config|
+ # config.action_view.full_sanitizer = MySpecialSanitizer.new
+ # end
+ #
+ def full_sanitizer
+ @full_sanitizer ||= HTML::FullSanitizer.new
+ end
+
+ # Gets the HTML::LinkSanitizer instance used by strip_links. Replace with
+ # any object that responds to #sanitize
+ #
+ # Rails::Initializer.run do |config|
+ # config.action_view.link_sanitizer = MySpecialSanitizer.new
+ # end
+ #
+ def link_sanitizer
+ @link_sanitizer ||= HTML::LinkSanitizer.new
+ end
+
+ # Gets the HTML::WhiteListSanitizer instance used by sanitize and sanitize_css.
+ # Replace with any object that responds to #sanitize
+ #
+ # Rails::Initializer.run do |config|
+ # config.action_view.white_list_sanitizer = MySpecialSanitizer.new
+ # end
+ #
+ def white_list_sanitizer
+ @white_list_sanitizer ||= HTML::WhiteListSanitizer.new
+ end
+
+ # Adds valid HTML attributes that the #sanitize helper checks for URIs.
+ #
+ # Rails::Initializer.run do |config|
+ # config.action_view.sanitized_uri_attributes = 'lowsrc', 'target'
+ # end
+ #
+ def sanitized_uri_attributes=(attributes)
+ HTML::WhiteListSanitizer.uri_attributes.merge(attributes)
+ end
+
+ # Adds to the Set of 'bad' tags for the #sanitize helper.
+ #
+ # Rails::Initializer.run do |config|
+ # config.action_view.sanitized_bad_tags = 'embed', 'object'
+ # end
+ #
+ def sanitized_bad_tags=(attributes)
+ HTML::WhiteListSanitizer.bad_tags.merge(attributes)
+ end
+ # Adds to the Set of allowed tags for the #sanitize helper.
+ #
+ # Rails::Initializer.run do |config|
+ # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
+ # end
+ #
+ def sanitized_allowed_tags=(attributes)
+ HTML::WhiteListSanitizer.allowed_tags.merge(attributes)
+ end
+
+ # Adds to the Set of allowed html attributes for the #sanitize helper.
+ #
+ # Rails::Initializer.run do |config|
+ # config.action_view.sanitized_allowed_attributes = 'onclick', 'longdesc'
+ # end
+ #
+ def sanitized_allowed_attributes=(attributes)
+ HTML::WhiteListSanitizer.allowed_attributes.merge(attributes)
+ end
+
+ # Adds to the Set of allowed css properties for the #sanitize and #sanitize_css heleprs.
+ #
+ # Rails::Initializer.run do |config|
+ # config.action_view.sanitized_allowed_css_properties = 'expression'
+ # end
+ #
+ def sanitized_allowed_css_properties=(attributes)
+ HTML::WhiteListSanitizer.allowed_css_properties.merge(attributes)
+ end
+
+ # Adds to the Set of allowed css keywords for the #sanitize and #sanitize_css helpers.
+ #
+ # Rails::Initializer.run do |config|
+ # config.action_view.sanitized_allowed_css_keywords = 'expression'
+ # end
+ #
+ def sanitized_allowed_css_keywords=(attributes)
+ HTML::WhiteListSanitizer.allowed_css_keywords.merge(attributes)
+ end
+
+ # Adds to the Set of allowed shorthand css properties for the #sanitize and #sanitize_css helpers.
+ #
+ # Rails::Initializer.run do |config|
+ # config.action_view.sanitized_shorthand_css_properties = 'expression'
+ # end
+ #
+ def sanitized_shorthand_css_properties=(attributes)
+ HTML::WhiteListSanitizer.shorthand_css_properties.merge(attributes)
+ end
+
+ # Adds to the Set of allowed protocols for the #sanitize helper.
+ #
+ # Rails::Initializer.run do |config|
+ # config.action_view.sanitized_allowed_protocols = 'ssh', 'feed'
+ # end
+ #
+ def sanitized_allowed_protocols=(attributes)
+ HTML::WhiteListSanitizer.allowed_protocols.merge(attributes)
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/scriptaculous_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
new file mode 100644
index 000000000..f7a9daf60
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
@@ -0,0 +1,199 @@
+require 'action_view/helpers/javascript_helper'
+
+module ActionView
+ module Helpers
+ # Provides a set of helpers for calling Scriptaculous JavaScript
+ # functions, including those which create Ajax controls and visual effects.
+ #
+ # To be able to use these helpers, you must include the Prototype
+ # JavaScript framework and the Scriptaculous JavaScript library in your
+ # pages. See the documentation for ActionView::Helpers::JavaScriptHelper
+ # for more information on including the necessary JavaScript.
+ #
+ # The Scriptaculous helpers' behavior can be tweaked with various options.
+ # See the documentation at http://script.aculo.us for more information on
+ # using these helpers in your application.
+ module ScriptaculousHelper
+ unless const_defined? :TOGGLE_EFFECTS
+ TOGGLE_EFFECTS = [:toggle_appear, :toggle_slide, :toggle_blind]
+ end
+
+ # Returns a JavaScript snippet to be used on the Ajax callbacks for
+ # starting visual effects.
+ #
+ # Example:
+ # <%= link_to_remote "Reload", :update => "posts",
+ # :url => { :action => "reload" },
+ # :complete => visual_effect(:highlight, "posts", :duration => 0.5)
+ #
+ # If no element_id is given, it assumes "element" which should be a local
+ # variable in the generated JavaScript execution context. This can be
+ # used for example with drop_receiving_element:
+ #
+ # <%= drop_receiving_element (...), :loading => visual_effect(:fade) %>
+ #
+ # This would fade the element that was dropped on the drop receiving
+ # element.
+ #
+ # For toggling visual effects, you can use :toggle_appear, :toggle_slide, and
+ # :toggle_blind which will alternate between appear/fade, slidedown/slideup, and
+ # blinddown/blindup respectively.
+ #
+ # You can change the behaviour with various options, see
+ # http://script.aculo.us for more documentation.
+ def visual_effect(name, element_id = false, js_options = {})
+ element = element_id ? element_id.to_json : "element"
+
+ js_options[:queue] = if js_options[:queue].is_a?(Hash)
+ '{' + js_options[:queue].map {|k, v| k == :limit ? "#{k}:#{v}" : "#{k}:'#{v}'" }.join(',') + '}'
+ elsif js_options[:queue]
+ "'#{js_options[:queue]}'"
+ end if js_options[:queue]
+
+ [:endcolor, :direction, :startcolor, :scaleMode, :restorecolor].each do |option|
+ js_options[option] = "'#{js_options[option]}'" if js_options[option]
+ end
+
+ if TOGGLE_EFFECTS.include? name.to_sym
+ "Effect.toggle(#{element},'#{name.to_s.gsub(/^toggle_/,'')}',#{options_for_javascript(js_options)});"
+ else
+ "new Effect.#{name.to_s.camelize}(#{element},#{options_for_javascript(js_options)});"
+ end
+ end
+
+ # Makes the element with the DOM ID specified by +element_id+ sortable
+ # by drag-and-drop and make an Ajax call whenever the sort order has
+ # changed. By default, the action called gets the serialized sortable
+ # element as parameters.
+ #
+ # Example:
+ # <%= sortable_element("my_list", :url => { :action => "order" }) %>
+ #
+ # In the example, the action gets a "my_list" array parameter
+ # containing the values of the ids of elements the sortable consists
+ # of, in the current order.
+ #
+ # Important: For this to work, the sortable elements must have id
+ # attributes in the form "string_identifier". For example, "item_1". Only
+ # the identifier part of the id attribute will be serialized.
+ #
+ # Additional +options+ are:
+ #
+ # <tt>:format</tt>:: A regular expression to determine what to send
+ # as the serialized id to the server (the default
+ # is <tt>/^[^_]*_(.*)$/</tt>).
+ #
+ # <tt>:constraint</tt>:: Whether to constrain the dragging to either <tt>:horizontal</tt>
+ # or <tt>:vertical</tt> (or false to make it unconstrained).
+ #
+ # <tt>:overlap</tt>:: Calculate the item overlap in the <tt>:horizontal</tt> or
+ # <tt>:vertical</tt> direction.
+ #
+ # <tt>:tag</tt>:: Which children of the container element to treat as
+ # sortable (default is <tt>li</tt>).
+ #
+ # <tt>:containment</tt>:: Takes an element or array of elements to treat as
+ # potential drop targets (defaults to the original
+ # target element).
+ #
+ # <tt>:only</tt>:: A CSS class name or arry of class names used to filter
+ # out child elements as candidates.
+ #
+ # <tt>:scroll</tt>:: Determines whether to scroll the list during drag
+ # operationsif the list runs past the visual border.
+ #
+ # <tt>:tree</tt>:: Determines whether to treat nested lists as part of the
+ # main sortable list. This means that you can create multi-
+ # layer lists, and not only sort items at the same level,
+ # but drag and sort items between levels.
+ #
+ # <tt>:hoverclass</tt>:: If set, the Droppable will have this additional CSS class
+ # when an accepted Draggable is hovered over it.
+ #
+ # <tt>:handle</tt>:: Sets whether the element should only be draggable by an
+ # embedded handle. The value may be a string referencing a
+ # CSS class value (as of script.aculo.us V1.5). The first
+ # child/grandchild/etc. element found within the element
+ # that has this CSS class value will be used as the handle.
+ #
+ # <tt>:ghosting</tt>:: Clones the element and drags the clone, leaving the original
+ # in place until the clone is dropped (defaut is <tt>false</tt>).
+ #
+ # <tt>:dropOnEmpty</tt>:: If set to true, the Sortable container will be made into
+ # a Droppable, that can receive a Draggable (as according to
+ # the containment rules) as a child element when there are no
+ # more elements inside (defaut is <tt>false</tt>).
+ #
+ # <tt>:onChange</tt>:: Called whenever the sort order changes while dragging. When
+ # dragging from one Sortable to another, the callback is
+ # called once on each Sortable. Gets the affected element as
+ # its parameter.
+ #
+ # <tt>:onUpdate</tt>:: Called when the drag ends and the Sortable's order is
+ # changed in any way. When dragging from one Sortable to
+ # another, the callback is called once on each Sortable. Gets
+ # the container as its parameter.
+ #
+ # See http://script.aculo.us for more documentation.
+ def sortable_element(element_id, options = {})
+ javascript_tag(sortable_element_js(element_id, options).chop!)
+ end
+
+ def sortable_element_js(element_id, options = {}) #:nodoc:
+ options[:with] ||= "Sortable.serialize(#{element_id.to_json})"
+ options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
+ options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
+
+ [:tag, :overlap, :constraint, :handle].each do |option|
+ options[option] = "'#{options[option]}'" if options[option]
+ end
+
+ options[:containment] = array_or_string_for_javascript(options[:containment]) if options[:containment]
+ options[:only] = array_or_string_for_javascript(options[:only]) if options[:only]
+
+ %(Sortable.create(#{element_id.to_json}, #{options_for_javascript(options)});)
+ end
+
+ # Makes the element with the DOM ID specified by +element_id+ draggable.
+ #
+ # Example:
+ # <%= draggable_element("my_image", :revert => true)
+ #
+ # You can change the behaviour with various options, see
+ # http://script.aculo.us for more documentation.
+ def draggable_element(element_id, options = {})
+ javascript_tag(draggable_element_js(element_id, options).chop!)
+ end
+
+ def draggable_element_js(element_id, options = {}) #:nodoc:
+ %(new Draggable(#{element_id.to_json}, #{options_for_javascript(options)});)
+ end
+
+ # Makes the element with the DOM ID specified by +element_id+ receive
+ # dropped draggable elements (created by draggable_element).
+ # and make an AJAX call By default, the action called gets the DOM ID
+ # of the element as parameter.
+ #
+ # Example:
+ # <%= drop_receiving_element("my_cart", :url =>
+ # { :controller => "cart", :action => "add" }) %>
+ #
+ # You can change the behaviour with various options, see
+ # http://script.aculo.us for more documentation.
+ def drop_receiving_element(element_id, options = {})
+ javascript_tag(drop_receiving_element_js(element_id, options).chop!)
+ end
+
+ def drop_receiving_element_js(element_id, options = {}) #:nodoc:
+ options[:with] ||= "'id=' + encodeURIComponent(element.id)"
+ options[:onDrop] ||= "function(element){" + remote_function(options) + "}"
+ options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
+
+ options[:accept] = array_or_string_for_javascript(options[:accept]) if options[:accept]
+ options[:hoverclass] = "'#{options[:hoverclass]}'" if options[:hoverclass]
+
+ %(Droppables.add(#{element_id.to_json}, #{options_for_javascript(options)});)
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/tag_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/tag_helper.rb
new file mode 100644
index 000000000..999cbfb52
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -0,0 +1,133 @@
+require 'cgi'
+require 'erb'
+
+module ActionView
+ module Helpers #:nodoc:
+ # Provides methods to generate HTML tags programmatically when you can't use
+ # a Builder. By default, they output XHTML compliant tags.
+ module TagHelper
+ include ERB::Util
+
+ BOOLEAN_ATTRIBUTES = Set.new(%w(disabled readonly multiple))
+
+ # Returns an empty HTML tag of type +name+ which by default is XHTML
+ # compliant. Set +open+ to true to create an open tag compatible
+ # with HTML 4.0 and below. Add HTML attributes by passing an attributes
+ # hash to +options+. Set +escape+ to false to disable attribute value
+ # escaping.
+ #
+ # ==== Options
+ # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
+ # <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
+ # symbols or strings for the attribute names.
+ #
+ # ==== Examples
+ # tag("br")
+ # # => <br />
+ #
+ # tag("br", nil, true)
+ # # => <br>
+ #
+ # tag("input", { :type => 'text', :disabled => true })
+ # # => <input type="text" disabled="disabled" />
+ #
+ # tag("img", { :src => "open & shut.png" })
+ # # => <img src="open &amp; shut.png" />
+ #
+ # tag("img", { :src => "open &amp; shut.png" }, false, false)
+ # # => <img src="open &amp; shut.png" />
+ def tag(name, options = nil, open = false, escape = true)
+ "<#{name}#{tag_options(options, escape) if options}" + (open ? ">" : " />")
+ end
+
+ # Returns an HTML block tag of type +name+ surrounding the +content+. Add
+ # HTML attributes by passing an attributes hash to +options+.
+ # Instead of passing the content as an argument, you can also use a block
+ # in which case, you pass your +options+ as the second parameter.
+ # Set escape to false to disable attribute value escaping.
+ #
+ # ==== Options
+ # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
+ # <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
+ # symbols or strings for the attribute names.
+ #
+ # ==== Examples
+ # content_tag(:p, "Hello world!")
+ # # => <p>Hello world!</p>
+ # content_tag(:div, content_tag(:p, "Hello world!"), :class => "strong")
+ # # => <div class="strong"><p>Hello world!</p></div>
+ # content_tag("select", options, :multiple => true)
+ # # => <select multiple="multiple">...options...</select>
+ #
+ # <% content_tag :div, :class => "strong" do -%>
+ # Hello world!
+ # <% end -%>
+ # # => <div class="strong"><p>Hello world!</p></div>
+ def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
+ if block_given?
+ options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
+ content = capture(&block)
+ content_tag = content_tag_string(name, content, options, escape)
+ block_is_within_action_view?(block) ? concat(content_tag, block.binding) : content_tag
+ else
+ content = content_or_options_with_block
+ content_tag_string(name, content, options, escape)
+ end
+ end
+
+ # Returns a CDATA section with the given +content+. CDATA sections
+ # are used to escape blocks of text containing characters which would
+ # otherwise be recognized as markup. CDATA sections begin with the string
+ # <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>.
+ #
+ # ==== Examples
+ # cdata_section("<hello world>")
+ # # => <![CDATA[<hello world>]]>
+ #
+ # cdata_section(File.read("hello_world.txt"))
+ # # => <![CDATA[<hello from a text file]]>
+ def cdata_section(content)
+ "<![CDATA[#{content}]]>"
+ end
+
+ # Returns an escaped version of +html+ without affecting existing escaped entities.
+ #
+ # ==== Examples
+ # escape_once("1 > 2 &amp; 3")
+ # # => "1 &lt; 2 &amp; 3"
+ #
+ # escape_once("&lt;&lt; Accept & Checkout")
+ # # => "&lt;&lt; Accept &amp; Checkout"
+ def escape_once(html)
+ html.to_s.gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] }
+ end
+
+ private
+ def content_tag_string(name, content, options, escape = true)
+ tag_options = tag_options(options, escape) if options
+ "<#{name}#{tag_options}>#{content}</#{name}>"
+ end
+
+ def tag_options(options, escape = true)
+ unless options.blank?
+ attrs = []
+ if escape
+ options.each do |key, value|
+ next unless value
+ key = key.to_s
+ value = BOOLEAN_ATTRIBUTES.include?(key) ? key : escape_once(value)
+ attrs << %(#{key}="#{value}")
+ end
+ else
+ attrs = options.map { |key, value| %(#{key}="#{value}") }
+ end
+ " #{attrs.sort * ' '}" unless attrs.empty?
+ end
+ end
+
+ def block_is_within_action_view?(block)
+ eval("defined? _erbout", block.binding)
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/text_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/text_helper.rb
new file mode 100644
index 000000000..e5747f28f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/text_helper.rb
@@ -0,0 +1,479 @@
+require 'action_view/helpers/tag_helper'
+require 'html/document'
+
+module ActionView
+ module Helpers #:nodoc:
+ # The TextHelper module provides a set of methods for filtering, formatting
+ # and transforming strings, which can reduce the amount of inline Ruby code in
+ # your views. These helper methods extend ActionView making them callable
+ # within your template files.
+ module TextHelper
+ # The preferred method of outputting text in your views is to use the
+ # <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods
+ # do not operate as expected in an eRuby code block. If you absolutely must
+ # output text within a non-output code block (i.e., <% %>), you can use the concat method.
+ #
+ # ==== Examples
+ # <%
+ # concat "hello", binding
+ # # is the equivalent of <%= "hello" %>
+ #
+ # if (logged_in == true):
+ # concat "Logged in!", binding
+ # else
+ # concat link_to('login', :action => login), binding
+ # end
+ # # will either display "Logged in!" or a login link
+ # %>
+ def concat(string, binding)
+ eval(ActionView::Base.erb_variable, binding) << string
+ end
+
+ # If +text+ is longer than +length+, +text+ will be truncated to the length of
+ # +length+ (defaults to 30) and the last characters will be replaced with the +truncate_string+
+ # (defaults to "...").
+ #
+ # ==== Examples
+ # truncate("Once upon a time in a world far far away", 14)
+ # # => Once upon a...
+ #
+ # truncate("Once upon a time in a world far far away")
+ # # => Once upon a time in a world f...
+ #
+ # truncate("And they found that many people were sleeping better.", 25, "(clipped)")
+ # # => And they found that many (clipped)
+ #
+ # truncate("And they found that many people were sleeping better.", 15, "... (continued)")
+ # # => And they found... (continued)
+ def truncate(text, length = 30, truncate_string = "...")
+ if text.nil? then return end
+ l = length - truncate_string.chars.length
+ (text.chars.length > length ? text.chars[0...l] + truncate_string : text).to_s
+ end
+
+ # Highlights one or more +phrases+ everywhere in +text+ by inserting it into
+ # a +highlighter+ string. The highlighter can be specialized by passing +highlighter+
+ # as a single-quoted string with \1 where the phrase is to be inserted (defaults to
+ # '<strong class="highlight">\1</strong>')
+ #
+ # ==== Examples
+ # highlight('You searched for: rails', 'rails')
+ # # => You searched for: <strong class="highlight">rails</strong>
+ #
+ # highlight('You searched for: ruby, rails, dhh', 'actionpack')
+ # # => You searched for: ruby, rails, dhh
+ #
+ # highlight('You searched for: rails', ['for', 'rails'], '<em>\1</em>')
+ # # => You searched <em>for</em>: <em>rails</em>
+ #
+ # highlight('You searched for: rails', 'rails', "<a href='search?q=\1'>\1</a>")
+ # # => You searched for: <a href='search?q=rails>rails</a>
+ def highlight(text, phrases, highlighter = '<strong class="highlight">\1</strong>')
+ if text.blank? || phrases.blank?
+ text
+ else
+ match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
+ text.gsub(/(#{match})/i, highlighter)
+ end
+ end
+
+ # Extracts an excerpt from +text+ that matches the first instance of +phrase+.
+ # The +radius+ expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
+ # defined in +radius+ (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
+ # then the +excerpt_string+ will be prepended/appended accordingly. If the +phrase+
+ # isn't found, nil is returned.
+ #
+ # ==== Examples
+ # excerpt('This is an example', 'an', 5)
+ # # => "...s is an examp..."
+ #
+ # excerpt('This is an example', 'is', 5)
+ # # => "This is an..."
+ #
+ # excerpt('This is an example', 'is')
+ # # => "This is an example"
+ #
+ # excerpt('This next thing is an example', 'ex', 2)
+ # # => "...next t..."
+ #
+ # excerpt('This is also an example', 'an', 8, '<chop> ')
+ # # => "<chop> is also an example"
+ def excerpt(text, phrase, radius = 100, excerpt_string = "...")
+ if text.nil? || phrase.nil? then return end
+ phrase = Regexp.escape(phrase)
+
+ if found_pos = text.chars =~ /(#{phrase})/i
+ start_pos = [ found_pos - radius, 0 ].max
+ end_pos = [ found_pos + phrase.chars.length + radius, text.chars.length ].min
+
+ prefix = start_pos > 0 ? excerpt_string : ""
+ postfix = end_pos < text.chars.length ? excerpt_string : ""
+
+ prefix + text.chars[start_pos..end_pos].strip + postfix
+ else
+ nil
+ end
+ end
+
+ # Attempts to pluralize the +singular+ word unless +count+ is 1. If +plural+
+ # is supplied, it will use that when count is > 1, if the ActiveSupport Inflector
+ # is loaded, it will use the Inflector to determine the plural form, otherwise
+ # it will just add an 's' to the +singular+ word.
+ #
+ # ==== Examples
+ # pluralize(1, 'person')
+ # # => 1 person
+ #
+ # pluralize(2, 'person')
+ # # => 2 people
+ #
+ # pluralize(3, 'person', 'users')
+ # # => 3 users
+ #
+ # pluralize(0, 'person')
+ # # => 0 people
+ def pluralize(count, singular, plural = nil)
+ "#{count || 0} " + if count == 1 || count == '1'
+ singular
+ elsif plural
+ plural
+ elsif Object.const_defined?("Inflector")
+ Inflector.pluralize(singular)
+ else
+ singular + "s"
+ end
+ end
+
+ # Wraps the +text+ into lines no longer than +line_width+ width. This method
+ # breaks on the first whitespace character that does not exceed +line_width+
+ # (which is 80 by default).
+ #
+ # ==== Examples
+ # word_wrap('Once upon a time', 4)
+ # # => Once\nupon\na\ntime
+ #
+ # word_wrap('Once upon a time', 8)
+ # # => Once upon\na time
+ #
+ # word_wrap('Once upon a time')
+ # # => Once upon a time
+ #
+ # word_wrap('Once upon a time', 1)
+ # # => Once\nupon\na\ntime
+ def word_wrap(text, line_width = 80)
+ text.split("\n").collect do |line|
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
+ end * "\n"
+ end
+
+ begin
+ require_library_or_gem "redcloth" unless Object.const_defined?(:RedCloth)
+
+ # Returns the text with all the Textile[http://www.textism.com/tools/textile] codes turned into HTML tags.
+ #
+ # You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile].
+ # <i>This method is only available if RedCloth[http://whytheluckystiff.net/ruby/redcloth/]
+ # is available</i>.
+ #
+ # ==== Examples
+ # textilize("*This is Textile!* Rejoice!")
+ # # => "<p><strong>This is Textile!</strong> Rejoice!</p>"
+ #
+ # textilize("I _love_ ROR(Ruby on Rails)!")
+ # # => "<p>I <em>love</em> <acronym title="Ruby on Rails">ROR</acronym>!</p>"
+ #
+ # textilize("h2. Textile makes markup -easy- simple!")
+ # # => "<h2>Textile makes markup <del>easy</del> simple!</h2>"
+ #
+ # textilize("Visit the Rails website "here":http://www.rubyonrails.org/.)
+ # # => "<p>Visit the Rails website <a href="http://www.rubyonrails.org/">here</a>.</p>"
+ def textilize(text)
+ if text.blank?
+ ""
+ else
+ textilized = RedCloth.new(text, [ :hard_breaks ])
+ textilized.hard_breaks = true if textilized.respond_to?("hard_breaks=")
+ textilized.to_html
+ end
+ end
+
+ # Returns the text with all the Textile codes turned into HTML tags,
+ # but without the bounding <p> tag that RedCloth adds.
+ #
+ # You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile].
+ # <i>This method is only available if RedCloth[http://whytheluckystiff.net/ruby/redcloth/]
+ # is available</i>.
+ #
+ # ==== Examples
+ # textilize_without_paragraph("*This is Textile!* Rejoice!")
+ # # => "<strong>This is Textile!</strong> Rejoice!"
+ #
+ # textilize_without_paragraph("I _love_ ROR(Ruby on Rails)!")
+ # # => "I <em>love</em> <acronym title="Ruby on Rails">ROR</acronym>!"
+ #
+ # textilize_without_paragraph("h2. Textile makes markup -easy- simple!")
+ # # => "<h2>Textile makes markup <del>easy</del> simple!</h2>"
+ #
+ # textilize_without_paragraph("Visit the Rails website "here":http://www.rubyonrails.org/.)
+ # # => "Visit the Rails website <a href="http://www.rubyonrails.org/">here</a>."
+ def textilize_without_paragraph(text)
+ textiled = textilize(text)
+ if textiled[0..2] == "<p>" then textiled = textiled[3..-1] end
+ if textiled[-4..-1] == "</p>" then textiled = textiled[0..-5] end
+ return textiled
+ end
+ rescue LoadError
+ # We can't really help what's not there
+ end
+
+ begin
+ require_library_or_gem "bluecloth" unless Object.const_defined?(:BlueCloth)
+
+ # Returns the text with all the Markdown codes turned into HTML tags.
+ # <i>This method is only available if BlueCloth[http://www.deveiate.org/projects/BlueCloth]
+ # is available</i>.
+ #
+ # ==== Examples
+ # markdown("We are using __Markdown__ now!")
+ # # => "<p>We are using <strong>Markdown</strong> now!</p>"
+ #
+ # markdown("We like to _write_ `code`, not just _read_ it!")
+ # # => "<p>We like to <em>write</em> <code>code</code>, not just <em>read</em> it!</p>"
+ #
+ # markdown("The [Markdown website](http://daringfireball.net/projects/markdown/) has more information.")
+ # # => "<p>The <a href="http://daringfireball.net/projects/markdown/">Markdown website</a>
+ # # has more information.</p>"
+ #
+ # markdown('![The ROR logo](http://rubyonrails.com/images/rails.png "Ruby on Rails")')
+ # # => '<p><img src="http://rubyonrails.com/images/rails.png" alt="The ROR logo" title="Ruby on Rails" /></p>'
+ def markdown(text)
+ text.blank? ? "" : BlueCloth.new(text).to_html
+ end
+ rescue LoadError
+ # We can't really help what's not there
+ end
+
+ # Returns +text+ transformed into HTML using simple formatting rules.
+ # Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a
+ # paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is
+ # considered as a linebreak and a <tt><br /></tt> tag is appended. This
+ # method does not remove the newlines from the +text+.
+ #
+ # ==== Examples
+ # my_text = """Here is some basic text...
+ # ...with a line break."""
+ #
+ # simple_format(my_text)
+ # # => "<p>Here is some basic text...<br />...with a line break.</p>"
+ #
+ # more_text = """We want to put a paragraph...
+ #
+ # ...right there."""
+ #
+ # simple_format(more_text)
+ # # => "<p>We want to put a paragraph...</p><p>...right there.</p>"
+ def simple_format(text)
+ content_tag 'p', text.to_s.
+ gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
+ gsub(/\n\n+/, "</p>\n\n<p>"). # 2+ newline -> paragraph
+ gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
+ end
+
+ # Turns all URLs and e-mail addresses into clickable links. The +link+ parameter
+ # will limit what should be linked. You can add HTML attributes to the links using
+ # +href_options+. Options for +link+ are <tt>:all</tt> (default),
+ # <tt>:email_addresses</tt>, and <tt>:urls</tt>. If a block is given, each URL and
+ # e-mail address is yielded and the result is used as the link text.
+ #
+ # ==== Examples
+ # auto_link("Go to http://www.rubyonrails.org and say hello to david@loudthinking.com")
+ # # => "Go to <a href=\"http://www.rubyonrails.org\">http://www.rubyonrails.org</a> and
+ # # say hello to <a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>"
+ #
+ # auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :urls)
+ # # => "Visit <a href=\"http://www.loudthinking.com/\">http://www.loudthinking.com/</a>
+ # # or e-mail david@loudthinking.com"
+ #
+ # auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :email_addresses)
+ # # => "Visit http://www.loudthinking.com/ or e-mail <a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>"
+ #
+ # post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com."
+ # auto_link(post_body, :all, :target => '_blank') do |text|
+ # truncate(text, 15)
+ # end
+ # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.m...</a>.
+ # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>."
+ #
+ def auto_link(text, link = :all, href_options = {}, &block)
+ return '' if text.blank?
+ case link
+ when :all then auto_link_email_addresses(auto_link_urls(text, href_options, &block), &block)
+ when :email_addresses then auto_link_email_addresses(text, &block)
+ when :urls then auto_link_urls(text, href_options, &block)
+ end
+ end
+
+ # 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. You can use named cycles to allow nesting in loops.
+ # Passing a Hash as the last parameter with a <tt>:name</tt> key will create a
+ # named cycle. You can manually reset a cycle by calling reset_cycle and passing the
+ # name of the cycle.
+ #
+ # ==== Examples
+ # # Alternate CSS classes for even and odd numbers...
+ # @items = [1,2,3,4]
+ # <table>
+ # <% @items.each do |item| %>
+ # <tr class="<%= cycle("even", "odd") -%>">
+ # <td>item</td>
+ # </tr>
+ # <% end %>
+ # </table>
+ #
+ #
+ # # Cycle CSS classes for rows, and text colors for values within each row
+ # @items = x = [{:first => 'Robert', :middle => 'Daniel', :last => 'James'},
+ # {:first => 'Emily', :middle => 'Shannon', :maiden => 'Pike', :last => 'Hicks'},
+ # {:first => 'June', :middle => 'Dae', :last => 'Jones'}]
+ # <% @items.each do |item| %>
+ # <tr class="<%= cycle("even", "odd", :name => "row_class") -%>">
+ # <td>
+ # <% item.values.each do |value| %>
+ # <%# Create a named cycle "colors" %>
+ # <span style="color:<%= cycle("red", "green", "blue", :name => "colors") -%>">
+ # <%= value %>
+ # </span>
+ # <% end %>
+ # <% reset_cycle("colors") %>
+ # </td>
+ # </tr>
+ # <% end %>
+ def cycle(first_value, *values)
+ if (values.last.instance_of? Hash)
+ params = values.pop
+ name = params[:name]
+ else
+ name = "default"
+ end
+ values.unshift(first_value)
+
+ cycle = get_cycle(name)
+ if (cycle.nil? || cycle.values != values)
+ cycle = set_cycle(name, Cycle.new(*values))
+ end
+ return cycle.to_s
+ end
+
+ # Resets a cycle so that it starts from the first element the next time
+ # it is called. Pass in +name+ to reset a named cycle.
+ #
+ # ==== Example
+ # # Alternate CSS classes for even and odd numbers...
+ # @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]]
+ # <table>
+ # <% @items.each do |item| %>
+ # <tr class="<%= cycle("even", "odd") -%>">
+ # <% item.each do |value| %>
+ # <span style="color:<%= cycle("#333", "#666", "#999", :name => "colors") -%>">
+ # <%= value %>
+ # </span>
+ # <% end %>
+ #
+ # <% reset_cycle("colors") %>
+ # </tr>
+ # <% end %>
+ # </table>
+ def reset_cycle(name = "default")
+ cycle = get_cycle(name)
+ cycle.reset unless cycle.nil?
+ end
+
+ class Cycle #:nodoc:
+ attr_reader :values
+
+ def initialize(first_value, *values)
+ @values = values.unshift(first_value)
+ reset
+ end
+
+ def reset
+ @index = 0
+ end
+
+ def to_s
+ value = @values[@index].to_s
+ @index = (@index + 1) % @values.size
+ return value
+ end
+ end
+
+ private
+ # The cycle helpers need to store the cycles in a place that is
+ # guaranteed to be reset every time a page is rendered, so it
+ # uses an instance variable of ActionView::Base.
+ def get_cycle(name)
+ @_cycles = Hash.new unless defined?(@_cycles)
+ return @_cycles[name]
+ end
+
+ def set_cycle(name, cycle_object)
+ @_cycles = Hash.new unless defined?(@_cycles)
+ @_cycles[name] = cycle_object
+ end
+
+ AUTO_LINK_RE = %r{
+ ( # leading text
+ <\w+.*?>| # leading HTML tag, or
+ [^=!:'"/]| # leading punctuation, or
+ ^ # beginning of line
+ )
+ (
+ (?:https?://)| # protocol spec, or
+ (?:www\.) # www.*
+ )
+ (
+ [-\w]+ # subdomain or domain
+ (?:\.[-\w]+)* # remaining subdomains or domain
+ (?::\d+)? # port
+ (?:/(?:(?:[~\w\+@%-]|(?:[,.;:][^\s$]))+)?)* # path
+ (?:\?[\w\+@%&=.;-]+)? # query string
+ (?:\#[\w\-]*)? # trailing anchor
+ )
+ ([[:punct:]]|\s|<|$) # trailing text
+ }x unless const_defined?(:AUTO_LINK_RE)
+
+ # Turns all urls into clickable links. If a block is given, each url
+ # is yielded and the result is used as the link text.
+ def auto_link_urls(text, href_options = {})
+ extra_options = tag_options(href_options.stringify_keys) || ""
+ text.gsub(AUTO_LINK_RE) do
+ all, a, b, c, d = $&, $1, $2, $3, $4
+ if a =~ /<a\s/i # don't replace URL's that are already linked
+ all
+ else
+ text = b + c
+ text = yield(text) if block_given?
+ %(#{a}<a href="#{b=="www."?"http://www.":b}#{c}"#{extra_options}>#{text}</a>#{d})
+ end
+ end
+ end
+
+ # Turns all email addresses into clickable links. If a block is given,
+ # each email is yielded and the result is used as the link text.
+ def auto_link_email_addresses(text)
+ body = text.dup
+ text.gsub(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
+ text = $1
+
+ if body.match(/<a\b[^>]*>(.*)(#{Regexp.escape(text)})(.*)<\/a>/)
+ text
+ else
+ display_text = (block_given?) ? yield(text) : text
+ %{<a href="mailto:#{text}">#{display_text}</a>}
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/url_helper.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/url_helper.rb
new file mode 100644
index 000000000..eb3391180
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/helpers/url_helper.rb
@@ -0,0 +1,524 @@
+require 'action_view/helpers/javascript_helper'
+
+module ActionView
+ module Helpers #:nodoc:
+ # Provides a set of methods for making links and getting URLs that
+ # depend on the routing subsystem (see ActionController::Routing).
+ # This allows you to use the same format for links in views
+ # and controllers.
+ module UrlHelper
+ include JavaScriptHelper
+
+ # Returns the URL for the set of +options+ provided. This takes the
+ # same options as url_for in ActionController (see the
+ # documentation for ActionController::Base#url_for). Note that by default
+ # <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative /controller/action
+ # instead of the fully qualified URL like http://example.com/controller/action.
+ #
+ # When called from a view, url_for returns an HTML escaped url. If you
+ # need an unescaped url, pass :escape => false in the +options+.
+ #
+ # ==== Options
+ # * <tt>:anchor</tt> -- specifies the anchor name to be appended to the path.
+ # * <tt>:only_path</tt> -- if true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified)
+ # * <tt>:trailing_slash</tt> -- if true, adds a trailing slash, as in "/archive/2005/". Note that this
+ # is currently not recommended since it breaks caching.
+ # * <tt>:host</tt> -- overrides the default (current) host if provided
+ # * <tt>:protocol</tt> -- overrides the default (current) protocol if provided
+ # * <tt>:user</tt> -- Inline HTTP authentication (only plucked out if :password is also present)
+ # * <tt>:password</tt> -- Inline HTTP authentication (only plucked out if :user is also present)
+ # * <tt>:escape</tt> -- Determines whether the returned URL will be HTML escaped or not (<tt>true</tt> by default)
+ #
+ # ==== Relying on named routes
+ #
+ # If you instead of a hash pass a record (like an Active Record or Active Resource) as the options parameter,
+ # you'll trigger the named route for that record. The lookup will happen on the name of the class. So passing
+ # a Workshop object will attempt to use the workshop_path route. If you have a nested route, such as
+ # admin_workshop_path you'll have to call that explicitly (it's impossible for url_for to guess that route).
+ #
+ # ==== Examples
+ # <%= url_for(:action => 'index') %>
+ # # => /blog/
+ #
+ # <%= url_for(:action => 'find', :controller => 'books') %>
+ # # => /books/find
+ #
+ # <%= url_for(:action => 'login', :controller => 'members', :only_path => false, :protocol => 'https') %>
+ # # => https://www.railsapplication.com/members/login/
+ #
+ # <%= url_for(:action => 'play', :anchor => 'player') %>
+ # # => /messages/play/#player
+ #
+ # <%= url_for(:action => 'checkout', :anchor => 'tax&ship') %>
+ # # => /testing/jump/#tax&amp;ship
+ #
+ # <%= url_for(:action => 'checkout', :anchor => 'tax&ship', :escape => false) %>
+ # # => /testing/jump/#tax&ship
+ #
+ # <%= url_for(Workshop.new) %>
+ # # relies on Workshop answering a new_record? call (and in this case returning true)
+ # # => /workshops
+ #
+ # <%= url_for(@workshop) %>
+ # # calls @workshop.to_s
+ # # => /workshops/5
+ def url_for(options = {})
+ case options
+ when Hash
+ show_path = options[:host].nil? ? true : false
+ options = { :only_path => show_path }.update(options.symbolize_keys)
+ escape = options.key?(:escape) ? options.delete(:escape) : true
+ url = @controller.send(:url_for, options)
+ when String
+ escape = true
+ url = options
+ when NilClass
+ url = @controller.send(:url_for, nil)
+ else
+ escape = false
+ url = polymorphic_path(options)
+ end
+
+ escape ? escape_once(url) : url
+ end
+
+ # Creates a link tag of the given +name+ using a URL created by the set
+ # of +options+. See the valid options in the documentation for
+ # url_for. It's also possible to pass a string instead
+ # of an options hash to get a link tag that uses the value of the string as the
+ # href for the link, or use +:back+ to link to the referrer - a JavaScript back
+ # link will be used in place of a referrer if none exists. If nil is passed as
+ # a name, the link itself will become the name.
+ #
+ # ==== Options
+ # * <tt>:confirm => 'question?'</tt> -- This will add a JavaScript confirm
+ # prompt with the question specified. If the user accepts, the link is
+ # processed normally, otherwise no action is taken.
+ # * <tt>:popup => true || array of window options</tt> -- This will force the
+ # link to open in a popup window. By passing true, a default browser window
+ # will be opened with the URL. You can also specify an array of options
+ # that are passed-thru to JavaScripts window.open method.
+ # * <tt>:method => symbol of HTTP verb</tt> -- This modifier will dynamically
+ # create an HTML form and immediately submit the form for processing using
+ # the HTTP verb specified. Useful for having links perform a POST operation
+ # in dangerous actions like deleting a record (which search bots can follow
+ # while spidering your site). Supported verbs are :post, :delete and :put.
+ # Note that if the user has JavaScript disabled, the request will fall back
+ # to using GET. If you are relying on the POST behavior, you should check
+ # for it in your controller's action by using the request object's methods
+ # for post?, delete? or put?.
+ # * The +html_options+ will accept a hash of html attributes for the link tag.
+ #
+ # Note that if the user has JavaScript disabled, the request will fall back
+ # to using GET. If :href=>'#' is used and the user has JavaScript disabled
+ # clicking the link will have no effect. If you are relying on the POST
+ # behavior, your should check for it in your controller's action by using the
+ # request object's methods for post?, delete? or put?.
+ #
+ # You can mix and match the +html_options+ with the exception of
+ # :popup and :method which will raise an ActionView::ActionViewError
+ # exception.
+ #
+ # ==== Examples
+ # link_to "Visit Other Site", "http://www.rubyonrails.org/", :confirm => "Are you sure?"
+ # # => <a href="http://www.rubyonrails.org/" onclick="return confirm('Are you sure?');">Visit Other Site</a>
+ #
+ # link_to "Help", { :action => "help" }, :popup => true
+ # # => <a href="/testing/help/" onclick="window.open(this.href);return false;">Help</a>
+ #
+ # link_to "View Image", { :action => "view" }, :popup => ['new_window_name', 'height=300,width=600']
+ # # => <a href="/testing/view/" onclick="window.open(this.href,'new_window_name','height=300,width=600');return false;">View Image</a>
+ #
+ # link_to "Delete Image", { :action => "delete", :id => @image.id }, :confirm => "Are you sure?", :method => :delete
+ # # => <a href="/testing/delete/9/" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form');
+ # f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;
+ # var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method');
+ # m.setAttribute('value', 'delete'); f.appendChild(m);f.submit(); };return false;">Delete Image</a>
+ def link_to(name, options = {}, html_options = nil)
+ url = case options
+ when String
+ options
+ when :back
+ @controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
+ else
+ self.url_for(options)
+ end
+
+ if html_options
+ html_options = html_options.stringify_keys
+ href = html_options['href']
+ convert_options_to_javascript!(html_options, url)
+ tag_options = tag_options(html_options)
+ else
+ tag_options = nil
+ end
+
+ href_attr = "href=\"#{url}\"" unless href
+ "<a #{href_attr}#{tag_options}>#{name || url}</a>"
+ end
+
+ # Generates a form containing a single button that submits to the URL created
+ # by the set of +options+. This is the safest method to ensure links that
+ # cause changes to your data are not triggered by search bots or accelerators.
+ # If the HTML button does not work with your layout, you can also consider
+ # using the link_to method with the <tt>:method</tt> modifier as described in
+ # the link_to documentation.
+ #
+ # The generated FORM element has a class name of <tt>button-to</tt>
+ # to allow styling of the form itself and its children. You can control
+ # the form submission and input element behavior using +html_options+.
+ # This method accepts the <tt>:method</tt> and <tt>:confirm</tt> modifiers
+ # described in the link_to documentation. If no <tt>:method</tt> modifier
+ # is given, it will default to performing a POST operation. You can also
+ # disable the button by passing <tt>:disabled => true</tt> in +html_options+.
+ # If you are using RESTful routes, you can pass the <tt>:method</tt>
+ # to change the HTTP verb used to submit the form.
+ #
+ # ==== Options
+ # The +options+ hash accepts the same options at url_for.
+ #
+ # There are a few special +html_options+:
+ # * <tt>:method</tt> -- specifies the anchor name to be appended to the path.
+ # * <tt>:disabled</tt> -- specifies the anchor name to be appended to the path.
+ # * <tt>:confirm</tt> -- This will add a JavaScript confirm
+ # prompt with the question specified. If the user accepts, the link is
+ # processed normally, otherwise no action is taken.
+ #
+ # ==== Examples
+ # <%= button_to "New", :action => "new" %>
+ # # => "<form method="post" action="/controller/new" class="button-to">
+ # # <div><input value="New" type="submit" /></div>
+ # # </form>"
+ #
+ # button_to "Delete Image", { :action => "delete", :id => @image.id },
+ # :confirm => "Are you sure?", :method => :delete
+ # # => "<form method="post" action="/images/delete/1" class="button-to">
+ # # <div>
+ # # <input type="hidden" name="_method" value="delete" />
+ # # <input onclick="return confirm('Are you sure?');"
+ # # value="Delete" type="submit" />
+ # # </div>
+ # # </form>"
+ def button_to(name, options = {}, html_options = {})
+ html_options = html_options.stringify_keys
+ convert_boolean_attributes!(html_options, %w( disabled ))
+
+ method_tag = ''
+ if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s)
+ method_tag = tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
+ end
+
+ form_method = method.to_s == 'get' ? 'get' : 'post'
+
+ request_token_tag = ''
+ if form_method == 'post' && protect_against_forgery?
+ request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
+ end
+
+ if confirm = html_options.delete("confirm")
+ html_options["onclick"] = "return #{confirm_javascript_function(confirm)};"
+ end
+
+ url = options.is_a?(String) ? options : self.url_for(options)
+ name ||= url
+
+ html_options.merge!("type" => "submit", "value" => name)
+
+ "<form method=\"#{form_method}\" action=\"#{escape_once url}\" class=\"button-to\"><div>" +
+ method_tag + tag("input", html_options) + request_token_tag + "</div></form>"
+ end
+
+
+ # Creates a link tag of the given +name+ using a URL created by the set of
+ # +options+ unless the current request URI is the same as the links, in
+ # which case only the name is returned (or the given block is yielded, if
+ # one exists). You can give link_to_unless_current a block which will
+ # specialize the default behavior (e.g., show a "Start Here" link rather
+ # than the link's text).
+ #
+ # ==== Examples
+ # Let's say you have a navigation menu...
+ #
+ # <ul id="navbar">
+ # <li><%= link_to_unless_current("Home", { :action => "index" }) %></li>
+ # <li><%= link_to_unless_current("About Us", { :action => "about" }) %></li>
+ # </ul>
+ #
+ # If in the "about" action, it will render...
+ #
+ # <ul id="navbar">
+ # <li><a href="/controller/index">Home</a></li>
+ # <li>About Us</li>
+ # </ul>
+ #
+ # ...but if in the "home" action, it will render:
+ #
+ # <ul id="navbar">
+ # <li><a href="/controller/index">Home</a></li>
+ # <li><a href="/controller/about">About Us</a></li>
+ # </ul>
+ #
+ # The implicit block given to link_to_unless_current is evaluated if the current
+ # action is the action given. So, if we had a comments page and wanted to render a
+ # "Go Back" link instead of a link to the comments page, we could do something like this...
+ #
+ # <%=
+ # link_to_unless_current("Comment", { :controller => 'comments', :action => 'new}) do
+ # link_to("Go back", { :controller => 'posts', :action => 'index' })
+ # end
+ # %>
+ def link_to_unless_current(name, options = {}, html_options = {}, &block)
+ link_to_unless current_page?(options), name, options, html_options, &block
+ end
+
+ # Creates a link tag of the given +name+ using a URL created by the set of
+ # +options+ unless +condition+ is true, in which case only the name is
+ # returned. To specialize the default behavior (i.e., show a login link rather
+ # than just the plaintext link text), you can pass a block that
+ # accepts the name or the full argument list for link_to_unless.
+ #
+ # ==== Examples
+ # <%= link_to_unless(@current_user.nil?, "Reply", { :action => "reply" }) %>
+ # # If the user is logged in...
+ # # => <a href="/controller/reply/">Reply</a>
+ #
+ # <%=
+ # link_to_unless(@current_user.nil?, "Reply", { :action => "reply" }) do |name|
+ # link_to(name, { :controller => "accounts", :action => "signup" })
+ # end
+ # %>
+ # # If the user is logged in...
+ # # => <a href="/controller/reply/">Reply</a>
+ # # If not...
+ # # => <a href="/accounts/signup">Reply</a>
+ def link_to_unless(condition, name, options = {}, html_options = {}, &block)
+ if condition
+ if block_given?
+ block.arity <= 1 ? yield(name) : yield(name, options, html_options)
+ else
+ name
+ end
+ else
+ link_to(name, options, html_options)
+ end
+ end
+
+ # Creates a link tag of the given +name+ using a URL created by the set of
+ # +options+ if +condition+ is true, in which case only the name is
+ # returned. To specialize the default behavior, you can pass a block that
+ # accepts the name or the full argument list for link_to_unless (see the examples
+ # in link_to_unless).
+ #
+ # ==== Examples
+ # <%= link_to_if(@current_user.nil?, "Login", { :controller => "sessions", :action => "new" }) %>
+ # # If the user isn't logged in...
+ # # => <a href="/sessions/new/">Login</a>
+ #
+ # <%=
+ # link_to_if(@current_user.nil?, "Login", { :controller => "sessions", :action => "new" }) do
+ # link_to(@current_user.login, { :controller => "accounts", :action => "show", :id => @current_user })
+ # end
+ # %>
+ # # If the user isn't logged in...
+ # # => <a href="/sessions/new/">Login</a>
+ # # If they are logged in...
+ # # => <a href="/accounts/show/3">my_username</a>
+ def link_to_if(condition, name, options = {}, html_options = {}, &block)
+ link_to_unless !condition, name, options, html_options, &block
+ end
+
+ # Creates a mailto link tag to the specified +email_address+, which is
+ # also used as the name of the link unless +name+ is specified. Additional
+ # HTML attributes for the link can be passed in +html_options+.
+ #
+ # mail_to has several methods for hindering email harvesters and customizing
+ # the email itself by passing special keys to +html_options+.
+ #
+ # ==== Options
+ # * <tt>:encode</tt> - This key will accept the strings "javascript" or "hex".
+ # Passing "javascript" will dynamically create and encode the mailto: link then
+ # eval it into the DOM of the page. This method will not show the link on
+ # the page if the user has JavaScript disabled. Passing "hex" will hex
+ # encode the +email_address+ before outputting the mailto: link.
+ # * <tt>:replace_at</tt> - When the link +name+ isn't provided, the
+ # +email_address+ is used for the link label. You can use this option to
+ # obfuscate the +email_address+ by substituting the @ sign with the string
+ # given as the value.
+ # * <tt>:replace_dot</tt> - When the link +name+ isn't provided, the
+ # +email_address+ is used for the link label. You can use this option to
+ # obfuscate the +email_address+ by substituting the . in the email with the
+ # string given as the value.
+ # * <tt>:subject</tt> - Preset the subject line of the email.
+ # * <tt>:body</tt> - Preset the body of the email.
+ # * <tt>:cc</tt> - Carbon Copy addition recipients on the email.
+ # * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
+ #
+ # ==== Examples
+ # mail_to "me@domain.com"
+ # # => <a href="mailto:me@domain.com">me@domain.com</a>
+ #
+ # mail_to "me@domain.com", "My email", :encode => "javascript"
+ # # => <script type="text/javascript">eval(unescape('%64%6f%63...%6d%65%6e'))</script>
+ #
+ # mail_to "me@domain.com", "My email", :encode => "hex"
+ # # => <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
+ #
+ # mail_to "me@domain.com", nil, :replace_at => "_at_", :replace_dot => "_dot_", :class => "email"
+ # # => <a href="mailto:me@domain.com" class="email">me_at_domain_dot_com</a>
+ #
+ # mail_to "me@domain.com", "My email", :cc => "ccaddress@domain.com",
+ # :subject => "This is an example email"
+ # # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
+ def mail_to(email_address, name = nil, html_options = {})
+ html_options = html_options.stringify_keys
+ encode = html_options.delete("encode").to_s
+ cc, bcc, subject, body = html_options.delete("cc"), html_options.delete("bcc"), html_options.delete("subject"), html_options.delete("body")
+
+ string = ''
+ extras = ''
+ extras << "cc=#{CGI.escape(cc).gsub("+", "%20")}&" unless cc.nil?
+ extras << "bcc=#{CGI.escape(bcc).gsub("+", "%20")}&" unless bcc.nil?
+ extras << "body=#{CGI.escape(body).gsub("+", "%20")}&" unless body.nil?
+ extras << "subject=#{CGI.escape(subject).gsub("+", "%20")}&" unless subject.nil?
+ extras = "?" << extras.gsub!(/&?$/,"") unless extras.empty?
+
+ email_address = email_address.to_s
+
+ email_address_obfuscated = email_address.dup
+ email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.has_key?("replace_at")
+ email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")
+
+ if encode == "javascript"
+ tmp = "document.write('#{content_tag("a", name || email_address, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');"
+ for i in 0...tmp.length
+ string << sprintf("%%%x",tmp[i])
+ end
+ "<script type=\"#{Mime::JS}\">eval(unescape('#{string}'))</script>"
+ elsif encode == "hex"
+ email_address_encoded = ''
+ email_address_obfuscated.each_byte do |c|
+ email_address_encoded << sprintf("&#%d;", c)
+ end
+
+ protocol = 'mailto:'
+ protocol.each_byte { |c| string << sprintf("&#%d;", c) }
+
+ for i in 0...email_address.length
+ if email_address[i,1] =~ /\w/
+ string << sprintf("%%%x",email_address[i])
+ else
+ string << email_address[i,1]
+ end
+ end
+ content_tag "a", name || email_address_encoded, html_options.merge({ "href" => "#{string}#{extras}" })
+ else
+ content_tag "a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:#{email_address}#{extras}" })
+ end
+ end
+
+ # True if the current request URI was generated by the given +options+.
+ #
+ # ==== Examples
+ # Let's say we're in the <tt>/shop/checkout</tt> action.
+ #
+ # current_page?(:action => 'process')
+ # # => false
+ #
+ # current_page?(:controller => 'shop', :action => 'checkout')
+ # # => true
+ #
+ # current_page?(:action => 'checkout')
+ # # => true
+ #
+ # current_page?(:controller => 'library', :action => 'checkout')
+ # # => false
+ def current_page?(options)
+ url_string = CGI.escapeHTML(url_for(options))
+ request = @controller.request
+ if url_string =~ /^\w+:\/\//
+ url_string == "#{request.protocol}#{request.host_with_port}#{request.request_uri}"
+ else
+ url_string == request.request_uri
+ end
+ end
+
+ private
+ def convert_options_to_javascript!(html_options, url = '')
+ confirm, popup = html_options.delete("confirm"), html_options.delete("popup")
+
+ method, href = html_options.delete("method"), html_options['href']
+
+ html_options["onclick"] = case
+ when popup && method
+ raise ActionView::ActionViewError, "You can't use :popup and :method in the same link"
+ when confirm && popup
+ "if (#{confirm_javascript_function(confirm)}) { #{popup_javascript_function(popup)} };return false;"
+ when confirm && method
+ "if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method)} };return false;"
+ when confirm
+ "return #{confirm_javascript_function(confirm)};"
+ when method
+ "#{method_javascript_function(method, url, href)}return false;"
+ when popup
+ popup_javascript_function(popup) + 'return false;'
+ else
+ html_options["onclick"]
+ end
+ end
+
+ def confirm_javascript_function(confirm)
+ "confirm('#{escape_javascript(confirm)}')"
+ end
+
+ def popup_javascript_function(popup)
+ popup.is_a?(Array) ? "window.open(this.href,'#{popup.first}','#{popup.last}');" : "window.open(this.href);"
+ end
+
+ def method_javascript_function(method, url = '', href = nil)
+ action = (href && url.size > 0) ? "'#{url}'" : 'this.href'
+ submit_function =
+ "var f = document.createElement('form'); f.style.display = 'none'; " +
+ "this.parentNode.appendChild(f); f.method = 'POST'; f.action = #{action};"
+
+ unless method == :post
+ submit_function << "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); "
+ submit_function << "m.setAttribute('name', '_method'); m.setAttribute('value', '#{method}'); f.appendChild(m);"
+ end
+
+ if protect_against_forgery?
+ submit_function << "var s = document.createElement('input'); s.setAttribute('type', 'hidden'); "
+ submit_function << "s.setAttribute('name', '#{request_forgery_protection_token}'); s.setAttribute('value', '#{escape_javascript form_authenticity_token}'); f.appendChild(s);"
+ end
+ submit_function << "f.submit();"
+ end
+
+ # Processes the _html_options_ hash, converting the boolean
+ # attributes from true/false form into the form required by
+ # HTML/XHTML. (An attribute is considered to be boolean if
+ # its name is listed in the given _bool_attrs_ array.)
+ #
+ # More specifically, for each boolean attribute in _html_options_
+ # given as:
+ #
+ # "attr" => bool_value
+ #
+ # if the associated _bool_value_ evaluates to true, it is
+ # replaced with the attribute's name; otherwise the attribute is
+ # removed from the _html_options_ hash. (See the XHTML 1.0 spec,
+ # section 4.5 "Attribute Minimization" for more:
+ # http://www.w3.org/TR/xhtml1/#h-4.5)
+ #
+ # Returns the updated _html_options_ hash, which is also modified
+ # in place.
+ #
+ # Example:
+ #
+ # convert_boolean_attributes!( html_options,
+ # %w( checked disabled readonly ) )
+ def convert_boolean_attributes!(html_options, bool_attrs)
+ bool_attrs.each { |x| html_options[x] = x if html_options.delete(x) }
+ html_options
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/partials.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/partials.rb
new file mode 100644
index 000000000..0795b8ec5
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/partials.rb
@@ -0,0 +1,200 @@
+module ActionView
+ # There's also a convenience method for rendering sub templates within the current controller that depends on a single object
+ # (we call this kind of sub templates for partials). It relies on the fact that partials should follow the naming convention of being
+ # prefixed with an underscore -- as to separate them from regular templates that could be rendered on their own.
+ #
+ # In a template for Advertiser#account:
+ #
+ # <%= render :partial => "account" %>
+ #
+ # This would render "advertiser/_account.erb" and pass the instance variable @account in as a local variable +account+ to
+ # the template for display.
+ #
+ # In another template for Advertiser#buy, we could have:
+ #
+ # <%= render :partial => "account", :locals => { :account => @buyer } %>
+ #
+ # <% for ad in @advertisements %>
+ # <%= render :partial => "ad", :locals => { :ad => ad } %>
+ # <% end %>
+ #
+ # This would first render "advertiser/_account.erb" with @buyer passed in as the local variable +account+, then render
+ # "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display.
+ #
+ # == Rendering a collection of partials
+ #
+ # The example of partial use describes a familiar pattern where a template needs to iterate over an array and render a sub
+ # template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders
+ # a partial by the same name as the elements contained within. So the three-lined example in "Using partials" can be rewritten
+ # with a single line:
+ #
+ # <%= render :partial => "ad", :collection => @advertisements %>
+ #
+ # This will render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. An iteration counter
+ # will automatically be made available to the template with a name of the form +partial_name_counter+. In the case of the
+ # example above, the template would be fed +ad_counter+.
+ #
+ # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also just keep domain objects,
+ # like Active Records, in there.
+ #
+ # == Rendering shared partials
+ #
+ # Two controllers can share a set of partials and render them like this:
+ #
+ # <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %>
+ #
+ # This will render the partial "advertisement/_ad.erb" regardless of which controller this is being called from.
+ #
+ # == Rendering partials with layouts
+ #
+ # Partials can have their own layouts applied to them. These layouts are different than the ones that are specified globally
+ # for the entire action, but they work in a similar fashion. Imagine a list with two types of users:
+ #
+ # <%# app/views/users/index.html.erb &>
+ # Here's the administrator:
+ # <%= render :partial => "user", :layout => "administrator", :locals => { :user => administrator } %>
+ #
+ # Here's the editor:
+ # <%= render :partial => "user", :layout => "editor", :locals => { :user => editor } %>
+ #
+ # <%# app/views/users/_user.html.erb &>
+ # Name: <%= user.name %>
+ #
+ # <%# app/views/users/_administrator.html.erb &>
+ # <div id="administrator">
+ # Budget: $<%= user.budget %>
+ # <%= yield %>
+ # </div>
+ #
+ # <%# app/views/users/_editor.html.erb &>
+ # <div id="editor">
+ # Deadline: $<%= user.deadline %>
+ # <%= yield %>
+ # </div>
+ #
+ # ...this will return:
+ #
+ # Here's the administrator:
+ # <div id="administrator">
+ # Budget: $<%= user.budget %>
+ # Name: <%= user.name %>
+ # </div>
+ #
+ # Here's the editor:
+ # <div id="editor">
+ # Deadline: $<%= user.deadline %>
+ # Name: <%= user.name %>
+ # </div>
+ #
+ # You can also apply a layout to a block within any template:
+ #
+ # <%# app/views/users/_chief.html.erb &>
+ # <% render(:layout => "administrator", :locals => { :user => chief }) do %>
+ # Title: <%= chief.title %>
+ # <% end %>
+ #
+ # ...this will return:
+ #
+ # <div id="administrator">
+ # Budget: $<%= user.budget %>
+ # Title: <%= chief.name %>
+ # </div>
+ #
+ # As you can see, the :locals hash is shared between both the partial and its layout.
+ module Partials
+ private
+ def render_partial(partial_path, object_assigns = nil, local_assigns = nil) #:nodoc:
+ case partial_path
+ when String, Symbol, NilClass
+ path, partial_name = partial_pieces(partial_path)
+ object = extracting_object(partial_name, object_assigns)
+ local_assigns = local_assigns ? local_assigns.clone : {}
+ add_counter_to_local_assigns!(partial_name, local_assigns)
+ add_object_to_local_assigns!(partial_name, local_assigns, object)
+
+ if logger && logger.debug?
+ ActionController::Base.benchmark("Rendered #{path}/_#{partial_name}", Logger::DEBUG, false) do
+ render("#{path}/_#{partial_name}", local_assigns)
+ end
+ else
+ render("#{path}/_#{partial_name}", local_assigns)
+ end
+ when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Associations::HasManyThroughAssociation
+ if partial_path.any?
+ path = ActionController::RecordIdentifier.partial_path(partial_path.first)
+ collection = partial_path
+ render_partial_collection(path, collection, nil, object_assigns.value)
+ else
+ ""
+ end
+ else
+ render_partial(
+ ActionController::RecordIdentifier.partial_path(partial_path),
+ object_assigns, local_assigns)
+ end
+ end
+
+ def render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = nil) #:nodoc:
+ collection_of_partials = Array.new
+ counter_name = partial_counter_name(partial_name)
+ local_assigns = local_assigns ? local_assigns.clone : {}
+ collection.each_with_index do |element, counter|
+ local_assigns[counter_name] = counter
+ collection_of_partials.push(render_partial(partial_name, element, local_assigns))
+ end
+
+ return " " if collection_of_partials.empty?
+
+ if partial_spacer_template
+ spacer_path, spacer_name = partial_pieces(partial_spacer_template)
+ collection_of_partials.join(render("#{spacer_path}/_#{spacer_name}"))
+ else
+ collection_of_partials.join
+ end
+ end
+
+ alias_method :render_collection_of_partials, :render_partial_collection
+
+ def partial_pieces(partial_path)
+ if partial_path.include?('/')
+ return File.dirname(partial_path), File.basename(partial_path)
+ else
+ return controller.class.controller_path, partial_path
+ end
+ end
+
+ def partial_counter_name(partial_name)
+ "#{partial_variable_name(partial_name)}_counter".intern
+ end
+
+ def partial_variable_name(partial_name)
+ partial_name.split('/').last.split('.').first.intern
+ end
+
+ def extracting_object(partial_name, object_assigns)
+ variable_name = partial_variable_name(partial_name)
+ if object_assigns.nil?
+ controller.instance_variable_get("@#{variable_name}")
+ else
+ object_assigns
+ end
+ end
+
+ def add_counter_to_local_assigns!(partial_name, local_assigns)
+ counter_name = partial_counter_name(partial_name)
+ local_assigns[counter_name] = 1 unless local_assigns.has_key?(counter_name)
+ end
+
+ def add_object_to_local_assigns!(partial_name, local_assigns, object)
+ variable_name = partial_variable_name(partial_name)
+
+ local_assigns[:object] ||=
+ local_assigns[variable_name] ||=
+ if object.is_a?(ActionView::Base::ObjectWrapper)
+ object.value
+ else
+ object
+ end || controller.instance_variable_get("@#{variable_name}")
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/template_error.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/template_error.rb
new file mode 100644
index 000000000..01b4b1555
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/template_error.rb
@@ -0,0 +1,102 @@
+module ActionView
+ # The TemplateError exception is raised when the compilation of the template fails. This exception then gathers a
+ # bunch of intimate details and uses it to report a very precise exception message.
+ class TemplateError < ActionViewError #:nodoc:
+ SOURCE_CODE_RADIUS = 3
+
+ attr_reader :original_exception
+
+ def initialize(base_path, file_path, assigns, source, original_exception)
+ @base_path, @assigns, @source, @original_exception =
+ base_path, assigns.dup, source, original_exception
+ @file_path = file_path
+ end
+
+ def message
+ ActiveSupport::Deprecation.silence { original_exception.message }
+ end
+
+ def clean_backtrace
+ original_exception.clean_backtrace
+ end
+
+ def sub_template_message
+ if @sub_templates
+ "Trace of template inclusion: " +
+ @sub_templates.collect { |template| strip_base_path(template) }.join(", ")
+ else
+ ""
+ end
+ end
+
+ def source_extract(indentation = 0)
+ return unless num = line_number
+ num = num.to_i
+
+ source_code = IO.readlines(@file_path)
+
+ start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max
+ end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
+
+ indent = ' ' * indentation
+ line_counter = start_on_line
+
+ source_code[start_on_line..end_on_line].sum do |line|
+ line_counter += 1
+ "#{indent}#{line_counter}: #{line}"
+ end
+ end
+
+ def sub_template_of(template_path)
+ @sub_templates ||= []
+ @sub_templates << template_path
+ end
+
+ def line_number
+ @line_number ||=
+ if file_name
+ regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
+
+ $1 if message =~ regexp or clean_backtrace.find { |line| line =~ regexp }
+ end
+ end
+
+ def file_name
+ stripped = strip_base_path(@file_path)
+ stripped.slice!(0,1) if stripped[0] == ?/
+ stripped
+ end
+
+ def to_s
+ "\n\n#{self.class} (#{message}) #{source_location}:\n" +
+ "#{source_extract}\n #{clean_backtrace.join("\n ")}\n\n"
+ end
+
+ def backtrace
+ [
+ "#{source_location.capitalize}\n\n#{source_extract(4)}\n " +
+ clean_backtrace.join("\n ")
+ ]
+ end
+
+ private
+ def strip_base_path(path)
+ stripped_path = File.expand_path(path).gsub(@base_path, "")
+ stripped_path.gsub!(/^#{Regexp.escape File.expand_path(RAILS_ROOT)}/, '') if defined?(RAILS_ROOT)
+ stripped_path
+ end
+
+ def source_location
+ if line_number
+ "on line ##{line_number} of "
+ else
+ 'in '
+ end + file_name
+ end
+ end
+end
+
+if defined?(Exception::TraceSubstitutions)
+ Exception::TraceSubstitutions << [/:in\s+`_run_(html|xml).*'\s*$/, '']
+ Exception::TraceSubstitutions << [%r{^\s*#{Regexp.escape RAILS_ROOT}/}, ''] if defined?(RAILS_ROOT)
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/template_handler.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/template_handler.rb
new file mode 100644
index 000000000..b9f4330a2
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/template_handler.rb
@@ -0,0 +1,17 @@
+module ActionView
+ class TemplateHandler
+ def self.line_offset
+ 0
+ end
+
+ def initialize(view)
+ @view = view
+ end
+
+ def render(template, local_assigns)
+ end
+
+ def compile(template)
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/template_handlers/builder.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/template_handlers/builder.rb
new file mode 100644
index 000000000..0f49d6ab3
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/template_handlers/builder.rb
@@ -0,0 +1,19 @@
+require 'builder'
+
+module ActionView
+ module TemplateHandlers
+ class Builder < TemplateHandler
+ def self.line_offset
+ 2
+ end
+
+ def compile(template)
+ content_type_handler = (@view.send!(:controller).respond_to?(:response) ? "controller.response" : "controller")
+ "#{content_type_handler}.content_type ||= Mime::XML\n" +
+ "xml = Builder::XmlMarkup.new(:indent => 2)\n" +
+ template +
+ "\nxml.target!\n"
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/template_handlers/erb.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/template_handlers/erb.rb
new file mode 100644
index 000000000..022fc362e
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/template_handlers/erb.rb
@@ -0,0 +1,21 @@
+require 'erb'
+
+class ERB
+ module Util
+ HTML_ESCAPE = { '&' => '&amp;', '"' => '&quot;', '>' => '&gt;', '<' => '&lt;' }
+
+ def html_escape(s)
+ s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }
+ end
+ end
+end
+
+module ActionView
+ module TemplateHandlers
+ class ERB < TemplateHandler
+ def compile(template)
+ ::ERB.new(template, nil, @view.erb_trim_mode).src
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_view/template_handlers/rjs.rb b/vendor/rails-2.0.2/actionpack/lib/action_view/template_handlers/rjs.rb
new file mode 100644
index 000000000..4ca9fc327
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/action_view/template_handlers/rjs.rb
@@ -0,0 +1,14 @@
+module ActionView
+ module TemplateHandlers
+ class RJS < TemplateHandler
+ def self.line_offset
+ 2
+ end
+
+ def compile(template)
+ "controller.response.content_type ||= Mime::JS\n" +
+ "update_page do |page|\n#{template}\nend"
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/lib/actionpack.rb b/vendor/rails-2.0.2/actionpack/lib/actionpack.rb
new file mode 100644
index 000000000..2fe2832f8
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/lib/actionpack.rb
@@ -0,0 +1 @@
+require 'action_pack'
diff --git a/vendor/rails-2.0.2/actionpack/test/abstract_unit.rb b/vendor/rails-2.0.2/actionpack/test/abstract_unit.rb
new file mode 100644
index 000000000..700bc1f5e
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/abstract_unit.rb
@@ -0,0 +1,36 @@
+$:.unshift(File.dirname(__FILE__) + '/../lib')
+$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib/active_support')
+$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers')
+
+require 'yaml'
+require 'stringio'
+require 'test/unit'
+require 'action_controller'
+require 'action_controller/cgi_ext'
+require 'action_controller/test_process'
+
+begin
+ require 'ruby-debug'
+rescue LoadError
+ # Debugging disabled. `gem install ruby-debug` to enable.
+end
+
+# Show backtraces for deprecated behavior for quicker cleanup.
+ActiveSupport::Deprecation.debug = true
+
+ActionController::Base.logger = nil
+ActionController::Base.ignore_missing_templates = false
+ActionController::Routing::Routes.reload rescue nil
+
+
+# Wrap tests that use Mocha and skip if unavailable.
+def uses_mocha(test_name)
+ unless Object.const_defined?(:Mocha)
+ require 'mocha'
+ require 'stubba'
+ end
+ yield
+rescue LoadError => load_error
+ raise unless load_error.message =~ /mocha/i
+ $stderr.puts "Skipping #{test_name} tests. `gem install mocha` and try again."
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/action_view_test.rb b/vendor/rails-2.0.2/actionpack/test/action_view_test.rb
new file mode 100644
index 000000000..a69ff36f9
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/action_view_test.rb
@@ -0,0 +1,44 @@
+require File.dirname(__FILE__) + '/abstract_unit'
+require 'test/unit'
+
+class ActionViewTests < Test::Unit::TestCase
+ def test_find_template_extension_from_first_render
+ base = ActionView::Base.new
+
+ assert_nil base.send(:find_template_extension_from_first_render)
+
+ {
+ nil => nil,
+ '' => nil,
+ 'foo' => nil,
+ '/foo' => nil,
+ 'foo.rb' => 'rb',
+ 'foo.bar.rb' => 'bar.rb',
+ 'baz/foo.rb' => 'rb',
+ 'baz/foo.bar.rb' => 'bar.rb',
+ 'baz/foo.o/foo.rb' => 'rb',
+ 'baz/foo.o/foo.bar.rb' => 'bar.rb',
+ }.each do |input,expectation|
+ base.instance_variable_set('@first_render', input)
+ assert_equal expectation, base.send(:find_template_extension_from_first_render)
+ end
+ end
+
+ def test_should_report_file_exists_correctly
+ base = ActionView::Base.new
+
+ assert_nil base.send(:find_template_extension_from_first_render)
+
+ assert_equal false, base.send(:file_exists?, 'test.rhtml')
+ assert_equal false, base.send(:file_exists?, 'test.rb')
+
+ base.instance_variable_set('@first_render', 'foo.rb')
+
+ assert_equal 'rb', base.send(:find_template_extension_from_first_render)
+
+ assert_equal false, base.send(:file_exists?, 'baz')
+ assert_equal false, base.send(:file_exists?, 'baz.rb')
+
+ end
+
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/active_record_unit.rb b/vendor/rails-2.0.2/actionpack/test/active_record_unit.rb
new file mode 100644
index 000000000..5f2745b59
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/active_record_unit.rb
@@ -0,0 +1,108 @@
+require File.dirname(__FILE__) + '/abstract_unit'
+
+# Define the essentials
+class ActiveRecordTestConnector
+ cattr_accessor :able_to_connect
+ cattr_accessor :connected
+
+ # Set our defaults
+ self.connected = false
+ self.able_to_connect = true
+end
+
+# Try to grab AR
+if defined?(ActiveRecord) && defined?(Fixtures)
+ $stderr.puts 'Active Record is already loaded, running tests'
+else
+ $stderr.print 'Attempting to load Active Record... '
+ begin
+ PATH_TO_AR = "#{File.dirname(__FILE__)}/../../activerecord/lib"
+ raise LoadError, "#{PATH_TO_AR} doesn't exist" unless File.directory?(PATH_TO_AR)
+ $LOAD_PATH.unshift PATH_TO_AR
+ require 'active_record'
+ require 'active_record/fixtures'
+ $stderr.puts 'success'
+ rescue LoadError => e
+ $stderr.print "failed. Skipping Active Record assertion tests: #{e}"
+ ActiveRecordTestConnector.able_to_connect = false
+ end
+end
+$stderr.flush
+
+
+
+# Define the rest of the connector
+class ActiveRecordTestConnector
+ class << self
+ def setup
+ unless self.connected || !self.able_to_connect
+ setup_connection
+ load_schema
+ require_fixture_models
+ self.connected = true
+ end
+ rescue Exception => e # errors from ActiveRecord setup
+ $stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}"
+ #$stderr.puts " #{e.backtrace.join("\n ")}\n"
+ self.able_to_connect = false
+ end
+
+ private
+
+ def setup_connection
+ if Object.const_defined?(:ActiveRecord)
+ defaults = { :database => ':memory:' }
+ begin
+ options = defaults.merge :adapter => 'sqlite3', :timeout => 500
+ ActiveRecord::Base.establish_connection(options)
+ ActiveRecord::Base.configurations = { 'sqlite3_ar_integration' => options }
+ ActiveRecord::Base.connection
+ rescue Exception # errors from establishing a connection
+ $stderr.puts 'SQLite 3 unavailable; trying SQLite 2.'
+ options = defaults.merge :adapter => 'sqlite'
+ ActiveRecord::Base.establish_connection(options)
+ ActiveRecord::Base.configurations = { 'sqlite2_ar_integration' => options }
+ ActiveRecord::Base.connection
+ end
+
+ Object.send(:const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')) unless Object.const_defined?(:QUOTED_TYPE)
+ else
+ raise "Can't setup connection since ActiveRecord isn't loaded."
+ end
+ end
+
+ # Load actionpack sqlite tables
+ def load_schema
+ File.read(File.dirname(__FILE__) + "/fixtures/db_definitions/sqlite.sql").split(';').each do |sql|
+ ActiveRecord::Base.connection.execute(sql) unless sql.blank?
+ end
+ end
+
+ def require_fixture_models
+ Dir.glob(File.dirname(__FILE__) + "/fixtures/*.rb").each {|f| require f}
+ end
+ end
+end
+
+# Test case for inheritance
+class ActiveRecordTestCase < Test::Unit::TestCase
+ # Set our fixture path
+ if ActiveRecordTestConnector.able_to_connect
+ self.fixture_path = "#{File.dirname(__FILE__)}/fixtures/"
+ self.use_transactional_fixtures = false
+ end
+
+ def self.fixtures(*args)
+ super if ActiveRecordTestConnector.connected
+ end
+
+ def run(*args)
+ super if ActiveRecordTestConnector.connected
+ end
+
+ # Default so Test::Unit::TestCase doesn't complain
+ def test_truth
+ end
+end
+
+ActiveRecordTestConnector.setup
diff --git a/vendor/rails-2.0.2/actionpack/test/activerecord/active_record_store_test.rb b/vendor/rails-2.0.2/actionpack/test/activerecord/active_record_store_test.rb
new file mode 100644
index 000000000..707a0a75c
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/activerecord/active_record_store_test.rb
@@ -0,0 +1,142 @@
+# These tests exercise CGI::Session::ActiveRecordStore, so you're going to
+# need AR in a sibling directory to AP and have SQLite installed.
+require File.dirname(__FILE__) + '/../active_record_unit'
+require 'action_controller/session/active_record_store'
+
+
+module CommonActiveRecordStoreTests
+ def test_basics
+ s = session_class.new(:session_id => '1234', :data => { 'foo' => 'bar' })
+ assert_equal 'bar', s.data['foo']
+ assert s.save
+ assert_equal 'bar', s.data['foo']
+
+ assert_not_nil t = session_class.find_by_session_id('1234')
+ assert_not_nil t.data
+ assert_equal 'bar', t.data['foo']
+ end
+
+ def test_reload_same_session
+ @new_session.update
+ reloaded = CGI::Session.new(CGI.new, 'session_id' => @new_session.session_id, 'database_manager' => CGI::Session::ActiveRecordStore)
+ assert_equal 'bar', reloaded['foo']
+ end
+
+ def test_tolerates_close_close
+ assert_nothing_raised do
+ @new_session.close
+ @new_session.close
+ end
+ end
+end
+
+class ActiveRecordStoreTest < ActiveRecordTestCase
+ include CommonActiveRecordStoreTests
+
+ def session_class
+ CGI::Session::ActiveRecordStore::Session
+ end
+
+ def session_id_column
+ "session_id"
+ end
+
+ def setup
+ session_class.create_table!
+
+ ENV['REQUEST_METHOD'] = 'GET'
+ ENV['REQUEST_URI'] = '/'
+ CGI::Session::ActiveRecordStore.session_class = session_class
+
+ @cgi = CGI.new
+ @new_session = CGI::Session.new(@cgi, 'database_manager' => CGI::Session::ActiveRecordStore, 'new_session' => true)
+ @new_session['foo'] = 'bar'
+ end
+
+# this test only applies for eager session saving
+# def test_another_instance
+# @another = CGI::Session.new(@cgi, 'session_id' => @new_session.session_id, 'database_manager' => CGI::Session::ActiveRecordStore)
+# assert_equal @new_session.session_id, @another.session_id
+# end
+
+ def test_model_attribute
+ assert_kind_of CGI::Session::ActiveRecordStore::Session, @new_session.model
+ assert_equal({ 'foo' => 'bar' }, @new_session.model.data)
+ end
+
+ def test_save_unloaded_session
+ c = session_class.connection
+ bogus_class = c.quote(Base64.encode64("\004\010o:\vBlammo\000"))
+ c.insert("INSERT INTO #{session_class.table_name} ('#{session_id_column}', 'data') VALUES ('abcdefghijklmnop', #{bogus_class})")
+
+ sess = session_class.find_by_session_id('abcdefghijklmnop')
+ assert_not_nil sess
+ assert !sess.loaded?
+
+ # because the session is not loaded, the save should be a no-op. If it
+ # isn't, this'll try and unmarshall the bogus class, and should get an error.
+ assert_nothing_raised { sess.save }
+ end
+
+ def teardown
+ session_class.drop_table!
+ end
+end
+
+class ColumnLimitTest < ActiveRecordTestCase
+ def setup
+ @session_class = CGI::Session::ActiveRecordStore::Session
+ @session_class.create_table!
+ end
+
+ def teardown
+ @session_class.drop_table!
+ end
+
+ def test_protection_from_data_larger_than_column
+ # Can't test this unless there is a limit
+ return unless limit = @session_class.data_column_size_limit
+ too_big = ':(' * limit
+ s = @session_class.new(:session_id => '666', :data => {'foo' => too_big})
+ s.data
+ assert_raise(ActionController::SessionOverflowError) { s.save }
+ end
+end
+
+class DeprecatedActiveRecordStoreTest < ActiveRecordStoreTest
+ def session_id_column
+ "sessid"
+ end
+
+ def setup
+ session_class.connection.execute 'create table old_sessions (id integer primary key, sessid text unique, data text)'
+ session_class.table_name = 'old_sessions'
+ session_class.send :setup_sessid_compatibility!
+
+ ENV['REQUEST_METHOD'] = 'GET'
+ CGI::Session::ActiveRecordStore.session_class = session_class
+
+ @new_session = CGI::Session.new(CGI.new, 'database_manager' => CGI::Session::ActiveRecordStore, 'new_session' => true)
+ @new_session['foo'] = 'bar'
+ end
+
+ def teardown
+ session_class.connection.execute 'drop table old_sessions'
+ session_class.table_name = 'sessions'
+ end
+end
+
+class SqlBypassActiveRecordStoreTest < ActiveRecordStoreTest
+ def session_class
+ unless defined? @session_class
+ @session_class = CGI::Session::ActiveRecordStore::SqlBypass
+ @session_class.connection = CGI::Session::ActiveRecordStore::Session.connection
+ end
+ @session_class
+ end
+
+ def test_model_attribute
+ assert_kind_of CGI::Session::ActiveRecordStore::SqlBypass, @new_session.model
+ assert_equal({ 'foo' => 'bar' }, @new_session.model.data)
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/activerecord/render_partial_with_record_identification_test.rb b/vendor/rails-2.0.2/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
new file mode 100644
index 000000000..ccebbefea
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
@@ -0,0 +1,74 @@
+require File.dirname(__FILE__) + '/../active_record_unit'
+
+class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase
+ fixtures :developers, :projects, :developers_projects, :topics, :replies
+
+ class RenderPartialWithRecordIdentificationController < ActionController::Base
+ def render_with_has_many_and_belongs_to_association
+ @developer = Developer.find(1)
+ render :partial => @developer.projects
+ end
+
+ def render_with_has_many_association
+ @topic = Topic.find(1)
+ render :partial => @topic.replies
+ end
+
+ def render_with_has_many_through_association
+ @developer = Developer.find(:first)
+ render :partial => @developer.topics
+ end
+
+ def render_with_belongs_to_association
+ @reply = Reply.find(1)
+ render :partial => @reply.topic
+ end
+
+ def render_with_record
+ @developer = Developer.find(:first)
+ render :partial => @developer
+ end
+
+ def render_with_record_collection
+ @developers = Developer.find(:all)
+ render :partial => @developers
+ end
+ end
+
+ def setup
+ @controller = RenderPartialWithRecordIdentificationController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ super
+ end
+
+ def test_rendering_partial_with_has_many_and_belongs_to_association
+ get :render_with_has_many_and_belongs_to_association
+ assert_template 'projects/_project'
+ end
+
+ def test_rendering_partial_with_has_many_association
+ get :render_with_has_many_association
+ assert_template 'replies/_reply'
+ end
+
+ def test_rendering_partial_with_has_many_association
+ get :render_with_has_many_through_association
+ assert_template 'topics/_topic'
+ end
+
+ def test_rendering_partial_with_belongs_to_association
+ get :render_with_belongs_to_association
+ assert_template 'topics/_topic'
+ end
+
+ def test_render_with_record
+ get :render_with_record
+ assert_template 'developers/_developer'
+ end
+
+ def test_render_with_record_collection
+ get :render_with_record_collection
+ assert_template 'developers/_developer'
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/action_pack_assertions_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/action_pack_assertions_test.rb
new file mode 100644
index 000000000..1eb9610d0
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/action_pack_assertions_test.rb
@@ -0,0 +1,492 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+# a controller class to facilitate the tests
+class ActionPackAssertionsController < ActionController::Base
+
+ # this does absolutely nothing
+ def nothing() head :ok end
+
+ # a standard template
+ def hello_world() render :template => "test/hello_world"; end
+
+ # a standard template
+ def hello_xml_world() render :template => "test/hello_xml_world"; end
+
+ # a redirect to an internal location
+ def redirect_internal() redirect_to "/nothing"; end
+
+ def redirect_to_action() redirect_to :action => "flash_me", :id => 1, :params => { "panda" => "fun" }; end
+
+ def redirect_to_controller() redirect_to :controller => "elsewhere", :action => "flash_me"; end
+
+ def redirect_to_controller_with_symbol() redirect_to :controller => :elsewhere, :action => :flash_me; end
+
+ def redirect_to_path() redirect_to '/some/path' end
+
+ def redirect_to_named_route() redirect_to route_one_url end
+
+ # a redirect to an external location
+ def redirect_external() redirect_to "http://www.rubyonrails.org"; end
+
+ # a 404
+ def response404() head '404 AWOL' end
+
+ # a 500
+ def response500() head '500 Sorry' end
+
+ # a fictional 599
+ def response599() head '599 Whoah!' end
+
+ # putting stuff in the flash
+ def flash_me
+ flash['hello'] = 'my name is inigo montoya...'
+ render :text => "Inconceivable!"
+ end
+
+ # we have a flash, but nothing is in it
+ def flash_me_naked
+ flash.clear
+ render :text => "wow!"
+ end
+
+ # assign some template instance variables
+ def assign_this
+ @howdy = "ho"
+ render :inline => "Mr. Henke"
+ end
+
+ def render_based_on_parameters
+ render :text => "Mr. #{params[:name]}"
+ end
+
+ def render_url
+ render :text => "<div>#{url_for(:action => 'flash_me', :only_path => true)}</div>"
+ end
+
+ def render_text_with_custom_content_type
+ render :text => "Hello!", :content_type => Mime::RSS
+ end
+
+ # puts something in the session
+ def session_stuffing
+ session['xmas'] = 'turkey'
+ render :text => "ho ho ho"
+ end
+
+ # raises exception on get requests
+ def raise_on_get
+ raise "get" if request.get?
+ render :text => "request method: #{request.env['REQUEST_METHOD']}"
+ end
+
+ # raises exception on post requests
+ def raise_on_post
+ raise "post" if request.post?
+ render :text => "request method: #{request.env['REQUEST_METHOD']}"
+ end
+
+ def get_valid_record
+ @record = Class.new do
+ def valid?
+ true
+ end
+
+ def errors
+ Class.new do
+ def full_messages; []; end
+ end.new
+ end
+
+ end.new
+
+ render :nothing => true
+ end
+
+
+ def get_invalid_record
+ @record = Class.new do
+
+ def valid?
+ false
+ end
+
+ def errors
+ Class.new do
+ def full_messages; ['...stuff...']; end
+ end.new
+ end
+ end.new
+
+ render :nothing => true
+ end
+
+ # 911
+ def rescue_action(e) raise; end
+end
+
+module Admin
+ class InnerModuleController < ActionController::Base
+ def index
+ render :nothing => true
+ end
+
+ def redirect_to_index
+ redirect_to admin_inner_module_path
+ end
+
+ def redirect_to_absolute_controller
+ redirect_to :controller => '/content'
+ end
+
+ def redirect_to_fellow_controller
+ redirect_to :controller => 'user'
+ end
+
+ def redirect_to_top_level_named_route
+ redirect_to top_level_url(:id => "foo")
+ end
+ end
+end
+
+# ---------------------------------------------------------------------------
+
+
+# tell the controller where to find its templates but start from parent
+# directory of test_request_response to simulate the behaviour of a
+# production environment
+ActionPackAssertionsController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
+
+# a test case to exercise the new capabilities TestRequest & TestResponse
+class ActionPackAssertionsControllerTest < Test::Unit::TestCase
+ # let's get this party started
+ def setup
+ ActionController::Routing::Routes.reload
+ ActionController::Routing.use_controllers!(%w(action_pack_assertions admin/inner_module content admin/user))
+ @controller = ActionPackAssertionsController.new
+ @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
+ end
+
+ def teardown
+ ActionController::Routing::Routes.reload
+ end
+
+ # -- assertion-based testing ------------------------------------------------
+
+ def test_assert_tag_and_url_for
+ get :render_url
+ assert_tag :content => "/action_pack_assertions/flash_me"
+ end
+
+ # test the get method, make sure the request really was a get
+ def test_get
+ assert_raise(RuntimeError) { get :raise_on_get }
+ get :raise_on_post
+ assert_equal @response.body, 'request method: GET'
+ end
+
+ # test the get method, make sure the request really was a get
+ def test_post
+ assert_raise(RuntimeError) { post :raise_on_post }
+ post :raise_on_get
+ assert_equal @response.body, 'request method: POST'
+ end
+
+# the following test fails because the request_method is now cached on the request instance
+# test the get/post switch within one test action
+# def test_get_post_switch
+# post :raise_on_get
+# assert_equal @response.body, 'request method: POST'
+# get :raise_on_post
+# assert_equal @response.body, 'request method: GET'
+# post :raise_on_get
+# assert_equal @response.body, 'request method: POST'
+# get :raise_on_post
+# assert_equal @response.body, 'request method: GET'
+# end
+
+ # test the redirection to a named route
+ def test_assert_redirect_to_named_route
+ with_routing do |set|
+ set.draw do |map|
+ map.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing'
+ map.connect ':controller/:action/:id'
+ end
+ set.install_helpers
+
+ process :redirect_to_named_route
+ assert_redirected_to 'http://test.host/route_one'
+ assert_redirected_to route_one_url
+ assert_redirected_to :route_one_url
+ end
+ end
+
+ def test_assert_redirect_to_named_route_failure
+ with_routing do |set|
+ set.draw do |map|
+ map.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'one'
+ map.route_two 'route_two', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two'
+ map.connect ':controller/:action/:id'
+ end
+ process :redirect_to_named_route
+ assert_raise(Test::Unit::AssertionFailedError) do
+ assert_redirected_to 'http://test.host/route_two'
+ end
+ assert_raise(Test::Unit::AssertionFailedError) do
+ assert_redirected_to :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two'
+ end
+ assert_raise(Test::Unit::AssertionFailedError) do
+ assert_redirected_to route_two_url
+ end
+ assert_raise(Test::Unit::AssertionFailedError) do
+ assert_redirected_to :route_two_url
+ end
+ end
+ end
+
+ def test_assert_redirect_to_nested_named_route
+ with_routing do |set|
+ set.draw do |map|
+ map.admin_inner_module 'admin/inner_module', :controller => 'admin/inner_module', :action => 'index'
+ map.connect ':controller/:action/:id'
+ end
+ @controller = Admin::InnerModuleController.new
+ process :redirect_to_index
+ # redirection is <{"action"=>"index", "controller"=>"admin/admin/inner_module"}>
+ assert_redirected_to admin_inner_module_path
+ end
+ end
+
+ def test_assert_redirected_to_top_level_named_route_from_nested_controller
+ with_routing do |set|
+ set.draw do |map|
+ map.top_level '/action_pack_assertions/:id', :controller => 'action_pack_assertions', :action => 'index'
+ map.connect ':controller/:action/:id'
+ end
+ @controller = Admin::InnerModuleController.new
+ process :redirect_to_top_level_named_route
+ # passes -> assert_redirected_to "http://test.host/action_pack_assertions/foo"
+ assert_redirected_to "/action_pack_assertions/foo"
+ end
+ end
+
+ # -- standard request/response object testing --------------------------------
+
+ # make sure that the template objects exist
+ def test_template_objects_alive
+ process :assign_this
+ assert !@response.has_template_object?('hi')
+ assert @response.has_template_object?('howdy')
+ end
+
+ # make sure we don't have template objects when we shouldn't
+ def test_template_object_missing
+ process :nothing
+ assert_nil @response.template_objects['howdy']
+ end
+
+ # check the empty flashing
+ def test_flash_me_naked
+ process :flash_me_naked
+ assert !@response.has_flash?
+ assert !@response.has_flash_with_contents?
+ end
+
+ # check if we have flash objects
+ def test_flash_haves
+ process :flash_me
+ assert @response.has_flash?
+ assert @response.has_flash_with_contents?
+ assert @response.has_flash_object?('hello')
+ end
+
+ # ensure we don't have flash objects
+ def test_flash_have_nots
+ process :nothing
+ assert !@response.has_flash?
+ assert !@response.has_flash_with_contents?
+ assert_nil @response.flash['hello']
+ end
+
+ # check if we were rendered by a file-based template?
+ def test_rendered_action
+ process :nothing
+ assert !@response.rendered_with_file?
+
+ process :hello_world
+ assert @response.rendered_with_file?
+ assert 'hello_world', @response.rendered_file
+ end
+
+ # check the redirection location
+ def test_redirection_location
+ process :redirect_internal
+ assert_equal 'http://test.host/nothing', @response.redirect_url
+
+ process :redirect_external
+ assert_equal 'http://www.rubyonrails.org', @response.redirect_url
+ end
+
+ def test_no_redirect_url
+ process :nothing
+ assert_nil @response.redirect_url
+ end
+
+
+ # check server errors
+ def test_server_error_response_code
+ process :response500
+ assert @response.server_error?
+
+ process :response599
+ assert @response.server_error?
+
+ process :response404
+ assert !@response.server_error?
+ end
+
+ # check a 404 response code
+ def test_missing_response_code
+ process :response404
+ assert @response.missing?
+ end
+
+ # check to see if our redirection matches a pattern
+ def test_redirect_url_match
+ process :redirect_external
+ assert @response.redirect?
+ assert @response.redirect_url_match?("rubyonrails")
+ assert @response.redirect_url_match?(/rubyonrails/)
+ assert !@response.redirect_url_match?("phpoffrails")
+ assert !@response.redirect_url_match?(/perloffrails/)
+ end
+
+ # check for a redirection
+ def test_redirection
+ process :redirect_internal
+ assert @response.redirect?
+
+ process :redirect_external
+ assert @response.redirect?
+
+ process :nothing
+ assert !@response.redirect?
+ end
+
+ # check a successful response code
+ def test_successful_response_code
+ process :nothing
+ assert @response.success?
+ end
+
+ # a basic check to make sure we have a TestResponse object
+ def test_has_response
+ process :nothing
+ assert_kind_of ActionController::TestResponse, @response
+ end
+
+ def test_render_based_on_parameters
+ process :render_based_on_parameters, "name" => "David"
+ assert_equal "Mr. David", @response.body
+ end
+
+ def test_follow_redirect
+ process :redirect_to_action
+ assert_redirected_to :action => "flash_me"
+
+ follow_redirect
+ assert_equal 1, @request.parameters["id"].to_i
+
+ assert "Inconceivable!", @response.body
+ end
+
+ def test_follow_redirect_outside_current_action
+ process :redirect_to_controller
+ assert_redirected_to :controller => "elsewhere", :action => "flash_me"
+
+ assert_raises(RuntimeError, "Can't follow redirects outside of current controller (elsewhere)") { follow_redirect }
+ end
+
+ def test_assert_redirection_fails_with_incorrect_controller
+ process :redirect_to_controller
+ assert_raise(Test::Unit::AssertionFailedError) do
+ assert_redirected_to :controller => "action_pack_assertions", :action => "flash_me"
+ end
+ end
+
+ def test_assert_redirection_with_extra_controller_option
+ get :redirect_to_action
+ assert_redirected_to :controller => 'action_pack_assertions', :action => "flash_me", :id => 1, :params => { :panda => 'fun' }
+ end
+
+ def test_redirected_to_url_leadling_slash
+ process :redirect_to_path
+ assert_redirected_to '/some/path'
+ end
+ def test_redirected_to_url_no_leadling_slash
+ process :redirect_to_path
+ assert_redirected_to 'some/path'
+ end
+ def test_redirected_to_url_full_url
+ process :redirect_to_path
+ assert_redirected_to 'http://test.host/some/path'
+ end
+
+ def test_assert_redirection_with_symbol
+ process :redirect_to_controller_with_symbol
+ assert_nothing_raised {
+ assert_redirected_to :controller => "elsewhere", :action => "flash_me"
+ }
+ process :redirect_to_controller_with_symbol
+ assert_nothing_raised {
+ assert_redirected_to :controller => :elsewhere, :action => :flash_me
+ }
+ end
+
+ def test_redirected_to_with_nested_controller
+ @controller = Admin::InnerModuleController.new
+ get :redirect_to_absolute_controller
+ assert_redirected_to :controller => 'content'
+
+ get :redirect_to_fellow_controller
+ assert_redirected_to :controller => 'admin/user'
+ end
+
+ def test_assert_valid
+ get :get_valid_record
+ assert_valid assigns('record')
+ end
+
+ def test_assert_valid_failing
+ get :get_invalid_record
+
+ begin
+ assert_valid assigns('record')
+ assert false
+ rescue Test::Unit::AssertionFailedError => e
+ end
+ end
+end
+
+class ActionPackHeaderTest < Test::Unit::TestCase
+ def setup
+ @controller = ActionPackAssertionsController.new
+ @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
+ end
+
+ def test_rendering_xml_sets_content_type
+ process :hello_xml_world
+ assert_equal('application/xml; charset=utf-8', @response.headers['type'])
+ end
+
+ def test_rendering_xml_respects_content_type
+ @response.headers['type'] = 'application/pdf'
+ process :hello_xml_world
+ assert_equal('application/pdf; charset=utf-8', @response.headers['type'])
+ end
+
+
+ def test_render_text_with_custom_content_type
+ get :render_text_with_custom_content_type
+ assert_equal 'application/rss+xml; charset=utf-8', @response.headers['type']
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/addresses_render_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/addresses_render_test.rb
new file mode 100644
index 000000000..d1e9122dc
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/addresses_render_test.rb
@@ -0,0 +1,43 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class Address
+
+ def Address.count(conditions = nil, join = nil)
+ nil
+ end
+
+ def Address.find_all(arg1, arg2, arg3, arg4)
+ []
+ end
+
+ def self.find(*args)
+ []
+ end
+end
+
+class AddressesTestController < ActionController::Base
+ def self.controller_name; "addresses"; end
+ def self.controller_path; "addresses"; end
+end
+
+AddressesTestController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
+
+class AddressesTest < Test::Unit::TestCase
+ def setup
+ @controller = AddressesTestController.new
+
+ # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
+ # a more accurate simulation of what happens in "real life".
+ @controller.logger = Logger.new(nil)
+
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_list
+ get :list
+ assert_equal "We only need to get this far!", @response.body.chomp
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/assert_select_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/assert_select_test.rb
new file mode 100644
index 000000000..b0f3d6ced
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/assert_select_test.rb
@@ -0,0 +1,682 @@
+#--
+# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
+# Under MIT and/or CC By license.
+#++
+
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+require "#{File.dirname(__FILE__)}/fake_controllers"
+
+
+unless defined?(ActionMailer)
+ begin
+ $:.unshift(File.dirname(__FILE__) + "/../../../actionmailer/lib")
+ require 'action_mailer'
+ rescue LoadError
+ require 'rubygems'
+ gem 'actionmailer'
+ end
+end
+
+class AssertSelectTest < Test::Unit::TestCase
+ class AssertSelectController < ActionController::Base
+ def response_with=(content)
+ @content = content
+ end
+
+ def response_with(&block)
+ @update = block
+ end
+
+ def html()
+ render :text=>@content, :layout=>false, :content_type=>Mime::HTML
+ @content = nil
+ end
+
+ def rjs()
+ render :update do |page|
+ @update.call page
+ end
+ @update = nil
+ end
+
+ def xml()
+ render :text=>@content, :layout=>false, :content_type=>Mime::XML
+ @content = nil
+ end
+
+ def rescue_action(e)
+ raise e
+ end
+ end
+
+ class AssertSelectMailer < ActionMailer::Base
+ def test(html)
+ recipients "test <test@test.host>"
+ from "test@test.host"
+ subject "Test e-mail"
+ part :content_type=>"text/html", :body=>html
+ end
+ end
+
+ AssertionFailedError = Test::Unit::AssertionFailedError
+
+ def setup
+ @controller = AssertSelectController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ ActionMailer::Base.delivery_method = :test
+ ActionMailer::Base.perform_deliveries = true
+ ActionMailer::Base.deliveries = []
+ end
+
+
+ def teardown
+ ActionMailer::Base.deliveries.clear
+ end
+
+ def assert_failure(message, &block)
+ e = assert_raises(AssertionFailedError, &block)
+ assert_match(message, e.message) if Regexp === message
+ assert_equal(message, e.message) if String === message
+ end
+
+ #
+ # Test assert select.
+ #
+
+ def test_assert_select
+ render_html %Q{<div id="1"></div><div id="2"></div>}
+ assert_select "div", 2
+ assert_failure(/Expected at least 3 elements matching \"div\", found 2/) { assert_select "div", 3 }
+ assert_failure(/Expected at least 1 element matching \"p\", found 0/) { assert_select "p" }
+ end
+
+
+ def test_equality_true_false
+ render_html %Q{<div id="1"></div><div id="2"></div>}
+ assert_nothing_raised { assert_select "div" }
+ assert_raises(AssertionFailedError) { assert_select "p" }
+ assert_nothing_raised { assert_select "div", true }
+ assert_raises(AssertionFailedError) { assert_select "p", true }
+ assert_raises(AssertionFailedError) { assert_select "div", false }
+ assert_nothing_raised { assert_select "p", false }
+ end
+
+
+ def test_equality_string_and_regexp
+ render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
+ assert_nothing_raised { assert_select "div", "foo" }
+ assert_raises(AssertionFailedError) { assert_select "div", "bar" }
+ assert_nothing_raised { assert_select "div", :text=>"foo" }
+ assert_raises(AssertionFailedError) { assert_select "div", :text=>"bar" }
+ assert_nothing_raised { assert_select "div", /(foo|bar)/ }
+ assert_raises(AssertionFailedError) { assert_select "div", /foobar/ }
+ assert_nothing_raised { assert_select "div", :text=>/(foo|bar)/ }
+ assert_raises(AssertionFailedError) { assert_select "div", :text=>/foobar/ }
+ assert_raises(AssertionFailedError) { assert_select "p", :text=>/foobar/ }
+ end
+
+
+ def test_equality_of_html
+ render_html %Q{<p>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</p>}
+ text = "\"This is not a big problem,\" he said."
+ html = "<em>\"This is <strong>not</strong> a big problem,\"</em> he said."
+ assert_nothing_raised { assert_select "p", text }
+ assert_raises(AssertionFailedError) { assert_select "p", html }
+ assert_nothing_raised { assert_select "p", :html=>html }
+ assert_raises(AssertionFailedError) { assert_select "p", :html=>text }
+ # No stripping for pre.
+ render_html %Q{<pre>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</pre>}
+ text = "\n\"This is not a big problem,\" he said.\n"
+ html = "\n<em>\"This is <strong>not</strong> a big problem,\"</em> he said.\n"
+ assert_nothing_raised { assert_select "pre", text }
+ assert_raises(AssertionFailedError) { assert_select "pre", html }
+ assert_nothing_raised { assert_select "pre", :html=>html }
+ assert_raises(AssertionFailedError) { assert_select "pre", :html=>text }
+ end
+
+
+ def test_counts
+ render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
+ assert_nothing_raised { assert_select "div", 2 }
+ assert_failure(/Expected at least 3 elements matching \"div\", found 2/) do
+ assert_select "div", 3
+ end
+ assert_nothing_raised { assert_select "div", 1..2 }
+ assert_failure(/Expected between 3 and 4 elements matching \"div\", found 2/) do
+ assert_select "div", 3..4
+ end
+ assert_nothing_raised { assert_select "div", :count=>2 }
+ assert_failure(/Expected at least 3 elements matching \"div\", found 2/) do
+ assert_select "div", :count=>3
+ end
+ assert_nothing_raised { assert_select "div", :minimum=>1 }
+ assert_nothing_raised { assert_select "div", :minimum=>2 }
+ assert_failure(/Expected at least 3 elements matching \"div\", found 2/) do
+ assert_select "div", :minimum=>3
+ end
+ assert_nothing_raised { assert_select "div", :maximum=>2 }
+ assert_nothing_raised { assert_select "div", :maximum=>3 }
+ assert_failure(/Expected at most 1 element matching \"div\", found 2/) do
+ assert_select "div", :maximum=>1
+ end
+ assert_nothing_raised { assert_select "div", :minimum=>1, :maximum=>2 }
+ assert_failure(/Expected between 3 and 4 elements matching \"div\", found 2/) do
+ assert_select "div", :minimum=>3, :maximum=>4
+ end
+ end
+
+
+ def test_substitution_values
+ render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
+ assert_select "div#?", /\d+/ do |elements|
+ assert_equal 2, elements.size
+ end
+ assert_select "div" do
+ assert_select "div#?", /\d+/ do |elements|
+ assert_equal 2, elements.size
+ assert_select "#1"
+ assert_select "#2"
+ end
+ end
+ end
+
+
+ def test_nested_assert_select
+ render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
+ assert_select "div" do |elements|
+ assert_equal 2, elements.size
+ assert_select elements[0], "#1"
+ assert_select elements[1], "#2"
+ end
+ assert_select "div" do
+ assert_select "div" do |elements|
+ assert_equal 2, elements.size
+ # Testing in a group is one thing
+ assert_select "#1,#2"
+ # Testing individually is another.
+ assert_select "#1"
+ assert_select "#2"
+ assert_select "#3", false
+ end
+ end
+
+ assert_failure(/Expected at least 1 element matching \"#4\", found 0\./) do
+ assert_select "div" do
+ assert_select "#4"
+ end
+ end
+ end
+
+
+ def test_assert_select_text_match
+ render_html %Q{<div id="1"><span>foo</span></div><div id="2"><span>bar</span></div>}
+ assert_select "div" do
+ assert_nothing_raised { assert_select "div", "foo" }
+ assert_nothing_raised { assert_select "div", "bar" }
+ assert_nothing_raised { assert_select "div", /\w*/ }
+ assert_nothing_raised { assert_select "div", /\w*/, :count=>2 }
+ assert_raises(AssertionFailedError) { assert_select "div", :text=>"foo", :count=>2 }
+ assert_nothing_raised { assert_select "div", :html=>"<span>bar</span>" }
+ assert_nothing_raised { assert_select "div", :html=>"<span>bar</span>" }
+ assert_nothing_raised { assert_select "div", :html=>/\w*/ }
+ assert_nothing_raised { assert_select "div", :html=>/\w*/, :count=>2 }
+ assert_raises(AssertionFailedError) { assert_select "div", :html=>"<span>foo</span>", :count=>2 }
+ end
+ end
+
+
+ # With single result.
+ def test_assert_select_from_rjs_with_single_result
+ render_rjs do |page|
+ page.replace_html "test", "<div id=\"1\">foo</div>\n<div id=\"2\">foo</div>"
+ end
+ assert_select "div" do |elements|
+ assert elements.size == 2
+ assert_select "#1"
+ assert_select "#2"
+ end
+ assert_select "div#?", /\d+/ do |elements|
+ assert_select "#1"
+ assert_select "#2"
+ end
+ end
+
+ # With multiple results.
+ def test_assert_select_from_rjs_with_multiple_results
+ render_rjs do |page|
+ page.replace_html "test", "<div id=\"1\">foo</div>"
+ page.replace_html "test2", "<div id=\"2\">foo</div>"
+ end
+ assert_select "div" do |elements|
+ assert elements.size == 2
+ assert_select "#1"
+ assert_select "#2"
+ end
+ end
+
+
+ #
+ # Test css_select.
+ #
+
+
+ def test_css_select
+ render_html %Q{<div id="1"></div><div id="2"></div>}
+ assert 2, css_select("div").size
+ assert 0, css_select("p").size
+ end
+
+
+ def test_nested_css_select
+ render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
+ assert_select "div#?", /\d+/ do |elements|
+ assert_equal 1, css_select(elements[0], "div").size
+ assert_equal 1, css_select(elements[1], "div").size
+ end
+ assert_select "div" do
+ assert_equal 2, css_select("div").size
+ css_select("div").each do |element|
+ # Testing as a group is one thing
+ assert !css_select("#1,#2").empty?
+ # Testing individually is another
+ assert !css_select("#1").empty?
+ assert !css_select("#2").empty?
+ end
+ end
+ end
+
+
+ # With one result.
+ def test_css_select_from_rjs_with_single_result
+ render_rjs do |page|
+ page.replace_html "test", "<div id=\"1\">foo</div>\n<div id=\"2\">foo</div>"
+ end
+ assert_equal 2, css_select("div").size
+ assert_equal 1, css_select("#1").size
+ assert_equal 1, css_select("#2").size
+ end
+
+ # With multiple results.
+ def test_css_select_from_rjs_with_multiple_results
+ render_rjs do |page|
+ page.replace_html "test", "<div id=\"1\">foo</div>"
+ page.replace_html "test2", "<div id=\"2\">foo</div>"
+ end
+
+ assert_equal 2, css_select("div").size
+ assert_equal 1, css_select("#1").size
+ assert_equal 1, css_select("#2").size
+ end
+
+
+ #
+ # Test assert_select_rjs.
+ #
+
+
+ # Test that we can pick up all statements in the result.
+ def test_assert_select_rjs_picks_up_all_statements
+ render_rjs do |page|
+ page.replace "test", "<div id=\"1\">foo</div>"
+ page.replace_html "test2", "<div id=\"2\">foo</div>"
+ page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
+ end
+
+ found = false
+ assert_select_rjs do
+ assert_select "#1"
+ assert_select "#2"
+ assert_select "#3"
+ found = true
+ end
+ assert found
+ end
+
+ # Test that we fail if there is nothing to pick.
+ def test_assert_select_rjs_fails_if_nothing_to_pick
+ render_rjs { }
+ assert_raises(AssertionFailedError) { assert_select_rjs }
+ end
+
+ def test_assert_select_rjs_with_unicode
+ # Test that non-ascii characters (which are converted into \uXXXX in RJS) are decoded correctly.
+ render_rjs do |page|
+ page.replace "test", "<div id=\"1\">\343\203\201\343\202\261\343\203\203\343\203\210</div>"
+ end
+ assert_select_rjs do
+ assert_select "#1", :text => "\343\203\201\343\202\261\343\203\203\343\203\210"
+ assert_select "#1", "\343\203\201\343\202\261\343\203\203\343\203\210"
+ assert_select "#1", Regexp.new("\343\203\201..\343\203\210",0,'U')
+ assert_raises(AssertionFailedError) { assert_select "#1", Regexp.new("\343\203\201.\343\203\210",0,'U') }
+ end
+ end
+
+ def test_assert_select_rjs_with_id
+ # Test that we can pick up all statements in the result.
+ render_rjs do |page|
+ page.replace "test1", "<div id=\"1\">foo</div>"
+ page.replace_html "test2", "<div id=\"2\">foo</div>"
+ page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
+ end
+ assert_select_rjs "test1" do
+ assert_select "div", 1
+ assert_select "#1"
+ end
+ assert_select_rjs "test2" do
+ assert_select "div", 1
+ assert_select "#2"
+ end
+ assert_select_rjs "test3" do
+ assert_select "div", 1
+ assert_select "#3"
+ end
+ assert_raises(AssertionFailedError) { assert_select_rjs "test4" }
+ end
+
+
+ def test_assert_select_rjs_for_replace
+ render_rjs do |page|
+ page.replace "test1", "<div id=\"1\">foo</div>"
+ page.replace_html "test2", "<div id=\"2\">foo</div>"
+ page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
+ end
+ # Replace.
+ assert_select_rjs :replace do
+ assert_select "div", 1
+ assert_select "#1"
+ end
+ assert_select_rjs :replace, "test1" do
+ assert_select "div", 1
+ assert_select "#1"
+ end
+ assert_raises(AssertionFailedError) { assert_select_rjs :replace, "test2" }
+ # Replace HTML.
+ assert_select_rjs :replace_html do
+ assert_select "div", 1
+ assert_select "#2"
+ end
+ assert_select_rjs :replace_html, "test2" do
+ assert_select "div", 1
+ assert_select "#2"
+ end
+ assert_raises(AssertionFailedError) { assert_select_rjs :replace_html, "test1" }
+ end
+
+ def test_assert_select_rjs_for_chained_replace
+ render_rjs do |page|
+ page['test1'].replace "<div id=\"1\">foo</div>"
+ page['test2'].replace_html "<div id=\"2\">foo</div>"
+ page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
+ end
+ # Replace.
+ assert_select_rjs :chained_replace do
+ assert_select "div", 1
+ assert_select "#1"
+ end
+ assert_select_rjs :chained_replace, "test1" do
+ assert_select "div", 1
+ assert_select "#1"
+ end
+ assert_raises(AssertionFailedError) { assert_select_rjs :chained_replace, "test2" }
+ # Replace HTML.
+ assert_select_rjs :chained_replace_html do
+ assert_select "div", 1
+ assert_select "#2"
+ end
+ assert_select_rjs :chained_replace_html, "test2" do
+ assert_select "div", 1
+ assert_select "#2"
+ end
+ assert_raises(AssertionFailedError) { assert_select_rjs :replace_html, "test1" }
+ end
+
+ # Simple remove
+ def test_assert_select_rjs_for_remove
+ render_rjs do |page|
+ page.remove "test1"
+ end
+
+ assert_select_rjs :remove, "test1"
+ end
+
+ def test_assert_select_rjs_for_remove_ignores_block
+ render_rjs do |page|
+ page.remove "test1"
+ end
+
+ assert_nothing_raised do
+ assert_select_rjs :remove, "test1" do
+ assert_select "p"
+ end
+ end
+ end
+
+ # Simple show
+ def test_assert_select_rjs_for_show
+ render_rjs do |page|
+ page.show "test1"
+ end
+
+ assert_select_rjs :show, "test1"
+ end
+
+ def test_assert_select_rjs_for_show_ignores_block
+ render_rjs do |page|
+ page.show "test1"
+ end
+
+ assert_nothing_raised do
+ assert_select_rjs :show, "test1" do
+ assert_select "p"
+ end
+ end
+ end
+
+ # Simple hide
+ def test_assert_select_rjs_for_hide
+ render_rjs do |page|
+ page.hide "test1"
+ end
+
+ assert_select_rjs :hide, "test1"
+ end
+
+ def test_assert_select_rjs_for_hide_ignores_block
+ render_rjs do |page|
+ page.hide "test1"
+ end
+
+ assert_nothing_raised do
+ assert_select_rjs :hide, "test1" do
+ assert_select "p"
+ end
+ end
+ end
+
+ # Simple toggle
+ def test_assert_select_rjs_for_toggle
+ render_rjs do |page|
+ page.toggle "test1"
+ end
+
+ assert_select_rjs :toggle, "test1"
+ end
+
+ def test_assert_select_rjs_for_toggle_ignores_block
+ render_rjs do |page|
+ page.toggle "test1"
+ end
+
+ assert_nothing_raised do
+ assert_select_rjs :toggle, "test1" do
+ assert_select "p"
+ end
+ end
+ end
+
+ # Non-positioned insert.
+ def test_assert_select_rjs_for_nonpositioned_insert
+ render_rjs do |page|
+ page.replace "test1", "<div id=\"1\">foo</div>"
+ page.replace_html "test2", "<div id=\"2\">foo</div>"
+ page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
+ end
+ assert_select_rjs :insert_html do
+ assert_select "div", 1
+ assert_select "#3"
+ end
+ assert_select_rjs :insert_html, "test3" do
+ assert_select "div", 1
+ assert_select "#3"
+ end
+ assert_raises(AssertionFailedError) { assert_select_rjs :insert_html, "test1" }
+ end
+
+ # Positioned insert.
+ def test_assert_select_rjs_for_positioned_insert
+ render_rjs do |page|
+ page.insert_html :top, "test1", "<div id=\"1\">foo</div>"
+ page.insert_html :bottom, "test2", "<div id=\"2\">foo</div>"
+ page.insert_html :before, "test3", "<div id=\"3\">foo</div>"
+ page.insert_html :after, "test4", "<div id=\"4\">foo</div>"
+ end
+ assert_select_rjs :insert, :top do
+ assert_select "div", 1
+ assert_select "#1"
+ end
+ assert_select_rjs :insert, :bottom do
+ assert_select "div", 1
+ assert_select "#2"
+ end
+ assert_select_rjs :insert, :before do
+ assert_select "div", 1
+ assert_select "#3"
+ end
+ assert_select_rjs :insert, :after do
+ assert_select "div", 1
+ assert_select "#4"
+ end
+ assert_select_rjs :insert_html do
+ assert_select "div", 4
+ end
+ end
+
+ # Simple selection from a single result.
+ def test_nested_assert_select_rjs_with_single_result
+ render_rjs do |page|
+ page.replace_html "test", "<div id=\"1\">foo</div>\n<div id=\"2\">foo</div>"
+ end
+
+ assert_select_rjs "test" do |elements|
+ assert_equal 2, elements.size
+ assert_select "#1"
+ assert_select "#2"
+ end
+ end
+
+ # Deal with two results.
+ def test_nested_assert_select_rjs_with_two_results
+ render_rjs do |page|
+ page.replace_html "test", "<div id=\"1\">foo</div>"
+ page.replace_html "test2", "<div id=\"2\">foo</div>"
+ end
+
+ assert_select_rjs "test" do |elements|
+ assert_equal 1, elements.size
+ assert_select "#1"
+ end
+
+ assert_select_rjs "test2" do |elements|
+ assert_equal 1, elements.size
+ assert_select "#2"
+ end
+ end
+
+
+ def test_feed_item_encoded
+ render_xml <<-EOF
+<rss version="2.0">
+ <channel>
+ <item>
+ <description>
+ <![CDATA[
+ <p>Test 1</p>
+ ]]>
+ </description>
+ </item>
+ <item>
+ <description>
+ <![CDATA[
+ <p>Test 2</p>
+ ]]>
+ </description>
+ </item>
+ </channel>
+</rss>
+EOF
+ assert_select "channel item description" do
+ # Test element regardless of wrapper.
+ assert_select_encoded do
+ assert_select "p", :count=>2, :text=>/Test/
+ end
+ # Test through encoded wrapper.
+ assert_select_encoded do
+ assert_select "encoded p", :count=>2, :text=>/Test/
+ end
+ # Use :root instead (recommended)
+ assert_select_encoded do
+ assert_select ":root p", :count=>2, :text=>/Test/
+ end
+ # Test individually.
+ assert_select "description" do |elements|
+ assert_select_encoded elements[0] do
+ assert_select "p", "Test 1"
+ end
+ assert_select_encoded elements[1] do
+ assert_select "p", "Test 2"
+ end
+ end
+ end
+
+ # Test that we only un-encode element itself.
+ assert_select "channel item" do
+ assert_select_encoded do
+ assert_select "p", 0
+ end
+ end
+ end
+
+
+ #
+ # Test assert_select_email
+ #
+
+ def test_assert_select_email
+ assert_raises(AssertionFailedError) { assert_select_email {} }
+ AssertSelectMailer.deliver_test "<div><p>foo</p><p>bar</p></div>"
+ assert_select_email do
+ assert_select "div:root" do
+ assert_select "p:first-child", "foo"
+ assert_select "p:last-child", "bar"
+ end
+ end
+ end
+
+
+ protected
+ def render_html(html)
+ @controller.response_with = html
+ get :html
+ end
+
+ def render_rjs(&block)
+ @controller.response_with &block
+ get :rjs
+ end
+
+ def render_xml(xml)
+ @controller.response_with = xml
+ get :xml
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/base_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/base_test.rb
new file mode 100644
index 000000000..60e61b628
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/base_test.rb
@@ -0,0 +1,134 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+require 'test/unit'
+require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
+
+# Provide some controller to run the tests on.
+module Submodule
+ class ContainedEmptyController < ActionController::Base
+ end
+ class ContainedNonEmptyController < ActionController::Base
+ def public_action
+ end
+
+ hide_action :hidden_action
+ def hidden_action
+ raise "Noooo!"
+ end
+
+ def another_hidden_action
+ end
+ hide_action :another_hidden_action
+ end
+ class SubclassedController < ContainedNonEmptyController
+ hide_action :public_action # Hiding it here should not affect the superclass.
+ end
+end
+class EmptyController < ActionController::Base
+end
+class NonEmptyController < ActionController::Base
+ def public_action
+ end
+
+ hide_action :hidden_action
+ def hidden_action
+ end
+end
+
+class MethodMissingController < ActionController::Base
+
+ hide_action :shouldnt_be_called
+ def shouldnt_be_called
+ raise "NO WAY!"
+ end
+
+protected
+
+ def method_missing(selector)
+ render :text => selector.to_s
+ end
+
+end
+
+class ControllerClassTests < Test::Unit::TestCase
+ def test_controller_path
+ assert_equal 'empty', EmptyController.controller_path
+ assert_equal EmptyController.controller_path, EmptyController.new.controller_path
+ assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path
+ assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path
+ end
+ def test_controller_name
+ assert_equal 'empty', EmptyController.controller_name
+ assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name
+ end
+end
+
+class ControllerInstanceTests < Test::Unit::TestCase
+ def setup
+ @empty = EmptyController.new
+ @contained = Submodule::ContainedEmptyController.new
+ @empty_controllers = [@empty, @contained, Submodule::SubclassedController.new]
+
+ @non_empty_controllers = [NonEmptyController.new,
+ Submodule::ContainedNonEmptyController.new]
+ end
+
+ def test_action_methods
+ @empty_controllers.each do |c|
+ hide_mocha_methods_from_controller(c)
+ assert_equal Set.new, c.send!(:action_methods), "#{c.controller_path} should be empty!"
+ end
+ @non_empty_controllers.each do |c|
+ hide_mocha_methods_from_controller(c)
+ assert_equal Set.new(%w(public_action)), c.send!(:action_methods), "#{c.controller_path} should not be empty!"
+ end
+ end
+
+ protected
+ # Mocha adds some public instance methods to Object that would be
+ # considered actions, so explicitly hide_action them.
+ def hide_mocha_methods_from_controller(controller)
+ mocha_methods = [:expects, :metaclass, :mocha, :mocha_inspect, :reset_mocha, :stubba_object, :stubba_method, :stubs, :verify, :__metaclass__, :__is_a__]
+ controller.class.send!(:hide_action, *mocha_methods)
+ end
+end
+
+
+class PerformActionTest < Test::Unit::TestCase
+ def use_controller(controller_class)
+ @controller = controller_class.new
+
+ # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
+ # a more accurate simulation of what happens in "real life".
+ @controller.logger = Logger.new(nil)
+
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_get_on_priv_should_show_selector
+ use_controller MethodMissingController
+ get :shouldnt_be_called
+ assert_response :success
+ assert_equal 'shouldnt_be_called', @response.body
+ end
+
+ def test_method_missing_is_not_an_action_name
+ use_controller MethodMissingController
+ assert ! @controller.send!(:action_methods).include?('method_missing')
+
+ get :method_missing
+ assert_response :success
+ assert_equal 'method_missing', @response.body
+ end
+
+ def test_get_on_hidden_should_fail
+ use_controller NonEmptyController
+ get :hidden_action
+ assert_response 404
+
+ get :another_hidden_action
+ assert_response 404
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/benchmark_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/benchmark_test.rb
new file mode 100644
index 000000000..f346e575e
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/benchmark_test.rb
@@ -0,0 +1,33 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+require 'test/unit'
+
+# Provide some static controllers.
+class BenchmarkedController < ActionController::Base
+ def public_action
+ render :nothing => true
+ end
+
+ def rescue_action(e)
+ raise e
+ end
+end
+
+class BenchmarkTest < Test::Unit::TestCase
+ class MockLogger
+ def method_missing(*args)
+ end
+ end
+
+ def setup
+ @controller = BenchmarkedController.new
+ # benchmark doesn't do anything unless a logger is set
+ @controller.logger = MockLogger.new
+ @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
+ @request.host = "test.actioncontroller.i"
+ end
+
+ def test_with_http_1_0_request
+ @request.host = nil
+ assert_nothing_raised { get :public_action }
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/caching_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/caching_test.rb
new file mode 100644
index 000000000..d6982fbc8
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/caching_test.rb
@@ -0,0 +1,349 @@
+require 'fileutils'
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+CACHE_DIR = 'test_cache'
+# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
+FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR)
+ActionController::Base.page_cache_directory = FILE_STORE_PATH
+ActionController::Base.fragment_cache_store = :file_store, FILE_STORE_PATH
+
+class PageCachingTestController < ActionController::Base
+ caches_page :ok, :no_content, :found, :not_found
+
+ def ok
+ head :ok
+ end
+
+ def no_content
+ head :no_content
+ end
+
+ def found
+ redirect_to :action => 'ok'
+ end
+
+ def not_found
+ head :not_found
+ end
+
+ def custom_path
+ render :text => "Super soaker"
+ cache_page("Super soaker", "/index.html")
+ end
+
+ def expire_custom_path
+ expire_page("/index.html")
+ head :ok
+ end
+
+ def trailing_slash
+ render :text => "Sneak attack"
+ end
+end
+
+class PageCachingTest < Test::Unit::TestCase
+ def setup
+ ActionController::Base.perform_caching = true
+
+ ActionController::Routing::Routes.draw do |map|
+ map.main '', :controller => 'posts'
+ map.resources :posts
+ map.connect ':controller/:action/:id'
+ end
+
+ @request = ActionController::TestRequest.new
+ @request.host = 'hostname.com'
+
+ @response = ActionController::TestResponse.new
+ @controller = PageCachingTestController.new
+
+ @params = {:controller => 'posts', :action => 'index', :only_path => true, :skip_relative_url_root => true}
+ @rewriter = ActionController::UrlRewriter.new(@request, @params)
+
+ FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
+ FileUtils.mkdir_p(FILE_STORE_PATH)
+ end
+
+ def teardown
+ FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
+
+ ActionController::Base.perform_caching = false
+ end
+
+ def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route
+ @params[:format] = 'rss'
+ assert_equal '/posts.rss', @rewriter.rewrite(@params)
+ @params[:format] = nil
+ assert_equal '/', @rewriter.rewrite(@params)
+ end
+
+ def test_should_cache_get_with_ok_status
+ get :ok
+ assert_response :ok
+ assert_page_cached :ok, "get with ok status should have been cached"
+ end
+
+ def test_should_cache_with_custom_path
+ get :custom_path
+ assert File.exist?("#{FILE_STORE_PATH}/index.html")
+ end
+
+ def test_should_expire_cache_with_custom_path
+ get :custom_path
+ assert File.exist?("#{FILE_STORE_PATH}/index.html")
+
+ get :expire_custom_path
+ assert !File.exist?("#{FILE_STORE_PATH}/index.html")
+ end
+
+ def test_should_cache_without_trailing_slash_on_url
+ @controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash'
+ assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html")
+ end
+
+ def test_should_cache_with_trailing_slash_on_url
+ @controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash/'
+ assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html")
+ end
+
+ uses_mocha("should_cache_ok_at_custom_path") do
+ def test_should_cache_ok_at_custom_path
+ @request.expects(:path).returns("/index.html")
+ get :ok
+ assert_response :ok
+ assert File.exist?("#{FILE_STORE_PATH}/index.html")
+ end
+ end
+
+ [:ok, :no_content, :found, :not_found].each do |status|
+ [:get, :post, :put, :delete].each do |method|
+ unless method == :get and status == :ok
+ define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
+ @request.env['REQUEST_METHOD'] = method.to_s.upcase
+ process status
+ assert_response status
+ assert_page_not_cached status, "#{method} with #{status} status shouldn't have been cached"
+ end
+ end
+ end
+ end
+
+ private
+ def assert_page_cached(action, message = "#{action} should have been cached")
+ assert page_cached?(action), message
+ end
+
+ def assert_page_not_cached(action, message = "#{action} shouldn't have been cached")
+ assert !page_cached?(action), message
+ end
+
+ def page_cached?(action)
+ File.exist? "#{FILE_STORE_PATH}/page_caching_test/#{action}.html"
+ end
+end
+
+
+class ActionCachingTestController < ActionController::Base
+ caches_action :index, :redirected, :forbidden
+ caches_action :show, :cache_path => 'http://test.host/custom/show'
+ caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" }
+
+ def index
+ @cache_this = Time.now.to_f.to_s
+ render :text => @cache_this
+ end
+
+ def redirected
+ redirect_to :action => 'index'
+ end
+
+ def forbidden
+ render :text => "Forbidden"
+ headers["Status"] = "403 Forbidden"
+ end
+
+ alias_method :show, :index
+ alias_method :edit, :index
+
+ def expire
+ expire_action :controller => 'action_caching_test', :action => 'index'
+ render :nothing => true
+ end
+
+end
+
+class ActionCachingMockController
+ attr_accessor :mock_url_for
+ attr_accessor :mock_path
+
+ def initialize
+ yield self if block_given?
+ end
+
+ def url_for(*args)
+ @mock_url_for
+ end
+
+ def request
+ mocked_path = @mock_path
+ Object.new.instance_eval(<<-EVAL)
+ def path; '#{@mock_path}' end
+ self
+ EVAL
+ end
+end
+
+class ActionCacheTest < Test::Unit::TestCase
+ def setup
+ reset!
+ FileUtils.mkdir_p(FILE_STORE_PATH)
+ @path_class = ActionController::Caching::Actions::ActionCachePath
+ @mock_controller = ActionCachingMockController.new
+ end
+
+ def teardown
+ FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
+ end
+
+ def test_simple_action_cache
+ get :index
+ cached_time = content_to_cache
+ assert_equal cached_time, @response.body
+ assert_cache_exists 'hostname.com/action_caching_test'
+ reset!
+
+ get :index
+ assert_equal cached_time, @response.body
+ end
+
+ def test_action_cache_with_custom_cache_path
+ get :show
+ cached_time = content_to_cache
+ assert_equal cached_time, @response.body
+ assert_cache_exists 'test.host/custom/show'
+ reset!
+
+ get :show
+ assert_equal cached_time, @response.body
+ end
+
+ def test_action_cache_with_custom_cache_path_in_block
+ get :edit
+ assert_cache_exists 'test.host/edit'
+ reset!
+
+ get :edit, :id => 1
+ assert_cache_exists 'test.host/1;edit'
+ end
+
+ def test_cache_expiration
+ get :index
+ cached_time = content_to_cache
+ reset!
+
+ get :index
+ assert_equal cached_time, @response.body
+ reset!
+
+ get :expire
+ reset!
+
+ get :index
+ new_cached_time = content_to_cache
+ assert_not_equal cached_time, @response.body
+ reset!
+
+ get :index
+ assert_response :success
+ assert_equal new_cached_time, @response.body
+ end
+
+ def test_cache_is_scoped_by_subdomain
+ @request.host = 'jamis.hostname.com'
+ get :index
+ jamis_cache = content_to_cache
+
+ reset!
+
+ @request.host = 'david.hostname.com'
+ get :index
+ david_cache = content_to_cache
+ assert_not_equal jamis_cache, @response.body
+
+ reset!
+
+ @request.host = 'jamis.hostname.com'
+ get :index
+ assert_equal jamis_cache, @response.body
+
+ reset!
+
+ @request.host = 'david.hostname.com'
+ get :index
+ assert_equal david_cache, @response.body
+ end
+
+ def test_redirect_is_not_cached
+ get :redirected
+ assert_response :redirect
+ reset!
+
+ get :redirected
+ assert_response :redirect
+ end
+
+ def test_forbidden_is_not_cached
+ get :forbidden
+ assert_response :forbidden
+ reset!
+
+ get :forbidden
+ assert_response :forbidden
+ end
+
+ def test_xml_version_of_resource_is_treated_as_different_cache
+ @mock_controller.mock_url_for = 'http://example.org/posts/'
+ @mock_controller.mock_path = '/posts/index.xml'
+ path_object = @path_class.new(@mock_controller, {})
+ assert_equal 'xml', path_object.extension
+ assert_equal 'example.org/posts/index.xml', path_object.path
+ end
+
+ def test_correct_content_type_is_returned_for_cache_hit
+ # run it twice to cache it the first time
+ get :index, :id => 'content-type.xml'
+ get :index, :id => 'content-type.xml'
+ assert_equal 'application/xml', @response.content_type
+ end
+
+ def test_empty_path_is_normalized
+ @mock_controller.mock_url_for = 'http://example.org/'
+ @mock_controller.mock_path = '/'
+
+ assert_equal 'example.org/index', @path_class.path_for(@mock_controller, {})
+ end
+
+ def test_file_extensions
+ get :index, :id => 'kitten.jpg'
+ get :index, :id => 'kitten.jpg'
+
+ assert_response :success
+ end
+
+ private
+ def content_to_cache
+ assigns(:cache_this)
+ end
+
+ def reset!
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = ActionCachingTestController.new
+ @request.host = 'hostname.com'
+ end
+
+ def assert_cache_exists(path)
+ full_path = File.join(FILE_STORE_PATH, path + '.cache')
+ assert File.exist?(full_path), "#{full_path.inspect} does not exist."
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/capture_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/capture_test.rb
new file mode 100644
index 000000000..7ec5f32a3
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/capture_test.rb
@@ -0,0 +1,89 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class CaptureController < ActionController::Base
+ def self.controller_name; "test"; end
+ def self.controller_path; "test"; end
+
+ def content_for
+ render :layout => "talk_from_action"
+ end
+
+ def content_for_with_parameter
+ render :layout => "talk_from_action"
+ end
+
+ def content_for_concatenated
+ render :layout => "talk_from_action"
+ end
+
+ def erb_content_for
+ render :layout => "talk_from_action"
+ end
+
+ def block_content_for
+ render :layout => "talk_from_action"
+ end
+
+ def non_erb_block_content_for
+ render :layout => "talk_from_action"
+ end
+
+ def rescue_action(e) raise end
+end
+
+CaptureController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
+
+class CaptureTest < Test::Unit::TestCase
+ def setup
+ @controller = CaptureController.new
+
+ # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
+ # a more accurate simulation of what happens in "real life".
+ @controller.logger = Logger.new(nil)
+
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_simple_capture
+ get :capturing
+ assert_equal "Dreamy days", @response.body.strip
+ end
+
+ def test_content_for
+ get :content_for
+ assert_equal expected_content_for_output, @response.body
+ end
+
+ def test_should_concatentate_content_for
+ get :content_for_concatenated
+ assert_equal expected_content_for_output, @response.body
+ end
+
+ def test_erb_content_for
+ get :erb_content_for
+ assert_equal expected_content_for_output, @response.body
+ end
+
+ def test_should_set_content_for_with_parameter
+ get :content_for_with_parameter
+ assert_equal expected_content_for_output, @response.body
+ end
+
+ def test_block_content_for
+ get :block_content_for
+ assert_equal expected_content_for_output, @response.body
+ end
+
+ def test_non_erb_block_content_for
+ get :non_erb_block_content_for
+ assert_equal expected_content_for_output, @response.body
+ end
+
+ private
+ def expected_content_for_output
+ "<title>Putting stuff in the title!</title>\n\nGreat stuff!"
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/cgi_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/cgi_test.rb
new file mode 100755
index 000000000..021781df9
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/cgi_test.rb
@@ -0,0 +1,115 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+require 'action_controller/cgi_process'
+
+class BaseCgiTest < Test::Unit::TestCase
+ def setup
+ @request_hash = {"HTTP_MAX_FORWARDS"=>"10", "SERVER_NAME"=>"glu.ttono.us:8007", "FCGI_ROLE"=>"RESPONDER", "HTTP_X_FORWARDED_HOST"=>"glu.ttono.us", "HTTP_ACCEPT_ENCODING"=>"gzip, deflate", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", "PATH_INFO"=>"", "HTTP_ACCEPT_LANGUAGE"=>"en", "HTTP_HOST"=>"glu.ttono.us:8007", "SERVER_PROTOCOL"=>"HTTP/1.1", "REDIRECT_URI"=>"/dispatch.fcgi", "SCRIPT_NAME"=>"/dispatch.fcgi", "SERVER_ADDR"=>"207.7.108.53", "REMOTE_ADDR"=>"207.7.108.53", "SERVER_SOFTWARE"=>"lighttpd/1.4.5", "HTTP_COOKIE"=>"_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes", "HTTP_X_FORWARDED_SERVER"=>"glu.ttono.us", "REQUEST_URI"=>"/admin", "DOCUMENT_ROOT"=>"/home/kevinc/sites/typo/public", "SERVER_PORT"=>"8007", "QUERY_STRING"=>"", "REMOTE_PORT"=>"63137", "GATEWAY_INTERFACE"=>"CGI/1.1", "HTTP_X_FORWARDED_FOR"=>"65.88.180.234", "HTTP_ACCEPT"=>"*/*", "SCRIPT_FILENAME"=>"/home/kevinc/sites/typo/public/dispatch.fcgi", "REDIRECT_STATUS"=>"200", "REQUEST_METHOD"=>"GET"}
+ # cookie as returned by some Nokia phone browsers (no space after semicolon separator)
+ @alt_cookie_fmt_request_hash = {"HTTP_COOKIE"=>"_session_id=c84ace84796670c052c6ceb2451fb0f2;is_admin=yes"}
+ @fake_cgi = Struct.new(:env_table).new(@request_hash)
+ @request = ActionController::CgiRequest.new(@fake_cgi)
+ end
+
+ def default_test; end
+end
+
+
+class CgiRequestTest < BaseCgiTest
+ def test_proxy_request
+ assert_equal 'glu.ttono.us', @request.host_with_port
+ end
+
+ def test_http_host
+ @request_hash.delete "HTTP_X_FORWARDED_HOST"
+ @request_hash['HTTP_HOST'] = "rubyonrails.org:8080"
+ assert_equal "rubyonrails.org:8080", @request.host_with_port
+
+ @request_hash['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
+ assert_equal "www.secondhost.org", @request.host
+ end
+
+ def test_http_host_with_default_port_overrides_server_port
+ @request_hash.delete "HTTP_X_FORWARDED_HOST"
+ @request_hash['HTTP_HOST'] = "rubyonrails.org"
+ assert_equal "rubyonrails.org", @request.host_with_port
+ end
+
+ def test_host_with_port_defaults_to_server_name_if_no_host_headers
+ @request_hash.delete "HTTP_X_FORWARDED_HOST"
+ @request_hash.delete "HTTP_HOST"
+ assert_equal "glu.ttono.us:8007", @request.host_with_port
+ end
+
+ def test_host_with_port_falls_back_to_server_addr_if_necessary
+ @request_hash.delete "HTTP_X_FORWARDED_HOST"
+ @request_hash.delete "HTTP_HOST"
+ @request_hash.delete "SERVER_NAME"
+ assert_equal "207.7.108.53:8007", @request.host_with_port
+ end
+
+ def test_host_with_port_if_http_standard_port_is_specified
+ @request_hash['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:80"
+ assert_equal "glu.ttono.us", @request.host_with_port
+ end
+
+ def test_host_with_port_if_https_standard_port_is_specified
+ @request_hash['HTTP_X_FORWARDED_PROTO'] = "https"
+ @request_hash['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:443"
+ assert_equal "glu.ttono.us", @request.host_with_port
+ end
+
+ def test_host_if_ipv6_reference
+ @request_hash.delete "HTTP_X_FORWARDED_HOST"
+ @request_hash['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]"
+ assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
+ end
+
+ def test_host_if_ipv6_reference_with_port
+ @request_hash.delete "HTTP_X_FORWARDED_HOST"
+ @request_hash['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]:8008"
+ assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
+ end
+
+ def test_cookie_syntax_resilience
+ cookies = CGI::Cookie::parse(@request_hash["HTTP_COOKIE"]);
+ assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"], cookies.inspect
+ assert_equal ["yes"], cookies["is_admin"], cookies.inspect
+
+ alt_cookies = CGI::Cookie::parse(@alt_cookie_fmt_request_hash["HTTP_COOKIE"]);
+ assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], alt_cookies["_session_id"], alt_cookies.inspect
+ assert_equal ["yes"], alt_cookies["is_admin"], alt_cookies.inspect
+ end
+end
+
+
+class CgiRequestParamsParsingTest < BaseCgiTest
+ def test_doesnt_break_when_content_type_has_charset
+ data = 'flamenco=love'
+ @request.env['CONTENT_LENGTH'] = data.length
+ @request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
+ @request.env['RAW_POST_DATA'] = data
+ assert_equal({"flamenco"=> "love"}, @request.request_parameters)
+ end
+
+ def test_doesnt_interpret_request_uri_as_query_string_when_missing
+ @request.env['REQUEST_URI'] = 'foo'
+ assert_equal({}, @request.query_parameters)
+ end
+end
+
+
+class CgiRequestNeedsRewoundTest < BaseCgiTest
+ def test_body_should_be_rewound
+ data = 'foo'
+ fake_cgi = Struct.new(:env_table, :query_string, :stdinput).new(@request_hash, '', StringIO.new(data))
+ fake_cgi.env_table['CONTENT_LENGTH'] = data.length
+ fake_cgi.env_table['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
+
+ # Read the request body by parsing params.
+ request = ActionController::CgiRequest.new(fake_cgi)
+ request.request_parameters
+
+ # Should have rewound the body.
+ assert_equal 0, request.body.pos
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/components_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/components_test.rb
new file mode 100644
index 000000000..debd8a274
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/components_test.rb
@@ -0,0 +1,140 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class CallerController < ActionController::Base
+ def calling_from_controller
+ render_component(:controller => "callee", :action => "being_called")
+ end
+
+ def calling_from_controller_with_params
+ render_component(:controller => "callee", :action => "being_called", :params => { "name" => "David" })
+ end
+
+ def calling_from_controller_with_different_status_code
+ render_component(:controller => "callee", :action => "blowing_up")
+ end
+
+ def calling_from_template
+ render :inline => "Ring, ring: <%= render_component(:controller => 'callee', :action => 'being_called') %>"
+ end
+
+ def internal_caller
+ render :inline => "Are you there? <%= render_component(:action => 'internal_callee') %>"
+ end
+
+ def internal_callee
+ render :text => "Yes, ma'am"
+ end
+
+ def set_flash
+ render_component(:controller => "callee", :action => "set_flash")
+ end
+
+ def use_flash
+ render_component(:controller => "callee", :action => "use_flash")
+ end
+
+ def calling_redirected
+ render_component(:controller => "callee", :action => "redirected")
+ end
+
+ def calling_redirected_as_string
+ render :inline => "<%= render_component(:controller => 'callee', :action => 'redirected') %>"
+ end
+
+ def rescue_action(e) raise end
+end
+
+class CalleeController < ActionController::Base
+ def being_called
+ render :text => "#{params[:name] || "Lady"} of the House, speaking"
+ end
+
+ def blowing_up
+ render :text => "It's game over, man, just game over, man!", :status => 500
+ end
+
+ def set_flash
+ flash[:notice] = 'My stoney baby'
+ render :text => 'flash is set'
+ end
+
+ def use_flash
+ render :text => flash[:notice] || 'no flash'
+ end
+
+ def redirected
+ redirect_to :controller => "callee", :action => "being_called"
+ end
+
+ def rescue_action(e) raise end
+end
+
+class ComponentsTest < Test::Unit::TestCase
+ def setup
+ @controller = CallerController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_calling_from_controller
+ get :calling_from_controller
+ assert_equal "Lady of the House, speaking", @response.body
+ end
+
+ def test_calling_from_controller_with_params
+ get :calling_from_controller_with_params
+ assert_equal "David of the House, speaking", @response.body
+ end
+
+ def test_calling_from_controller_with_different_status_code
+ get :calling_from_controller_with_different_status_code
+ assert_equal 500, @response.response_code
+ end
+
+ def test_calling_from_template
+ get :calling_from_template
+ assert_equal "Ring, ring: Lady of the House, speaking", @response.body
+ end
+
+ def test_etag_is_set_for_parent_template_when_calling_from_template
+ get :calling_from_template
+ expected_etag = etag_for("Ring, ring: Lady of the House, speaking")
+ assert_equal expected_etag, @response.headers['ETag']
+ end
+
+ def test_internal_calling
+ get :internal_caller
+ assert_equal "Are you there? Yes, ma'am", @response.body
+ end
+
+ def test_flash
+ get :set_flash
+ assert_equal 'My stoney baby', flash[:notice]
+ get :use_flash
+ assert_equal 'My stoney baby', @response.body
+ get :use_flash
+ assert_equal 'no flash', @response.body
+ end
+
+ def test_component_redirect_redirects
+ get :calling_redirected
+
+ assert_redirected_to :action => "being_called"
+ end
+
+ def test_component_multiple_redirect_redirects
+ test_component_redirect_redirects
+ test_internal_calling
+ end
+
+ def test_component_as_string_redirect_renders_redirected_action
+ get :calling_redirected_as_string
+
+ assert_equal "Lady of the House, speaking", @response.body
+ end
+
+ protected
+ def etag_for(text)
+ %("#{Digest::MD5.hexdigest(text)}")
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/content_type_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/content_type_test.rb
new file mode 100644
index 000000000..1841d37c0
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/content_type_test.rb
@@ -0,0 +1,139 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class ContentTypeController < ActionController::Base
+ def render_content_type_from_body
+ response.content_type = Mime::RSS
+ render :text => "hello world!"
+ end
+
+ def render_defaults
+ render :text => "hello world!"
+ end
+
+ def render_content_type_from_render
+ render :text => "hello world!", :content_type => Mime::RSS
+ end
+
+ def render_charset_from_body
+ response.charset = "utf-16"
+ render :text => "hello world!"
+ end
+
+ def render_default_for_rhtml
+ end
+
+ def render_default_for_rxml
+ end
+
+ def render_default_for_rjs
+ end
+
+ def render_change_for_rxml
+ response.content_type = Mime::HTML
+ render :action => "render_default_for_rxml"
+ end
+
+ def render_default_content_types_for_respond_to
+ respond_to do |format|
+ format.html { render :text => "hello world!" }
+ format.xml { render :action => "render_default_content_types_for_respond_to.rhtml" }
+ format.js { render :text => "hello world!" }
+ format.rss { render :text => "hello world!", :content_type => Mime::XML }
+ end
+ end
+
+ def rescue_action(e) raise end
+end
+
+ContentTypeController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
+
+class ContentTypeTest < Test::Unit::TestCase
+ def setup
+ @controller = ContentTypeController.new
+
+ # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
+ # a more accurate simulation of what happens in "real life".
+ @controller.logger = Logger.new(nil)
+
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_render_defaults
+ get :render_defaults
+ assert_equal "utf-8", @response.charset
+ assert_equal Mime::HTML, @response.content_type
+ end
+
+ def test_render_changed_charset_default
+ ContentTypeController.default_charset = "utf-16"
+ get :render_defaults
+ assert_equal "utf-16", @response.charset
+ assert_equal Mime::HTML, @response.content_type
+ ContentTypeController.default_charset = "utf-8"
+ end
+
+ def test_content_type_from_body
+ get :render_content_type_from_body
+ assert_equal "application/rss+xml", @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ def test_content_type_from_render
+ get :render_content_type_from_render
+ assert_equal "application/rss+xml", @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ def test_charset_from_body
+ get :render_charset_from_body
+ assert_equal "utf-16", @response.charset
+ assert_equal Mime::HTML, @response.content_type
+ end
+
+ def test_default_for_rhtml
+ get :render_default_for_rhtml
+ assert_equal Mime::HTML, @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ def test_default_for_rxml
+ get :render_default_for_rxml
+ assert_equal Mime::XML, @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ def test_default_for_rjs
+ xhr :post, :render_default_for_rjs
+ assert_equal Mime::JS, @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ def test_change_for_rxml
+ get :render_change_for_rxml
+ assert_equal Mime::HTML, @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ def test_render_default_content_types_for_respond_to
+ @request.env["HTTP_ACCEPT"] = Mime::HTML.to_s
+ get :render_default_content_types_for_respond_to
+ assert_equal Mime::HTML, @response.content_type
+
+ @request.env["HTTP_ACCEPT"] = Mime::JS.to_s
+ get :render_default_content_types_for_respond_to
+ assert_equal Mime::JS, @response.content_type
+ end
+
+ def test_render_default_content_types_for_respond_to_with_template
+ @request.env["HTTP_ACCEPT"] = Mime::XML.to_s
+ get :render_default_content_types_for_respond_to
+ assert_equal Mime::XML, @response.content_type
+ end
+
+ def test_render_default_content_types_for_respond_to_with_overwrite
+ @request.env["HTTP_ACCEPT"] = Mime::RSS.to_s
+ get :render_default_content_types_for_respond_to
+ assert_equal Mime::XML, @response.content_type
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb b/vendor/rails-2.0.2/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb b/vendor/rails-2.0.2/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb b/vendor/rails-2.0.2/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/cookie_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/cookie_test.rb
new file mode 100644
index 000000000..6a833fee3
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/cookie_test.rb
@@ -0,0 +1,135 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class CookieTest < Test::Unit::TestCase
+ class TestController < ActionController::Base
+ def authenticate
+ cookies["user_name"] = "david"
+ end
+
+ def authenticate_for_fourteen_days
+ cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) }
+ end
+
+ def authenticate_for_fourteen_days_with_symbols
+ cookies[:user_name] = { :value => "david", :expires => Time.local(2005, 10, 10) }
+ end
+
+ def set_multiple_cookies
+ cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) }
+ cookies["login"] = "XJ-122"
+ end
+
+ def access_frozen_cookies
+ cookies["will"] = "work"
+ end
+
+ def logout
+ cookies.delete("user_name")
+ end
+
+ def delete_cookie_with_path
+ cookies.delete("user_name", :path => '/beaten')
+ render :text => "hello world"
+ end
+
+ def authenticate_with_http_only
+ cookies["user_name"] = { :value => "david", :http_only => true }
+ end
+
+ def rescue_action(e)
+ raise unless ActionController::MissingTemplate # No templates here, and we don't care about the output
+ end
+ end
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @controller = TestController.new
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_setting_cookie
+ get :authenticate
+ assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david") ], @response.headers["cookie"]
+ end
+
+ def test_setting_cookie_for_fourteen_days
+ get :authenticate_for_fourteen_days
+ assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], @response.headers["cookie"]
+ end
+
+ def test_setting_cookie_for_fourteen_days_with_symbols
+ get :authenticate_for_fourteen_days
+ assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], @response.headers["cookie"]
+ end
+
+ def test_setting_cookie_with_http_only
+ get :authenticate_with_http_only
+ assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "http_only" => true) ], @response.headers["cookie"]
+ assert_equal CGI::Cookie::new("name" => "user_name", "value" => "david", "path" => "/", "http_only" => true).to_s, @response.headers["cookie"][0].to_s
+ end
+
+ def test_multiple_cookies
+ get :set_multiple_cookies
+ assert_equal 2, @response.cookies.size
+ end
+
+ def test_setting_test_cookie
+ assert_nothing_raised { get :access_frozen_cookies }
+ end
+
+ def test_expiring_cookie
+ get :logout
+ assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "", "expires" => Time.at(0)) ], @response.headers["cookie"]
+ end
+
+ def test_cookiejar_accessor
+ @request.cookies["user_name"] = CGI::Cookie.new("name" => "user_name", "value" => "david", "expires" => Time.local(2025, 10, 10))
+ @controller.request = @request
+ jar = ActionController::CookieJar.new(@controller)
+ assert_equal "david", jar["user_name"]
+ assert_equal nil, jar["something_else"]
+ end
+
+ def test_cookiejar_accessor_with_array_value
+ a = %w{1 2 3}
+ @request.cookies["pages"] = CGI::Cookie.new("name" => "pages", "value" => a, "expires" => Time.local(2025, 10, 10))
+ @controller.request = @request
+ jar = ActionController::CookieJar.new(@controller)
+ assert_equal a, jar["pages"]
+ end
+
+ def test_delete_cookie_with_path
+ get :delete_cookie_with_path
+ assert_equal "/beaten", @response.headers["cookie"].first.path
+ assert_not_equal "/", @response.headers["cookie"].first.path
+ end
+
+ def test_cookie_to_s_simple_values
+ assert_equal 'myname=myvalue; path=', CGI::Cookie.new('myname', 'myvalue').to_s
+ end
+
+ def test_cookie_to_s_hash
+ cookie_str = CGI::Cookie.new(
+ 'name' => 'myname',
+ 'value' => 'myvalue',
+ 'domain' => 'mydomain',
+ 'path' => 'mypath',
+ 'expires' => Time.utc(2007, 10, 20),
+ 'secure' => true,
+ 'http_only' => true).to_s
+ assert_equal 'myname=myvalue; domain=mydomain; path=mypath; expires=Sat, 20 Oct 2007 00:00:00 GMT; secure; HttpOnly', cookie_str
+ end
+
+ def test_cookie_to_s_hash_default_not_secure_not_http_only
+ cookie_str = CGI::Cookie.new(
+ 'name' => 'myname',
+ 'value' => 'myvalue',
+ 'domain' => 'mydomain',
+ 'path' => 'mypath',
+ 'expires' => Time.utc(2007, 10, 20))
+ assert cookie_str !~ /secure/
+ assert cookie_str !~ /HttpOnly/
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/custom_handler_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/custom_handler_test.rb
new file mode 100644
index 000000000..2747a0f3c
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/custom_handler_test.rb
@@ -0,0 +1,41 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class CustomHandler
+ def initialize( view )
+ @view = view
+ end
+
+ def render( template, local_assigns )
+ [ template,
+ local_assigns,
+ @view ]
+ end
+end
+
+class CustomHandlerTest < Test::Unit::TestCase
+ def setup
+ ActionView::Base.register_template_handler "foo", CustomHandler
+ ActionView::Base.register_template_handler :foo2, CustomHandler
+ @view = ActionView::Base.new
+ end
+
+ def test_custom_render
+ result = @view.render_template( "foo", "hello <%= one %>", nil, :one => "two" )
+ assert_equal(
+ [ "hello <%= one %>", { :one => "two" }, @view ],
+ result )
+ end
+
+ def test_custom_render2
+ result = @view.render_template( "foo2", "hello <%= one %>", nil, :one => "two" )
+ assert_equal(
+ [ "hello <%= one %>", { :one => "two" }, @view ],
+ result )
+ end
+
+ def test_unhandled_extension
+ # uses the ERb handler by default if the extension isn't recognized
+ result = @view.render_template( "bar", "hello <%= one %>", nil, :one => "two" )
+ assert_equal "hello two", result
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb
new file mode 100644
index 000000000..6d7157e1a
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb
@@ -0,0 +1,37 @@
+require File.dirname(__FILE__) + '/../../abstract_unit'
+
+class DeprecatedBaseMethodsTest < Test::Unit::TestCase
+ class Target < ActionController::Base
+
+ def home_url(greeting)
+ "http://example.com/#{greeting}"
+ end
+
+ def raises_name_error
+ this_method_doesnt_exist
+ end
+
+ def rescue_action(e) raise e end
+ end
+
+ Target.view_paths = [ File.dirname(__FILE__) + "/../../fixtures" ]
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = Target.new
+ end
+
+ def test_log_error_silences_deprecation_warnings
+ get :raises_name_error
+ rescue => e
+ assert_not_deprecated { @controller.send :log_error, e }
+ end
+
+ def test_assertion_failed_error_silences_deprecation_warnings
+ get :raises_name_error
+ rescue => e
+ error = Test::Unit::Error.new('testing ur doodz', e)
+ assert_not_deprecated { error.message }
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/dispatcher_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/dispatcher_test.rb
new file mode 100644
index 000000000..ec937ebfd
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/dispatcher_test.rb
@@ -0,0 +1,123 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+uses_mocha 'dispatcher tests' do
+
+require 'action_controller/dispatcher'
+
+class DispatcherTest < Test::Unit::TestCase
+ Dispatcher = ActionController::Dispatcher
+
+ def setup
+ @output = StringIO.new
+ ENV['REQUEST_METHOD'] = 'GET'
+
+ Dispatcher.callbacks[:prepare].clear
+ @dispatcher = Dispatcher.new(@output)
+ end
+
+ def teardown
+ ENV['REQUEST_METHOD'] = nil
+ end
+
+ def test_clears_dependencies_after_dispatch_if_in_loading_mode
+ Dependencies.stubs(:load?).returns(true)
+
+ ActionController::Routing::Routes.expects(:reload).once
+ Dependencies.expects(:clear).once
+
+ dispatch
+ end
+
+ def test_leaves_dependencies_after_dispatch_if_not_in_loading_mode
+ Dependencies.stubs(:load?).returns(false)
+
+ ActionController::Routing::Routes.expects(:reload).never
+ Dependencies.expects(:clear).never
+
+ dispatch
+ end
+
+ def test_failsafe_response
+ CGI.expects(:new).raises('some multipart parsing failure')
+
+ ActionController::Routing::Routes.stubs(:reload)
+ Dispatcher.stubs(:log_failsafe_exception)
+
+ assert_nothing_raised { dispatch }
+
+ assert_equal "Status: 400 Bad Request\r\nContent-Type: text/html\r\n\r\n<html><body><h1>400 Bad Request</h1></body></html>", @output.string
+ end
+
+ def test_reload_application_sets_unprepared_if_loading_dependencies
+ Dependencies.stubs(:load?).returns(false)
+ ActionController::Routing::Routes.expects(:reload).never
+ @dispatcher.unprepared = false
+ @dispatcher.send!(:reload_application)
+ assert !@dispatcher.unprepared
+
+ Dependencies.stubs(:load?).returns(true)
+ ActionController::Routing::Routes.expects(:reload).once
+ @dispatcher.send!(:reload_application)
+ assert @dispatcher.unprepared
+ end
+
+ def test_prepare_application_runs_callbacks_if_unprepared
+ a = b = c = nil
+ Dispatcher.to_prepare { a = b = c = 1 }
+ Dispatcher.to_prepare { b = c = 2 }
+ Dispatcher.to_prepare { c = 3 }
+
+ # Skip the callbacks when already prepared.
+ @dispatcher.unprepared = false
+ @dispatcher.send! :prepare_application
+ assert_nil a || b || c
+
+ # Perform the callbacks when unprepared.
+ @dispatcher.unprepared = true
+ @dispatcher.send! :prepare_application
+ assert_equal 1, a
+ assert_equal 2, b
+ assert_equal 3, c
+
+ # But when not :load, make sure they are only run once
+ a = b = c = nil
+ @dispatcher.send! :prepare_application
+ assert_nil a || b || c
+ end
+
+ def test_to_prepare_with_identifier_replaces
+ a = b = nil
+ Dispatcher.to_prepare(:unique_id) { a = b = 1 }
+ Dispatcher.to_prepare(:unique_id) { a = 2 }
+
+ @dispatcher.unprepared = true
+ @dispatcher.send! :prepare_application
+ assert_equal 2, a
+ assert_equal nil, b
+ end
+
+ def test_to_prepare_only_runs_once_if_not_loading_dependencies
+ Dependencies.stubs(:load?).returns(false)
+ called = 0
+ Dispatcher.to_prepare(:unprepared_test) { called += 1 }
+ 2.times { dispatch }
+ assert_equal 1, called
+ end
+
+ private
+ def dispatch(output = @output)
+ controller = mock
+ controller.stubs(:process).returns(controller)
+ controller.stubs(:out).with(output).returns('response')
+
+ ActionController::Routing::Routes.stubs(:recognize).returns(controller)
+
+ Dispatcher.dispatch(nil, {}, output)
+ end
+
+ def assert_subclasses(howmany, klass, message = klass.subclasses.inspect)
+ assert_equal howmany, klass.subclasses.size, message
+ end
+end
+
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/fake_controllers.rb b/vendor/rails-2.0.2/actionpack/test/controller/fake_controllers.rb
new file mode 100644
index 000000000..5f958b284
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/fake_controllers.rb
@@ -0,0 +1,16 @@
+class << Object; alias_method :const_available?, :const_defined?; end
+
+class ContentController < Class.new(ActionController::Base)
+end
+class NotAController
+end
+module Admin
+ class << self; alias_method :const_available?, :const_defined?; end
+ class UserController < Class.new(ActionController::Base); end
+ class NewsFeedController < Class.new(ActionController::Base); end
+end
+
+ActionController::Routing::Routes.draw do |map|
+ map.route_one 'route_one', :controller => 'elsewhere', :action => 'flash_me'
+ map.connect ':controller/:action/:id'
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/fake_models.rb b/vendor/rails-2.0.2/actionpack/test/controller/fake_models.rb
new file mode 100644
index 000000000..2761b09f2
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/fake_models.rb
@@ -0,0 +1,5 @@
+class Customer < Struct.new(:name, :id)
+ def to_param
+ id.to_s
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/filter_params_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/filter_params_test.rb
new file mode 100644
index 000000000..7b810b162
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/filter_params_test.rb
@@ -0,0 +1,43 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class FilterParamController < ActionController::Base
+end
+
+class FilterParamTest < Test::Unit::TestCase
+ def setup
+ @controller = FilterParamController.new
+ end
+
+ def test_filter_parameters
+ assert FilterParamController.respond_to?(:filter_parameter_logging)
+ assert !@controller.respond_to?(:filter_parameters)
+
+ FilterParamController.filter_parameter_logging
+ assert @controller.respond_to?(:filter_parameters)
+
+ test_hashes = [[{},{},[]],
+ [{'foo'=>nil},{'foo'=>nil},[]],
+ [{'foo'=>'bar'},{'foo'=>'bar'},[]],
+ [{'foo'=>'bar'},{'foo'=>'bar'},%w'food'],
+ [{'foo'=>'bar'},{'foo'=>'[FILTERED]'},%w'foo'],
+ [{'foo'=>'bar', 'bar'=>'foo'},{'foo'=>'[FILTERED]', 'bar'=>'foo'},%w'foo baz'],
+ [{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'],
+ [{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'],
+ [{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana']]
+
+ test_hashes.each do |before_filter, after_filter, filter_words|
+ FilterParamController.filter_parameter_logging(*filter_words)
+ assert_equal after_filter, @controller.filter_parameters(before_filter)
+
+ filter_words.push('blah')
+ FilterParamController.filter_parameter_logging(*filter_words) do |key, value|
+ value.reverse! if key =~ /bargain/
+ end
+
+ before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}}
+ after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}}
+
+ assert_equal after_filter, @controller.filter_parameters(before_filter)
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/filters_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/filters_test.rb
new file mode 100644
index 000000000..188e75afd
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/filters_test.rb
@@ -0,0 +1,856 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+# FIXME: crashes Ruby 1.9
+class FilterTest < Test::Unit::TestCase
+ class TestController < ActionController::Base
+ before_filter :ensure_login
+ after_filter :clean_up
+
+ def show
+ render :inline => "ran action"
+ end
+
+ private
+ def ensure_login
+ @ran_filter ||= []
+ @ran_filter << "ensure_login"
+ end
+
+ def clean_up
+ @ran_after_filter ||= []
+ @ran_after_filter << "clean_up"
+ end
+ end
+
+ class ChangingTheRequirementsController < TestController
+ before_filter :ensure_login, :except => [:go_wild]
+
+ def go_wild
+ render :text => "gobble"
+ end
+ end
+
+ class TestMultipleFiltersController < ActionController::Base
+ before_filter :try_1
+ before_filter :try_2
+ before_filter :try_3
+
+ (1..3).each do |i|
+ define_method "fail_#{i}" do
+ render :text => i.to_s
+ end
+ end
+
+ protected
+ (1..3).each do |i|
+ define_method "try_#{i}" do
+ instance_variable_set :@try, i
+ if action_name == "fail_#{i}"
+ head(404)
+ end
+ end
+ end
+ end
+
+ class RenderingController < ActionController::Base
+ before_filter :render_something_else
+
+ def show
+ @ran_action = true
+ render :inline => "ran action"
+ end
+
+ private
+ def render_something_else
+ render :inline => "something else"
+ end
+ end
+
+ class ConditionalFilterController < ActionController::Base
+ def show
+ render :inline => "ran action"
+ end
+
+ def another_action
+ render :inline => "ran action"
+ end
+
+ def show_without_filter
+ render :inline => "ran action without filter"
+ end
+
+ private
+ def ensure_login
+ @ran_filter ||= []
+ @ran_filter << "ensure_login"
+ end
+
+ def clean_up_tmp
+ @ran_filter ||= []
+ @ran_filter << "clean_up_tmp"
+ end
+
+ def rescue_action(e) raise(e) end
+ end
+
+ class ConditionalCollectionFilterController < ConditionalFilterController
+ before_filter :ensure_login, :except => [ :show_without_filter, :another_action ]
+ end
+
+ class OnlyConditionSymController < ConditionalFilterController
+ before_filter :ensure_login, :only => :show
+ end
+
+ class ExceptConditionSymController < ConditionalFilterController
+ before_filter :ensure_login, :except => :show_without_filter
+ end
+
+ class BeforeAndAfterConditionController < ConditionalFilterController
+ before_filter :ensure_login, :only => :show
+ after_filter :clean_up_tmp, :only => :show
+ end
+
+ class OnlyConditionProcController < ConditionalFilterController
+ before_filter(:only => :show) {|c| c.assigns["ran_proc_filter"] = true }
+ end
+
+ class ExceptConditionProcController < ConditionalFilterController
+ before_filter(:except => :show_without_filter) {|c| c.assigns["ran_proc_filter"] = true }
+ end
+
+ class ConditionalClassFilter
+ def self.filter(controller) controller.assigns["ran_class_filter"] = true end
+ end
+
+ class OnlyConditionClassController < ConditionalFilterController
+ before_filter ConditionalClassFilter, :only => :show
+ end
+
+ class ExceptConditionClassController < ConditionalFilterController
+ before_filter ConditionalClassFilter, :except => :show_without_filter
+ end
+
+ class AnomolousYetValidConditionController < ConditionalFilterController
+ before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.assigns["ran_proc_filter1"] = true }, :except => :show_without_filter) { |c| c.assigns["ran_proc_filter2"] = true}
+ end
+
+ class EmptyFilterChainController < TestController
+ self.filter_chain.clear
+ def show
+ @action_executed = true
+ render :text => "yawp!"
+ end
+ end
+
+ class PrependingController < TestController
+ prepend_before_filter :wonderful_life
+ # skip_before_filter :fire_flash
+
+ private
+ def wonderful_life
+ @ran_filter ||= []
+ @ran_filter << "wonderful_life"
+ end
+ end
+
+ class ConditionalSkippingController < TestController
+ skip_before_filter :ensure_login, :only => [ :login ]
+ skip_after_filter :clean_up, :only => [ :login ]
+
+ before_filter :find_user, :only => [ :change_password ]
+
+ def login
+ render :inline => "ran action"
+ end
+
+ def change_password
+ render :inline => "ran action"
+ end
+
+ protected
+ def find_user
+ @ran_filter ||= []
+ @ran_filter << "find_user"
+ end
+ end
+
+ class ConditionalParentOfConditionalSkippingController < ConditionalFilterController
+ before_filter :conditional_in_parent, :only => [:show, :another_action]
+ after_filter :conditional_in_parent, :only => [:show, :another_action]
+
+ private
+
+ def conditional_in_parent
+ @ran_filter ||= []
+ @ran_filter << 'conditional_in_parent'
+ end
+ end
+
+ class ChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController
+ skip_before_filter :conditional_in_parent, :only => :another_action
+ skip_after_filter :conditional_in_parent, :only => :another_action
+ end
+
+ class AnotherChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController
+ skip_before_filter :conditional_in_parent, :only => :show
+ end
+
+ class ProcController < PrependingController
+ before_filter(proc { |c| c.assigns["ran_proc_filter"] = true })
+ end
+
+ class ImplicitProcController < PrependingController
+ before_filter { |c| c.assigns["ran_proc_filter"] = true }
+ end
+
+ class AuditFilter
+ def self.filter(controller)
+ controller.assigns["was_audited"] = true
+ end
+ end
+
+ class AroundFilter
+ def before(controller)
+ @execution_log = "before"
+ controller.class.execution_log << " before aroundfilter " if controller.respond_to? :execution_log
+ controller.assigns["before_ran"] = true
+ end
+
+ def after(controller)
+ controller.assigns["execution_log"] = @execution_log + " and after"
+ controller.assigns["after_ran"] = true
+ controller.class.execution_log << " after aroundfilter " if controller.respond_to? :execution_log
+ end
+ end
+
+ class AppendedAroundFilter
+ def before(controller)
+ controller.class.execution_log << " before appended aroundfilter "
+ end
+
+ def after(controller)
+ controller.class.execution_log << " after appended aroundfilter "
+ end
+ end
+
+ class AuditController < ActionController::Base
+ before_filter(AuditFilter)
+
+ def show
+ render :text => "hello"
+ end
+ end
+
+ class AroundFilterController < PrependingController
+ around_filter AroundFilter.new
+ end
+
+ class BeforeAfterClassFilterController < PrependingController
+ begin
+ filter = AroundFilter.new
+ before_filter filter
+ after_filter filter
+ end
+ end
+
+ class MixedFilterController < PrependingController
+ cattr_accessor :execution_log
+
+ def initialize
+ @@execution_log = ""
+ end
+
+ before_filter { |c| c.class.execution_log << " before procfilter " }
+ prepend_around_filter AroundFilter.new
+
+ after_filter { |c| c.class.execution_log << " after procfilter " }
+ append_around_filter AppendedAroundFilter.new
+ end
+
+ class MixedSpecializationController < ActionController::Base
+ class OutOfOrder < StandardError; end
+
+ before_filter :first
+ before_filter :second, :only => :foo
+
+ def foo
+ render :text => 'foo'
+ end
+
+ def bar
+ render :text => 'bar'
+ end
+
+ protected
+ def first
+ @first = true
+ end
+
+ def second
+ raise OutOfOrder unless @first
+ end
+ end
+
+ class DynamicDispatchController < ActionController::Base
+ before_filter :choose
+
+ %w(foo bar baz).each do |action|
+ define_method(action) { render :text => action }
+ end
+
+ private
+ def choose
+ self.action_name = params[:choose]
+ end
+ end
+
+ class PrependingBeforeAndAfterController < ActionController::Base
+ prepend_before_filter :before_all
+ prepend_after_filter :after_all
+ before_filter :between_before_all_and_after_all
+
+ def before_all
+ @ran_filter ||= []
+ @ran_filter << 'before_all'
+ end
+
+ def after_all
+ @ran_filter ||= []
+ @ran_filter << 'after_all'
+ end
+
+ def between_before_all_and_after_all
+ @ran_filter ||= []
+ @ran_filter << 'between_before_all_and_after_all'
+ end
+ def show
+ render :text => 'hello'
+ end
+ end
+
+ class ErrorToRescue < Exception; end
+
+ class RescuingAroundFilterWithBlock
+ def filter(controller)
+ begin
+ yield
+ rescue ErrorToRescue => ex
+ controller.send! :render, :text => "I rescued this: #{ex.inspect}"
+ end
+ end
+ end
+
+ class RescuedController < ActionController::Base
+ around_filter RescuingAroundFilterWithBlock.new
+
+ def show
+ raise ErrorToRescue.new("Something made the bad noise.")
+ end
+
+ private
+ def rescue_action(exception)
+ raise exception
+ end
+ end
+
+ class NonYieldingAroundFilterController < ActionController::Base
+
+ before_filter :filter_one
+ around_filter :non_yielding_filter
+ before_filter :filter_two
+ after_filter :filter_three
+
+ def index
+ render :inline => "index"
+ end
+
+ #make sure the controller complains
+ def rescue_action(e); raise e; end
+
+ private
+
+ def filter_one
+ @filters ||= []
+ @filters << "filter_one"
+ end
+
+ def filter_two
+ @filters << "filter_two"
+ end
+
+ def non_yielding_filter
+ @filters << "zomg it didn't yield"
+ @filter_return_value
+ end
+
+ def filter_three
+ @filters << "filter_three"
+ end
+
+ end
+
+ def test_non_yielding_around_filters_not_returning_false_do_not_raise
+ controller = NonYieldingAroundFilterController.new
+ controller.instance_variable_set "@filter_return_value", true
+ assert_nothing_raised do
+ test_process(controller, "index")
+ end
+ end
+
+ def test_non_yielding_around_filters_returning_false_do_not_raise
+ controller = NonYieldingAroundFilterController.new
+ controller.instance_variable_set "@filter_return_value", false
+ assert_nothing_raised do
+ test_process(controller, "index")
+ end
+ end
+
+ def test_after_filters_are_not_run_if_around_filter_returns_false
+ controller = NonYieldingAroundFilterController.new
+ controller.instance_variable_set "@filter_return_value", false
+ test_process(controller, "index")
+ assert_equal ["filter_one", "zomg it didn't yield"], controller.assigns['filters']
+ end
+
+ def test_after_filters_are_not_run_if_around_filter_does_not_yield
+ controller = NonYieldingAroundFilterController.new
+ controller.instance_variable_set "@filter_return_value", true
+ test_process(controller, "index")
+ assert_equal ["filter_one", "zomg it didn't yield"], controller.assigns['filters']
+ end
+
+ def test_empty_filter_chain
+ assert_equal 0, EmptyFilterChainController.filter_chain.size
+ assert test_process(EmptyFilterChainController).template.assigns['action_executed']
+ end
+
+ def test_added_filter_to_inheritance_graph
+ assert_equal [ :ensure_login ], TestController.before_filters
+ end
+
+ def test_base_class_in_isolation
+ assert_equal [ ], ActionController::Base.before_filters
+ end
+
+ def test_prepending_filter
+ assert_equal [ :wonderful_life, :ensure_login ], PrependingController.before_filters
+ end
+
+ def test_running_filters
+ assert_equal %w( wonderful_life ensure_login ), test_process(PrependingController).template.assigns["ran_filter"]
+ end
+
+ def test_running_filters_with_proc
+ assert test_process(ProcController).template.assigns["ran_proc_filter"]
+ end
+
+ def test_running_filters_with_implicit_proc
+ assert test_process(ImplicitProcController).template.assigns["ran_proc_filter"]
+ end
+
+ def test_running_filters_with_class
+ assert test_process(AuditController).template.assigns["was_audited"]
+ end
+
+ def test_running_anomolous_yet_valid_condition_filters
+ response = test_process(AnomolousYetValidConditionController)
+ assert_equal %w( ensure_login ), response.template.assigns["ran_filter"]
+ assert response.template.assigns["ran_class_filter"]
+ assert response.template.assigns["ran_proc_filter1"]
+ assert response.template.assigns["ran_proc_filter2"]
+
+ response = test_process(AnomolousYetValidConditionController, "show_without_filter")
+ assert_equal nil, response.template.assigns["ran_filter"]
+ assert !response.template.assigns["ran_class_filter"]
+ assert !response.template.assigns["ran_proc_filter1"]
+ assert !response.template.assigns["ran_proc_filter2"]
+ end
+
+ def test_running_collection_condition_filters
+ assert_equal %w( ensure_login ), test_process(ConditionalCollectionFilterController).template.assigns["ran_filter"]
+ assert_equal nil, test_process(ConditionalCollectionFilterController, "show_without_filter").template.assigns["ran_filter"]
+ assert_equal nil, test_process(ConditionalCollectionFilterController, "another_action").template.assigns["ran_filter"]
+ end
+
+ def test_running_only_condition_filters
+ assert_equal %w( ensure_login ), test_process(OnlyConditionSymController).template.assigns["ran_filter"]
+ assert_equal nil, test_process(OnlyConditionSymController, "show_without_filter").template.assigns["ran_filter"]
+
+ assert test_process(OnlyConditionProcController).template.assigns["ran_proc_filter"]
+ assert !test_process(OnlyConditionProcController, "show_without_filter").template.assigns["ran_proc_filter"]
+
+ assert test_process(OnlyConditionClassController).template.assigns["ran_class_filter"]
+ assert !test_process(OnlyConditionClassController, "show_without_filter").template.assigns["ran_class_filter"]
+ end
+
+ def test_running_except_condition_filters
+ assert_equal %w( ensure_login ), test_process(ExceptConditionSymController).template.assigns["ran_filter"]
+ assert_equal nil, test_process(ExceptConditionSymController, "show_without_filter").template.assigns["ran_filter"]
+
+ assert test_process(ExceptConditionProcController).template.assigns["ran_proc_filter"]
+ assert !test_process(ExceptConditionProcController, "show_without_filter").template.assigns["ran_proc_filter"]
+
+ assert test_process(ExceptConditionClassController).template.assigns["ran_class_filter"]
+ assert !test_process(ExceptConditionClassController, "show_without_filter").template.assigns["ran_class_filter"]
+ end
+
+ def test_running_before_and_after_condition_filters
+ assert_equal %w( ensure_login clean_up_tmp), test_process(BeforeAndAfterConditionController).template.assigns["ran_filter"]
+ assert_equal nil, test_process(BeforeAndAfterConditionController, "show_without_filter").template.assigns["ran_filter"]
+ end
+
+ def test_bad_filter
+ bad_filter_controller = Class.new(ActionController::Base)
+ assert_raises(ActionController::ActionControllerError) do
+ bad_filter_controller.before_filter 2
+ end
+ end
+
+ def test_around_filter
+ controller = test_process(AroundFilterController)
+ assert controller.template.assigns["before_ran"]
+ assert controller.template.assigns["after_ran"]
+ end
+
+ def test_before_after_class_filter
+ controller = test_process(BeforeAfterClassFilterController)
+ assert controller.template.assigns["before_ran"]
+ assert controller.template.assigns["after_ran"]
+ end
+
+ def test_having_properties_in_around_filter
+ controller = test_process(AroundFilterController)
+ assert_equal "before and after", controller.template.assigns["execution_log"]
+ end
+
+ def test_prepending_and_appending_around_filter
+ controller = test_process(MixedFilterController)
+ assert_equal " before aroundfilter before procfilter before appended aroundfilter " +
+ " after appended aroundfilter after aroundfilter after procfilter ",
+ MixedFilterController.execution_log
+ end
+
+ def test_rendering_breaks_filtering_chain
+ response = test_process(RenderingController)
+ assert_equal "something else", response.body
+ assert !response.template.assigns["ran_action"]
+ end
+
+ def test_filters_with_mixed_specialization_run_in_order
+ assert_nothing_raised do
+ response = test_process(MixedSpecializationController, 'bar')
+ assert_equal 'bar', response.body
+ end
+
+ assert_nothing_raised do
+ response = test_process(MixedSpecializationController, 'foo')
+ assert_equal 'foo', response.body
+ end
+ end
+
+ def test_dynamic_dispatch
+ %w(foo bar baz).each do |action|
+ request = ActionController::TestRequest.new
+ request.query_parameters[:choose] = action
+ response = DynamicDispatchController.process(request, ActionController::TestResponse.new)
+ assert_equal action, response.body
+ end
+ end
+
+ def test_running_prepended_before_and_after_filter
+ assert_equal 3, PrependingBeforeAndAfterController.filter_chain.length
+ response = test_process(PrependingBeforeAndAfterController)
+ assert_equal %w( before_all between_before_all_and_after_all after_all ), response.template.assigns["ran_filter"]
+ end
+
+ def test_conditional_skipping_of_filters
+ assert_nil test_process(ConditionalSkippingController, "login").template.assigns["ran_filter"]
+ assert_equal %w( ensure_login find_user ), test_process(ConditionalSkippingController, "change_password").template.assigns["ran_filter"]
+
+ assert_nil test_process(ConditionalSkippingController, "login").template.controller.instance_variable_get("@ran_after_filter")
+ assert_equal %w( clean_up ), test_process(ConditionalSkippingController, "change_password").template.controller.instance_variable_get("@ran_after_filter")
+ end
+
+ def test_conditional_skipping_of_filters_when_parent_filter_is_also_conditional
+ assert_equal %w( conditional_in_parent conditional_in_parent ), test_process(ChildOfConditionalParentController).template.assigns['ran_filter']
+ assert_nil test_process(ChildOfConditionalParentController, 'another_action').template.assigns['ran_filter']
+ end
+
+ def test_condition_skipping_of_filters_when_siblings_also_have_conditions
+ assert_equal %w( conditional_in_parent conditional_in_parent ), test_process(ChildOfConditionalParentController).template.assigns['ran_filter'], "1"
+ assert_equal nil, test_process(AnotherChildOfConditionalParentController).template.assigns['ran_filter']
+ assert_equal %w( conditional_in_parent conditional_in_parent ), test_process(ChildOfConditionalParentController).template.assigns['ran_filter']
+ end
+
+ def test_changing_the_requirements
+ assert_equal nil, test_process(ChangingTheRequirementsController, "go_wild").template.assigns['ran_filter']
+ end
+
+ def test_a_rescuing_around_filter
+ response = nil
+ assert_nothing_raised do
+ response = test_process(RescuedController)
+ end
+
+ assert response.success?
+ assert_equal("I rescued this: #<FilterTest::ErrorToRescue: Something made the bad noise.>", response.body)
+ end
+
+ private
+ def test_process(controller, action = "show")
+ request = ActionController::TestRequest.new
+ request.action = action
+ controller.process(request, ActionController::TestResponse.new)
+ end
+end
+
+
+
+class PostsController < ActionController::Base
+ def rescue_action(e); raise e; end
+
+ module AroundExceptions
+ class Error < StandardError ; end
+ class Before < Error ; end
+ class After < Error ; end
+ end
+ include AroundExceptions
+
+ class DefaultFilter
+ include AroundExceptions
+ end
+
+ module_eval %w(raises_before raises_after raises_both no_raise no_filter).map { |action| "def #{action}; default_action end" }.join("\n")
+
+ private
+ def default_action
+ render :inline => "#{action_name} called"
+ end
+end
+
+class ControllerWithSymbolAsFilter < PostsController
+ around_filter :raise_before, :only => :raises_before
+ around_filter :raise_after, :only => :raises_after
+ around_filter :without_exception, :only => :no_raise
+
+ private
+ def raise_before
+ raise Before
+ yield
+ end
+
+ def raise_after
+ yield
+ raise After
+ end
+
+ def without_exception
+ # Do stuff...
+ 1 + 1
+
+ yield
+
+ # Do stuff...
+ 1 + 1
+ end
+end
+
+class ControllerWithFilterClass < PostsController
+ class YieldingFilter < DefaultFilter
+ def self.filter(controller)
+ yield
+ raise After
+ end
+ end
+
+ around_filter YieldingFilter, :only => :raises_after
+end
+
+class ControllerWithFilterInstance < PostsController
+ class YieldingFilter < DefaultFilter
+ def filter(controller)
+ yield
+ raise After
+ end
+ end
+
+ around_filter YieldingFilter.new, :only => :raises_after
+end
+
+class ControllerWithFilterMethod < PostsController
+ class YieldingFilter < DefaultFilter
+ def filter(controller)
+ yield
+ raise After
+ end
+ end
+
+ around_filter YieldingFilter.new.method(:filter), :only => :raises_after
+end
+
+class ControllerWithProcFilter < PostsController
+ around_filter(:only => :no_raise) do |c,b|
+ c.assigns['before'] = true
+ b.call
+ c.assigns['after'] = true
+ end
+end
+
+class ControllerWithWrongFilterType < PostsController
+ around_filter lambda { yield }, :only => :no_raise
+end
+
+class ControllerWithNestedFilters < ControllerWithSymbolAsFilter
+ around_filter :raise_before, :raise_after, :without_exception, :only => :raises_both
+end
+
+class ControllerWithAllTypesOfFilters < PostsController
+ before_filter :before
+ around_filter :around
+ after_filter :after
+ around_filter :around_again
+
+ private
+ def before
+ @ran_filter ||= []
+ @ran_filter << 'before'
+ end
+
+ def around
+ @ran_filter << 'around (before yield)'
+ yield
+ @ran_filter << 'around (after yield)'
+ end
+
+ def after
+ @ran_filter << 'after'
+ end
+
+ def around_again
+ @ran_filter << 'around_again (before yield)'
+ yield
+ @ran_filter << 'around_again (after yield)'
+ end
+end
+
+class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters
+ skip_filter :around_again
+ skip_filter :after
+end
+
+class YieldingAroundFiltersTest < Test::Unit::TestCase
+ include PostsController::AroundExceptions
+
+ def test_filters_registering
+ assert_equal 1, ControllerWithFilterMethod.filter_chain.size
+ assert_equal 1, ControllerWithFilterClass.filter_chain.size
+ assert_equal 1, ControllerWithFilterInstance.filter_chain.size
+ assert_equal 3, ControllerWithSymbolAsFilter.filter_chain.size
+ assert_equal 1, ControllerWithWrongFilterType.filter_chain.size
+ assert_equal 6, ControllerWithNestedFilters.filter_chain.size
+ assert_equal 4, ControllerWithAllTypesOfFilters.filter_chain.size
+ end
+
+ def test_wrong_filter_type
+ assert_raise(ActionController::ActionControllerError) do
+ test_process(ControllerWithWrongFilterType,'no_raise')
+ end
+ end
+
+ def test_base
+ controller = PostsController
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ assert_nothing_raised { test_process(controller,'raises_before') }
+ assert_nothing_raised { test_process(controller,'raises_after') }
+ assert_nothing_raised { test_process(controller,'no_filter') }
+ end
+
+ def test_with_symbol
+ controller = ControllerWithSymbolAsFilter
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ assert_raise(Before) { test_process(controller,'raises_before') }
+ assert_raise(After) { test_process(controller,'raises_after') }
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ end
+
+ def test_with_class
+ controller = ControllerWithFilterClass
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ assert_raise(After) { test_process(controller,'raises_after') }
+ end
+
+ def test_with_instance
+ controller = ControllerWithFilterInstance
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ assert_raise(After) { test_process(controller,'raises_after') }
+ end
+
+ def test_with_method
+ controller = ControllerWithFilterMethod
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ assert_raise(After) { test_process(controller,'raises_after') }
+ end
+
+ def test_with_proc
+ controller = test_process(ControllerWithProcFilter,'no_raise')
+ assert controller.template.assigns['before']
+ assert controller.template.assigns['after']
+ end
+
+ def test_nested_filters
+ controller = ControllerWithNestedFilters
+ assert_nothing_raised do
+ begin
+ test_process(controller,'raises_both')
+ rescue Before, After
+ end
+ end
+ assert_raise Before do
+ begin
+ test_process(controller,'raises_both')
+ rescue After
+ end
+ end
+ end
+
+ def test_filter_order_with_all_filter_types
+ controller = test_process(ControllerWithAllTypesOfFilters,'no_raise')
+ assert_equal 'before around (before yield) around_again (before yield) around_again (after yield) around (after yield) after',controller.template.assigns['ran_filter'].join(' ')
+ end
+
+ def test_filter_order_with_skip_filter_method
+ controller = test_process(ControllerWithTwoLessFilters,'no_raise')
+ assert_equal 'before around (before yield) around (after yield)',controller.template.assigns['ran_filter'].join(' ')
+ end
+
+ def test_first_filter_in_multiple_before_filter_chain_halts
+ controller = ::FilterTest::TestMultipleFiltersController.new
+ response = test_process(controller, 'fail_1')
+ assert_equal ' ', response.body
+ assert_equal 1, controller.instance_variable_get(:@try)
+ assert controller.instance_variable_get(:@before_filter_chain_aborted)
+ end
+
+ def test_second_filter_in_multiple_before_filter_chain_halts
+ controller = ::FilterTest::TestMultipleFiltersController.new
+ response = test_process(controller, 'fail_2')
+ assert_equal ' ', response.body
+ assert_equal 2, controller.instance_variable_get(:@try)
+ assert controller.instance_variable_get(:@before_filter_chain_aborted)
+ end
+
+ def test_last_filter_in_multiple_before_filter_chain_halts
+ controller = ::FilterTest::TestMultipleFiltersController.new
+ response = test_process(controller, 'fail_3')
+ assert_equal ' ', response.body
+ assert_equal 3, controller.instance_variable_get(:@try)
+ assert controller.instance_variable_get(:@before_filter_chain_aborted)
+ end
+
+ protected
+ def test_process(controller, action = "show")
+ request = ActionController::TestRequest.new
+ request.action = action
+ controller.process(request, ActionController::TestResponse.new)
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/flash_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/flash_test.rb
new file mode 100644
index 000000000..4a6f3c9e0
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/flash_test.rb
@@ -0,0 +1,146 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class FlashTest < Test::Unit::TestCase
+ class TestController < ActionController::Base
+ def set_flash
+ flash["that"] = "hello"
+ render :inline => "hello"
+ end
+
+ def set_flash_now
+ flash.now["that"] = "hello"
+ flash.now["foo"] ||= "bar"
+ flash.now["foo"] ||= "err"
+ @flashy = flash.now["that"]
+ @flash_copy = {}.update flash
+ render :inline => "hello"
+ end
+
+ def attempt_to_use_flash_now
+ @flash_copy = {}.update flash
+ @flashy = flash["that"]
+ render :inline => "hello"
+ end
+
+ def use_flash
+ @flash_copy = {}.update flash
+ @flashy = flash["that"]
+ render :inline => "hello"
+ end
+
+ def use_flash_and_keep_it
+ @flash_copy = {}.update flash
+ @flashy = flash["that"]
+ flash.keep
+ render :inline => "hello"
+ end
+
+ def use_flash_and_update_it
+ flash.update("this" => "hello again")
+ @flash_copy = {}.update flash
+ render :inline => "hello"
+ end
+
+ def use_flash_after_reset_session
+ flash["that"] = "hello"
+ @flashy_that = flash["that"]
+ reset_session
+ @flashy_that_reset = flash["that"]
+ flash["this"] = "good-bye"
+ @flashy_this = flash["this"]
+ render :inline => "hello"
+ end
+
+ def rescue_action(e)
+ raise unless ActionController::MissingTemplate === e
+ end
+
+ # methods for test_sweep_after_halted_filter_chain
+ before_filter :halt_and_redir, :only => "filter_halting_action"
+
+ def std_action
+ @flash_copy = {}.update(flash)
+ end
+
+ def filter_halting_action
+ @flash_copy = {}.update(flash)
+ end
+
+ def halt_and_redir
+ flash["foo"] = "bar"
+ redirect_to :action => "std_action"
+ @flash_copy = {}.update(flash)
+ end
+ end
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = TestController.new
+ end
+
+ def test_flash
+ get :set_flash
+
+ get :use_flash
+ assert_equal "hello", @response.template.assigns["flash_copy"]["that"]
+ assert_equal "hello", @response.template.assigns["flashy"]
+
+ get :use_flash
+ assert_nil @response.template.assigns["flash_copy"]["that"], "On second flash"
+ end
+
+ def test_keep_flash
+ get :set_flash
+
+ get :use_flash_and_keep_it
+ assert_equal "hello", @response.template.assigns["flash_copy"]["that"]
+ assert_equal "hello", @response.template.assigns["flashy"]
+
+ get :use_flash
+ assert_equal "hello", @response.template.assigns["flash_copy"]["that"], "On second flash"
+
+ get :use_flash
+ assert_nil @response.template.assigns["flash_copy"]["that"], "On third flash"
+ end
+
+ def test_flash_now
+ get :set_flash_now
+ assert_equal "hello", @response.template.assigns["flash_copy"]["that"]
+ assert_equal "bar" , @response.template.assigns["flash_copy"]["foo"]
+ assert_equal "hello", @response.template.assigns["flashy"]
+
+ get :attempt_to_use_flash_now
+ assert_nil @response.template.assigns["flash_copy"]["that"]
+ assert_nil @response.template.assigns["flash_copy"]["foo"]
+ assert_nil @response.template.assigns["flashy"]
+ end
+
+ def test_update_flash
+ get :set_flash
+ get :use_flash_and_update_it
+ assert_equal "hello", @response.template.assigns["flash_copy"]["that"]
+ assert_equal "hello again", @response.template.assigns["flash_copy"]["this"]
+ get :use_flash
+ assert_nil @response.template.assigns["flash_copy"]["that"], "On second flash"
+ assert_equal "hello again", @response.template.assigns["flash_copy"]["this"], "On second flash"
+ end
+
+ def test_flash_after_reset_session
+ get :use_flash_after_reset_session
+ assert_equal "hello", @response.template.assigns["flashy_that"]
+ assert_equal "good-bye", @response.template.assigns["flashy_this"]
+ assert_nil @response.template.assigns["flashy_that_reset"]
+ end
+
+ def test_sweep_after_halted_filter_chain
+ get :std_action
+ assert_nil @response.template.assigns["flash_copy"]["foo"]
+ get :filter_halting_action
+ assert_equal "bar", @response.template.assigns["flash_copy"]["foo"]
+ get :std_action # follow redirection
+ assert_equal "bar", @response.template.assigns["flash_copy"]["foo"]
+ get :std_action
+ assert_nil @response.template.assigns["flash_copy"]["foo"]
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/fragment_store_setting_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/fragment_store_setting_test.rb
new file mode 100644
index 000000000..3df6fd0be
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/fragment_store_setting_test.rb
@@ -0,0 +1,47 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+MemCache = Struct.new(:MemCache, :address) unless Object.const_defined?(:MemCache)
+
+class FragmentCacheStoreSettingTest < Test::Unit::TestCase
+ def teardown
+ ActionController::Base.fragment_cache_store = ActionController::Caching::Fragments::MemoryStore.new
+ end
+
+ def test_file_fragment_cache_store
+ ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory"
+ assert_kind_of(
+ ActionController::Caching::Fragments::FileStore,
+ ActionController::Base.fragment_cache_store
+ )
+ assert_equal "/path/to/cache/directory", ActionController::Base.fragment_cache_store.cache_path
+ end
+
+ def test_drb_fragment_cache_store
+ ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192"
+ assert_kind_of(
+ ActionController::Caching::Fragments::DRbStore,
+ ActionController::Base.fragment_cache_store
+ )
+ assert_equal "druby://localhost:9192", ActionController::Base.fragment_cache_store.address
+ end
+
+ if defined? CGI::Session::MemCacheStore
+ def test_mem_cache_fragment_cache_store
+ ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost"
+ assert_kind_of(
+ ActionController::Caching::Fragments::MemCacheStore,
+ ActionController::Base.fragment_cache_store
+ )
+ assert_equal %w(localhost), ActionController::Base.fragment_cache_store.addresses
+ end
+ end
+
+ def test_object_assigned_fragment_cache_store
+ ActionController::Base.fragment_cache_store = ActionController::Caching::Fragments::FileStore.new("/path/to/cache/directory")
+ assert_kind_of(
+ ActionController::Caching::Fragments::FileStore,
+ ActionController::Base.fragment_cache_store
+ )
+ assert_equal "/path/to/cache/directory", ActionController::Base.fragment_cache_store.cache_path
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/helper_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/helper_test.rb
new file mode 100644
index 000000000..117f73b76
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/helper_test.rb
@@ -0,0 +1,206 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+ActionController::Helpers::HELPERS_DIR.replace File.dirname(__FILE__) + '/../fixtures/helpers'
+
+class TestController < ActionController::Base
+ attr_accessor :delegate_attr
+ def delegate_method() end
+ def rescue_action(e) raise end
+end
+
+module Fun
+ class GamesController < ActionController::Base
+ def render_hello_world
+ render :inline => "hello: <%= stratego %>"
+ end
+
+ def rescue_action(e) raise end
+ end
+
+ class PdfController < ActionController::Base
+ def test
+ render :inline => "test: <%= foobar %>"
+ end
+
+ def rescue_action(e) raise end
+ end
+end
+
+class ApplicationController < ActionController::Base
+ helper :all
+end
+
+module LocalAbcHelper
+ def a() end
+ def b() end
+ def c() end
+end
+
+class HelperTest < Test::Unit::TestCase
+ def setup
+ # Increment symbol counter.
+ @symbol = (@@counter ||= 'A0').succ!.dup
+
+ # Generate new controller class.
+ controller_class_name = "Helper#{@symbol}Controller"
+ eval("class #{controller_class_name} < TestController; end")
+ @controller_class = self.class.const_get(controller_class_name)
+
+ # Generate new template class and assign to controller.
+ template_class_name = "Test#{@symbol}View"
+ eval("class #{template_class_name} < ActionView::Base; end")
+ @template_class = self.class.const_get(template_class_name)
+ @controller_class.template_class = @template_class
+
+ # Set default test helper.
+ self.test_helper = LocalAbcHelper
+ end
+
+ def teardown
+ # Reset template class.
+ #ActionController::Base.template_class = ActionView::Base
+ end
+
+
+ def test_deprecated_helper
+ assert_equal expected_helper_methods, missing_methods
+ assert_nothing_raised { @controller_class.helper TestHelper }
+ assert_equal [], missing_methods
+ end
+
+ def test_declare_helper
+ require 'abc_helper'
+ self.test_helper = AbcHelper
+ assert_equal expected_helper_methods, missing_methods
+ assert_nothing_raised { @controller_class.helper :abc }
+ assert_equal [], missing_methods
+ end
+
+ def test_declare_missing_helper
+ assert_equal expected_helper_methods, missing_methods
+ assert_raise(MissingSourceFile) { @controller_class.helper :missing }
+ end
+
+ def test_declare_missing_file_from_helper
+ require 'broken_helper'
+ rescue LoadError => e
+ assert_nil(/\bbroken_helper\b/.match(e.to_s)[1])
+ end
+
+ def test_helper_block
+ assert_nothing_raised {
+ @controller_class.helper { def block_helper_method; end }
+ }
+ assert master_helper_methods.include?('block_helper_method')
+ end
+
+ def test_helper_block_include
+ assert_equal expected_helper_methods, missing_methods
+ assert_nothing_raised {
+ @controller_class.helper { include TestHelper }
+ }
+ assert [], missing_methods
+ end
+
+ def test_helper_method
+ assert_nothing_raised { @controller_class.helper_method :delegate_method }
+ assert master_helper_methods.include?('delegate_method')
+ end
+
+ def test_helper_attr
+ assert_nothing_raised { @controller_class.helper_attr :delegate_attr }
+ assert master_helper_methods.include?('delegate_attr')
+ assert master_helper_methods.include?('delegate_attr=')
+ end
+
+ def test_helper_for_nested_controller
+ request = ActionController::TestRequest.new
+ response = ActionController::TestResponse.new
+ request.action = 'render_hello_world'
+
+ assert_equal 'hello: Iz guuut!', Fun::GamesController.process(request, response).body
+ end
+
+ def test_helper_for_acronym_controller
+ request = ActionController::TestRequest.new
+ response = ActionController::TestResponse.new
+ request.action = 'test'
+
+ assert_equal 'test: baz', Fun::PdfController.process(request, response).body
+ end
+
+ def test_all_helpers
+ methods = ApplicationController.master_helper_module.instance_methods.map(&:to_s)
+
+ # abc_helper.rb
+ assert methods.include?('bare_a')
+
+ # fun/games_helper.rb
+ assert methods.include?('stratego')
+
+ # fun/pdf_helper.rb
+ assert methods.include?('foobar')
+ end
+
+ private
+ def expected_helper_methods
+ TestHelper.instance_methods.map(&:to_s)
+ end
+
+ def master_helper_methods
+ @controller_class.master_helper_module.instance_methods.map(&:to_s)
+ end
+
+ def missing_methods
+ expected_helper_methods - master_helper_methods
+ end
+
+ def test_helper=(helper_module)
+ silence_warnings { self.class.const_set('TestHelper', helper_module) }
+ end
+end
+
+
+class IsolatedHelpersTest < Test::Unit::TestCase
+ class A < ActionController::Base
+ def index
+ render :inline => '<%= shout %>'
+ end
+
+ def rescue_action(e) raise end
+ end
+
+ class B < A
+ helper { def shout; 'B' end }
+
+ def index
+ render :inline => '<%= shout %>'
+ end
+ end
+
+ class C < A
+ helper { def shout; 'C' end }
+
+ def index
+ render :inline => '<%= shout %>'
+ end
+ end
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @request.action = 'index'
+ end
+
+ def test_helper_in_a
+ assert_raise(NameError) { A.process(@request, @response) }
+ end
+
+ def test_helper_in_b
+ assert_equal 'B', B.process(@request, @response).body
+ end
+
+ def test_helper_in_c
+ assert_equal 'C', C.process(@request, @response).body
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/document_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/document_test.rb
new file mode 100644
index 000000000..0719883f3
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/document_test.rb
@@ -0,0 +1,124 @@
+require File.dirname(__FILE__) + '/../../abstract_unit'
+require 'test/unit'
+
+class DocumentTest < Test::Unit::TestCase
+ def test_handle_doctype
+ doc = nil
+ assert_nothing_raised do
+ doc = HTML::Document.new <<-HTML.strip
+ <!DOCTYPE "blah" "blah" "blah">
+ <html>
+ </html>
+ HTML
+ end
+ assert_equal 3, doc.root.children.length
+ assert_equal %{<!DOCTYPE "blah" "blah" "blah">}, doc.root.children[0].content
+ assert_match %r{\s+}m, doc.root.children[1].content
+ assert_equal "html", doc.root.children[2].name
+ end
+
+ def test_find_img
+ doc = HTML::Document.new <<-HTML.strip
+ <html>
+ <body>
+ <p><img src="hello.gif"></p>
+ </body>
+ </html>
+ HTML
+ assert doc.find(:tag=>"img", :attributes=>{"src"=>"hello.gif"})
+ end
+
+ def test_find_all
+ doc = HTML::Document.new <<-HTML.strip
+ <html>
+ <body>
+ <p class="test"><img src="hello.gif"></p>
+ <div class="foo">
+ <p class="test">something</p>
+ <p>here is <em class="test">more</em></p>
+ </div>
+ </body>
+ </html>
+ HTML
+ all = doc.find_all :attributes => { :class => "test" }
+ assert_equal 3, all.length
+ assert_equal [ "p", "p", "em" ], all.map { |n| n.name }
+ end
+
+ def test_find_with_text
+ doc = HTML::Document.new <<-HTML.strip
+ <html>
+ <body>
+ <p>Some text</p>
+ </body>
+ </html>
+ HTML
+ assert doc.find(:content => "Some text")
+ assert doc.find(:tag => "p", :child => { :content => "Some text" })
+ assert doc.find(:tag => "p", :child => "Some text")
+ assert doc.find(:tag => "p", :content => "Some text")
+ end
+
+ def test_parse_xml
+ assert_nothing_raised { HTML::Document.new("<tags><tag/></tags>", true, true) }
+ assert_nothing_raised { HTML::Document.new("<outer><link>something</link></outer>", true, true) }
+ end
+
+ def test_parse_document
+ doc = HTML::Document.new(<<-HTML)
+ <div>
+ <h2>blah</h2>
+ <table>
+ </table>
+ </div>
+ HTML
+ assert_not_nil doc.find(:tag => "div", :children => { :count => 1, :only => { :tag => "table" } })
+ end
+
+ def test_tag_nesting_nothing_to_s
+ doc = HTML::Document.new("<tag></tag>")
+ assert_equal "<tag></tag>", doc.root.to_s
+ end
+
+ def test_tag_nesting_space_to_s
+ doc = HTML::Document.new("<tag> </tag>")
+ assert_equal "<tag> </tag>", doc.root.to_s
+ end
+
+ def test_tag_nesting_text_to_s
+ doc = HTML::Document.new("<tag>text</tag>")
+ assert_equal "<tag>text</tag>", doc.root.to_s
+ end
+
+ def test_tag_nesting_tag_to_s
+ doc = HTML::Document.new("<tag><nested /></tag>")
+ assert_equal "<tag><nested /></tag>", doc.root.to_s
+ end
+
+ def test_parse_cdata
+ doc = HTML::Document.new(<<-HTML)
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+ <head>
+ <title><![CDATA[<br>]]></title>
+ </head>
+ <body>
+ <p>this document has &lt;br&gt; for a title</p>
+ </body>
+</html>
+HTML
+
+ assert_nil doc.find(:tag => "title", :descendant => { :tag => "br" })
+ assert doc.find(:tag => "title", :child => "<br>")
+ end
+
+ def test_find_empty_tag
+ doc = HTML::Document.new("<div id='map'></div>")
+ assert_nil doc.find(:tag => "div", :attributes => { :id => "map" }, :content => /./)
+ assert doc.find(:tag => "div", :attributes => { :id => "map" }, :content => /\A\Z/)
+ assert doc.find(:tag => "div", :attributes => { :id => "map" }, :content => /^$/)
+ assert doc.find(:tag => "div", :attributes => { :id => "map" }, :content => "")
+ assert doc.find(:tag => "div", :attributes => { :id => "map" }, :content => nil)
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/node_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/node_test.rb
new file mode 100644
index 000000000..1cf0a4bb6
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/node_test.rb
@@ -0,0 +1,69 @@
+require File.dirname(__FILE__) + '/../../abstract_unit'
+require 'test/unit'
+
+class NodeTest < Test::Unit::TestCase
+
+ class MockNode
+ def initialize(matched, value)
+ @matched = matched
+ @value = value
+ end
+
+ def find(conditions)
+ @matched && self
+ end
+
+ def to_s
+ @value.to_s
+ end
+ end
+
+ def setup
+ @node = HTML::Node.new("parent")
+ @node.children.concat [MockNode.new(false,1), MockNode.new(true,"two"), MockNode.new(false,:three)]
+ end
+
+ def test_match
+ assert !@node.match("foo")
+ end
+
+ def test_tag
+ assert !@node.tag?
+ end
+
+ def test_to_s
+ assert_equal "1twothree", @node.to_s
+ end
+
+ def test_find
+ assert_equal "two", @node.find('blah').to_s
+ end
+
+ def test_parse_strict
+ s = "<b foo='hello'' bar='baz'>"
+ assert_raise(RuntimeError) { HTML::Node.parse(nil,0,0,s) }
+ end
+
+ def test_parse_relaxed
+ s = "<b foo='hello'' bar='baz'>"
+ node = nil
+ assert_nothing_raised { node = HTML::Node.parse(nil,0,0,s,false) }
+ assert node.attributes.has_key?("foo")
+ assert !node.attributes.has_key?("bar")
+ end
+
+ def test_to_s_with_boolean_attrs
+ s = "<b foo bar>"
+ node = HTML::Node.parse(nil,0,0,s)
+ assert node.attributes.has_key?("foo")
+ assert node.attributes.has_key?("bar")
+ assert "<b foo bar>", node.to_s
+ end
+
+ def test_parse_with_unclosed_tag
+ s = "<span onmouseover='bang'"
+ node = nil
+ assert_nothing_raised { node = HTML::Node.parse(nil,0,0,s,false) }
+ assert node.attributes.has_key?("onmouseover")
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/sanitizer_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/sanitizer_test.rb
new file mode 100644
index 000000000..8fe9bbc5e
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/sanitizer_test.rb
@@ -0,0 +1,250 @@
+require File.dirname(__FILE__) + '/../../abstract_unit'
+require 'test/unit'
+
+class SanitizerTest < Test::Unit::TestCase
+ def setup
+ @sanitizer = nil # used by assert_sanitizer
+ end
+
+ def test_strip_tags
+ sanitizer = HTML::FullSanitizer.new
+ assert_equal("<<<bad html", sanitizer.sanitize("<<<bad html"))
+ assert_equal("<<", sanitizer.sanitize("<<<bad html>"))
+ assert_equal("Dont touch me", sanitizer.sanitize("Dont touch me"))
+ assert_equal("This is a test.", sanitizer.sanitize("<p>This <u>is<u> a <a href='test.html'><strong>test</strong></a>.</p>"))
+ assert_equal("Weirdos", sanitizer.sanitize("Wei<<a>a onclick='alert(document.cookie);'</a>/>rdos"))
+ assert_equal("This is a test.", sanitizer.sanitize("This is a test."))
+ assert_equal(
+ %{This is a test.\n\n\nIt no longer contains any HTML.\n}, sanitizer.sanitize(
+ %{<title>This is <b>a <a href="" target="_blank">test</a></b>.</title>\n\n<!-- it has a comment -->\n\n<p>It no <b>longer <strong>contains <em>any <strike>HTML</strike></em>.</strong></b></p>\n}))
+ assert_equal "This has a here.", sanitizer.sanitize("This has a <!-- comment --> here.")
+ [nil, '', ' '].each { |blank| assert_equal blank, sanitizer.sanitize(blank) }
+ end
+
+ def test_strip_links
+ sanitizer = HTML::LinkSanitizer.new
+ assert_equal "Dont touch me", sanitizer.sanitize("Dont touch me")
+ assert_equal "on my mind\nall day long", sanitizer.sanitize("<a href='almost'>on my mind</a>\n<A href='almost'>all day long</A>")
+ assert_equal "0wn3d", sanitizer.sanitize("<a href='http://www.rubyonrails.com/'><a href='http://www.rubyonrails.com/' onlclick='steal()'>0wn3d</a></a>")
+ assert_equal "Magic", sanitizer.sanitize("<a href='http://www.rubyonrails.com/'>Mag<a href='http://www.ruby-lang.org/'>ic")
+ assert_equal "FrrFox", sanitizer.sanitize("<href onlclick='steal()'>FrrFox</a></href>")
+ assert_equal "My mind\nall <b>day</b> long", sanitizer.sanitize("<a href='almost'>My mind</a>\n<A href='almost'>all <b>day</b> long</A>")
+ assert_equal "all <b>day</b> long", sanitizer.sanitize("<<a>a href='hello'>all <b>day</b> long<</A>/a>")
+
+ assert_equal "<a<a", sanitizer.sanitize("<a<a")
+ end
+
+ def test_sanitize_form
+ assert_sanitized "<form action=\"/foo/bar\" method=\"post\"><input></form>", ''
+ end
+
+ def test_sanitize_plaintext
+ raw = "<plaintext><span>foo</span></plaintext>"
+ assert_sanitized raw, "<span>foo</span>"
+ end
+
+ def test_sanitize_script
+ assert_sanitized "a b c<script language=\"Javascript\">blah blah blah</script>d e f", "a b cd e f"
+ end
+
+ # fucked
+ def test_sanitize_js_handlers
+ raw = %{onthis="do that" <a href="#" onclick="hello" name="foo" onbogus="remove me">hello</a>}
+ assert_sanitized raw, %{onthis="do that" <a name="foo" href="#">hello</a>}
+ end
+
+ def test_sanitize_javascript_href
+ raw = %{href="javascript:bang" <a href="javascript:bang" name="hello">foo</a>, <span href="javascript:bang">bar</span>}
+ assert_sanitized raw, %{href="javascript:bang" <a name="hello">foo</a>, <span>bar</span>}
+ end
+
+ def test_sanitize_image_src
+ raw = %{src="javascript:bang" <img src="javascript:bang" width="5">foo</img>, <span src="javascript:bang">bar</span>}
+ assert_sanitized raw, %{src="javascript:bang" <img width="5">foo</img>, <span>bar</span>}
+ end
+
+ HTML::WhiteListSanitizer.allowed_tags.each do |tag_name|
+ define_method "test_should_allow_#{tag_name}_tag" do
+ assert_sanitized "start <#{tag_name} title=\"1\" onclick=\"foo\">foo <bad>bar</bad> baz</#{tag_name}> end", %(start <#{tag_name} title="1">foo bar baz</#{tag_name}> end)
+ end
+ end
+
+ def test_should_allow_anchors
+ assert_sanitized %(<a href="foo" onclick="bar"><script>baz</script></a>), %(<a href="foo"></a>)
+ end
+
+ # RFC 3986, sec 4.2
+ def test_allow_colons_in_path_component
+ assert_sanitized("<a href=\"./this:that\">foo</a>")
+ end
+
+ %w(src width height alt).each do |img_attr|
+ define_method "test_should_allow_image_#{img_attr}_attribute" do
+ assert_sanitized %(<img #{img_attr}="foo" onclick="bar" />), %(<img #{img_attr}="foo" />)
+ end
+ end
+
+ def test_should_handle_non_html
+ assert_sanitized 'abc'
+ end
+
+ def test_should_handle_blank_text
+ assert_sanitized nil
+ assert_sanitized ''
+ end
+
+ def test_should_allow_custom_tags
+ text = "<u>foo</u>"
+ sanitizer = HTML::WhiteListSanitizer.new
+ assert_equal(text, sanitizer.sanitize(text, :tags => %w(u)))
+ end
+
+ def test_should_allow_only_custom_tags
+ text = "<u>foo</u> with <i>bar</i>"
+ sanitizer = HTML::WhiteListSanitizer.new
+ assert_equal("<u>foo</u> with bar", sanitizer.sanitize(text, :tags => %w(u)))
+ end
+
+ def test_should_allow_custom_tags_with_attributes
+ text = %(<blockquote cite="http://example.com/">foo</blockquote>)
+ sanitizer = HTML::WhiteListSanitizer.new
+ assert_equal(text, sanitizer.sanitize(text))
+ end
+
+ def test_should_allow_custom_tags_with_custom_attributes
+ text = %(<blockquote foo="bar">Lorem ipsum</blockquote>)
+ sanitizer = HTML::WhiteListSanitizer.new
+ assert_equal(text, sanitizer.sanitize(text, :attributes => ['foo']))
+ end
+
+ [%w(img src), %w(a href)].each do |(tag, attr)|
+ define_method "test_should_strip_#{attr}_attribute_in_#{tag}_with_bad_protocols" do
+ assert_sanitized %(<#{tag} #{attr}="javascript:bang" title="1">boo</#{tag}>), %(<#{tag} title="1">boo</#{tag}>)
+ end
+ end
+
+ def test_should_flag_bad_protocols
+ sanitizer = HTML::WhiteListSanitizer.new
+ %w(about chrome data disk hcp help javascript livescript lynxcgi lynxexec ms-help ms-its mhtml mocha opera res resource shell vbscript view-source vnd.ms.radio wysiwyg).each do |proto|
+ assert sanitizer.send(:contains_bad_protocols?, 'src', "#{proto}://bad")
+ end
+ end
+
+ def test_should_accept_good_protocols
+ sanitizer = HTML::WhiteListSanitizer.new
+ HTML::WhiteListSanitizer.allowed_protocols.each do |proto|
+ assert !sanitizer.send(:contains_bad_protocols?, 'src', "#{proto}://good")
+ end
+ end
+
+ def test_should_reject_hex_codes_in_protocol
+ assert_sanitized %(<a href="&#37;6A&#37;61&#37;76&#37;61&#37;73&#37;63&#37;72&#37;69&#37;70&#37;74&#37;3A&#37;61&#37;6C&#37;65&#37;72&#37;74&#37;28&#37;22&#37;58&#37;53&#37;53&#37;22&#37;29">1</a>), "<a>1</a>"
+ assert @sanitizer.send(:contains_bad_protocols?, 'src', "%6A%61%76%61%73%63%72%69%70%74%3A%61%6C%65%72%74%28%22%58%53%53%22%29")
+ end
+
+ def test_should_block_script_tag
+ assert_sanitized %(<SCRIPT\nSRC=http://ha.ckers.org/xss.js></SCRIPT>), ""
+ end
+
+ [%(<IMG SRC="javascript:alert('XSS');">),
+ %(<IMG SRC=javascript:alert('XSS')>),
+ %(<IMG SRC=JaVaScRiPt:alert('XSS')>),
+ %(<IMG """><SCRIPT>alert("XSS")</SCRIPT>">),
+ %(<IMG SRC=javascript:alert(&quot;XSS&quot;)>),
+ %(<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>),
+ %(<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>),
+ %(<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>),
+ %(<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>),
+ %(<IMG SRC="jav\tascript:alert('XSS');">),
+ %(<IMG SRC="jav&#x09;ascript:alert('XSS');">),
+ %(<IMG SRC="jav&#x0A;ascript:alert('XSS');">),
+ %(<IMG SRC="jav&#x0D;ascript:alert('XSS');">),
+ %(<IMG SRC=" &#14; javascript:alert('XSS');">),
+ %(<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>)].each_with_index do |img_hack, i|
+ define_method "test_should_not_fall_for_xss_image_hack_#{i+1}" do
+ assert_sanitized img_hack, "<img>"
+ end
+ end
+
+ def test_should_sanitize_tag_broken_up_by_null
+ assert_sanitized %(<SCR\0IPT>alert(\"XSS\")</SCR\0IPT>), "alert(\"XSS\")"
+ end
+
+ def test_should_sanitize_invalid_script_tag
+ assert_sanitized %(<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT>), ""
+ end
+
+ def test_should_sanitize_script_tag_with_multiple_open_brackets
+ assert_sanitized %(<<SCRIPT>alert("XSS");//<</SCRIPT>), "&lt;"
+ assert_sanitized %(<iframe src=http://ha.ckers.org/scriptlet.html\n<a), %(&lt;a)
+ end
+
+ def test_should_sanitize_unclosed_script
+ assert_sanitized %(<SCRIPT SRC=http://ha.ckers.org/xss.js?<B>), "<b>"
+ end
+
+ def test_should_sanitize_half_open_scripts
+ assert_sanitized %(<IMG SRC="javascript:alert('XSS')"), "<img>"
+ end
+
+ def test_should_not_fall_for_ridiculous_hack
+ img_hack = %(<IMG\nSRC\n=\n"\nj\na\nv\na\ns\nc\nr\ni\np\nt\n:\na\nl\ne\nr\nt\n(\n'\nX\nS\nS\n'\n)\n"\n>)
+ assert_sanitized img_hack, "<img>"
+ end
+
+ # fucked
+ def test_should_sanitize_attributes
+ assert_sanitized %(<SPAN title="'><script>alert()</script>">blah</SPAN>), %(<span title="'&gt;&lt;script&gt;alert()&lt;/script&gt;">blah</span>)
+ end
+
+ def test_should_sanitize_illegal_style_properties
+ raw = %(display:block; position:absolute; left:0; top:0; width:100%; height:100%; z-index:1; background-color:black; background-image:url(http://www.ragingplatypus.com/i/cam-full.jpg); background-x:center; background-y:center; background-repeat:repeat;)
+ expected = %(display: block; width: 100%; height: 100%; background-color: black; background-image: ; background-x: center; background-y: center;)
+ assert_equal expected, sanitize_css(raw)
+ end
+
+ def test_should_sanitize_xul_style_attributes
+ raw = %(-moz-binding:url('http://ha.ckers.org/xssmoz.xml#xss'))
+ assert_equal '', sanitize_css(raw)
+ end
+
+ def test_should_sanitize_invalid_tag_names
+ assert_sanitized(%(a b c<script/XSS src="http://ha.ckers.org/xss.js"></script>d e f), "a b cd e f")
+ end
+
+ def test_should_sanitize_non_alpha_and_non_digit_characters_in_tags
+ assert_sanitized('<a onclick!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>foo</a>', "<a>foo</a>")
+ end
+
+ def test_should_sanitize_invalid_tag_names_in_single_tags
+ assert_sanitized('<img/src="http://ha.ckers.org/xss.js"/>', "<img />")
+ end
+
+ def test_should_sanitize_img_dynsrc_lowsrc
+ assert_sanitized(%(<img lowsrc="javascript:alert('XSS')" />), "<img />")
+ end
+
+ def test_should_sanitize_div_background_image_unicode_encoded
+ raw = %(background-image:\0075\0072\006C\0028'\006a\0061\0076\0061\0073\0063\0072\0069\0070\0074\003a\0061\006c\0065\0072\0074\0028.1027\0058.1053\0053\0027\0029'\0029)
+ assert_equal '', sanitize_css(raw)
+ end
+
+ def test_should_sanitize_div_style_expression
+ raw = %(width: expression(alert('XSS'));)
+ assert_equal '', sanitize_css(raw)
+ end
+
+ def test_should_sanitize_img_vbscript
+ assert_sanitized %(<img src='vbscript:msgbox("XSS")' />), '<img />'
+ end
+
+protected
+ def assert_sanitized(input, expected = nil)
+ @sanitizer ||= HTML::WhiteListSanitizer.new
+ assert_equal expected || input, @sanitizer.sanitize(input)
+ end
+
+ def sanitize_css(input)
+ (@sanitizer ||= HTML::WhiteListSanitizer.new).sanitize_css(input)
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/tag_node_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/tag_node_test.rb
new file mode 100644
index 000000000..daeada9b9
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/tag_node_test.rb
@@ -0,0 +1,239 @@
+require File.dirname(__FILE__) + '/../../abstract_unit'
+require 'test/unit'
+
+class TagNodeTest < Test::Unit::TestCase
+ def test_open_without_attributes
+ node = tag("<tag>")
+ assert_equal "tag", node.name
+ assert_equal Hash.new, node.attributes
+ assert_nil node.closing
+ end
+
+ def test_open_with_attributes
+ node = tag("<TAG1 foo=hey_ho x:bar=\"blah blah\" BAZ='blah blah blah' >")
+ assert_equal "tag1", node.name
+ assert_equal "hey_ho", node["foo"]
+ assert_equal "blah blah", node["x:bar"]
+ assert_equal "blah blah blah", node["baz"]
+ end
+
+ def test_self_closing_without_attributes
+ node = tag("<tag/>")
+ assert_equal "tag", node.name
+ assert_equal Hash.new, node.attributes
+ assert_equal :self, node.closing
+ end
+
+ def test_self_closing_with_attributes
+ node = tag("<tag a=b/>")
+ assert_equal "tag", node.name
+ assert_equal( { "a" => "b" }, node.attributes )
+ assert_equal :self, node.closing
+ end
+
+ def test_closing_without_attributes
+ node = tag("</tag>")
+ assert_equal "tag", node.name
+ assert_nil node.attributes
+ assert_equal :close, node.closing
+ end
+
+ def test_bracket_op_when_no_attributes
+ node = tag("</tag>")
+ assert_nil node["foo"]
+ end
+
+ def test_bracket_op_when_attributes
+ node = tag("<tag a=b/>")
+ assert_equal "b", node["a"]
+ end
+
+ def test_attributes_with_escaped_quotes
+ node = tag("<tag a='b\\'c' b=\"bob \\\"float\\\"\">")
+ assert_equal "b\\'c", node["a"]
+ assert_equal "bob \\\"float\\\"", node["b"]
+ end
+
+ def test_to_s
+ node = tag("<a b=c d='f' g=\"h 'i'\" />")
+ assert_equal %(<a b='c' d='f' g='h \\'i\\'' />), node.to_s
+ end
+
+ def test_tag
+ assert tag("<tag>").tag?
+ end
+
+ def test_match_tag_as_string
+ assert tag("<tag>").match(:tag => "tag")
+ assert !tag("<tag>").match(:tag => "b")
+ end
+
+ def test_match_tag_as_regexp
+ assert tag("<tag>").match(:tag => /t.g/)
+ assert !tag("<tag>").match(:tag => /t[bqs]g/)
+ end
+
+ def test_match_attributes_as_string
+ t = tag("<tag a=something b=else />")
+ assert t.match(:attributes => {"a" => "something"})
+ assert t.match(:attributes => {"b" => "else"})
+ end
+
+ def test_match_attributes_as_regexp
+ t = tag("<tag a=something b=else />")
+ assert t.match(:attributes => {"a" => /^something$/})
+ assert t.match(:attributes => {"b" => /e.*e/})
+ assert t.match(:attributes => {"a" => /me..i/, "b" => /.ls.$/})
+ end
+
+ def test_match_attributes_as_number
+ t = tag("<tag a=15 b=3.1415 />")
+ assert t.match(:attributes => {"a" => 15})
+ assert t.match(:attributes => {"b" => 3.1415})
+ assert t.match(:attributes => {"a" => 15, "b" => 3.1415})
+ end
+
+ def test_match_attributes_exist
+ t = tag("<tag a=15 b=3.1415 />")
+ assert t.match(:attributes => {"a" => true})
+ assert t.match(:attributes => {"b" => true})
+ assert t.match(:attributes => {"a" => true, "b" => true})
+ end
+
+ def test_match_attributes_not_exist
+ t = tag("<tag a=15 b=3.1415 />")
+ assert t.match(:attributes => {"c" => false})
+ assert t.match(:attributes => {"c" => nil})
+ assert t.match(:attributes => {"a" => true, "c" => false})
+ end
+
+ def test_match_parent_success
+ t = tag("<tag a=15 b='hello'>", tag("<foo k='value'>"))
+ assert t.match(:parent => {:tag => "foo", :attributes => {"k" => /v.l/, "j" => false}})
+ end
+
+ def test_match_parent_fail
+ t = tag("<tag a=15 b='hello'>", tag("<foo k='value'>"))
+ assert !t.match(:parent => {:tag => /kafka/})
+ end
+
+ def test_match_child_success
+ t = tag("<tag x:k='something'>")
+ tag("<child v=john a=kelly>", t)
+ tag("<sib m=vaughn v=james>", t)
+ assert t.match(:child => { :tag => "sib", :attributes => {"v" => /j/}})
+ assert t.match(:child => { :attributes => {"a" => "kelly"}})
+ end
+
+ def test_match_child_fail
+ t = tag("<tag x:k='something'>")
+ tag("<child v=john a=kelly>", t)
+ tag("<sib m=vaughn v=james>", t)
+ assert !t.match(:child => { :tag => "sib", :attributes => {"v" => /r/}})
+ assert !t.match(:child => { :attributes => {"v" => false}})
+ end
+
+ def test_match_ancestor_success
+ t = tag("<tag x:k='something'>", tag("<parent v=john a=kelly>", tag("<grandparent m=vaughn v=james>")))
+ assert t.match(:ancestor => {:tag => "parent", :attributes => {"a" => /ll/}})
+ assert t.match(:ancestor => {:attributes => {"m" => "vaughn"}})
+ end
+
+ def test_match_ancestor_fail
+ t = tag("<tag x:k='something'>", tag("<parent v=john a=kelly>", tag("<grandparent m=vaughn v=james>")))
+ assert !t.match(:ancestor => {:tag => /^parent/, :attributes => {"v" => /m/}})
+ assert !t.match(:ancestor => {:attributes => {"v" => false}})
+ end
+
+ def test_match_descendant_success
+ tag("<grandchild m=vaughn v=james>", tag("<child v=john a=kelly>", t = tag("<tag x:k='something'>")))
+ assert t.match(:descendant => {:tag => "child", :attributes => {"a" => /ll/}})
+ assert t.match(:descendant => {:attributes => {"m" => "vaughn"}})
+ end
+
+ def test_match_descendant_fail
+ tag("<grandchild m=vaughn v=james>", tag("<child v=john a=kelly>", t = tag("<tag x:k='something'>")))
+ assert !t.match(:descendant => {:tag => /^child/, :attributes => {"v" => /m/}})
+ assert !t.match(:descendant => {:attributes => {"v" => false}})
+ end
+
+ def test_match_child_count
+ t = tag("<tag x:k='something'>")
+ tag("hello", t)
+ tag("<child v=john a=kelly>", t)
+ tag("<sib m=vaughn v=james>", t)
+ assert t.match(:children => { :count => 2 })
+ assert t.match(:children => { :count => 2..4 })
+ assert t.match(:children => { :less_than => 4 })
+ assert t.match(:children => { :greater_than => 1 })
+ assert !t.match(:children => { :count => 3 })
+ end
+
+ def test_conditions_as_strings
+ t = tag("<tag x:k='something'>")
+ assert t.match("tag" => "tag")
+ assert t.match("attributes" => { "x:k" => "something" })
+ assert !t.match("tag" => "gat")
+ assert !t.match("attributes" => { "x:j" => "something" })
+ end
+
+ def test_attributes_as_symbols
+ t = tag("<child v=john a=kelly>")
+ assert t.match(:attributes => { :v => /oh/ })
+ assert t.match(:attributes => { :a => /ll/ })
+ end
+
+ def test_match_sibling
+ t = tag("<tag x:k='something'>")
+ tag("hello", t)
+ tag("<span a=b>", t)
+ tag("world", t)
+ m = tag("<span k=r>", t)
+ tag("<span m=l>", t)
+
+ assert m.match(:sibling => {:tag => "span", :attributes => {:a => true}})
+ assert m.match(:sibling => {:tag => "span", :attributes => {:m => true}})
+ assert !m.match(:sibling => {:tag => "span", :attributes => {:k => true}})
+ end
+
+ def test_match_sibling_before
+ t = tag("<tag x:k='something'>")
+ tag("hello", t)
+ tag("<span a=b>", t)
+ tag("world", t)
+ m = tag("<span k=r>", t)
+ tag("<span m=l>", t)
+
+ assert m.match(:before => {:tag => "span", :attributes => {:m => true}})
+ assert !m.match(:before => {:tag => "span", :attributes => {:a => true}})
+ assert !m.match(:before => {:tag => "span", :attributes => {:k => true}})
+ end
+
+ def test_match_sibling_after
+ t = tag("<tag x:k='something'>")
+ tag("hello", t)
+ tag("<span a=b>", t)
+ tag("world", t)
+ m = tag("<span k=r>", t)
+ tag("<span m=l>", t)
+
+ assert m.match(:after => {:tag => "span", :attributes => {:a => true}})
+ assert !m.match(:after => {:tag => "span", :attributes => {:m => true}})
+ assert !m.match(:after => {:tag => "span", :attributes => {:k => true}})
+ end
+
+ def test_to_s
+ t = tag("<b x='foo'>")
+ tag("hello", t)
+ tag("<hr />", t)
+ assert_equal %(<b x="foo">hello<hr /></b>), t.to_s
+ end
+
+ private
+
+ def tag(content, parent=nil)
+ node = HTML::Node.parse(parent,0,0,content)
+ parent.children << node if parent
+ node
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/text_node_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/text_node_test.rb
new file mode 100644
index 000000000..9853701fd
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/text_node_test.rb
@@ -0,0 +1,51 @@
+require File.dirname(__FILE__) + '/../../abstract_unit'
+require 'test/unit'
+
+class TextNodeTest < Test::Unit::TestCase
+ def setup
+ @node = HTML::Text.new(nil, 0, 0, "hello, howdy, aloha, annyeong")
+ end
+
+ def test_to_s
+ assert_equal "hello, howdy, aloha, annyeong", @node.to_s
+ end
+
+ def test_find_string
+ assert_equal @node, @node.find("hello, howdy, aloha, annyeong")
+ assert_equal false, @node.find("bogus")
+ end
+
+ def test_find_regexp
+ assert_equal @node, @node.find(/an+y/)
+ assert_nil @node.find(/b/)
+ end
+
+ def test_find_hash
+ assert_equal @node, @node.find(:content => /howdy/)
+ assert_nil @node.find(:content => /^howdy$/)
+ assert_equal false, @node.find(:content => "howdy")
+ end
+
+ def test_find_other
+ assert_nil @node.find(:hello)
+ end
+
+ def test_match_string
+ assert @node.match("hello, howdy, aloha, annyeong")
+ assert_equal false, @node.match("bogus")
+ end
+
+ def test_match_regexp
+ assert_not_nil @node, @node.match(/an+y/)
+ assert_nil @node.match(/b/)
+ end
+
+ def test_match_hash
+ assert_not_nil @node, @node.match(:content => "howdy")
+ assert_nil @node.match(:content => /^howdy$/)
+ end
+
+ def test_match_other
+ assert_nil @node.match(:hello)
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/tokenizer_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/tokenizer_test.rb
new file mode 100644
index 000000000..437136b9a
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/html-scanner/tokenizer_test.rb
@@ -0,0 +1,125 @@
+require File.dirname(__FILE__) + '/../../abstract_unit'
+require 'test/unit'
+
+class TokenizerTest < Test::Unit::TestCase
+
+ def test_blank
+ tokenize ""
+ assert_end
+ end
+
+ def test_space
+ tokenize " "
+ assert_next " "
+ assert_end
+ end
+
+ def test_tag_simple_open
+ tokenize "<tag>"
+ assert_next "<tag>"
+ assert_end
+ end
+
+ def test_tag_simple_self_closing
+ tokenize "<tag />"
+ assert_next "<tag />"
+ assert_end
+ end
+
+ def test_tag_simple_closing
+ tokenize "</tag>"
+ assert_next "</tag>"
+ end
+
+ def test_tag_with_single_quoted_attribute
+ tokenize %{<tag a='hello'>x}
+ assert_next %{<tag a='hello'>}
+ end
+
+ def test_tag_with_single_quoted_attribute_with_escape
+ tokenize %{<tag a='hello\\''>x}
+ assert_next %{<tag a='hello\\''>}
+ end
+
+ def test_tag_with_double_quoted_attribute
+ tokenize %{<tag a="hello">x}
+ assert_next %{<tag a="hello">}
+ end
+
+ def test_tag_with_double_quoted_attribute_with_escape
+ tokenize %{<tag a="hello\\"">x}
+ assert_next %{<tag a="hello\\"">}
+ end
+
+ def test_tag_with_unquoted_attribute
+ tokenize %{<tag a=hello>x}
+ assert_next %{<tag a=hello>}
+ end
+
+ def test_tag_with_lt_char_in_attribute
+ tokenize %{<tag a="x < y">x}
+ assert_next %{<tag a="x < y">}
+ end
+
+ def test_tag_with_gt_char_in_attribute
+ tokenize %{<tag a="x > y">x}
+ assert_next %{<tag a="x > y">}
+ end
+
+ def test_doctype_tag
+ tokenize %{<!DOCTYPE "blah" "blah" "blah">\n <html>}
+ assert_next %{<!DOCTYPE "blah" "blah" "blah">}
+ assert_next %{\n }
+ assert_next %{<html>}
+ end
+
+ def test_cdata_tag
+ tokenize %{<![CDATA[<br>]]>}
+ assert_next %{<![CDATA[<br>]]>}
+ assert_end
+ end
+
+ def test_less_than_with_space
+ tokenize %{original < hello > world}
+ assert_next %{original }
+ assert_next %{< hello > world}
+ end
+
+ def test_less_than_without_matching_greater_than
+ tokenize %{hello <span onmouseover="gotcha"\n<b>foo</b>\nbar</span>}
+ assert_next %{hello }
+ assert_next %{<span onmouseover="gotcha"\n}
+ assert_next %{<b>}
+ assert_next %{foo}
+ assert_next %{</b>}
+ assert_next %{\nbar}
+ assert_next %{</span>}
+ assert_end
+ end
+
+ def test_unterminated_comment
+ tokenize %{hello <!-- neverending...}
+ assert_next %{hello }
+ assert_next %{<!-- neverending...}
+ assert_end
+ end
+
+ private
+
+ def tokenize(text)
+ @tokenizer = HTML::Tokenizer.new(text)
+ end
+
+ def assert_next(expected, message=nil)
+ token = @tokenizer.next
+ assert_equal expected, token, message
+ end
+
+ def assert_sequence(*expected)
+ assert_next expected.shift until expected.empty?
+ end
+
+ def assert_end(message=nil)
+ assert_nil @tokenizer.next, message
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/http_authentication_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/http_authentication_test.rb
new file mode 100644
index 000000000..6f7b31a41
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/http_authentication_test.rb
@@ -0,0 +1,54 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class HttpBasicAuthenticationTest < Test::Unit::TestCase
+ include ActionController::HttpAuthentication::Basic
+
+ class DummyController
+ attr_accessor :headers, :renders, :request
+
+ def initialize
+ @headers, @renders = {}, []
+ @request = ActionController::TestRequest.new
+ end
+
+ def render(options)
+ self.renders << options
+ end
+ end
+
+ def setup
+ @controller = DummyController.new
+ @credentials = ActionController::HttpAuthentication::Basic.encode_credentials("dhh", "secret")
+ end
+
+ def test_successful_authentication
+ login = Proc.new { |user_name, password| user_name == "dhh" && password == "secret" }
+ set_headers
+ assert authenticate(@controller, &login)
+
+ set_headers ''
+ assert_nothing_raised do
+ assert !authenticate(@controller, &login)
+ end
+
+ set_headers nil
+ set_headers @credentials, 'REDIRECT_X_HTTP_AUTHORIZATION'
+ assert authenticate(@controller, &login)
+ end
+
+ def test_failing_authentication
+ set_headers
+ assert !authenticate(@controller) { |user_name, password| user_name == "dhh" && password == "incorrect" }
+ end
+
+ def test_authentication_request
+ authentication_request(@controller, "Megaglobalapp")
+ assert_equal 'Basic realm="Megaglobalapp"', @controller.headers["WWW-Authenticate"]
+ assert_equal :unauthorized, @controller.renders.first[:status]
+ end
+
+ private
+ def set_headers(value = @credentials, name = 'HTTP_AUTHORIZATION')
+ @controller.request.env[name] = value
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/integration_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/integration_test.rb
new file mode 100644
index 000000000..4213bb4af
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/integration_test.rb
@@ -0,0 +1,255 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+$:.unshift File.dirname(__FILE__) + '/../../../railties/lib'
+require 'action_controller/integration'
+
+uses_mocha 'integration' do
+
+# Stub process for testing.
+module ActionController
+ module Integration
+ class Session
+ def process(*args)
+ end
+
+ def generic_url_rewriter
+ end
+ end
+ end
+end
+
+class SessionTest < Test::Unit::TestCase
+ def setup
+ @session = ActionController::Integration::Session.new
+ end
+ def test_https_bang_works_and_sets_truth_by_default
+ assert !@session.https?
+ @session.https!
+ assert @session.https?
+ @session.https! false
+ assert !@session.https?
+ end
+
+ def test_host!
+ assert_not_equal "glu.ttono.us", @session.host
+ @session.host! "rubyonrails.com"
+ assert_equal "rubyonrails.com", @session.host
+ end
+
+ def test_follow_redirect_raises_when_no_redirect
+ @session.stubs(:redirect?).returns(false)
+ assert_raise(RuntimeError) { @session.follow_redirect! }
+ end
+
+ def test_follow_redirect_calls_get_and_returns_status
+ @session.stubs(:redirect?).returns(true)
+ @session.stubs(:headers).returns({"location" => ["www.google.com"]})
+ @session.stubs(:status).returns(200)
+ @session.expects(:get)
+ assert_equal 200, @session.follow_redirect!
+ end
+
+ def test_request_via_redirect_uses_given_method
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"}
+ @session.expects(:put).with(path, args, headers)
+ @session.stubs(:redirect?).returns(false)
+ @session.request_via_redirect(:put, path, args, headers)
+ end
+
+ def test_request_via_redirect_follows_redirects
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"}
+ @session.stubs(:redirect?).returns(true, true, false)
+ @session.expects(:follow_redirect!).times(2)
+ @session.request_via_redirect(:get, path, args, headers)
+ end
+
+ def test_request_via_redirect_returns_status
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"}
+ @session.stubs(:redirect?).returns(false)
+ @session.stubs(:status).returns(200)
+ assert_equal 200, @session.request_via_redirect(:get, path, args, headers)
+ end
+
+ def test_get_via_redirect
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ @session.expects(:request_via_redirect).with(:get, path, args, headers)
+ @session.get_via_redirect(path, args, headers)
+ end
+
+ def test_post_via_redirect
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ @session.expects(:request_via_redirect).with(:post, path, args, headers)
+ @session.post_via_redirect(path, args, headers)
+ end
+
+ def test_put_via_redirect
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ @session.expects(:request_via_redirect).with(:put, path, args, headers)
+ @session.put_via_redirect(path, args, headers)
+ end
+
+ def test_delete_via_redirect
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ @session.expects(:request_via_redirect).with(:delete, path, args, headers)
+ @session.delete_via_redirect(path, args, headers)
+ end
+
+ def test_url_for_with_controller
+ options = {:action => 'show'}
+ mock_controller = mock()
+ mock_controller.expects(:url_for).with(options).returns('/show')
+ @session.stubs(:controller).returns(mock_controller)
+ assert_equal '/show', @session.url_for(options)
+ end
+
+ def test_url_for_without_controller
+ options = {:action => 'show'}
+ mock_rewriter = mock()
+ mock_rewriter.expects(:rewrite).with(options).returns('/show')
+ @session.stubs(:generic_url_rewriter).returns(mock_rewriter)
+ @session.stubs(:controller).returns(nil)
+ assert_equal '/show', @session.url_for(options)
+ end
+
+ def test_redirect_bool_with_status_in_300s
+ @session.stubs(:status).returns 301
+ assert @session.redirect?
+ end
+
+ def test_redirect_bool_with_status_in_200s
+ @session.stubs(:status).returns 200
+ assert !@session.redirect?
+ end
+
+ def test_get
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:get,path,params,headers)
+ @session.get(path,params,headers)
+ end
+
+ def test_post
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:post,path,params,headers)
+ @session.post(path,params,headers)
+ end
+
+ def test_put
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:put,path,params,headers)
+ @session.put(path,params,headers)
+ end
+
+ def test_delete
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:delete,path,params,headers)
+ @session.delete(path,params,headers)
+ end
+
+ def test_head
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:head,path,params,headers)
+ @session.head(path,params,headers)
+ end
+
+ def test_xml_http_request_get
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "X-Requested-With" => "XMLHttpRequest",
+ "Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:get,path,params,headers_after_xhr)
+ @session.xml_http_request(:get,path,params,headers)
+ end
+
+ def test_xml_http_request_post
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "X-Requested-With" => "XMLHttpRequest",
+ "Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:post,path,params,headers_after_xhr)
+ @session.xml_http_request(:post,path,params,headers)
+ end
+
+ def test_xml_http_request_put
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "X-Requested-With" => "XMLHttpRequest",
+ "Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:put,path,params,headers_after_xhr)
+ @session.xml_http_request(:put,path,params,headers)
+ end
+
+ def test_xml_http_request_delete
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "X-Requested-With" => "XMLHttpRequest",
+ "Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:delete,path,params,headers_after_xhr)
+ @session.xml_http_request(:delete,path,params,headers)
+ end
+
+ def test_xml_http_request_head
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "X-Requested-With" => "XMLHttpRequest",
+ "Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:head,path,params,headers_after_xhr)
+ @session.xml_http_request(:head,path,params,headers)
+ end
+
+ def test_xml_http_request_override_accept
+ path = "/index"; params = "blah"; headers = {:location => 'blah', "Accept" => "application/xml"}
+ headers_after_xhr = headers.merge(
+ "X-Requested-With" => "XMLHttpRequest"
+ )
+ @session.expects(:process).with(:post,path,params,headers_after_xhr)
+ @session.xml_http_request(:post,path,params,headers)
+ end
+end
+
+class IntegrationTestTest < Test::Unit::TestCase
+
+ def setup
+ @test = ::ActionController::IntegrationTest.new(:default_test)
+ @test.class.stubs(:fixture_table_names).returns([])
+ @session = @test.open_session
+ end
+
+ def test_opens_new_session
+ @test.class.expects(:fixture_table_names).times(2).returns(['foo'])
+
+ session1 = @test.open_session { |sess| }
+ session2 = @test.open_session # implicit session
+
+ assert_equal ::ActionController::Integration::Session, session1.class
+ assert_equal ::ActionController::Integration::Session, session2.class
+ assert_not_equal session1, session2
+ end
+
+end
+
+# Tests that integration tests don't call Controller test methods for processing.
+# Integration tests have their own setup and teardown.
+class IntegrationTestUsesCorrectClass < ActionController::IntegrationTest
+
+ def self.fixture_table_names
+ []
+ end
+
+ def test_integration_methods_called
+ %w( get post head put delete ).each do |verb|
+ assert_nothing_raised("'#{verb}' should use integration test methods") { send!(verb, '/') }
+ end
+ end
+
+end
+
+# TODO
+# class MockCGITest < Test::Unit::TestCase
+# end
+
+end # uses_mocha
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/layout_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/layout_test.rb
new file mode 100644
index 000000000..85cc3a084
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/layout_test.rb
@@ -0,0 +1,239 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+# The view_paths array must be set on Base and not LayoutTest so that LayoutTest's inherited
+# method has access to the view_paths array when looking for a layout to automatically assign.
+old_load_paths = ActionController::Base.view_paths
+ActionController::Base.view_paths = [ File.dirname(__FILE__) + '/../fixtures/layout_tests/' ]
+
+class LayoutTest < ActionController::Base
+ def self.controller_path; 'views' end
+ self.view_paths = ActionController::Base.view_paths.dup
+end
+
+# Restore view_paths to previous value
+ActionController::Base.view_paths = old_load_paths
+
+class ProductController < LayoutTest
+end
+
+class ItemController < LayoutTest
+end
+
+class ThirdPartyTemplateLibraryController < LayoutTest
+end
+
+module ControllerNameSpace
+end
+
+class ControllerNameSpace::NestedController < LayoutTest
+end
+
+class MultipleExtensions < LayoutTest
+end
+
+class MabView
+ def initialize(view)
+ end
+
+ def render(text, locals = {})
+ text
+ end
+end
+
+ActionView::Base::register_template_handler :mab, MabView
+
+class LayoutAutoDiscoveryTest < Test::Unit::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_application_layout_is_default_when_no_controller_match
+ @controller = ProductController.new
+ get :hello
+ assert_equal 'layout_test.rhtml hello.rhtml', @response.body
+ end
+
+ def test_controller_name_layout_name_match
+ @controller = ItemController.new
+ get :hello
+ assert_equal 'item.rhtml hello.rhtml', @response.body
+ end
+
+ def test_third_party_template_library_auto_discovers_layout
+ @controller = ThirdPartyTemplateLibraryController.new
+ get :hello
+ assert_equal 'layouts/third_party_template_library', @controller.active_layout
+ assert_equal 'layouts/third_party_template_library', @response.layout
+ assert_equal 'Mab', @response.body
+ end
+
+ def test_namespaced_controllers_auto_detect_layouts
+ @controller = ControllerNameSpace::NestedController.new
+ get :hello
+ assert_equal 'layouts/controller_name_space/nested', @controller.active_layout
+ assert_equal 'controller_name_space/nested.rhtml hello.rhtml', @response.body
+ end
+
+ def test_namespaced_controllers_auto_detect_layouts
+ @controller = MultipleExtensions.new
+ get :hello
+ assert_equal 'layouts/multiple_extensions', @controller.active_layout
+ assert_equal 'multiple_extensions.html.erb hello.rhtml', @response.body.strip
+ end
+end
+
+class ExemptFromLayoutTest < Test::Unit::TestCase
+ def setup
+ @controller = LayoutTest.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_rjs_exempt_from_layout
+ assert @controller.send!(:template_exempt_from_layout?, 'test.rjs')
+ end
+
+ def test_rhtml_and_rxml_not_exempt_from_layout
+ assert !@controller.send!(:template_exempt_from_layout?, 'test.rhtml')
+ assert !@controller.send!(:template_exempt_from_layout?, 'test.rxml')
+ end
+
+ def test_other_extension_not_exempt_from_layout
+ assert !@controller.send!(:template_exempt_from_layout?, 'test.random')
+ end
+
+ def test_add_extension_to_exempt_from_layout
+ ['rpdf', :rpdf].each do |ext|
+ assert_nothing_raised do
+ ActionController::Base.exempt_from_layout ext
+ end
+ assert @controller.send!(:template_exempt_from_layout?, "test.#{ext}")
+ end
+ end
+
+ def test_add_regexp_to_exempt_from_layout
+ ActionController::Base.exempt_from_layout /\.rdoc/
+ assert @controller.send!(:template_exempt_from_layout?, 'test.rdoc')
+ end
+
+ def test_rhtml_exempt_from_layout_status_should_prevent_layout_render
+ ActionController::Base.exempt_from_layout :rhtml
+
+ assert @controller.send!(:template_exempt_from_layout?, 'test.rhtml')
+ assert @controller.send!(:template_exempt_from_layout?, 'hello.rhtml')
+
+ get :hello
+ assert_equal 'hello.rhtml', @response.body
+ ActionController::Base.exempt_from_layout.delete(/\.rhtml$/)
+ end
+end
+
+
+class DefaultLayoutController < LayoutTest
+end
+
+class HasOwnLayoutController < LayoutTest
+ layout 'item'
+end
+
+class SetsLayoutInRenderController < LayoutTest
+ def hello
+ render :layout => 'third_party_template_library'
+ end
+end
+
+class RendersNoLayoutController < LayoutTest
+ def hello
+ render :layout => false
+ end
+end
+
+class LayoutSetInResponseTest < Test::Unit::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_layout_set_when_using_default_layout
+ @controller = DefaultLayoutController.new
+ get :hello
+ assert_equal 'layouts/layout_test', @response.layout
+ end
+
+ def test_layout_set_when_set_in_controller
+ @controller = HasOwnLayoutController.new
+ get :hello
+ assert_equal 'layouts/item', @response.layout
+ end
+
+ def test_layout_set_when_using_render
+ @controller = SetsLayoutInRenderController.new
+ get :hello
+ assert_equal 'layouts/third_party_template_library', @response.layout
+ end
+
+ def test_layout_is_not_set_when_none_rendered
+ @controller = RendersNoLayoutController.new
+ get :hello
+ assert_nil @response.layout
+ end
+
+ def test_exempt_from_layout_honored_by_render_template
+ ActionController::Base.exempt_from_layout :rhtml
+ @controller = RenderWithTemplateOptionController.new
+
+ assert @controller.send(:template_exempt_from_layout?, 'alt/hello.rhtml')
+
+ get :hello
+ assert_equal "alt/hello.rhtml", @response.body.strip
+
+ ensure
+ ActionController::Base.exempt_from_layout.delete(/\.rhtml$/)
+ end
+end
+
+class RenderWithTemplateOptionController < LayoutTest
+ def hello
+ render :template => 'alt/hello'
+ end
+end
+
+class SetsNonExistentLayoutFile < LayoutTest
+ layout "nofile.rhtml"
+end
+
+class LayoutExceptionRaised < Test::Unit::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_exception_raised_when_layout_file_not_found
+ @controller = SetsNonExistentLayoutFile.new
+ get :hello
+ @response.template.class.module_eval { attr_accessor :exception }
+ assert_equal ActionController::MissingTemplate, @response.template.exception.class
+ end
+end
+
+class LayoutStatusIsRendered < LayoutTest
+ def hello
+ render :status => 401
+ end
+end
+
+class LayoutStatusIsRenderedTest < Test::Unit::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_layout_status_is_rendered
+ @controller = LayoutStatusIsRendered.new
+ get :hello
+ assert_response 401
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/mime_responds_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/mime_responds_test.rb
new file mode 100644
index 000000000..f121dd9f8
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/mime_responds_test.rb
@@ -0,0 +1,506 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class RespondToController < ActionController::Base
+ layout :set_layout
+
+ def html_xml_or_rss
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.xml { render :text => "XML" }
+ type.rss { render :text => "RSS" }
+ type.all { render :text => "Nothing" }
+ end
+ end
+
+ def js_or_html
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.js { render :text => "JS" }
+ type.all { render :text => "Nothing" }
+ end
+ end
+
+ def json_or_yaml
+ respond_to do |type|
+ type.json { render :text => "JSON" }
+ type.yaml { render :text => "YAML" }
+ end
+ end
+
+ def html_or_xml
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.xml { render :text => "XML" }
+ type.all { render :text => "Nothing" }
+ end
+ end
+
+ def forced_xml
+ request.format = :xml
+
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.xml { render :text => "XML" }
+ end
+ end
+
+ def just_xml
+ respond_to do |type|
+ type.xml { render :text => "XML" }
+ end
+ end
+
+ def using_defaults
+ respond_to do |type|
+ type.html
+ type.js
+ type.xml
+ end
+ end
+
+ def using_defaults_with_type_list
+ respond_to(:html, :js, :xml)
+ end
+
+ def made_for_content_type
+ respond_to do |type|
+ type.rss { render :text => "RSS" }
+ type.atom { render :text => "ATOM" }
+ type.all { render :text => "Nothing" }
+ end
+ end
+
+ def custom_type_handling
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.custom("application/crazy-xml") { render :text => "Crazy XML" }
+ type.all { render :text => "Nothing" }
+ end
+ end
+
+ def custom_constant_handling
+ Mime::Type.register("text/x-mobile", :mobile)
+
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.mobile { render :text => "Mobile" }
+ end
+ ensure
+ Mime.module_eval { remove_const :MOBILE if const_defined?(:MOBILE) }
+ end
+
+ def custom_constant_handling_without_block
+ Mime::Type.register("text/x-mobile", :mobile)
+
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.mobile
+ end
+
+ ensure
+ Mime.module_eval { remove_const :MOBILE if const_defined?(:MOBILE) }
+ end
+
+ def handle_any
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.any(:js, :xml) { render :text => "Either JS or XML" }
+ end
+ end
+
+ def all_types_with_layout
+ respond_to do |type|
+ type.html
+ type.js
+ end
+ end
+
+ def iphone_with_html_response_type
+ Mime::Type.register_alias("text/html", :iphone)
+ request.format = :iphone if request.env["HTTP_ACCEPT"] == "text/iphone"
+
+ respond_to do |type|
+ type.html { @type = "Firefox" }
+ type.iphone { @type = "iPhone" }
+ end
+
+ ensure
+ Mime.module_eval { remove_const :IPHONE if const_defined?(:IPHONE) }
+ end
+
+ def iphone_with_html_response_type_without_layout
+ Mime::Type.register_alias("text/html", :iphone)
+ request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone"
+
+ respond_to do |type|
+ type.html { @type = "Firefox"; render :action => "iphone_with_html_response_type" }
+ type.iphone { @type = "iPhone" ; render :action => "iphone_with_html_response_type" }
+ end
+
+ ensure
+ Mime.module_eval { remove_const :IPHONE if const_defined?(:IPHONE) }
+ end
+
+ def rescue_action(e)
+ raise
+ end
+
+ protected
+ def set_layout
+ if ["all_types_with_layout", "iphone_with_html_response_type"].include?(action_name)
+ "respond_to/layouts/standard"
+ elsif action_name == "iphone_with_html_response_type_without_layout"
+ "respond_to/layouts/missing"
+ end
+ end
+end
+
+RespondToController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
+
+class MimeControllerTest < Test::Unit::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @controller = RespondToController.new
+ @request.host = "www.example.com"
+ end
+
+ def test_html
+ @request.env["HTTP_ACCEPT"] = "text/html"
+ get :js_or_html
+ assert_equal 'HTML', @response.body
+
+ get :html_or_xml
+ assert_equal 'HTML', @response.body
+
+ get :just_xml
+ assert_response 406
+ end
+
+ def test_all
+ @request.env["HTTP_ACCEPT"] = "*/*"
+ get :js_or_html
+ assert_equal 'HTML', @response.body # js is not part of all
+
+ get :html_or_xml
+ assert_equal 'HTML', @response.body
+
+ get :just_xml
+ assert_equal 'XML', @response.body
+ end
+
+ def test_xml
+ @request.env["HTTP_ACCEPT"] = "application/xml"
+ get :html_xml_or_rss
+ assert_equal 'XML', @response.body
+ end
+
+ def test_js_or_html
+ @request.env["HTTP_ACCEPT"] = "text/javascript, text/html"
+ get :js_or_html
+ assert_equal 'JS', @response.body
+
+ get :html_or_xml
+ assert_equal 'HTML', @response.body
+
+ get :just_xml
+ assert_response 406
+ end
+
+ def test_json_or_yaml
+ get :json_or_yaml
+ assert_equal 'JSON', @response.body
+
+ get :json_or_yaml, :format => 'json'
+ assert_equal 'JSON', @response.body
+
+ get :json_or_yaml, :format => 'yaml'
+ assert_equal 'YAML', @response.body
+
+ { 'YAML' => %w(text/yaml),
+ 'JSON' => %w(application/json text/x-json)
+ }.each do |body, content_types|
+ content_types.each do |content_type|
+ @request.env['HTTP_ACCEPT'] = content_type
+ get :json_or_yaml
+ assert_equal body, @response.body
+ end
+ end
+ end
+
+ def test_js_or_anything
+ @request.env["HTTP_ACCEPT"] = "text/javascript, */*"
+ get :js_or_html
+ assert_equal 'JS', @response.body
+
+ get :html_or_xml
+ assert_equal 'HTML', @response.body
+
+ get :just_xml
+ assert_equal 'XML', @response.body
+ end
+
+ def test_using_defaults
+ @request.env["HTTP_ACCEPT"] = "*/*"
+ get :using_defaults
+ assert_equal "text/html", @response.content_type
+ assert_equal 'Hello world!', @response.body
+
+ @request.env["HTTP_ACCEPT"] = "text/javascript"
+ get :using_defaults
+ assert_equal "text/javascript", @response.content_type
+ assert_equal '$("body").visualEffect("highlight");', @response.body
+
+ @request.env["HTTP_ACCEPT"] = "application/xml"
+ get :using_defaults
+ assert_equal "application/xml", @response.content_type
+ assert_equal "<p>Hello world!</p>\n", @response.body
+ end
+
+ def test_using_defaults_with_type_list
+ @request.env["HTTP_ACCEPT"] = "*/*"
+ get :using_defaults_with_type_list
+ assert_equal "text/html", @response.content_type
+ assert_equal 'Hello world!', @response.body
+
+ @request.env["HTTP_ACCEPT"] = "text/javascript"
+ get :using_defaults_with_type_list
+ assert_equal "text/javascript", @response.content_type
+ assert_equal '$("body").visualEffect("highlight");', @response.body
+
+ @request.env["HTTP_ACCEPT"] = "application/xml"
+ get :using_defaults_with_type_list
+ assert_equal "application/xml", @response.content_type
+ assert_equal "<p>Hello world!</p>\n", @response.body
+ end
+
+ def test_with_atom_content_type
+ @request.env["CONTENT_TYPE"] = "application/atom+xml"
+ get :made_for_content_type
+ assert_equal "ATOM", @response.body
+ end
+
+ def test_with_rss_content_type
+ @request.env["CONTENT_TYPE"] = "application/rss+xml"
+ get :made_for_content_type
+ assert_equal "RSS", @response.body
+ end
+
+ def test_synonyms
+ @request.env["HTTP_ACCEPT"] = "application/javascript"
+ get :js_or_html
+ assert_equal 'JS', @response.body
+
+ @request.env["HTTP_ACCEPT"] = "application/x-xml"
+ get :html_xml_or_rss
+ assert_equal "XML", @response.body
+ end
+
+ def test_custom_types
+ @request.env["HTTP_ACCEPT"] = "application/crazy-xml"
+ get :custom_type_handling
+ assert_equal "application/crazy-xml", @response.content_type
+ assert_equal 'Crazy XML', @response.body
+
+ @request.env["HTTP_ACCEPT"] = "text/html"
+ get :custom_type_handling
+ assert_equal "text/html", @response.content_type
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_xhtml_alias
+ @request.env["HTTP_ACCEPT"] = "application/xhtml+xml,application/xml"
+ get :html_or_xml
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_firefox_simulation
+ @request.env["HTTP_ACCEPT"] = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
+ get :html_or_xml
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_handle_any
+ @request.env["HTTP_ACCEPT"] = "*/*"
+ get :handle_any
+ assert_equal 'HTML', @response.body
+
+ @request.env["HTTP_ACCEPT"] = "text/javascript"
+ get :handle_any
+ assert_equal 'Either JS or XML', @response.body
+
+ @request.env["HTTP_ACCEPT"] = "text/xml"
+ get :handle_any
+ assert_equal 'Either JS or XML', @response.body
+ end
+
+ def test_rjs_type_skips_layout
+ @request.env["HTTP_ACCEPT"] = "text/javascript"
+ get :all_types_with_layout
+ assert_equal 'RJS for all_types_with_layout', @response.body
+ end
+
+ def test_html_type_with_layout
+ @request.env["HTTP_ACCEPT"] = "text/html"
+ get :all_types_with_layout
+ assert_equal '<html><div id="html">HTML for all_types_with_layout</div></html>', @response.body
+ end
+
+ def test_xhr
+ xhr :get, :js_or_html
+ assert_equal 'JS', @response.body
+
+ xhr :get, :using_defaults
+ assert_equal '$("body").visualEffect("highlight");', @response.body
+ end
+
+ def test_custom_constant
+ get :custom_constant_handling, :format => "mobile"
+ assert_equal "text/x-mobile", @response.content_type
+ assert_equal "Mobile", @response.body
+ end
+
+ def test_custom_constant_handling_without_block
+ get :custom_constant_handling_without_block, :format => "mobile"
+ assert_equal "text/x-mobile", @response.content_type
+ assert_equal "Mobile", @response.body
+ end
+
+ def test_forced_format
+ get :html_xml_or_rss
+ assert_equal "HTML", @response.body
+
+ get :html_xml_or_rss, :format => "html"
+ assert_equal "HTML", @response.body
+
+ get :html_xml_or_rss, :format => "xml"
+ assert_equal "XML", @response.body
+
+ get :html_xml_or_rss, :format => "rss"
+ assert_equal "RSS", @response.body
+ end
+
+ def test_internally_forced_format
+ get :forced_xml
+ assert_equal "XML", @response.body
+
+ get :forced_xml, :format => "html"
+ assert_equal "XML", @response.body
+ end
+
+ def test_extension_synonyms
+ get :html_xml_or_rss, :format => "xhtml"
+ assert_equal "HTML", @response.body
+ end
+
+ def test_render_action_for_html
+ @controller.instance_eval do
+ def render(*args)
+ unless args.empty?
+ @action = args.first[:action]
+ end
+ response.body = "#{@action} - #{@template.template_format}"
+ end
+ end
+
+ get :using_defaults
+ assert_equal "using_defaults - html", @response.body
+
+ get :using_defaults, :format => "xml"
+ assert_equal "using_defaults - xml", @response.body
+ end
+
+ def test_format_with_custom_response_type
+ get :iphone_with_html_response_type
+ assert_equal '<html><div id="html">Hello future from Firefox!</div></html>', @response.body
+
+ get :iphone_with_html_response_type, :format => "iphone"
+ assert_equal "text/html", @response.content_type
+ assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
+ end
+
+ def test_format_with_custom_response_type_and_request_headers
+ @request.env["HTTP_ACCEPT"] = "text/iphone"
+ get :iphone_with_html_response_type
+ assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
+ assert_equal "text/html", @response.content_type
+ end
+
+ def test_format_with_custom_response_type_and_request_headers_with_only_one_layout_present
+ get :iphone_with_html_response_type_without_layout
+ assert_equal '<html><div id="html_missing">Hello future from Firefox!</div></html>', @response.body
+
+ @request.env["HTTP_ACCEPT"] = "text/iphone"
+ assert_raises(ActionController::MissingTemplate) { get :iphone_with_html_response_type_without_layout }
+ end
+end
+
+class AbstractPostController < ActionController::Base
+ class << self
+ def view_paths
+ [ File.dirname(__FILE__) + "/../fixtures/post_test/" ]
+ end
+ end
+end
+
+# For testing layouts which are set automatically
+class PostController < AbstractPostController
+ around_filter :with_iphone
+
+ def index
+ respond_to do |type|
+ type.html
+ type.iphone
+ end
+ end
+
+ protected
+ def with_iphone
+ Mime::Type.register_alias("text/html", :iphone)
+ request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone"
+ yield
+ ensure
+ Mime.module_eval { remove_const :IPHONE if const_defined?(:IPHONE) }
+ end
+end
+
+class SuperPostController < PostController
+ def index
+ respond_to do |type|
+ type.html
+ type.iphone
+ end
+ end
+end
+
+class MimeControllerLayoutsTest < Test::Unit::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @controller = PostController.new
+ @request.host = "www.example.com"
+ end
+
+ def test_missing_layout_renders_properly
+ get :index
+ assert_equal '<html><div id="html">Hello Firefox</div></html>', @response.body
+
+ @request.env["HTTP_ACCEPT"] = "text/iphone"
+ get :index
+ assert_equal 'Hello iPhone', @response.body
+ end
+
+ def test_format_with_inherited_layouts
+ @controller = SuperPostController.new
+
+ get :index
+ assert_equal 'Super Firefox', @response.body
+
+ @request.env["HTTP_ACCEPT"] = "text/iphone"
+ get :index
+ assert_equal '<html><div id="super_iphone">Super iPhone</div></html>', @response.body
+ end
+end
+
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/mime_type_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/mime_type_test.rb
new file mode 100644
index 000000000..d4aea3c01
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/mime_type_test.rb
@@ -0,0 +1,55 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class MimeTypeTest < Test::Unit::TestCase
+ Mime::Type.register "image/png", :png
+ Mime::Type.register "application/pdf", :pdf
+
+ def test_parse_single
+ Mime::LOOKUP.keys.each do |mime_type|
+ assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type)
+ end
+ end
+
+ def test_parse_without_q
+ accept = "text/xml,application/xhtml+xml,text/yaml,application/xml,text/html,image/png,text/plain,application/pdf,*/*"
+ expect = [Mime::HTML, Mime::XML, Mime::YAML, Mime::PNG, Mime::TEXT, Mime::PDF, Mime::ALL]
+ assert_equal expect, Mime::Type.parse(accept)
+ end
+
+ def test_parse_with_q
+ accept = "text/xml,application/xhtml+xml,text/yaml; q=0.3,application/xml,text/html; q=0.8,image/png,text/plain; q=0.5,application/pdf,*/*; q=0.2"
+ expect = [Mime::HTML, Mime::XML, Mime::PNG, Mime::PDF, Mime::TEXT, Mime::YAML, Mime::ALL]
+ assert_equal expect, Mime::Type.parse(accept)
+ end
+
+ # Accept header send with user HTTP_USER_AGENT: Sunrise/0.42j (Windows XP)
+ def test_parse_crappy_broken_acceptlines
+ accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/*,,*/*;q=0.5"
+ expect = [Mime::HTML, Mime::XML, "image/*", Mime::TEXT, Mime::ALL]
+ assert_equal expect, Mime::Type.parse(accept).collect { |c| c.to_s }
+ end
+
+ def test_custom_type
+ Mime::Type.register("image/gif", :gif)
+ assert_nothing_raised do
+ Mime::GIF
+ assert_equal Mime::GIF, Mime::SET.last
+ end
+ ensure
+ Mime.module_eval { remove_const :GIF if const_defined?(:GIF) }
+ end
+
+ def test_type_convenience_methods
+ types = [:html, :xml, :png, :pdf, :yaml, :url_encoded_form]
+ types.each do |type|
+ mime = Mime.const_get(type.to_s.upcase)
+ assert mime.send("#{type}?"), "Mime::#{type.to_s.upcase} is not #{type}?"
+ (types - [type]).each { |t| assert !mime.send("#{t}?"), "Mime::#{t.to_s.upcase} is #{t}?" }
+ end
+ end
+
+ def test_mime_all_is_html
+ assert Mime::ALL.all?, "Mime::ALL is not all?"
+ assert Mime::ALL.html?, "Mime::ALL is not html?"
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/new_render_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/new_render_test.rb
new file mode 100644
index 000000000..3168daeae
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/new_render_test.rb
@@ -0,0 +1,832 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+require File.dirname(__FILE__) + '/fake_models'
+
+class CustomersController < ActionController::Base
+end
+
+module Fun
+ class GamesController < ActionController::Base
+ def hello_world
+ end
+ end
+end
+
+module NewRenderTestHelper
+ def rjs_helper_method_from_module
+ page.visual_effect :highlight
+ end
+end
+
+class NewRenderTestController < ActionController::Base
+ layout :determine_layout
+
+ def self.controller_name; "test"; end
+ def self.controller_path; "test"; end
+
+ def hello_world
+ end
+
+ def render_hello_world
+ render :template => "test/hello_world"
+ end
+
+ def render_hello_world_from_variable
+ @person = "david"
+ render :text => "hello #{@person}"
+ end
+
+ def render_action_hello_world
+ render :action => "hello_world"
+ end
+
+ def render_action_hello_world_as_symbol
+ render :action => :hello_world
+ end
+
+ def render_text_hello_world
+ render :text => "hello world"
+ end
+
+ def render_text_hello_world_with_layout
+ @variable_for_layout = ", I'm here!"
+ render :text => "hello world", :layout => true
+ end
+
+ def hello_world_with_layout_false
+ render :layout => false
+ end
+
+ def render_custom_code
+ render :text => "hello world", :status => "404 Moved"
+ end
+
+ def render_file_with_instance_variables
+ @secret = 'in the sauce'
+ path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb')
+ render :file => path
+ end
+
+ def render_file_with_locals
+ path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.erb')
+ render :file => path, :locals => {:secret => 'in the sauce'}
+ end
+
+ def render_file_not_using_full_path
+ @secret = 'in the sauce'
+ render :file => 'test/render_file_with_ivar', :use_full_path => true
+ end
+
+ def render_file_not_using_full_path_with_relative_path
+ @secret = 'in the sauce'
+ render :file => 'test/../test/render_file_with_ivar', :use_full_path => true
+ end
+
+ def render_file_not_using_full_path_with_dot_in_path
+ @secret = 'in the sauce'
+ render :file => 'test/dot.directory/render_file_with_ivar', :use_full_path => true
+ end
+
+ def render_xml_hello
+ @name = "David"
+ render :template => "test/hello"
+ end
+
+ def greeting
+ # let's just rely on the template
+ end
+
+ def layout_test
+ render :action => "hello_world"
+ end
+
+ def layout_test_with_different_layout
+ render :action => "hello_world", :layout => "standard"
+ end
+
+ def rendering_without_layout
+ render :action => "hello_world", :layout => false
+ end
+
+ def layout_overriding_layout
+ render :action => "hello_world", :layout => "standard"
+ end
+
+ def rendering_nothing_on_layout
+ render :nothing => true
+ end
+
+ def builder_layout_test
+ render :action => "hello"
+ end
+
+ def partials_list
+ @test_unchanged = 'hello'
+ @customers = [ Customer.new("david"), Customer.new("mary") ]
+ render :action => "list"
+ end
+
+ def partial_only
+ render :partial => true
+ end
+
+ def partial_only_with_layout
+ render :partial => "partial_only", :layout => true
+ end
+
+ def partial_with_locals
+ render :partial => "customer", :locals => { :customer => Customer.new("david") }
+ end
+
+ def partial_collection
+ render :partial => "customer", :collection => [ Customer.new("david"), Customer.new("mary") ]
+ end
+
+ def partial_collection_with_locals
+ render :partial => "customer_greeting", :collection => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" }
+ end
+
+ def empty_partial_collection
+ render :partial => "customer", :collection => []
+ end
+
+ def partial_with_hash_object
+ render :partial => "hash_object", :object => {:first_name => "Sam"}
+ end
+
+ def partial_hash_collection
+ render :partial => "hash_object", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ]
+ end
+
+ def partial_hash_collection_with_locals
+ render :partial => "hash_greeting", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ], :locals => { :greeting => "Hola" }
+ end
+
+ def partial_with_implicit_local_assignment
+ @customer = Customer.new("Marcel")
+ render :partial => "customer"
+ end
+
+ def missing_partial
+ render :partial => 'thisFileIsntHere'
+ end
+
+ def hello_in_a_string
+ @customers = [ Customer.new("david"), Customer.new("mary") ]
+ render :text => "How's there? " << render_to_string(:template => "test/list")
+ end
+
+ def render_to_string_with_assigns
+ @before = "i'm before the render"
+ render_to_string :text => "foo"
+ @after = "i'm after the render"
+ render :action => "test/hello_world"
+ end
+
+ def render_to_string_with_partial
+ @partial_only = render_to_string :partial => "partial_only"
+ @partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") }
+ render :action => "test/hello_world"
+ end
+
+ def render_to_string_with_exception
+ render_to_string :file => "exception that will not be caught - this will certainly not work", :use_full_path => true
+ end
+
+ def render_to_string_with_caught_exception
+ @before = "i'm before the render"
+ begin
+ render_to_string :file => "exception that will be caught- hope my future instance vars still work!", :use_full_path => true
+ rescue
+ end
+ @after = "i'm after the render"
+ render :action => "test/hello_world"
+ end
+
+ def accessing_params_in_template
+ render :inline => "Hello: <%= params[:name] %>"
+ end
+
+ def accessing_params_in_template_with_layout
+ render :layout => nil, :inline => "Hello: <%= params[:name] %>"
+ end
+
+ def render_with_explicit_template
+ render :template => "test/hello_world"
+ end
+
+ def double_render
+ render :text => "hello"
+ render :text => "world"
+ end
+
+ def double_redirect
+ redirect_to :action => "double_render"
+ redirect_to :action => "double_render"
+ end
+
+ def render_and_redirect
+ render :text => "hello"
+ redirect_to :action => "double_render"
+ end
+
+ def render_to_string_and_render
+ @stuff = render_to_string :text => "here is some cached stuff"
+ render :text => "Hi web users! #{@stuff}"
+ end
+
+ def rendering_with_conflicting_local_vars
+ @name = "David"
+ def @template.name() nil end
+ render :action => "potential_conflicts"
+ end
+
+ def hello_world_from_rxml_using_action
+ render :action => "hello_world_from_rxml.builder"
+ end
+
+ def hello_world_from_rxml_using_template
+ render :template => "test/hello_world_from_rxml.builder"
+ end
+
+ def head_with_location_header
+ head :location => "/foo"
+ end
+
+ def head_with_symbolic_status
+ head :status => params[:status].intern
+ end
+
+ def head_with_integer_status
+ head :status => params[:status].to_i
+ end
+
+ def head_with_string_status
+ head :status => params[:status]
+ end
+
+ def head_with_custom_header
+ head :x_custom_header => "something"
+ end
+
+ def head_with_status_code_first
+ head :forbidden, :x_custom_header => "something"
+ end
+
+ def render_with_location
+ render :xml => "<hello/>", :location => "http://example.com", :status => 201
+ end
+
+ def render_with_object_location
+ customer = Customer.new("Some guy", 1)
+ render :xml => "<customer/>", :location => customer_url(customer), :status => :created
+ end
+
+ def render_with_to_xml
+ to_xmlable = Class.new do
+ def to_xml
+ "<i-am-xml/>"
+ end
+ end.new
+
+ render :xml => to_xmlable
+ end
+
+ helper NewRenderTestHelper
+ helper do
+ def rjs_helper_method(value)
+ page.visual_effect :highlight, value
+ end
+ end
+
+ def enum_rjs_test
+ render :update do |page|
+ page.select('.product').each do |value|
+ page.rjs_helper_method_from_module
+ page.rjs_helper_method(value)
+ page.sortable(value, :url => { :action => "order" })
+ page.draggable(value)
+ end
+ end
+ end
+
+ def delete_with_js
+ @project_id = 4
+ end
+
+ def render_js_with_explicit_template
+ @project_id = 4
+ render :template => 'test/delete_with_js'
+ end
+
+ def render_js_with_explicit_action_template
+ @project_id = 4
+ render :action => 'delete_with_js'
+ end
+
+ def update_page
+ render :update do |page|
+ page.replace_html 'balance', '$37,000,000.00'
+ page.visual_effect :highlight, 'balance'
+ end
+ end
+
+ def update_page_with_instance_variables
+ @money = '$37,000,000.00'
+ @div_id = 'balance'
+ render :update do |page|
+ page.replace_html @div_id, @money
+ page.visual_effect :highlight, @div_id
+ end
+ end
+
+ def action_talk_to_layout
+ # Action template sets variable that's picked up by layout
+ end
+
+ def render_text_with_assigns
+ @hello = "world"
+ render :text => "foo"
+ end
+
+ def yield_content_for
+ render :action => "content_for", :layout => "yield"
+ end
+
+ def render_content_type_from_body
+ response.content_type = Mime::RSS
+ render :text => "hello world!"
+ end
+
+ def render_call_to_partial_with_layout
+ render :action => "calling_partial_with_layout"
+ end
+
+ def render_using_layout_around_block
+ render :action => "using_layout_around_block"
+ end
+
+ def rescue_action(e) raise end
+
+ private
+ def determine_layout
+ case action_name
+ when "hello_world", "layout_test", "rendering_without_layout",
+ "rendering_nothing_on_layout", "render_text_hello_world",
+ "render_text_hello_world_with_layout",
+ "hello_world_with_layout_false",
+ "partial_only", "partial_only_with_layout",
+ "accessing_params_in_template",
+ "accessing_params_in_template_with_layout",
+ "render_with_explicit_template",
+ "render_js_with_explicit_template",
+ "render_js_with_explicit_action_template",
+ "delete_with_js", "update_page", "update_page_with_instance_variables"
+
+ "layouts/standard"
+ when "builder_layout_test"
+ "layouts/builder"
+ when "action_talk_to_layout", "layout_overriding_layout"
+ "layouts/talk_from_action"
+ end
+ end
+end
+
+NewRenderTestController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
+Fun::GamesController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
+
+class NewRenderTest < Test::Unit::TestCase
+ def setup
+ @controller = NewRenderTestController.new
+
+ # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
+ # a more accurate simulation of what happens in "real life".
+ @controller.logger = Logger.new(nil)
+
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_simple_show
+ get :hello_world
+ assert_response :success
+ assert_template "test/hello_world"
+ assert_equal "<html>Hello world!</html>", @response.body
+ end
+
+ def test_do_with_render
+ get :render_hello_world
+ assert_template "test/hello_world"
+ end
+
+ def test_do_with_render_from_variable
+ get :render_hello_world_from_variable
+ assert_equal "hello david", @response.body
+ end
+
+ def test_do_with_render_action
+ get :render_action_hello_world
+ assert_template "test/hello_world"
+ end
+
+ def test_do_with_render_action_as_symbol
+ get :render_action_hello_world_as_symbol
+ assert_template "test/hello_world"
+ end
+
+ def test_do_with_render_text
+ get :render_text_hello_world
+ assert_equal "hello world", @response.body
+ end
+
+ def test_do_with_render_text_and_layout
+ get :render_text_hello_world_with_layout
+ assert_equal "<html>hello world, I'm here!</html>", @response.body
+ end
+
+ def test_do_with_render_action_and_layout_false
+ get :hello_world_with_layout_false
+ assert_equal 'Hello world!', @response.body
+ end
+
+ def test_do_with_render_custom_code
+ get :render_custom_code
+ assert_response :missing
+ end
+
+ def test_render_file_with_instance_variables
+ get :render_file_with_instance_variables
+ assert_equal "The secret is in the sauce\n", @response.body
+ end
+
+ def test_render_file_not_using_full_path
+ get :render_file_not_using_full_path
+ assert_equal "The secret is in the sauce\n", @response.body
+ end
+
+ def test_render_file_not_using_full_path_with_relative_path
+ get :render_file_not_using_full_path_with_relative_path
+ assert_equal "The secret is in the sauce\n", @response.body
+ end
+
+ def test_render_file_not_using_full_path_with_dot_in_path
+ get :render_file_not_using_full_path_with_dot_in_path
+ assert_equal "The secret is in the sauce\n", @response.body
+ end
+
+ def test_render_file_with_locals
+ get :render_file_with_locals
+ assert_equal "The secret is in the sauce\n", @response.body
+ end
+
+ def test_attempt_to_access_object_method
+ assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone }
+ end
+
+ def test_private_methods
+ assert_raises(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout }
+ end
+
+ def test_access_to_request_in_view
+ view_internals_old_value = ActionController::Base.view_controller_internals
+
+ ActionController::Base.view_controller_internals = false
+ ActionController::Base.protected_variables_cache = nil
+
+ get :hello_world
+ assert !assigns.include?('request'), 'request should not be in assigns'
+
+ ActionController::Base.view_controller_internals = true
+ ActionController::Base.protected_variables_cache = nil
+
+ get :hello_world
+ assert !assigns.include?('request'), 'request should not be in assigns'
+ assert_kind_of ActionController::AbstractRequest, assigns['_request']
+ assert_kind_of ActionController::AbstractRequest, @response.template.request
+
+ ensure
+ ActionController::Base.view_controller_internals = view_internals_old_value
+ ActionController::Base.protected_variables_cache = nil
+ end
+
+ def test_render_xml
+ get :render_xml_hello
+ assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body
+ end
+
+ def test_enum_rjs_test
+ get :enum_rjs_test
+ assert_equal <<-EOS.strip, @response.body
+$$(".product").each(function(value, index) {
+new Effect.Highlight(element,{});
+new Effect.Highlight(value,{});
+Sortable.create(value, {onUpdate:function(){new Ajax.Request('/test/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(value)})}});
+new Draggable(value, {});
+});
+EOS
+ end
+
+ def test_render_xml_with_default
+ get :greeting
+ assert_equal "<p>This is grand!</p>\n", @response.body
+ end
+
+ def test_render_rjs_with_default
+ get :delete_with_js
+ assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
+ end
+
+ def test_render_rjs_template_explicitly
+ get :render_js_with_explicit_template
+ assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
+ end
+
+ def test_rendering_rjs_action_explicitly
+ get :render_js_with_explicit_action_template
+ assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
+ end
+
+ def test_layout_rendering
+ get :layout_test
+ assert_equal "<html>Hello world!</html>", @response.body
+ end
+
+ def test_layout_test_with_different_layout
+ get :layout_test_with_different_layout
+ assert_equal "<html>Hello world!</html>", @response.body
+ end
+
+ def test_rendering_without_layout
+ get :rendering_without_layout
+ assert_equal "Hello world!", @response.body
+ end
+
+ def test_layout_overriding_layout
+ get :layout_overriding_layout
+ assert_no_match %r{<title>}, @response.body
+ end
+
+ def test_rendering_nothing_on_layout
+ get :rendering_nothing_on_layout
+ assert_equal " ", @response.body
+ end
+
+ def test_render_xml_with_layouts
+ get :builder_layout_test
+ assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
+ end
+
+ def test_partial_only
+ get :partial_only
+ assert_equal "only partial", @response.body
+ end
+
+ def test_partial_only_with_layout
+ get :partial_only_with_layout
+ assert_equal "<html>only partial</html>", @response.body
+ end
+
+ def test_render_to_string
+ assert_not_deprecated { get :hello_in_a_string }
+ assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body
+ end
+
+ def test_render_to_string_doesnt_break_assigns
+ get :render_to_string_with_assigns
+ assert_equal "i'm before the render", assigns(:before)
+ assert_equal "i'm after the render", assigns(:after)
+ end
+
+ def test_render_to_string_partial
+ get :render_to_string_with_partial
+ assert_equal "only partial", assigns(:partial_only)
+ assert_equal "Hello: david", assigns(:partial_with_locals)
+ end
+
+ def test_bad_render_to_string_still_throws_exception
+ assert_raises(ActionController::MissingTemplate) { get :render_to_string_with_exception }
+ end
+
+ def test_render_to_string_that_throws_caught_exception_doesnt_break_assigns
+ assert_nothing_raised { get :render_to_string_with_caught_exception }
+ assert_equal "i'm before the render", assigns(:before)
+ assert_equal "i'm after the render", assigns(:after)
+ end
+
+ def test_nested_rendering
+ get :hello_world
+ assert_equal "Living in a nested world", Fun::GamesController.process(@request, @response).body
+ end
+
+ def test_accessing_params_in_template
+ get :accessing_params_in_template, :name => "David"
+ assert_equal "Hello: David", @response.body
+ end
+
+ def test_accessing_params_in_template_with_layout
+ get :accessing_params_in_template_with_layout, :name => "David"
+ assert_equal "<html>Hello: David</html>", @response.body
+ end
+
+ def test_render_with_explicit_template
+ get :render_with_explicit_template
+ assert_response :success
+ end
+
+ def test_double_render
+ assert_raises(ActionController::DoubleRenderError) { get :double_render }
+ end
+
+ def test_double_redirect
+ assert_raises(ActionController::DoubleRenderError) { get :double_redirect }
+ end
+
+ def test_render_and_redirect
+ assert_raises(ActionController::DoubleRenderError) { get :render_and_redirect }
+ end
+
+ # specify the one exception to double render rule - render_to_string followed by render
+ def test_render_to_string_and_render
+ get :render_to_string_and_render
+ assert_equal("Hi web users! here is some cached stuff", @response.body)
+ end
+
+ def test_rendering_with_conflicting_local_vars
+ get :rendering_with_conflicting_local_vars
+ assert_equal("First: David\nSecond: Stephan\nThird: David\nFourth: David\nFifth: ", @response.body)
+ end
+
+ def test_action_talk_to_layout
+ get :action_talk_to_layout
+ assert_equal "<title>Talking to the layout</title>\nAction was here!", @response.body
+ end
+
+ def test_partials_list
+ get :partials_list
+ assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body
+ end
+
+ def test_partial_with_locals
+ get :partial_with_locals
+ assert_equal "Hello: david", @response.body
+ end
+
+ def test_partial_collection
+ get :partial_collection
+ assert_equal "Hello: davidHello: mary", @response.body
+ end
+
+ def test_partial_collection_with_locals
+ get :partial_collection_with_locals
+ assert_equal "Bonjour: davidBonjour: mary", @response.body
+ end
+
+ def test_empty_partial_collection
+ get :empty_partial_collection
+ assert_equal " ", @response.body
+ end
+
+ def test_partial_with_hash_object
+ get :partial_with_hash_object
+ assert_equal "Sam\nmaS\n", @response.body
+ end
+
+ def test_hash_partial_collection
+ get :partial_hash_collection
+ assert_equal "Pratik\nkitarP\nAmy\nymA\n", @response.body
+ end
+
+ def test_partial_hash_collection_with_locals
+ get :partial_hash_collection_with_locals
+ assert_equal "Hola: PratikHola: Amy", @response.body
+ end
+
+ def test_partial_with_implicit_local_assignment
+ get :partial_with_implicit_local_assignment
+ assert_equal "Hello: Marcel", @response.body
+ end
+
+ def test_render_missing_partial_template
+ assert_raises(ActionView::ActionViewError) do
+ get :missing_partial
+ end
+ end
+
+ def test_render_text_with_assigns
+ get :render_text_with_assigns
+ assert_equal "world", assigns["hello"]
+ end
+
+ def test_update_page
+ get :update_page
+ assert_template nil
+ assert_equal 'text/javascript; charset=utf-8', @response.headers['type']
+ assert_equal 2, @response.body.split($/).length
+ end
+
+ def test_update_page_with_instance_variables
+ get :update_page_with_instance_variables
+ assert_template nil
+ assert_equal 'text/javascript; charset=utf-8', @response.headers['type']
+ assert_match /balance/, @response.body
+ assert_match /\$37/, @response.body
+ end
+
+ def test_yield_content_for
+ assert_not_deprecated { get :yield_content_for }
+ assert_equal "<title>Putting stuff in the title!</title>\n\nGreat stuff!\n", @response.body
+ end
+
+
+ def test_overwritting_rendering_relative_file_with_extension
+ get :hello_world_from_rxml_using_template
+ assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body
+
+ get :hello_world_from_rxml_using_action
+ assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body
+ end
+
+
+ def test_head_with_location_header
+ get :head_with_location_header
+ assert @response.body.blank?
+ assert_equal "/foo", @response.headers["Location"]
+ assert_response :ok
+ end
+
+ def test_head_with_custom_header
+ get :head_with_custom_header
+ assert @response.body.blank?
+ assert_equal "something", @response.headers["X-Custom-Header"]
+ assert_response :ok
+ end
+
+ def test_head_with_symbolic_status
+ get :head_with_symbolic_status, :status => "ok"
+ assert_equal "200 OK", @response.headers["Status"]
+ assert_response :ok
+
+ get :head_with_symbolic_status, :status => "not_found"
+ assert_equal "404 Not Found", @response.headers["Status"]
+ assert_response :not_found
+
+ ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE.each do |status, code|
+ get :head_with_symbolic_status, :status => status.to_s
+ assert_equal code, @response.response_code
+ assert_response status
+ end
+ end
+
+ def test_head_with_integer_status
+ ActionController::StatusCodes::STATUS_CODES.each do |code, message|
+ get :head_with_integer_status, :status => code.to_s
+ assert_equal message, @response.message
+ end
+ end
+
+ def test_head_with_string_status
+ get :head_with_string_status, :status => "404 Eat Dirt"
+ assert_equal 404, @response.response_code
+ assert_equal "Eat Dirt", @response.message
+ assert_response :not_found
+ end
+
+ def test_head_with_status_code_first
+ get :head_with_status_code_first
+ assert_equal 403, @response.response_code
+ assert_equal "Forbidden", @response.message
+ assert_equal "something", @response.headers["X-Custom-Header"]
+ assert_response :forbidden
+ end
+
+ def test_rendering_with_location_should_set_header
+ get :render_with_location
+ assert_equal "http://example.com", @response.headers["Location"]
+ end
+
+ def test_rendering_xml_should_call_to_xml_if_possible
+ get :render_with_to_xml
+ assert_equal "<i-am-xml/>", @response.body
+ end
+
+ def test_rendering_with_object_location_should_set_header_with_url_for
+ ActionController::Routing::Routes.draw do |map|
+ map.resources :customers
+ map.connect ':controller/:action/:id'
+ end
+
+ get :render_with_object_location
+ assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"]
+ end
+
+ def test_render_call_to_partial_with_layout
+ get :render_call_to_partial_with_layout
+ assert_equal "Before (David)\nInside from partial (David)\nAfter", @response.body
+ end
+
+ def test_using_layout_around_block
+ get :using_layout_around_block
+ assert_equal "Before (David)\nInside from block\nAfter", @response.body
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/polymorphic_routes_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/polymorphic_routes_test.rb
new file mode 100644
index 000000000..3e72063e9
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/polymorphic_routes_test.rb
@@ -0,0 +1,98 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class Article
+ attr_reader :id
+ def save; @id = 1 end
+ def new_record?; @id.nil? end
+ def name
+ @id.nil? ? 'new post' : "post ##{@id}"
+ end
+end
+
+class Comment
+ attr_reader :id
+ def post_id; 1 end
+ def save; @id = 1 end
+ def new_record?; @id.nil? end
+ def name
+ @id.nil? ? 'new comment' : "comment ##{@id}"
+ end
+end
+
+class Comment::Nested < Comment; end
+
+class Test::Unit::TestCase
+ protected
+ def articles_url
+ 'http://www.example.com/articles'
+ end
+ alias_method :new_article_url, :articles_url
+
+ def article_url(article)
+ "http://www.example.com/articles/#{article.id}"
+ end
+
+ def article_comments_url(article)
+ "http://www.example.com/articles/#{article.id}/comments"
+ end
+
+ def article_comment_url(article, comment)
+ "http://www.example.com/articles/#{article.id}/comments/#{comment.id}"
+ end
+
+ def admin_articles_url
+ "http://www.example.com/admin/articles"
+ end
+ alias_method :new_admin_article_url, :admin_articles_url
+
+ def admin_article_url(article)
+ "http://www.example.com/admin/articles/#{article.id}"
+ end
+
+ def admin_article_comments_url(article)
+ "http://www.example.com/admin/articles/#{article.id}/comments"
+ end
+
+ def admin_article_comment_url(article, comment)
+ "http://www.example.com/admin/test/articles/#{article.id}/comments/#{comment.id}"
+ end
+end
+
+
+class PolymorphicRoutesTest < Test::Unit::TestCase
+ include ActionController::PolymorphicRoutes
+
+ def setup
+ @article = Article.new
+ @comment = Comment.new
+ end
+
+ def test_with_record
+ assert_equal(articles_url, polymorphic_url(@article, :action => 'new'))
+ assert_equal(articles_url, polymorphic_url(@article))
+ @article.save
+ assert_equal(article_url(@article), polymorphic_url(@article))
+ end
+
+ # TODO: Needs to be updated to correctly know about whether the object is in a hash or not
+ def xtest_with_hash
+ @article.save
+ assert_equal(article_url(@article), polymorphic_url(:id => @article))
+ end
+
+ def test_with_array
+ assert_equal(article_comments_url(@article), polymorphic_url([@article, @comment]))
+ @comment.save
+ assert_equal(article_comment_url(@article, @comment), polymorphic_url([@article, @comment]))
+ end
+
+ def test_with_array_and_namespace
+ assert_equal(admin_articles_url, polymorphic_url([:admin, @article], :action => 'new'))
+ assert_equal(admin_articles_url, polymorphic_url([:admin, @article]))
+ @article.save
+ assert_equal(admin_article_url(@article), polymorphic_url([:admin, @article]))
+ assert_equal(admin_article_comments_url(@article), polymorphic_url([:admin, @article, @comment]))
+ @comment.save
+ assert_equal(admin_article_comment_url(@article, @comment), polymorphic_url([:admin, @article, @comment]))
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/record_identifier_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/record_identifier_test.rb
new file mode 100644
index 000000000..86d196cfd
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/record_identifier_test.rb
@@ -0,0 +1,103 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class Comment
+ attr_reader :id
+ def save; @id = 1 end
+ def new_record?; @id.nil? end
+ def name
+ @id.nil? ? 'new comment' : "comment ##{@id}"
+ end
+end
+
+class Comment::Nested < Comment; end
+
+class Test::Unit::TestCase
+ protected
+ def comments_url
+ 'http://www.example.com/comments'
+ end
+
+ def comment_url(comment)
+ "http://www.example.com/comments/#{comment.id}"
+ end
+end
+
+
+class RecordIdentifierTest < Test::Unit::TestCase
+ include ActionController::RecordIdentifier
+
+ def setup
+ @klass = Comment
+ @record = @klass.new
+ @singular = 'comment'
+ @plural = 'comments'
+ end
+
+ def test_dom_id_with_new_record
+ assert_equal "new_#{@singular}", dom_id(@record)
+ end
+
+ def test_dom_id_with_new_record_and_prefix
+ assert_equal "custom_prefix_#{@singular}", dom_id(@record, :custom_prefix)
+ end
+
+ def test_dom_id_with_saved_record
+ @record.save
+ assert_equal "#{@singular}_1", dom_id(@record)
+ end
+
+ def test_dom_id_with_prefix
+ @record.save
+ assert_equal "edit_#{@singular}_1", dom_id(@record, :edit)
+ end
+
+ def test_partial_path
+ expected = "#{@plural}/#{@singular}"
+ assert_equal expected, partial_path(@record)
+ assert_equal expected, partial_path(Comment)
+ end
+
+ def test_dom_class
+ assert_equal @singular, dom_class(@record)
+ end
+
+ def test_dom_class_with_prefix
+ assert_equal "custom_prefix_#{@singular}", dom_class(@record, :custom_prefix)
+ end
+
+ def test_singular_class_name
+ assert_equal @singular, singular_class_name(@record)
+ end
+
+ def test_singular_class_name_for_class
+ assert_equal @singular, singular_class_name(@klass)
+ end
+
+ def test_plural_class_name
+ assert_equal @plural, plural_class_name(@record)
+ end
+
+ def test_plural_class_name_for_class
+ assert_equal @plural, plural_class_name(@klass)
+ end
+
+ private
+ def method_missing(method, *args)
+ RecordIdentifier.send(method, *args)
+ end
+end
+
+class NestedRecordIdentifierTest < RecordIdentifierTest
+ def setup
+ @klass = Comment::Nested
+ @record = @klass.new
+ @singular = 'comment_nested'
+ @plural = 'comment_nesteds'
+ end
+
+ def test_partial_path
+ expected = "comment/nesteds/nested"
+ assert_equal expected, partial_path(@record)
+ assert_equal expected, partial_path(Comment::Nested)
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/redirect_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/redirect_test.rb
new file mode 100755
index 000000000..4a3b8254a
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/redirect_test.rb
@@ -0,0 +1,258 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class WorkshopsController < ActionController::Base
+end
+
+class Workshop
+ attr_accessor :id, :new_record
+
+ def initialize(id, new_record)
+ @id, @new_record = id, new_record
+ end
+
+ def new_record?
+ @new_record
+ end
+
+ def to_s
+ id.to_s
+ end
+end
+
+class RedirectController < ActionController::Base
+ def simple_redirect
+ redirect_to :action => "hello_world"
+ end
+
+ def redirect_with_status
+ redirect_to({:action => "hello_world", :status => 301})
+ end
+
+ def redirect_with_status_hash
+ redirect_to({:action => "hello_world"}, {:status => 301})
+ end
+
+ def url_redirect_with_status
+ redirect_to("http://www.example.com", :status => :moved_permanently)
+ end
+
+ def url_redirect_with_status_hash
+ redirect_to("http://www.example.com", {:status => 301})
+ end
+
+ def relative_url_redirect_with_status
+ redirect_to("/things/stuff", :status => :found)
+ end
+
+ def relative_url_redirect_with_status_hash
+ redirect_to("/things/stuff", {:status => 301})
+ end
+
+ def redirect_to_back_with_status
+ redirect_to :back, :status => 307
+ end
+
+ def host_redirect
+ redirect_to :action => "other_host", :only_path => false, :host => 'other.test.host'
+ end
+
+ def module_redirect
+ redirect_to :controller => 'module_test/module_redirect', :action => "hello_world"
+ end
+
+ def redirect_with_assigns
+ @hello = "world"
+ redirect_to :action => "hello_world"
+ end
+
+ def redirect_to_back
+ redirect_to :back
+ end
+
+ def redirect_to_existing_record
+ redirect_to Workshop.new(5, false)
+ end
+
+ def redirect_to_new_record
+ redirect_to Workshop.new(5, true)
+ end
+
+ def rescue_errors(e) raise e end
+
+ def rescue_action(e) raise end
+
+ protected
+ def dashbord_url(id, message)
+ url_for :action => "dashboard", :params => { "id" => id, "message" => message }
+ end
+end
+
+class RedirectTest < Test::Unit::TestCase
+ def setup
+ @controller = RedirectController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_simple_redirect
+ get :simple_redirect
+ assert_response :redirect
+ assert_equal "http://test.host/redirect/hello_world", redirect_to_url
+ end
+
+ def test_redirect_with_no_status
+ get :simple_redirect
+ assert_response 302
+ assert_equal "http://test.host/redirect/hello_world", redirect_to_url
+ end
+
+ def test_redirect_with_status
+ get :redirect_with_status
+ assert_response 301
+ assert_equal "http://test.host/redirect/hello_world", redirect_to_url
+ end
+
+ def test_redirect_with_status_hash
+ get :redirect_with_status_hash
+ assert_response 301
+ assert_equal "http://test.host/redirect/hello_world", redirect_to_url
+ end
+
+ def test_url_redirect_with_status
+ get :url_redirect_with_status
+ assert_response 301
+ assert_equal "http://www.example.com", redirect_to_url
+ end
+
+ def test_url_redirect_with_status_hash
+ get :url_redirect_with_status_hash
+ assert_response 301
+ assert_equal "http://www.example.com", redirect_to_url
+ end
+
+
+ def test_relative_url_redirect_with_status
+ get :relative_url_redirect_with_status
+ assert_response 302
+ assert_equal "http://test.host/things/stuff", redirect_to_url
+ end
+
+ def test_relative_url_redirect_with_status_hash
+ get :relative_url_redirect_with_status_hash
+ assert_response 301
+ assert_equal "http://test.host/things/stuff", redirect_to_url
+ end
+
+ def test_redirect_to_back_with_status
+ @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from"
+ get :redirect_to_back_with_status
+ assert_response 307
+ assert_equal "http://www.example.com/coming/from", redirect_to_url
+ end
+
+ def test_simple_redirect_using_options
+ get :host_redirect
+ assert_response :redirect
+ assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host'
+ end
+
+ def test_redirect_error_with_pretty_diff
+ get :host_redirect
+ assert_response :redirect
+ begin
+ assert_redirected_to :action => "other_host", :only_path => true
+ rescue Test::Unit::AssertionFailedError => err
+ expected_msg, redirection_msg, diff_msg = err.message.scan(/<\{[^\}]+\}>/).collect { |s| s[2..-3] }
+ assert_match %r("only_path"=>false), redirection_msg
+ assert_match %r("host"=>"other.test.host"), redirection_msg
+ assert_match %r("action"=>"other_host"), redirection_msg
+ assert_match %r("only_path"=>false), diff_msg
+ assert_match %r("host"=>"other.test.host"), diff_msg
+ end
+ end
+
+ def test_module_redirect
+ get :module_redirect
+ assert_response :redirect
+ assert_redirected_to "http://test.host/module_test/module_redirect/hello_world"
+ end
+
+ def test_module_redirect_using_options
+ get :module_redirect
+ assert_response :redirect
+ assert_redirected_to :controller => 'module_test/module_redirect', :action => 'hello_world'
+ end
+
+ def test_redirect_with_assigns
+ get :redirect_with_assigns
+ assert_response :redirect
+ assert_equal "world", assigns["hello"]
+ end
+
+ def test_redirect_to_back
+ @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from"
+ get :redirect_to_back
+ assert_response :redirect
+ assert_equal "http://www.example.com/coming/from", redirect_to_url
+ end
+
+ def test_redirect_to_back_with_no_referer
+ assert_raises(ActionController::RedirectBackError) {
+ @request.env["HTTP_REFERER"] = nil
+ get :redirect_to_back
+ }
+ end
+
+ def test_redirect_to_record
+ ActionController::Routing::Routes.draw do |map|
+ map.resources :workshops
+ map.connect ':controller/:action/:id'
+ end
+
+ get :redirect_to_existing_record
+ assert_equal "http://test.host/workshops/5", redirect_to_url
+
+ get :redirect_to_new_record
+ assert_equal "http://test.host/workshops", redirect_to_url
+ end
+end
+
+module ModuleTest
+ class ModuleRedirectController < ::RedirectController
+ def module_redirect
+ redirect_to :controller => '/redirect', :action => "hello_world"
+ end
+ end
+
+ class ModuleRedirectTest < Test::Unit::TestCase
+ def setup
+ @controller = ModuleRedirectController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_simple_redirect
+ get :simple_redirect
+ assert_response :redirect
+ assert_equal "http://test.host/module_test/module_redirect/hello_world", redirect_to_url
+ end
+
+ def test_simple_redirect_using_options
+ get :host_redirect
+ assert_response :redirect
+ assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host'
+ end
+
+ def test_module_redirect
+ get :module_redirect
+ assert_response :redirect
+ assert_equal "http://test.host/redirect/hello_world", redirect_to_url
+ end
+
+ def test_module_redirect_using_options
+ get :module_redirect
+ assert_response :redirect
+ assert_redirected_to :controller => 'redirect', :action => "hello_world"
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/render_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/render_test.rb
new file mode 100644
index 000000000..2827d7b71
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/render_test.rb
@@ -0,0 +1,464 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+require File.dirname(__FILE__) + '/fake_models'
+
+module Fun
+ class GamesController < ActionController::Base
+ def hello_world
+ end
+ end
+end
+
+
+# FIXME: crashes Ruby 1.9
+class TestController < ActionController::Base
+ layout :determine_layout
+
+ def hello_world
+ end
+
+ def render_hello_world
+ render :template => "test/hello_world"
+ end
+
+ def render_hello_world_from_variable
+ @person = "david"
+ render :text => "hello #{@person}"
+ end
+
+ def render_action_hello_world
+ render :action => "hello_world"
+ end
+
+ def render_action_hello_world_with_symbol
+ render :action => :hello_world
+ end
+
+ def render_text_hello_world
+ render :text => "hello world"
+ end
+
+ def render_json_hello_world
+ render :json => {:hello => 'world'}.to_json
+ end
+
+ def render_json_hello_world_with_callback
+ render :json => {:hello => 'world'}.to_json, :callback => 'alert'
+ end
+
+ def render_json_with_custom_content_type
+ render :json => {:hello => 'world'}.to_json, :content_type => 'text/javascript'
+ end
+
+ def render_symbol_json
+ render :json => {:hello => 'world'}.to_json
+ end
+
+ def render_custom_code
+ render :text => "hello world", :status => 404
+ end
+
+ def render_nothing_with_appendix
+ render :text => "appended"
+ end
+
+ def render_invalid_args
+ render("test/hello")
+ end
+
+ def render_xml_hello
+ @name = "David"
+ render :template => "test/hello"
+ end
+
+ def render_xml_with_custom_content_type
+ render :xml => "<blah/>", :content_type => "application/atomsvc+xml"
+ end
+
+ def heading
+ head :ok
+ end
+
+ def greeting
+ # let's just rely on the template
+ end
+
+ def layout_test
+ render :action => "hello_world"
+ end
+
+ def builder_layout_test
+ render :action => "hello"
+ end
+
+ def builder_partial_test
+ render :action => "hello_world_container"
+ end
+
+ def partials_list
+ @test_unchanged = 'hello'
+ @customers = [ Customer.new("david"), Customer.new("mary") ]
+ render :action => "list"
+ end
+
+ def partial_only
+ render :partial => true
+ end
+
+ def hello_in_a_string
+ @customers = [ Customer.new("david"), Customer.new("mary") ]
+ render :text => "How's there? " + render_to_string(:template => "test/list")
+ end
+
+ def accessing_params_in_template
+ render :inline => "Hello: <%= params[:name] %>"
+ end
+
+ def accessing_local_assigns_in_inline_template
+ name = params[:local_name]
+ render :inline => "<%= 'Goodbye, ' + local_name %>",
+ :locals => { :local_name => name }
+ end
+
+ def accessing_local_assigns_in_inline_template_with_string_keys
+ name = params[:local_name]
+ ActionView::Base.local_assigns_support_string_keys = true
+ render :inline => "<%= 'Goodbye, ' + local_name %>",
+ :locals => { "local_name" => name }
+ ActionView::Base.local_assigns_support_string_keys = false
+ end
+
+ def formatted_html_erb
+ end
+
+ def formatted_xml_erb
+ end
+
+ def render_to_string_test
+ @foo = render_to_string :inline => "this is a test"
+ end
+
+ def partial
+ render :partial => 'partial'
+ end
+
+ def partial_dot_html
+ render :partial => 'partial.html.erb'
+ end
+
+ def partial_as_rjs
+ render :update do |page|
+ page.replace :foo, :partial => 'partial'
+ end
+ end
+
+ def respond_to_partial_as_rjs
+ respond_to do |format|
+ format.js do
+ render :update do |page|
+ page.replace :foo, :partial => 'partial'
+ end
+ end
+ end
+ end
+
+ def default_render
+ if @alternate_default_render
+ @alternate_default_render.call
+ else
+ render
+ end
+ end
+
+ def render_alternate_default
+ # For this test, the method "default_render" is overridden:
+ @alternate_default_render = lambda {
+ render :update do |page|
+ page.replace :foo, :partial => 'partial'
+ end
+ }
+ end
+
+ def rescue_action(e) raise end
+
+ private
+ def determine_layout
+ case action_name
+ when "layout_test"; "layouts/standard"
+ when "builder_layout_test"; "layouts/builder"
+ when "render_symbol_json"; "layouts/standard" # to make sure layouts don't interfere
+ end
+ end
+end
+
+TestController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
+Fun::GamesController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
+
+class RenderTest < Test::Unit::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = TestController.new
+
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_simple_show
+ get :hello_world
+ assert_response 200
+ assert_template "test/hello_world"
+ end
+
+ def test_render
+ get :render_hello_world
+ assert_template "test/hello_world"
+ end
+
+ def test_render_from_variable
+ get :render_hello_world_from_variable
+ assert_equal "hello david", @response.body
+ end
+
+ def test_render_action
+ get :render_action_hello_world
+ assert_template "test/hello_world"
+ end
+
+ def test_render_action_with_symbol
+ get :render_action_hello_world_with_symbol
+ assert_template "test/hello_world"
+ end
+
+ def test_render_text
+ get :render_text_hello_world
+ assert_equal "hello world", @response.body
+ end
+
+ def test_render_json
+ get :render_json_hello_world
+ assert_equal '{"hello": "world"}', @response.body
+ assert_equal 'application/json', @response.content_type
+ end
+
+ def test_render_json_with_callback
+ get :render_json_hello_world_with_callback
+ assert_equal 'alert({"hello": "world"})', @response.body
+ assert_equal 'application/json', @response.content_type
+ end
+
+ def test_render_json_with_custom_content_type
+ get :render_json_with_custom_content_type
+ assert_equal '{"hello": "world"}', @response.body
+ assert_equal 'text/javascript', @response.content_type
+ end
+
+ def test_render_symbol_json
+ get :render_symbol_json
+ assert_equal '{"hello": "world"}', @response.body
+ assert_equal 'application/json', @response.content_type
+ end
+
+ def test_render_custom_code
+ get :render_custom_code
+ assert_response 404
+ assert_equal 'hello world', @response.body
+ end
+
+ def test_render_nothing_with_appendix
+ get :render_nothing_with_appendix
+ assert_response 200
+ assert_equal 'appended', @response.body
+ end
+
+ def test_attempt_to_render_with_invalid_arguments
+ assert_raises(ActionController::RenderError) { get :render_invalid_args }
+ end
+
+ def test_attempt_to_access_object_method
+ assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone }
+ end
+
+ def test_private_methods
+ assert_raises(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout }
+ end
+
+ def test_render_xml
+ get :render_xml_hello
+ assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body
+ assert_equal "application/xml", @response.content_type
+ end
+
+ def test_render_xml_with_default
+ get :greeting
+ assert_equal "<p>This is grand!</p>\n", @response.body
+ end
+
+ def test_render_xml_with_partial
+ get :builder_partial_test
+ assert_equal "<test>\n <hello/>\n</test>\n", @response.body
+ end
+
+ def test_layout_rendering
+ get :layout_test
+ assert_equal "<html>Hello world!</html>", @response.body
+ end
+
+ def test_render_xml_with_layouts
+ get :builder_layout_test
+ assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
+ end
+
+ # def test_partials_list
+ # get :partials_list
+ # assert_equal "goodbyeHello: davidHello: marygoodbye\n", process_request.body
+ # end
+
+ def test_partial_only
+ get :partial_only
+ assert_equal "only partial", @response.body
+ end
+
+ def test_render_to_string
+ get :hello_in_a_string
+ assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body
+ end
+
+ def test_render_to_string_resets_assigns
+ get :render_to_string_test
+ assert_equal "The value of foo is: ::this is a test::\n", @response.body
+ end
+
+ def test_nested_rendering
+ @controller = Fun::GamesController.new
+ get :hello_world
+ assert_equal "Living in a nested world", @response.body
+ end
+
+ def test_accessing_params_in_template
+ get :accessing_params_in_template, :name => "David"
+ assert_equal "Hello: David", @response.body
+ end
+
+ def test_accessing_local_assigns_in_inline_template
+ get :accessing_local_assigns_in_inline_template, :local_name => "Local David"
+ assert_equal "Goodbye, Local David", @response.body
+ end
+
+ def test_accessing_local_assigns_in_inline_template_with_string_keys
+ get :accessing_local_assigns_in_inline_template_with_string_keys, :local_name => "Local David"
+ assert_equal "Goodbye, Local David", @response.body
+ end
+
+ def test_render_200_should_set_etag
+ get :render_hello_world_from_variable
+ assert_equal etag_for("hello david"), @response.headers['ETag']
+ assert_equal "private, max-age=0, must-revalidate", @response.headers['Cache-Control']
+ end
+
+ def test_render_against_etag_request_should_304_when_match
+ @request.headers["HTTP_IF_NONE_MATCH"] = etag_for("hello david")
+ get :render_hello_world_from_variable
+ assert_equal "304 Not Modified", @response.headers['Status']
+ assert @response.body.empty?
+ end
+
+ def test_render_against_etag_request_should_200_when_no_match
+ @request.headers["HTTP_IF_NONE_MATCH"] = etag_for("hello somewhere else")
+ get :render_hello_world_from_variable
+ assert_equal "200 OK", @response.headers['Status']
+ assert !@response.body.empty?
+ end
+
+ def test_render_with_etag
+ get :render_hello_world_from_variable
+ expected_etag = etag_for('hello david')
+ assert_equal expected_etag, @response.headers['ETag']
+
+ @request.headers["HTTP_IF_NONE_MATCH"] = expected_etag
+ get :render_hello_world_from_variable
+ assert_equal "304 Not Modified", @response.headers['Status']
+
+ @request.headers["HTTP_IF_NONE_MATCH"] = "\"diftag\""
+ get :render_hello_world_from_variable
+ assert_equal "200 OK", @response.headers['Status']
+ end
+
+ def render_with_404_shouldnt_have_etag
+ get :render_custom_code
+ assert_nil @response.headers['ETag']
+ end
+
+ def test_etag_should_not_be_changed_when_already_set
+ expected_etag = etag_for("hello somewhere else")
+ @response.headers["ETag"] = expected_etag
+ get :render_hello_world_from_variable
+ assert_equal expected_etag, @response.headers['ETag']
+ end
+
+ def test_etag_should_govern_renders_with_layouts_too
+ get :builder_layout_test
+ assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
+ assert_equal etag_for("<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n"), @response.headers['ETag']
+ end
+
+ def test_should_render_formatted_template
+ get :formatted_html_erb
+ assert_equal 'formatted html erb', @response.body
+ end
+
+ def test_should_render_formatted_xml_erb_template
+ get :formatted_xml_erb, :format => :xml
+ assert_equal '<test>passed formatted xml erb</test>', @response.body
+ end
+
+ def test_should_render_formatted_html_erb_template
+ get :formatted_xml_erb
+ assert_equal '<test>passed formatted html erb</test>', @response.body
+ end
+
+ def test_should_render_formatted_html_erb_template_with_faulty_accepts_header
+ @request.env["HTTP_ACCEPT"] = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, appliction/x-shockwave-flash, */*"
+ get :formatted_xml_erb
+ assert_equal '<test>passed formatted html erb</test>', @response.body
+ end
+
+ def test_should_render_html_formatted_partial
+ get :partial
+ assert_equal 'partial html', @response.body
+ end
+
+ def test_should_render_html_partial_with_dot
+ get :partial_dot_html
+ assert_equal 'partial html', @response.body
+ end
+
+ def test_should_render_html_formatted_partial_with_rjs
+ xhr :get, :partial_as_rjs
+ assert_equal %(Element.replace("foo", "partial html");), @response.body
+ end
+
+ def test_should_render_html_formatted_partial_with_rjs_and_js_format
+ xhr :get, :respond_to_partial_as_rjs
+ assert_equal %(Element.replace("foo", "partial html");), @response.body
+ end
+
+ def test_should_render_js_partial
+ xhr :get, :partial, :format => 'js'
+ assert_equal 'partial js', @response.body
+ end
+
+ def test_should_render_with_alternate_default_render
+ xhr :get, :render_alternate_default
+ assert_equal %(Element.replace("foo", "partial html");), @response.body
+ end
+
+ def test_should_render_xml_but_keep_custom_content_type
+ get :render_xml_with_custom_content_type
+ assert_equal "application/atomsvc+xml", @response.content_type
+ end
+
+ protected
+
+ def etag_for(text)
+ %("#{Digest::MD5.hexdigest(text)}")
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/request_forgery_protection_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/request_forgery_protection_test.rb
new file mode 100644
index 000000000..616ff4f21
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/request_forgery_protection_test.rb
@@ -0,0 +1,217 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+require 'digest/sha1'
+
+ActionController::Routing::Routes.draw do |map|
+ map.connect ':controller/:action/:id'
+end
+
+# simulates cookie session store
+class FakeSessionDbMan
+ def self.generate_digest(data)
+ Digest::SHA1.hexdigest("secure")
+ end
+end
+
+# common controller actions
+module RequestForgeryProtectionActions
+ def index
+ render :inline => "<%= form_tag('/') {} %>"
+ end
+
+ def show_button
+ render :inline => "<%= button_to('New', '/') {} %>"
+ end
+
+ def unsafe
+ render :text => 'pwn'
+ end
+
+ def rescue_action(e) raise e end
+end
+
+# sample controllers
+class RequestForgeryProtectionController < ActionController::Base
+ include RequestForgeryProtectionActions
+ protect_from_forgery :only => :index, :secret => 'abc'
+end
+
+class RequestForgeryProtectionWithoutSecretController < ActionController::Base
+ include RequestForgeryProtectionActions
+ protect_from_forgery
+end
+
+# no token is given, assume the cookie store is used
+class CsrfCookieMonsterController < ActionController::Base
+ include RequestForgeryProtectionActions
+ protect_from_forgery :only => :index
+end
+
+class FreeCookieController < CsrfCookieMonsterController
+ self.allow_forgery_protection = false
+
+ def index
+ render :inline => "<%= form_tag('/') {} %>"
+ end
+
+ def show_button
+ render :inline => "<%= button_to('New', '/') {} %>"
+ end
+end
+
+# common test methods
+
+module RequestForgeryProtectionTests
+ def teardown
+ ActionController::Base.request_forgery_protection_token = nil
+ end
+
+ def test_should_render_form_with_token_tag
+ get :index
+ assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
+ end
+
+ def test_should_render_button_to_with_token_tag
+ get :show_button
+ assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
+ end
+
+ def test_should_allow_get
+ get :index
+ assert_response :success
+ end
+
+ def test_should_allow_post_without_token_on_unsafe_action
+ post :unsafe
+ assert_response :success
+ end
+
+ def test_should_not_allow_post_without_token
+ assert_raises(ActionController::InvalidAuthenticityToken) { post :index }
+ end
+
+ def test_should_not_allow_put_without_token
+ assert_raises(ActionController::InvalidAuthenticityToken) { put :index }
+ end
+
+ def test_should_not_allow_delete_without_token
+ assert_raises(ActionController::InvalidAuthenticityToken) { delete :index }
+ end
+
+ def test_should_not_allow_xhr_post_without_token
+ assert_raises(ActionController::InvalidAuthenticityToken) { xhr :post, :index }
+ end
+
+ def test_should_not_allow_xhr_put_without_token
+ assert_raises(ActionController::InvalidAuthenticityToken) { xhr :put, :index }
+ end
+
+ def test_should_not_allow_xhr_delete_without_token
+ assert_raises(ActionController::InvalidAuthenticityToken) { xhr :delete, :index }
+ end
+
+ def test_should_allow_post_with_token
+ post :index, :authenticity_token => @token
+ assert_response :success
+ end
+
+ def test_should_allow_put_with_token
+ put :index, :authenticity_token => @token
+ assert_response :success
+ end
+
+ def test_should_allow_delete_with_token
+ delete :index, :authenticity_token => @token
+ assert_response :success
+ end
+
+ def test_should_allow_post_with_xml
+ post :index, :format => 'xml'
+ assert_response :success
+ end
+
+ def test_should_allow_put_with_xml
+ put :index, :format => 'xml'
+ assert_response :success
+ end
+
+ def test_should_allow_delete_with_xml
+ delete :index, :format => 'xml'
+ assert_response :success
+ end
+end
+
+# OK let's get our test on
+
+class RequestForgeryProtectionControllerTest < Test::Unit::TestCase
+ include RequestForgeryProtectionTests
+ def setup
+ @controller = RequestForgeryProtectionController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ class << @request.session
+ def session_id() '123' end
+ end
+ @token = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123')
+ ActionController::Base.request_forgery_protection_token = :authenticity_token
+ end
+end
+
+class RequestForgeryProtectionWithoutSecretControllerTest < Test::Unit::TestCase
+ def setup
+ @controller = RequestForgeryProtectionWithoutSecretController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ class << @request.session
+ def session_id() '123' end
+ end
+ @token = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123')
+ ActionController::Base.request_forgery_protection_token = :authenticity_token
+ end
+
+ def test_should_raise_error_without_secret
+ assert_raises ActionController::InvalidAuthenticityToken do
+ get :index
+ end
+ end
+end
+
+class CsrfCookieMonsterControllerTest < Test::Unit::TestCase
+ include RequestForgeryProtectionTests
+ def setup
+ @controller = CsrfCookieMonsterController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ class << @request.session
+ attr_accessor :dbman
+ end
+ # simulate a cookie session store
+ @request.session.dbman = FakeSessionDbMan
+ @token = Digest::SHA1.hexdigest("secure")
+ ActionController::Base.request_forgery_protection_token = :authenticity_token
+ end
+end
+
+class FreeCookieControllerTest < Test::Unit::TestCase
+ def setup
+ @controller = FreeCookieController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @token = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123')
+ end
+
+ def test_should_not_render_form_with_token_tag
+ get :index
+ assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false
+ end
+
+ def test_should_not_render_button_to_with_token_tag
+ get :show_button
+ assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false
+ end
+
+ def test_should_allow_all_methods_without_token
+ [:post, :put, :delete].each do |method|
+ assert_nothing_raised { send(method, :index)}
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/request_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/request_test.rb
new file mode 100644
index 000000000..698d3cbd2
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/request_test.rb
@@ -0,0 +1,836 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+require 'action_controller/integration'
+
+class RequestTest < Test::Unit::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ end
+
+ def test_remote_ip
+ assert_equal '0.0.0.0', @request.remote_ip
+
+ @request.remote_addr = '1.2.3.4'
+ assert_equal '1.2.3.4', @request.remote_ip
+
+ @request.env['HTTP_CLIENT_IP'] = '2.3.4.5'
+ assert_equal '2.3.4.5', @request.remote_ip
+ @request.env.delete 'HTTP_CLIENT_IP'
+
+ @request.env['HTTP_X_FORWARDED_FOR'] = '3.4.5.6'
+ assert_equal '3.4.5.6', @request.remote_ip
+
+ @request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,3.4.5.6'
+ assert_equal '3.4.5.6', @request.remote_ip
+
+ @request.env['HTTP_X_FORWARDED_FOR'] = '172.16.0.1,3.4.5.6'
+ assert_equal '3.4.5.6', @request.remote_ip
+
+ @request.env['HTTP_X_FORWARDED_FOR'] = '192.168.0.1,3.4.5.6'
+ assert_equal '3.4.5.6', @request.remote_ip
+
+ @request.env['HTTP_X_FORWARDED_FOR'] = '10.0.0.1,3.4.5.6'
+ assert_equal '3.4.5.6', @request.remote_ip
+
+ @request.env['HTTP_X_FORWARDED_FOR'] = '10.0.0.1, 10.0.0.1, 3.4.5.6'
+ assert_equal '3.4.5.6', @request.remote_ip
+
+ @request.env['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,3.4.5.6'
+ assert_equal '127.0.0.1', @request.remote_ip
+
+ @request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,192.168.0.1'
+ assert_equal '1.2.3.4', @request.remote_ip
+ @request.env.delete 'HTTP_X_FORWARDED_FOR'
+ end
+
+ def test_domains
+ @request.host = "www.rubyonrails.org"
+ assert_equal "rubyonrails.org", @request.domain
+
+ @request.host = "www.rubyonrails.co.uk"
+ assert_equal "rubyonrails.co.uk", @request.domain(2)
+
+ @request.host = "192.168.1.200"
+ assert_nil @request.domain
+
+ @request.host = "foo.192.168.1.200"
+ assert_nil @request.domain
+
+ @request.host = "192.168.1.200.com"
+ assert_equal "200.com", @request.domain
+
+ @request.host = nil
+ assert_nil @request.domain
+ end
+
+ def test_subdomains
+ @request.host = "www.rubyonrails.org"
+ assert_equal %w( www ), @request.subdomains
+
+ @request.host = "www.rubyonrails.co.uk"
+ assert_equal %w( www ), @request.subdomains(2)
+
+ @request.host = "dev.www.rubyonrails.co.uk"
+ assert_equal %w( dev www ), @request.subdomains(2)
+
+ @request.host = "foobar.foobar.com"
+ assert_equal %w( foobar ), @request.subdomains
+
+ @request.host = "192.168.1.200"
+ assert_equal [], @request.subdomains
+
+ @request.host = "foo.192.168.1.200"
+ assert_equal [], @request.subdomains
+
+ @request.host = "192.168.1.200.com"
+ assert_equal %w( 192 168 1 ), @request.subdomains
+
+ @request.host = nil
+ assert_equal [], @request.subdomains
+ end
+
+ def test_port_string
+ @request.port = 80
+ assert_equal "", @request.port_string
+
+ @request.port = 8080
+ assert_equal ":8080", @request.port_string
+ end
+
+ def test_relative_url_root
+ @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
+ @request.env['SERVER_SOFTWARE'] = 'lighttpd/1.2.3'
+ assert_equal '', @request.relative_url_root, "relative_url_root should be disabled on lighttpd"
+
+ @request.env['SERVER_SOFTWARE'] = 'apache/1.2.3 some random text'
+
+ @request.env['SCRIPT_NAME'] = nil
+ assert_equal "", @request.relative_url_root
+
+ @request.env['SCRIPT_NAME'] = "/dispatch.cgi"
+ assert_equal "", @request.relative_url_root
+
+ @request.env['SCRIPT_NAME'] = "/myapp.rb"
+ assert_equal "", @request.relative_url_root
+
+ @request.relative_url_root = nil
+ @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
+ assert_equal "/hieraki", @request.relative_url_root
+
+ @request.relative_url_root = nil
+ @request.env['SCRIPT_NAME'] = "/collaboration/hieraki/dispatch.cgi"
+ assert_equal "/collaboration/hieraki", @request.relative_url_root
+
+ # apache/scgi case
+ @request.relative_url_root = nil
+ @request.env['SCRIPT_NAME'] = "/collaboration/hieraki"
+ assert_equal "/collaboration/hieraki", @request.relative_url_root
+
+ @request.relative_url_root = nil
+ @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
+ @request.env['SERVER_SOFTWARE'] = 'lighttpd/1.2.3'
+ @request.env['RAILS_RELATIVE_URL_ROOT'] = "/hieraki"
+ assert_equal "/hieraki", @request.relative_url_root
+
+ # @env overrides path guess
+ @request.relative_url_root = nil
+ @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
+ @request.env['SERVER_SOFTWARE'] = 'apache/1.2.3 some random text'
+ @request.env['RAILS_RELATIVE_URL_ROOT'] = "/real_url"
+ assert_equal "/real_url", @request.relative_url_root
+ end
+
+ def test_request_uri
+ @request.env['SERVER_SOFTWARE'] = 'Apache 42.342.3432'
+
+ @request.relative_url_root = nil
+ @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri?mapped=1"
+ assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
+ assert_equal "/path/of/some/uri", @request.path
+
+ @request.relative_url_root = nil
+ @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri"
+ assert_equal "/path/of/some/uri", @request.request_uri
+ assert_equal "/path/of/some/uri", @request.path
+
+ @request.relative_url_root = nil
+ @request.set_REQUEST_URI "/path/of/some/uri"
+ assert_equal "/path/of/some/uri", @request.request_uri
+ assert_equal "/path/of/some/uri", @request.path
+
+ @request.relative_url_root = nil
+ @request.set_REQUEST_URI "/"
+ assert_equal "/", @request.request_uri
+ assert_equal "/", @request.path
+
+ @request.relative_url_root = nil
+ @request.set_REQUEST_URI "/?m=b"
+ assert_equal "/?m=b", @request.request_uri
+ assert_equal "/", @request.path
+
+ @request.relative_url_root = nil
+ @request.set_REQUEST_URI "/"
+ @request.env['SCRIPT_NAME'] = "/dispatch.cgi"
+ assert_equal "/", @request.request_uri
+ assert_equal "/", @request.path
+
+ @request.relative_url_root = nil
+ @request.set_REQUEST_URI "/hieraki/"
+ @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
+ assert_equal "/hieraki/", @request.request_uri
+ assert_equal "/", @request.path
+
+ @request.relative_url_root = nil
+ @request.set_REQUEST_URI "/collaboration/hieraki/books/edit/2"
+ @request.env['SCRIPT_NAME'] = "/collaboration/hieraki/dispatch.cgi"
+ assert_equal "/collaboration/hieraki/books/edit/2", @request.request_uri
+ assert_equal "/books/edit/2", @request.path
+
+ # The following tests are for when REQUEST_URI is not supplied (as in IIS)
+ @request.relative_url_root = nil
+ @request.set_REQUEST_URI nil
+ @request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1"
+ @request.env['SCRIPT_NAME'] = nil #"/path/dispatch.rb"
+ assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
+ assert_equal "/path/of/some/uri", @request.path
+
+ @request.set_REQUEST_URI nil
+ @request.relative_url_root = nil
+ @request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1"
+ @request.env['SCRIPT_NAME'] = "/path/dispatch.rb"
+ assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
+ assert_equal "/of/some/uri", @request.path
+
+ @request.set_REQUEST_URI nil
+ @request.relative_url_root = nil
+ @request.env['PATH_INFO'] = "/path/of/some/uri"
+ @request.env['SCRIPT_NAME'] = nil
+ assert_equal "/path/of/some/uri", @request.request_uri
+ assert_equal "/path/of/some/uri", @request.path
+
+ @request.set_REQUEST_URI nil
+ @request.relative_url_root = nil
+ @request.env['PATH_INFO'] = "/"
+ assert_equal "/", @request.request_uri
+ assert_equal "/", @request.path
+
+ @request.set_REQUEST_URI nil
+ @request.relative_url_root = nil
+ @request.env['PATH_INFO'] = "/?m=b"
+ assert_equal "/?m=b", @request.request_uri
+ assert_equal "/", @request.path
+
+ @request.set_REQUEST_URI nil
+ @request.relative_url_root = nil
+ @request.env['PATH_INFO'] = "/"
+ @request.env['SCRIPT_NAME'] = "/dispatch.cgi"
+ assert_equal "/", @request.request_uri
+ assert_equal "/", @request.path
+
+ @request.set_REQUEST_URI nil
+ @request.relative_url_root = nil
+ @request.env['PATH_INFO'] = "/hieraki/"
+ @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
+ assert_equal "/hieraki/", @request.request_uri
+ assert_equal "/", @request.path
+
+ @request.set_REQUEST_URI '/hieraki/dispatch.cgi'
+ @request.relative_url_root = '/hieraki'
+ assert_equal "/dispatch.cgi", @request.path
+ @request.relative_url_root = nil
+
+ @request.set_REQUEST_URI '/hieraki/dispatch.cgi'
+ @request.relative_url_root = '/foo'
+ assert_equal "/hieraki/dispatch.cgi", @request.path
+ @request.relative_url_root = nil
+
+ # This test ensures that Rails uses REQUEST_URI over PATH_INFO
+ @request.relative_url_root = nil
+ @request.env['REQUEST_URI'] = "/some/path"
+ @request.env['PATH_INFO'] = "/another/path"
+ @request.env['SCRIPT_NAME'] = "/dispatch.cgi"
+ assert_equal "/some/path", @request.request_uri
+ assert_equal "/some/path", @request.path
+ end
+
+
+ def test_host_with_default_port
+ @request.host = "rubyonrails.org"
+ @request.port = 80
+ assert_equal "rubyonrails.org", @request.host_with_port
+ end
+
+ def test_host_with_non_default_port
+ @request.host = "rubyonrails.org"
+ @request.port = 81
+ assert_equal "rubyonrails.org:81", @request.host_with_port
+ end
+
+ def test_server_software
+ assert_equal nil, @request.server_software
+
+ @request.env['SERVER_SOFTWARE'] = 'Apache3.422'
+ assert_equal 'apache', @request.server_software
+
+ @request.env['SERVER_SOFTWARE'] = 'lighttpd(1.1.4)'
+ assert_equal 'lighttpd', @request.server_software
+ end
+
+ def test_xml_http_request
+ assert !@request.xml_http_request?
+ assert !@request.xhr?
+
+ @request.env['HTTP_X_REQUESTED_WITH'] = "DefinitelyNotAjax1.0"
+ assert !@request.xml_http_request?
+ assert !@request.xhr?
+
+ @request.env['HTTP_X_REQUESTED_WITH'] = "XMLHttpRequest"
+ assert @request.xml_http_request?
+ assert @request.xhr?
+ end
+
+ def test_reports_ssl
+ assert !@request.ssl?
+ @request.env['HTTPS'] = 'on'
+ assert @request.ssl?
+ end
+
+ def test_reports_ssl_when_proxied_via_lighttpd
+ assert !@request.ssl?
+ @request.env['HTTP_X_FORWARDED_PROTO'] = 'https'
+ assert @request.ssl?
+ end
+
+ def test_symbolized_request_methods
+ [:get, :post, :put, :delete].each do |method|
+ set_request_method_to method
+ assert_equal method, @request.method
+ end
+ end
+
+ def test_invalid_http_method_raises_exception
+ set_request_method_to :random_method
+ assert_raises(ActionController::UnknownHttpMethod) do
+ @request.method
+ end
+ end
+
+ def test_allow_method_hacking_on_post
+ set_request_method_to :post
+ [:get, :head, :options, :put, :post, :delete].each do |method|
+ @request.instance_eval { @parameters = { :_method => method } ; @request_method = nil }
+ assert_equal(method == :head ? :get : method, @request.method)
+ end
+ end
+
+ def test_invalid_method_hacking_on_post_raises_exception
+ set_request_method_to :post
+ @request.instance_eval { @parameters = { :_method => :random_method } ; @request_method = nil }
+ assert_raises(ActionController::UnknownHttpMethod) do
+ @request.method
+ end
+ end
+
+ def test_restrict_method_hacking
+ @request.instance_eval { @parameters = { :_method => 'put' } }
+ [:get, :put, :delete].each do |method|
+ set_request_method_to method
+ assert_equal method, @request.method
+ end
+ end
+
+ def test_head_masquarading_as_get
+ set_request_method_to :head
+ assert_equal :get, @request.method
+ assert @request.get?
+ assert @request.head?
+ end
+
+ def test_xml_format
+ @request.instance_eval { @parameters = { :format => 'xml' } }
+ assert_equal Mime::XML, @request.format
+ end
+
+ def test_xhtml_format
+ @request.instance_eval { @parameters = { :format => 'xhtml' } }
+ assert_equal Mime::HTML, @request.format
+ end
+
+ def test_txt_format
+ @request.instance_eval { @parameters = { :format => 'txt' } }
+ assert_equal Mime::TEXT, @request.format
+ end
+
+ def test_nil_format
+ @request.instance_eval { @parameters = { :format => nil } }
+ @request.env["HTTP_ACCEPT"] = "text/javascript"
+ assert_equal Mime::JS, @request.format
+ end
+
+ def test_content_type
+ @request.env["CONTENT_TYPE"] = "text/html"
+ assert_equal Mime::HTML, @request.content_type
+ end
+
+ def test_content_no_type
+ assert_equal nil, @request.content_type
+ end
+
+ def test_content_type_xml
+ @request.env["CONTENT_TYPE"] = "application/xml"
+ assert_equal Mime::XML, @request.content_type
+ end
+
+ def test_content_type_with_charset
+ @request.env["CONTENT_TYPE"] = "application/xml; charset=UTF-8"
+ assert_equal Mime::XML, @request.content_type
+ end
+
+ def test_user_agent
+ assert_not_nil @request.user_agent
+ end
+
+ def test_parameters
+ @request.instance_eval { @request_parameters = { "foo" => 1 } }
+ @request.instance_eval { @query_parameters = { "bar" => 2 } }
+
+ assert_equal({"foo" => 1, "bar" => 2}, @request.parameters)
+ assert_equal({"foo" => 1}, @request.request_parameters)
+ assert_equal({"bar" => 2}, @request.query_parameters)
+ end
+
+ protected
+ def set_request_method_to(method)
+ @request.env['REQUEST_METHOD'] = method.to_s.upcase
+ @request.instance_eval { @request_method = nil }
+ end
+end
+
+
+class UrlEncodedRequestParameterParsingTest < Test::Unit::TestCase
+ def setup
+ @query_string = "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1"
+ @query_string_with_empty = "action=create_customer&full_name="
+ @query_string_with_array = "action=create_customer&selected[]=1&selected[]=2&selected[]=3"
+ @query_string_with_amps = "action=create_customer&name=Don%27t+%26+Does"
+ @query_string_with_multiple_of_same_name =
+ "action=update_order&full_name=Lau%20Taarnskov&products=4&products=2&products=3"
+ @query_string_with_many_equal = "action=create_customer&full_name=abc=def=ghi"
+ @query_string_without_equal = "action"
+ @query_string_with_many_ampersands =
+ "&action=create_customer&&&full_name=David%20Heinemeier%20Hansson"
+ @query_string_with_empty_key = "action=create_customer&full_name=David%20Heinemeier%20Hansson&=Save"
+ end
+
+ def test_query_string
+ assert_equal(
+ { "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"},
+ ActionController::AbstractRequest.parse_query_parameters(@query_string)
+ )
+ end
+
+ def test_deep_query_string
+ expected = {'x' => {'y' => {'z' => '10'}}}
+ assert_equal(expected, ActionController::AbstractRequest.parse_query_parameters('x[y][z]=10'))
+ end
+
+ def test_deep_query_string_with_array
+ assert_equal({'x' => {'y' => {'z' => ['10']}}}, ActionController::AbstractRequest.parse_query_parameters('x[y][z][]=10'))
+ assert_equal({'x' => {'y' => {'z' => ['10', '5']}}}, ActionController::AbstractRequest.parse_query_parameters('x[y][z][]=10&x[y][z][]=5'))
+ end
+
+ def test_deep_query_string_with_array_of_hash
+ assert_equal({'x' => {'y' => [{'z' => '10'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10'))
+ assert_equal({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][w]=10'))
+ end
+
+ def test_deep_query_string_with_array_of_hashes_with_one_pair
+ assert_equal({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][z]=20'))
+ assert_equal("10", ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')["x"]["y"].first["z"])
+ assert_equal("10", ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][z]=20').with_indifferent_access[:x][:y].first[:z])
+ end
+
+ def test_deep_query_string_with_array_of_hashes_with_multiple_pairs
+ assert_equal(
+ {'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}},
+ ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b')
+ )
+ end
+
+ def test_query_string_with_nil
+ assert_equal(
+ { "action" => "create_customer", "full_name" => ''},
+ ActionController::AbstractRequest.parse_query_parameters(@query_string_with_empty)
+ )
+ end
+
+ def test_query_string_with_array
+ assert_equal(
+ { "action" => "create_customer", "selected" => ["1", "2", "3"]},
+ ActionController::AbstractRequest.parse_query_parameters(@query_string_with_array)
+ )
+ end
+
+ def test_query_string_with_amps
+ assert_equal(
+ { "action" => "create_customer", "name" => "Don't & Does"},
+ ActionController::AbstractRequest.parse_query_parameters(@query_string_with_amps)
+ )
+ end
+
+ def test_query_string_with_many_equal
+ assert_equal(
+ { "action" => "create_customer", "full_name" => "abc=def=ghi"},
+ ActionController::AbstractRequest.parse_query_parameters(@query_string_with_many_equal)
+ )
+ end
+
+ def test_query_string_without_equal
+ assert_equal(
+ { "action" => nil },
+ ActionController::AbstractRequest.parse_query_parameters(@query_string_without_equal)
+ )
+ end
+
+ def test_query_string_with_empty_key
+ assert_equal(
+ { "action" => "create_customer", "full_name" => "David Heinemeier Hansson" },
+ ActionController::AbstractRequest.parse_query_parameters(@query_string_with_empty_key)
+ )
+ end
+
+ def test_query_string_with_many_ampersands
+ assert_equal(
+ { "action" => "create_customer", "full_name" => "David Heinemeier Hansson"},
+ ActionController::AbstractRequest.parse_query_parameters(@query_string_with_many_ampersands)
+ )
+ end
+
+ def test_unbalanced_query_string_with_array
+ assert_equal(
+ {'location' => ["1", "2"], 'age_group' => ["2"]},
+ ActionController::AbstractRequest.parse_query_parameters("location[]=1&location[]=2&age_group[]=2")
+ )
+ assert_equal(
+ {'location' => ["1", "2"], 'age_group' => ["2"]},
+ ActionController::AbstractRequest.parse_request_parameters({'location[]' => ["1", "2"],
+ 'age_group[]' => ["2"]})
+ )
+ end
+
+
+ def test_request_hash_parsing
+ query = {
+ "note[viewers][viewer][][type]" => ["User", "Group"],
+ "note[viewers][viewer][][id]" => ["1", "2"]
+ }
+
+ expected = { "note" => { "viewers"=>{"viewer"=>[{ "id"=>"1", "type"=>"User"}, {"type"=>"Group", "id"=>"2"} ]} } }
+
+ assert_equal(expected, ActionController::AbstractRequest.parse_request_parameters(query))
+ end
+
+
+ def test_parse_params
+ input = {
+ "customers[boston][first][name]" => [ "David" ],
+ "customers[boston][first][url]" => [ "http://David" ],
+ "customers[boston][second][name]" => [ "Allan" ],
+ "customers[boston][second][url]" => [ "http://Allan" ],
+ "something_else" => [ "blah" ],
+ "something_nil" => [ nil ],
+ "something_empty" => [ "" ],
+ "products[first]" => [ "Apple Computer" ],
+ "products[second]" => [ "Pc" ],
+ "" => [ 'Save' ]
+ }
+
+ expected_output = {
+ "customers" => {
+ "boston" => {
+ "first" => {
+ "name" => "David",
+ "url" => "http://David"
+ },
+ "second" => {
+ "name" => "Allan",
+ "url" => "http://Allan"
+ }
+ }
+ },
+ "something_else" => "blah",
+ "something_empty" => "",
+ "something_nil" => "",
+ "products" => {
+ "first" => "Apple Computer",
+ "second" => "Pc"
+ }
+ }
+
+ assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input)
+ end
+
+ UploadedStringIO = ActionController::UploadedStringIO
+ class MockUpload < UploadedStringIO
+ def initialize(content_type, original_path, *args)
+ self.content_type = content_type
+ self.original_path = original_path
+ super *args
+ end
+ end
+
+ def test_parse_params_from_multipart_upload
+ file = MockUpload.new('img/jpeg', 'foo.jpg')
+ ie_file = MockUpload.new('img/jpeg', 'c:\\Documents and Settings\\foo\\Desktop\\bar.jpg')
+ non_file_text_part = MockUpload.new('text/plain', '', 'abc')
+
+ input = {
+ "something" => [ UploadedStringIO.new("") ],
+ "array_of_stringios" => [[ UploadedStringIO.new("One"), UploadedStringIO.new("Two") ]],
+ "mixed_types_array" => [[ UploadedStringIO.new("Three"), "NotStringIO" ]],
+ "mixed_types_as_checkboxes[strings][nested]" => [[ file, "String", UploadedStringIO.new("StringIO")]],
+ "ie_mixed_types_as_checkboxes[strings][nested]" => [[ ie_file, "String", UploadedStringIO.new("StringIO")]],
+ "products[string]" => [ UploadedStringIO.new("Apple Computer") ],
+ "products[file]" => [ file ],
+ "ie_products[string]" => [ UploadedStringIO.new("Microsoft") ],
+ "ie_products[file]" => [ ie_file ],
+ "text_part" => [non_file_text_part]
+ }
+
+ expected_output = {
+ "something" => "",
+ "array_of_stringios" => ["One", "Two"],
+ "mixed_types_array" => [ "Three", "NotStringIO" ],
+ "mixed_types_as_checkboxes" => {
+ "strings" => {
+ "nested" => [ file, "String", "StringIO" ]
+ },
+ },
+ "ie_mixed_types_as_checkboxes" => {
+ "strings" => {
+ "nested" => [ ie_file, "String", "StringIO" ]
+ },
+ },
+ "products" => {
+ "string" => "Apple Computer",
+ "file" => file
+ },
+ "ie_products" => {
+ "string" => "Microsoft",
+ "file" => ie_file
+ },
+ "text_part" => "abc"
+ }
+
+ params = ActionController::AbstractRequest.parse_request_parameters(input)
+ assert_equal expected_output, params
+
+ # Lone filenames are preserved.
+ assert_equal 'foo.jpg', params['mixed_types_as_checkboxes']['strings']['nested'].first.original_filename
+ assert_equal 'foo.jpg', params['products']['file'].original_filename
+
+ # But full Windows paths are reduced to their basename.
+ assert_equal 'bar.jpg', params['ie_mixed_types_as_checkboxes']['strings']['nested'].first.original_filename
+ assert_equal 'bar.jpg', params['ie_products']['file'].original_filename
+ end
+
+ def test_parse_params_with_file
+ input = {
+ "customers[boston][first][name]" => [ "David" ],
+ "something_else" => [ "blah" ],
+ "logo" => [ File.new(File.dirname(__FILE__) + "/cgi_test.rb").path ]
+ }
+
+ expected_output = {
+ "customers" => {
+ "boston" => {
+ "first" => {
+ "name" => "David"
+ }
+ }
+ },
+ "something_else" => "blah",
+ "logo" => File.new(File.dirname(__FILE__) + "/cgi_test.rb").path,
+ }
+
+ assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input)
+ end
+
+ def test_parse_params_with_array
+ input = { "selected[]" => [ "1", "2", "3" ] }
+
+ expected_output = { "selected" => [ "1", "2", "3" ] }
+
+ assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input)
+ end
+
+ def test_parse_params_with_non_alphanumeric_name
+ input = { "a/b[c]" => %w(d) }
+ expected = { "a/b" => { "c" => "d" }}
+ assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
+ end
+
+ def test_parse_params_with_single_brackets_in_middle
+ input = { "a/b[c]d" => %w(e) }
+ expected = { "a/b" => {} }
+ assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
+ end
+
+ def test_parse_params_with_separated_brackets
+ input = { "a/b@[c]d[e]" => %w(f) }
+ expected = { "a/b@" => { }}
+ assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
+ end
+
+ def test_parse_params_with_separated_brackets_and_array
+ input = { "a/b@[c]d[e][]" => %w(f) }
+ expected = { "a/b@" => { }}
+ assert_equal expected , ActionController::AbstractRequest.parse_request_parameters(input)
+ end
+
+ def test_parse_params_with_unmatched_brackets_and_array
+ input = { "a/b@[c][d[e][]" => %w(f) }
+ expected = { "a/b@" => { "c" => { }}}
+ assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
+ end
+
+ def test_parse_params_with_nil_key
+ input = { nil => nil, "test2" => %w(value1) }
+ expected = { "test2" => "value1" }
+ assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
+ end
+end
+
+
+class MultipartRequestParameterParsingTest < Test::Unit::TestCase
+ FIXTURE_PATH = File.dirname(__FILE__) + '/../fixtures/multipart'
+
+ def test_single_parameter
+ params = process('single_parameter')
+ assert_equal({ 'foo' => 'bar' }, params)
+ end
+
+ def test_bracketed_param
+ assert_equal({ 'foo' => { 'baz' => 'bar'}}, process('bracketed_param'))
+ end
+
+ def test_text_file
+ params = process('text_file')
+ assert_equal %w(file foo), params.keys.sort
+ assert_equal 'bar', params['foo']
+
+ file = params['file']
+ assert_kind_of StringIO, file
+ assert_equal 'file.txt', file.original_filename
+ assert_equal "text/plain", file.content_type
+ assert_equal 'contents', file.read
+ end
+
+ def test_large_text_file
+ params = process('large_text_file')
+ assert_equal %w(file foo), params.keys.sort
+ assert_equal 'bar', params['foo']
+
+ file = params['file']
+ assert_kind_of Tempfile, file
+ assert_equal 'file.txt', file.original_filename
+ assert_equal "text/plain", file.content_type
+ assert ('a' * 20480) == file.read
+ end
+
+ uses_mocha "test_no_rewind_stream" do
+ def test_no_rewind_stream
+ # Ensures that parse_multipart_form_parameters works with streams that cannot be rewound
+ file = File.open(File.join(FIXTURE_PATH, 'large_text_file'), 'rb')
+ file.expects(:rewind).raises(Errno::ESPIPE)
+ params = ActionController::AbstractRequest.parse_multipart_form_parameters(file, 'AaB03x', file.stat.size, {})
+ assert_not_equal 0, file.pos # file was not rewound after reading
+ end
+ end
+
+ def test_binary_file
+ params = process('binary_file')
+ assert_equal %w(file flowers foo), params.keys.sort
+ assert_equal 'bar', params['foo']
+
+ file = params['file']
+ assert_kind_of StringIO, file
+ assert_equal 'file.csv', file.original_filename
+ assert_nil file.content_type
+ assert_equal 'contents', file.read
+
+ file = params['flowers']
+ assert_kind_of StringIO, file
+ assert_equal 'flowers.jpg', file.original_filename
+ assert_equal "image/jpeg", file.content_type
+ assert_equal 19512, file.size
+ #assert_equal File.read(File.dirname(__FILE__) + '/../../../activerecord/test/fixtures/flowers.jpg'), file.read
+ end
+
+ def test_mixed_files
+ params = process('mixed_files')
+ assert_equal %w(files foo), params.keys.sort
+ assert_equal 'bar', params['foo']
+
+ # Ruby CGI doesn't handle multipart/mixed for us.
+ assert_kind_of String, params['files']
+ assert_equal 19756, params['files'].size
+ end
+
+ private
+ def process(name)
+ File.open(File.join(FIXTURE_PATH, name), 'rb') do |file|
+ params = ActionController::AbstractRequest.parse_multipart_form_parameters(file, 'AaB03x', file.stat.size, {})
+ assert_equal 0, file.pos # file was rewound after reading
+ params
+ end
+ end
+end
+
+
+class XmlParamsParsingTest < Test::Unit::TestCase
+ def test_single_file
+ person = parse_body("<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{Base64.encode64('ABC')}</avatar></person>")
+
+ assert_equal "image/jpg", person['person']['avatar'].content_type
+ assert_equal "me.jpg", person['person']['avatar'].original_filename
+ assert_equal "ABC", person['person']['avatar'].read
+ end
+
+ def test_multiple_files
+ person = parse_body(<<-end_body)
+ <person>
+ <name>David</name>
+ <avatars>
+ <avatar type='file' name='me.jpg' content_type='image/jpg'>#{Base64.encode64('ABC')}</avatar>
+ <avatar type='file' name='you.gif' content_type='image/gif'>#{Base64.encode64('DEF')}</avatar>
+ </avatars>
+ </person>
+ end_body
+
+ assert_equal "image/jpg", person['person']['avatars']['avatar'].first.content_type
+ assert_equal "me.jpg", person['person']['avatars']['avatar'].first.original_filename
+ assert_equal "ABC", person['person']['avatars']['avatar'].first.read
+
+ assert_equal "image/gif", person['person']['avatars']['avatar'].last.content_type
+ assert_equal "you.gif", person['person']['avatars']['avatar'].last.original_filename
+ assert_equal "DEF", person['person']['avatars']['avatar'].last.read
+ end
+
+ private
+ def parse_body(body)
+ env = { 'CONTENT_TYPE' => 'application/xml',
+ 'CONTENT_LENGTH' => body.size.to_s }
+ cgi = ActionController::Integration::Session::StubCGI.new(env, body)
+ ActionController::CgiRequest.new(cgi).request_parameters
+ end
+end
+
+class LegacyXmlParamsParsingTest < XmlParamsParsingTest
+ private
+ def parse_body(body)
+ env = { 'HTTP_X_POST_DATA_FORMAT' => 'xml',
+ 'CONTENT_LENGTH' => body.size.to_s }
+ cgi = ActionController::Integration::Session::StubCGI.new(env, body)
+ ActionController::CgiRequest.new(cgi).request_parameters
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/rescue_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/rescue_test.rb
new file mode 100644
index 000000000..a63fd0600
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/rescue_test.rb
@@ -0,0 +1,501 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+uses_mocha 'rescue' do
+
+class RescueController < ActionController::Base
+ class NotAuthorized < StandardError
+ end
+ class NotAuthorizedToRescueAsString < StandardError
+ end
+
+ class RecordInvalid < StandardError
+ end
+ class RecordInvalidToRescueAsString < StandardError
+ end
+
+ class NotAllowed < StandardError
+ end
+ class NotAllowedToRescueAsString < StandardError
+ end
+
+ class InvalidRequest < StandardError
+ end
+ class InvalidRequestToRescueAsString < StandardError
+ end
+
+ class BadGateway < StandardError
+ end
+ class BadGatewayToRescueAsString < StandardError
+ end
+
+ class ResourceUnavailable < StandardError
+ end
+ class ResourceUnavailableToRescueAsString < StandardError
+ end
+
+ # We use a fully-qualified name in some strings, and a relative constant
+ # name in some other to test correct handling of both cases.
+
+ rescue_from NotAuthorized, :with => :deny_access
+ rescue_from 'RescueController::NotAuthorizedToRescueAsString', :with => :deny_access
+
+ rescue_from RecordInvalid, :with => :show_errors
+ rescue_from 'RescueController::RecordInvalidToRescueAsString', :with => :show_errors
+
+ rescue_from NotAllowed, :with => proc { head :forbidden }
+ rescue_from 'RescueController::NotAllowedToRescueAsString', :with => proc { head :forbidden }
+
+ rescue_from InvalidRequest, :with => proc { |exception| render :text => exception.message }
+ rescue_from 'InvalidRequestToRescueAsString', :with => proc { |exception| render :text => exception.message }
+
+ rescue_from BadGateway do
+ head :status => 502
+ end
+ rescue_from 'BadGatewayToRescueAsString' do
+ head :status => 502
+ end
+
+ rescue_from ResourceUnavailable do |exception|
+ render :text => exception.message
+ end
+ rescue_from 'ResourceUnavailableToRescueAsString' do |exception|
+ render :text => exception.message
+ end
+
+ def raises
+ render :text => 'already rendered'
+ raise "don't panic!"
+ end
+
+ def method_not_allowed
+ raise ActionController::MethodNotAllowed.new(:get, :head, :put)
+ end
+
+ def not_implemented
+ raise ActionController::NotImplemented.new(:get, :put)
+ end
+
+ def not_authorized
+ raise NotAuthorized
+ end
+ def not_authorized_raise_as_string
+ raise NotAuthorizedToRescueAsString
+ end
+
+ def not_allowed
+ raise NotAllowed
+ end
+ def not_allowed_raise_as_string
+ raise NotAllowedToRescueAsString
+ end
+
+ def invalid_request
+ raise InvalidRequest
+ end
+ def invalid_request_raise_as_string
+ raise InvalidRequestToRescueAsString
+ end
+
+ def record_invalid
+ raise RecordInvalid
+ end
+ def record_invalid_raise_as_string
+ raise RecordInvalidToRescueAsString
+ end
+
+ def bad_gateway
+ raise BadGateway
+ end
+ def bad_gateway_raise_as_string
+ raise BadGatewayToRescueAsString
+ end
+
+ def resource_unavailable
+ raise ResourceUnavailable
+ end
+ def resource_unavailable_raise_as_string
+ raise ResourceUnavailableToRescueAsString
+ end
+
+ def missing_template
+ end
+
+ protected
+ def deny_access
+ head :forbidden
+ end
+
+ def show_errors(exception)
+ head :unprocessable_entity
+ end
+end
+
+class RescueTest < Test::Unit::TestCase
+ FIXTURE_PUBLIC = "#{File.dirname(__FILE__)}/../fixtures".freeze
+
+ def setup
+ @controller = RescueController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ RescueController.consider_all_requests_local = true
+ @request.remote_addr = '1.2.3.4'
+ @request.host = 'example.com'
+
+ begin
+ raise 'foo'
+ rescue => @exception
+ end
+ end
+
+ def test_rescue_action_locally_if_all_requests_local
+ @controller.expects(:local_request?).never
+ @controller.expects(:rescue_action_locally).with(@exception)
+ @controller.expects(:rescue_action_in_public).never
+
+ with_all_requests_local do
+ @controller.send :rescue_action, @exception
+ end
+ end
+
+ def test_rescue_action_locally_if_remote_addr_is_localhost
+ @controller.expects(:local_request?).returns(true)
+ @controller.expects(:rescue_action_locally).with(@exception)
+ @controller.expects(:rescue_action_in_public).never
+
+ with_all_requests_local false do
+ @controller.send :rescue_action, @exception
+ end
+ end
+
+ def test_rescue_action_in_public_otherwise
+ @controller.expects(:local_request?).returns(false)
+ @controller.expects(:rescue_action_locally).never
+ @controller.expects(:rescue_action_in_public).with(@exception)
+
+ with_all_requests_local false do
+ @controller.send :rescue_action, @exception
+ end
+ end
+
+ def test_rescue_action_in_public_with_error_file
+ with_rails_root FIXTURE_PUBLIC do
+ with_all_requests_local false do
+ get :raises
+ end
+ end
+
+ assert_response :internal_server_error
+ body = File.read("#{FIXTURE_PUBLIC}/public/500.html")
+ assert_equal body, @response.body
+ end
+
+ def test_rescue_action_in_public_without_error_file
+ with_rails_root '/tmp' do
+ with_all_requests_local false do
+ get :raises
+ end
+ end
+
+ assert_response :internal_server_error
+ assert_equal ' ', @response.body
+ end
+
+ def test_rescue_unknown_action_in_public_with_error_file
+ with_rails_root FIXTURE_PUBLIC do
+ with_all_requests_local false do
+ get :foobar_doesnt_exist
+ end
+ end
+
+ assert_response :not_found
+ body = File.read("#{FIXTURE_PUBLIC}/public/404.html")
+ assert_equal body, @response.body
+ end
+
+ def test_rescue_unknown_action_in_public_without_error_file
+ with_rails_root '/tmp' do
+ with_all_requests_local false do
+ get :foobar_doesnt_exist
+ end
+ end
+
+ assert_response :not_found
+ assert_equal ' ', @response.body
+ end
+
+ def test_rescue_missing_template_in_public
+ with_rails_root FIXTURE_PUBLIC do
+ with_all_requests_local true do
+ get :missing_template
+ end
+ end
+
+ assert_response :internal_server_error
+ assert @response.body.include?('missing_template'), "Response should include the template name."
+ end
+
+ def test_rescue_action_locally
+ get :raises
+ assert_response :internal_server_error
+ assert_template 'diagnostics.erb'
+ assert @response.body.include?('RescueController#raises'), "Response should include controller and action."
+ assert @response.body.include?("don't panic"), "Response should include exception message."
+ end
+
+ def test_local_request_when_remote_addr_is_localhost
+ @controller.expects(:request).returns(@request).at_least_once
+ with_remote_addr '127.0.0.1' do
+ assert @controller.send(:local_request?)
+ end
+ end
+
+ def test_local_request_when_remote_addr_isnt_locahost
+ @controller.expects(:request).returns(@request)
+ with_remote_addr '1.2.3.4' do
+ assert !@controller.send(:local_request?)
+ end
+ end
+
+ def test_rescue_responses
+ responses = ActionController::Base.rescue_responses
+
+ assert_equal ActionController::Rescue::DEFAULT_RESCUE_RESPONSE, responses.default
+ assert_equal ActionController::Rescue::DEFAULT_RESCUE_RESPONSE, responses[Exception.new]
+
+ assert_equal :not_found, responses[ActionController::RoutingError.name]
+ assert_equal :not_found, responses[ActionController::UnknownAction.name]
+ assert_equal :not_found, responses['ActiveRecord::RecordNotFound']
+ assert_equal :conflict, responses['ActiveRecord::StaleObjectError']
+ assert_equal :unprocessable_entity, responses['ActiveRecord::RecordInvalid']
+ assert_equal :unprocessable_entity, responses['ActiveRecord::RecordNotSaved']
+ assert_equal :method_not_allowed, responses['ActionController::MethodNotAllowed']
+ assert_equal :not_implemented, responses['ActionController::NotImplemented']
+ end
+
+ def test_rescue_templates
+ templates = ActionController::Base.rescue_templates
+
+ assert_equal ActionController::Rescue::DEFAULT_RESCUE_TEMPLATE, templates.default
+ assert_equal ActionController::Rescue::DEFAULT_RESCUE_TEMPLATE, templates[Exception.new]
+
+ assert_equal 'missing_template', templates[ActionController::MissingTemplate.name]
+ assert_equal 'routing_error', templates[ActionController::RoutingError.name]
+ assert_equal 'unknown_action', templates[ActionController::UnknownAction.name]
+ assert_equal 'template_error', templates[ActionView::TemplateError.name]
+ end
+
+ def test_clean_backtrace
+ with_rails_root nil do
+ # No action if RAILS_ROOT isn't set.
+ cleaned = @controller.send(:clean_backtrace, @exception)
+ assert_equal @exception.backtrace, cleaned
+ end
+
+ with_rails_root Dir.pwd do
+ # RAILS_ROOT is removed from backtrace.
+ cleaned = @controller.send(:clean_backtrace, @exception)
+ expected = @exception.backtrace.map { |line| line.sub(RAILS_ROOT, '') }
+ assert_equal expected, cleaned
+
+ # No action if backtrace is nil.
+ assert_nil @controller.send(:clean_backtrace, Exception.new)
+ end
+ end
+
+ def test_not_implemented
+ with_all_requests_local false do
+ head :not_implemented
+ end
+ assert_response :not_implemented
+ assert_equal "GET, PUT", @response.headers['Allow']
+ end
+
+ def test_method_not_allowed
+ with_all_requests_local false do
+ get :method_not_allowed
+ end
+ assert_response :method_not_allowed
+ assert_equal "GET, HEAD, PUT", @response.headers['Allow']
+ end
+
+ def test_rescue_handler
+ get :not_authorized
+ assert_response :forbidden
+ end
+ def test_rescue_handler_string
+ get :not_authorized_raise_as_string
+ assert_response :forbidden
+ end
+
+ def test_rescue_handler_with_argument
+ @controller.expects(:show_errors).once.with { |e| e.is_a?(Exception) }
+ get :record_invalid
+ end
+ def test_rescue_handler_with_argument_as_string
+ @controller.expects(:show_errors).once.with { |e| e.is_a?(Exception) }
+ get :record_invalid_raise_as_string
+ end
+
+ def test_proc_rescue_handler
+ get :not_allowed
+ assert_response :forbidden
+ end
+ def test_proc_rescue_handler_as_string
+ get :not_allowed_raise_as_string
+ assert_response :forbidden
+ end
+
+ def test_proc_rescue_handle_with_argument
+ get :invalid_request
+ assert_equal "RescueController::InvalidRequest", @response.body
+ end
+ def test_proc_rescue_handle_with_argument_as_string
+ get :invalid_request_raise_as_string
+ assert_equal "RescueController::InvalidRequestToRescueAsString", @response.body
+ end
+
+ def test_block_rescue_handler
+ get :bad_gateway
+ assert_response 502
+ end
+ def test_block_rescue_handler_as_string
+ get :bad_gateway_raise_as_string
+ assert_response 502
+ end
+
+ def test_block_rescue_handler_with_argument
+ get :resource_unavailable
+ assert_equal "RescueController::ResourceUnavailable", @response.body
+ end
+
+ def test_block_rescue_handler_with_argument_as_string
+ get :resource_unavailable_raise_as_string
+ assert_equal "RescueController::ResourceUnavailableToRescueAsString", @response.body
+ end
+
+
+ protected
+ def with_all_requests_local(local = true)
+ old_local, ActionController::Base.consider_all_requests_local =
+ ActionController::Base.consider_all_requests_local, local
+ yield
+ ensure
+ ActionController::Base.consider_all_requests_local = old_local
+ end
+
+ def with_remote_addr(addr)
+ old_remote_addr, @request.remote_addr = @request.remote_addr, addr
+ yield
+ ensure
+ @request.remote_addr = old_remote_addr
+ end
+
+ def with_rails_root(path = nil)
+ old_rails_root = RAILS_ROOT if defined?(RAILS_ROOT)
+ if path
+ silence_warnings { Object.const_set(:RAILS_ROOT, path) }
+ else
+ Object.remove_const(:RAILS_ROOT) rescue nil
+ end
+
+ yield
+
+ ensure
+ if old_rails_root
+ silence_warnings { Object.const_set(:RAILS_ROOT, old_rails_root) }
+ else
+ Object.remove_const(:RAILS_ROOT) rescue nil
+ end
+ end
+end
+
+class ExceptionInheritanceRescueController < ActionController::Base
+
+ class ParentException < StandardError
+ end
+
+ class ChildException < ParentException
+ end
+
+ class GrandchildException < ChildException
+ end
+
+ rescue_from ChildException, :with => lambda { head :ok }
+ rescue_from ParentException, :with => lambda { head :created }
+ rescue_from GrandchildException, :with => lambda { head :no_content }
+
+ def raise_parent_exception
+ raise ParentException
+ end
+
+ def raise_child_exception
+ raise ChildException
+ end
+
+ def raise_grandchild_exception
+ raise GrandchildException
+ end
+end
+
+class ExceptionInheritanceRescueTest < Test::Unit::TestCase
+
+ def setup
+ @controller = ExceptionInheritanceRescueController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_bottom_first
+ get :raise_grandchild_exception
+ assert_response :no_content
+ end
+
+ def test_inheritance_works
+ get :raise_child_exception
+ assert_response :created
+ end
+end
+
+class ControllerInheritanceRescueController < ExceptionInheritanceRescueController
+ class FirstExceptionInChildController < StandardError
+ end
+
+ class SecondExceptionInChildController < StandardError
+ end
+
+ rescue_from FirstExceptionInChildController, 'SecondExceptionInChildController', :with => lambda { head :gone }
+
+ def raise_first_exception_in_child_controller
+ raise FirstExceptionInChildController
+ end
+
+ def raise_second_exception_in_child_controller
+ raise SecondExceptionInChildController
+ end
+end
+
+class ControllerInheritanceRescueControllerTest < Test::Unit::TestCase
+
+ def setup
+ @controller = ControllerInheritanceRescueController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_first_exception_in_child_controller
+ get :raise_first_exception_in_child_controller
+ assert_response :gone
+ end
+
+ def test_second_exception_in_child_controller
+ get :raise_second_exception_in_child_controller
+ assert_response :gone
+ end
+
+ def test_exception_in_parent_controller
+ get :raise_parent_exception
+ assert_response :created
+ end
+end
+end # uses_mocha
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/resources_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/resources_test.rb
new file mode 100644
index 000000000..8772e6628
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/resources_test.rb
@@ -0,0 +1,787 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class ResourcesController < ActionController::Base
+ def index() render :nothing => true end
+ alias_method :show, :index
+ def rescue_action(e) raise e end
+end
+
+class ThreadsController < ResourcesController; end
+class MessagesController < ResourcesController; end
+class CommentsController < ResourcesController; end
+class AuthorsController < ResourcesController; end
+class LogosController < ResourcesController; end
+
+class AccountsController < ResourcesController; end
+class AdminController < ResourcesController; end
+
+module Backoffice
+ class ProductsController < ResourcesController; end
+ class TagsController < ResourcesController; end
+ class ManufacturersController < ResourcesController; end
+ class ImagesController < ResourcesController; end
+
+ module Admin
+ class ProductsController < ResourcesController; end
+ class ImagesController < ResourcesController; end
+ end
+end
+
+class ResourcesTest < Test::Unit::TestCase
+
+
+ # The assertions in these tests are incompatible with the hash method
+ # optimisation. This could indicate user level problems
+ def setup
+ ActionController::Base.optimise_named_routes = false
+ end
+
+ def tear_down
+ ActionController::Base.optimise_named_routes = true
+ end
+
+ def test_should_arrange_actions
+ resource = ActionController::Resources::Resource.new(:messages,
+ :collection => { :rss => :get, :reorder => :post, :csv => :post },
+ :member => { :rss => :get, :atom => :get, :upload => :post, :fix => :post },
+ :new => { :preview => :get, :draft => :get })
+
+ assert_resource_methods [:rss], resource, :collection, :get
+ assert_resource_methods [:csv, :reorder], resource, :collection, :post
+ assert_resource_methods [:edit, :rss, :atom], resource, :member, :get
+ assert_resource_methods [:upload, :fix], resource, :member, :post
+ assert_resource_methods [:new, :preview, :draft], resource, :new, :get
+ end
+
+ def test_should_resource_controller_name_equal_resource_name_by_default
+ resource = ActionController::Resources::Resource.new(:messages, {})
+ assert_equal 'messages', resource.controller
+ end
+
+ def test_should_resource_controller_name_equal_controller_option
+ resource = ActionController::Resources::Resource.new(:messages, :controller => 'posts')
+ assert_equal 'posts', resource.controller
+ end
+
+ def test_should_all_singleton_paths_be_the_same
+ [ :path, :nesting_path_prefix, :member_path ].each do |method|
+ resource = ActionController::Resources::SingletonResource.new(:messages, :path_prefix => 'admin')
+ assert_equal 'admin/messages', resource.send(method)
+ end
+ end
+
+ def test_default_restful_routes
+ with_restful_routing :messages do
+ assert_simply_restful_for :messages
+ end
+ end
+
+ def test_multiple_default_restful_routes
+ with_restful_routing :messages, :comments do
+ assert_simply_restful_for :messages
+ assert_simply_restful_for :comments
+ end
+ end
+
+ def test_with_custom_conditions
+ with_restful_routing :messages, :conditions => { :subdomain => 'app' } do
+ assert_equal 'app', ActionController::Routing::Routes.named_routes.routes[:messages].conditions[:subdomain]
+ end
+ end
+
+ def test_irregular_id_with_no_requirements_should_raise_error
+ expected_options = {:controller => 'messages', :action => 'show', :id => '1.1.1'}
+
+ with_restful_routing :messages do
+ assert_raises(ActionController::RoutingError) do
+ assert_recognizes(expected_options, :path => 'messages/1.1.1', :method => :get)
+ end
+ end
+ end
+
+ def test_irregular_id_with_requirements_should_pass
+ expected_options = {:controller => 'messages', :action => 'show', :id => '1.1.1'}
+
+ with_restful_routing(:messages, :requirements => {:id => /[0-9]\.[0-9]\.[0-9]/}) do
+ assert_recognizes(expected_options, :path => 'messages/1.1.1', :method => :get)
+ end
+ end
+
+ def test_with_path_prefix_requirements
+ expected_options = {:controller => 'messages', :action => 'show', :thread_id => '1.1.1', :id => '1'}
+ with_restful_routing :messages, :path_prefix => '/thread/:thread_id', :requirements => {:thread_id => /[0-9]\.[0-9]\.[0-9]/} do
+ assert_recognizes(expected_options, :path => 'thread/1.1.1/messages/1', :method => :get)
+ end
+ end
+
+ def test_with_path_prefix
+ with_restful_routing :messages, :path_prefix => '/thread/:thread_id' do
+ assert_simply_restful_for :messages, :path_prefix => 'thread/5/', :options => { :thread_id => '5' }
+ end
+ end
+
+ def test_multiple_with_path_prefix
+ with_restful_routing :messages, :comments, :path_prefix => '/thread/:thread_id' do
+ assert_simply_restful_for :messages, :path_prefix => 'thread/5/', :options => { :thread_id => '5' }
+ assert_simply_restful_for :comments, :path_prefix => 'thread/5/', :options => { :thread_id => '5' }
+ end
+ end
+
+ def test_with_name_prefix
+ with_restful_routing :messages, :name_prefix => 'post_' do
+ assert_simply_restful_for :messages, :name_prefix => 'post_'
+ end
+ end
+
+ def test_with_collection_actions
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
+
+ with_restful_routing :messages, :collection => actions do
+ assert_restful_routes_for :messages do |options|
+ actions.each do |action, method|
+ assert_recognizes(options.merge(:action => action), :path => "/messages/#{action}", :method => method)
+ end
+ end
+
+ assert_restful_named_routes_for :messages do |options|
+ actions.keys.each do |action|
+ assert_named_route "/messages/#{action}", "#{action}_messages_path", :action => action
+ end
+ end
+ end
+ end
+
+ def test_with_collection_actions_and_name_prefix
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
+
+ with_restful_routing :messages, :path_prefix => '/threads/:thread_id', :name_prefix => "thread_", :collection => actions do
+ assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ actions.each do |action, method|
+ assert_recognizes(options.merge(:action => action), :path => "/threads/1/messages/#{action}", :method => method)
+ end
+ end
+
+ assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ actions.keys.each do |action|
+ assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", :action => action
+ end
+ end
+ end
+ end
+
+ def test_with_collection_action_and_name_prefix_and_formatted
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
+
+ with_restful_routing :messages, :path_prefix => '/threads/:thread_id', :name_prefix => "thread_", :collection => actions do
+ assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ actions.each do |action, method|
+ assert_recognizes(options.merge(:action => action, :format => 'xml'), :path => "/threads/1/messages/#{action}.xml", :method => method)
+ end
+ end
+
+ assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ actions.keys.each do |action|
+ assert_named_route "/threads/1/messages/#{action}.xml", "formatted_#{action}_thread_messages_path", :action => action, :format => 'xml'
+ end
+ end
+ end
+ end
+
+ def test_with_member_action
+ [:put, :post].each do |method|
+ with_restful_routing :messages, :member => { :mark => method } do
+ mark_options = {:action => 'mark', :id => '1'}
+ mark_path = "/messages/1/mark"
+ assert_restful_routes_for :messages do |options|
+ assert_recognizes(options.merge(mark_options), :path => mark_path, :method => method)
+ end
+
+ assert_restful_named_routes_for :messages do |options|
+ assert_named_route mark_path, :mark_message_path, mark_options
+ end
+ end
+ end
+ end
+
+ def test_with_two_member_actions_with_same_method
+ [:put, :post].each do |method|
+ with_restful_routing :messages, :member => { :mark => method, :unmark => method } do
+ %w(mark unmark).each do |action|
+ action_options = {:action => action, :id => '1'}
+ action_path = "/messages/1/#{action}"
+ assert_restful_routes_for :messages do |options|
+ assert_recognizes(options.merge(action_options), :path => action_path, :method => method)
+ end
+
+ assert_restful_named_routes_for :messages do |options|
+ assert_named_route action_path, "#{action}_message_path".to_sym, action_options
+ end
+ end
+ end
+ end
+ end
+
+ def test_with_new_action
+ with_restful_routing :messages, :new => { :preview => :post } do
+ preview_options = {:action => 'preview'}
+ preview_path = "/messages/new/preview"
+ assert_restful_routes_for :messages do |options|
+ assert_recognizes(options.merge(preview_options), :path => preview_path, :method => :post)
+ end
+
+ assert_restful_named_routes_for :messages do |options|
+ assert_named_route preview_path, :preview_new_message_path, preview_options
+ end
+ end
+ end
+
+ def test_with_new_action_with_name_prefix
+ with_restful_routing :messages, :new => { :preview => :post }, :path_prefix => '/threads/:thread_id', :name_prefix => 'thread_' do
+ preview_options = {:action => 'preview', :thread_id => '1'}
+ preview_path = "/threads/1/messages/new/preview"
+ assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ assert_recognizes(options.merge(preview_options), :path => preview_path, :method => :post)
+ end
+
+ assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ assert_named_route preview_path, :preview_new_thread_message_path, preview_options
+ end
+ end
+ end
+
+ def test_with_formatted_new_action_with_name_prefix
+ with_restful_routing :messages, :new => { :preview => :post }, :path_prefix => '/threads/:thread_id', :name_prefix => 'thread_' do
+ preview_options = {:action => 'preview', :thread_id => '1', :format => 'xml'}
+ preview_path = "/threads/1/messages/new/preview.xml"
+ assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ assert_recognizes(options.merge(preview_options), :path => preview_path, :method => :post)
+ end
+
+ assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ assert_named_route preview_path, :formatted_preview_new_thread_message_path, preview_options
+ end
+ end
+ end
+
+ def test_override_new_method
+ with_restful_routing :messages do
+ assert_restful_routes_for :messages do |options|
+ assert_recognizes(options.merge(:action => "new"), :path => "/messages/new", :method => :get)
+ assert_raises(ActionController::MethodNotAllowed) do
+ ActionController::Routing::Routes.recognize_path("/messages/new", :method => :post)
+ end
+ end
+ end
+
+ with_restful_routing :messages, :new => { :new => :any } do
+ assert_restful_routes_for :messages do |options|
+ assert_recognizes(options.merge(:action => "new"), :path => "/messages/new", :method => :post)
+ assert_recognizes(options.merge(:action => "new"), :path => "/messages/new", :method => :get)
+ end
+ end
+ end
+
+ def test_nested_restful_routes
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :threads do |map|
+ map.resources :messages do |map|
+ map.resources :comments
+ end
+ end
+ end
+
+ assert_simply_restful_for :threads
+ assert_simply_restful_for :messages,
+ :name_prefix => 'thread_',
+ :path_prefix => 'threads/1/',
+ :options => { :thread_id => '1' }
+ assert_simply_restful_for :comments,
+ :name_prefix => 'thread_message_',
+ :path_prefix => 'threads/1/messages/2/',
+ :options => { :thread_id => '1', :message_id => '2' }
+ end
+ end
+
+ def test_nested_restful_routes_with_overwritten_defaults
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :threads do |map|
+ map.resources :messages, :name_prefix => nil do |map|
+ map.resources :comments, :name_prefix => nil
+ end
+ end
+ end
+
+ assert_simply_restful_for :threads
+ assert_simply_restful_for :messages,
+ :path_prefix => 'threads/1/',
+ :options => { :thread_id => '1' }
+ assert_simply_restful_for :comments,
+ :path_prefix => 'threads/1/messages/2/',
+ :options => { :thread_id => '1', :message_id => '2' }
+ end
+ end
+
+ def test_restful_routes_dont_generate_duplicates
+ with_restful_routing :messages do
+ routes = ActionController::Routing::Routes.routes
+ routes.each do |route|
+ routes.each do |r|
+ next if route === r # skip the comparison instance
+ assert distinct_routes?(route, r), "Duplicate Route: #{route}"
+ end
+ end
+ end
+ end
+
+ def test_should_create_singleton_resource_routes
+ with_singleton_resources :account do
+ assert_singleton_restful_for :account
+ end
+ end
+
+ def test_should_create_multiple_singleton_resource_routes
+ with_singleton_resources :account, :logo do
+ assert_singleton_restful_for :account
+ assert_singleton_restful_for :logo
+ end
+ end
+
+ def test_should_create_nested_singleton_resource_routes
+ with_routing do |set|
+ set.draw do |map|
+ map.resource :admin, :controller => 'admin' do |admin|
+ admin.resource :account
+ end
+ end
+
+ assert_singleton_restful_for :admin, :controller => 'admin'
+ assert_singleton_restful_for :account, :name_prefix => "admin_", :path_prefix => 'admin/'
+ end
+ end
+
+ def test_resource_has_many_should_become_nested_resources
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :messages, :has_many => [ :comments, :authors ]
+ end
+
+ assert_simply_restful_for :messages
+ assert_simply_restful_for :comments, :name_prefix => "message_", :path_prefix => 'messages/1/', :options => { :message_id => '1' }
+ assert_simply_restful_for :authors, :name_prefix => "message_", :path_prefix => 'messages/1/', :options => { :message_id => '1' }
+ end
+ end
+
+ def test_resource_has_one_should_become_nested_resources
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :messages, :has_one => :logo
+ end
+
+ assert_simply_restful_for :messages
+ assert_singleton_restful_for :logo, :name_prefix => 'message_', :path_prefix => 'messages/1/', :options => { :message_id => '1' }
+ end
+ end
+
+ def test_singleton_resource_with_member_action
+ [:put, :post].each do |method|
+ with_singleton_resources :account, :member => { :reset => method } do
+ reset_options = {:action => 'reset'}
+ reset_path = "/account/reset"
+ assert_singleton_routes_for :account do |options|
+ assert_recognizes(options.merge(reset_options), :path => reset_path, :method => method)
+ end
+
+ assert_singleton_named_routes_for :account do |options|
+ assert_named_route reset_path, :reset_account_path, reset_options
+ end
+ end
+ end
+ end
+
+ def test_singleton_resource_with_two_member_actions_with_same_method
+ [:put, :post].each do |method|
+ with_singleton_resources :account, :member => { :reset => method, :disable => method } do
+ %w(reset disable).each do |action|
+ action_options = {:action => action}
+ action_path = "/account/#{action}"
+ assert_singleton_routes_for :account do |options|
+ assert_recognizes(options.merge(action_options), :path => action_path, :method => method)
+ end
+
+ assert_singleton_named_routes_for :account do |options|
+ assert_named_route action_path, "#{action}_account_path".to_sym, action_options
+ end
+ end
+ end
+ end
+ end
+
+ def test_should_nest_resources_in_singleton_resource
+ with_routing do |set|
+ set.draw do |map|
+ map.resource :account do |account|
+ account.resources :messages
+ end
+ end
+
+ assert_singleton_restful_for :account
+ assert_simply_restful_for :messages, :name_prefix => "account_", :path_prefix => 'account/'
+ end
+ end
+
+ def test_should_nest_resources_in_singleton_resource_with_path_prefix
+ with_routing do |set|
+ set.draw do |map|
+ map.resource(:account, :path_prefix => ':site_id') do |account|
+ account.resources :messages
+ end
+ end
+
+ assert_singleton_restful_for :account, :path_prefix => '7/', :options => { :site_id => '7' }
+ assert_simply_restful_for :messages, :name_prefix => "account_", :path_prefix => '7/account/', :options => { :site_id => '7' }
+ end
+ end
+
+ def test_should_nest_singleton_resource_in_resources
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :threads do |thread|
+ thread.resource :admin, :controller => 'admin'
+ end
+ end
+
+ assert_simply_restful_for :threads
+ assert_singleton_restful_for :admin, :controller => 'admin', :name_prefix => 'thread_', :path_prefix => 'threads/5/', :options => { :thread_id => '5' }
+ end
+ end
+
+ def test_should_not_allow_delete_or_put_on_collection_path
+ controller_name = :messages
+ with_restful_routing controller_name do
+ options = { :controller => controller_name.to_s }
+ collection_path = "/#{controller_name}"
+
+ assert_raises(ActionController::MethodNotAllowed) do
+ assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put)
+ end
+
+ assert_raises(ActionController::MethodNotAllowed) do
+ assert_recognizes(options.merge(:action => 'destroy'), :path => collection_path, :method => :delete)
+ end
+ end
+ end
+
+ def test_resource_action_separator
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :messages, :collection => {:search => :get}, :new => {:preview => :any}, :name_prefix => 'thread_', :path_prefix => '/threads/:thread_id'
+ map.resource :account, :member => {:login => :get}, :new => {:preview => :any}, :name_prefix => 'admin_', :path_prefix => '/admin'
+ end
+
+ action_separator = ActionController::Base.resource_action_separator
+
+ assert_simply_restful_for :messages, :name_prefix => 'thread_', :path_prefix => 'threads/1/', :options => { :thread_id => '1' }
+ assert_named_route "/threads/1/messages#{action_separator}search", "search_thread_messages_path", {}
+ assert_named_route "/threads/1/messages/new", "new_thread_message_path", {}
+ assert_named_route "/threads/1/messages/new#{action_separator}preview", "preview_new_thread_message_path", {}
+ assert_singleton_restful_for :account, :name_prefix => 'admin_', :path_prefix => 'admin/'
+ assert_named_route "/admin/account#{action_separator}login", "login_admin_account_path", {}
+ assert_named_route "/admin/account/new", "new_admin_account_path", {}
+ assert_named_route "/admin/account/new#{action_separator}preview", "preview_new_admin_account_path", {}
+ end
+ end
+
+ def test_new_style_named_routes_for_resource
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :messages, :collection => {:search => :get}, :new => {:preview => :any}, :name_prefix => 'thread_', :path_prefix => '/threads/:thread_id'
+ end
+ assert_simply_restful_for :messages, :name_prefix => 'thread_', :path_prefix => 'threads/1/', :options => { :thread_id => '1' }
+ assert_named_route "/threads/1/messages/search", "search_thread_messages_path", {}
+ assert_named_route "/threads/1/messages/new", "new_thread_message_path", {}
+ assert_named_route "/threads/1/messages/new/preview", "preview_new_thread_message_path", {}
+ end
+ end
+
+ def test_new_style_named_routes_for_singleton_resource
+ with_routing do |set|
+ set.draw do |map|
+ map.resource :account, :member => {:login => :get}, :new => {:preview => :any}, :name_prefix => 'admin_', :path_prefix => '/admin'
+ end
+ assert_singleton_restful_for :account, :name_prefix => 'admin_', :path_prefix => 'admin/'
+ assert_named_route "/admin/account/login", "login_admin_account_path", {}
+ assert_named_route "/admin/account/new", "new_admin_account_path", {}
+ assert_named_route "/admin/account/new/preview", "preview_new_admin_account_path", {}
+ end
+ end
+
+ def test_resources_in_namespace
+ with_routing do |set|
+ set.draw do |map|
+ map.namespace :backoffice do |backoffice|
+ backoffice.resources :products
+ end
+ end
+
+ assert_simply_restful_for :products, :controller => "backoffice/products", :name_prefix => 'backoffice_', :path_prefix => 'backoffice/'
+ end
+ end
+
+ def test_resource_has_many_in_namespace
+ with_routing do |set|
+ set.draw do |map|
+ map.namespace :backoffice do |backoffice|
+ backoffice.resources :products, :has_many => :tags
+ end
+ end
+
+ assert_simply_restful_for :products, :controller => "backoffice/products", :name_prefix => 'backoffice_', :path_prefix => 'backoffice/'
+ assert_simply_restful_for :tags, :controller => "backoffice/tags", :name_prefix => "backoffice_product_", :path_prefix => 'backoffice/products/1/', :options => { :product_id => '1' }
+ end
+ end
+
+ def test_resource_has_one_in_namespace
+ with_routing do |set|
+ set.draw do |map|
+ map.namespace :backoffice do |backoffice|
+ backoffice.resources :products, :has_one => :manufacturer
+ end
+ end
+
+ assert_simply_restful_for :products, :controller => "backoffice/products", :name_prefix => 'backoffice_', :path_prefix => 'backoffice/'
+ assert_singleton_restful_for :manufacturer, :controller => "backoffice/manufacturers", :name_prefix => 'backoffice_product_', :path_prefix => 'backoffice/products/1/', :options => { :product_id => '1' }
+ end
+ end
+
+ def test_resources_in_nested_namespace
+ with_routing do |set|
+ set.draw do |map|
+ map.namespace :backoffice do |backoffice|
+ backoffice.namespace :admin do |admin|
+ admin.resources :products
+ end
+ end
+ end
+
+ assert_simply_restful_for :products, :controller => "backoffice/admin/products", :name_prefix => 'backoffice_admin_', :path_prefix => 'backoffice/admin/'
+ end
+ end
+
+ def test_resources_using_namespace
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :products, :namespace => "backoffice/"
+ end
+
+ assert_simply_restful_for :products, :controller => "backoffice/products"
+ end
+ end
+
+ def test_nested_resources_using_namespace
+ with_routing do |set|
+ set.draw do |map|
+ map.namespace :backoffice do |backoffice|
+ backoffice.resources :products do |products|
+ products.resources :images
+ end
+ end
+ end
+
+ assert_simply_restful_for :images, :controller => "backoffice/images", :name_prefix => 'backoffice_product_', :path_prefix => 'backoffice/products/1/', :options => {:product_id => '1'}
+ end
+ end
+
+ def test_nested_resources_in_nested_namespace
+ with_routing do |set|
+ set.draw do |map|
+ map.namespace :backoffice do |backoffice|
+ backoffice.namespace :admin do |admin|
+ admin.resources :products do |products|
+ products.resources :images
+ end
+ end
+ end
+ end
+
+ assert_simply_restful_for :images, :controller => "backoffice/admin/images", :name_prefix => 'backoffice_admin_product_', :path_prefix => 'backoffice/admin/products/1/', :options => {:product_id => '1'}
+ end
+ end
+
+ protected
+ def with_restful_routing(*args)
+ with_routing do |set|
+ set.draw { |map| map.resources(*args) }
+ yield
+ end
+ end
+
+ def with_singleton_resources(*args)
+ with_routing do |set|
+ set.draw { |map| map.resource(*args) }
+ yield
+ end
+ end
+
+ # runs assert_restful_routes_for and assert_restful_named_routes for on the controller_name and options, without passing a block.
+ def assert_simply_restful_for(controller_name, options = {})
+ assert_restful_routes_for controller_name, options
+ assert_restful_named_routes_for controller_name, nil, options
+ end
+
+ def assert_singleton_restful_for(singleton_name, options = {})
+ assert_singleton_routes_for singleton_name, options
+ assert_singleton_named_routes_for singleton_name, options
+ end
+
+ def assert_restful_routes_for(controller_name, options = {})
+ options[:options] ||= {}
+ options[:options][:controller] = options[:controller] || controller_name.to_s
+
+ collection_path = "/#{options[:path_prefix]}#{controller_name}"
+ member_path = "#{collection_path}/1"
+ new_path = "#{collection_path}/new"
+ edit_member_path = "#{member_path}/edit"
+ formatted_edit_member_path = "#{member_path}/edit.xml"
+
+ with_options(options[:options]) do |controller|
+ controller.assert_routing collection_path, :action => 'index'
+ controller.assert_routing new_path, :action => 'new'
+ controller.assert_routing member_path, :action => 'show', :id => '1'
+ controller.assert_routing edit_member_path, :action => 'edit', :id => '1'
+ controller.assert_routing "#{collection_path}.xml", :action => 'index', :format => 'xml'
+ controller.assert_routing "#{new_path}.xml", :action => 'new', :format => 'xml'
+ controller.assert_routing "#{member_path}.xml", :action => 'show', :id => '1', :format => 'xml'
+ controller.assert_routing formatted_edit_member_path, :action => 'edit', :id => '1', :format => 'xml'
+ end
+
+ assert_recognizes(options[:options].merge(:action => 'index'), :path => collection_path, :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'new'), :path => new_path, :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'create'), :path => collection_path, :method => :post)
+ assert_recognizes(options[:options].merge(:action => 'show', :id => '1'), :path => member_path, :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'edit', :id => '1'), :path => edit_member_path, :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'update', :id => '1'), :path => member_path, :method => :put)
+ assert_recognizes(options[:options].merge(:action => 'destroy', :id => '1'), :path => member_path, :method => :delete)
+
+ assert_recognizes(options[:options].merge(:action => 'index', :format => 'xml'), :path => "#{collection_path}.xml", :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{collection_path}.xml", :method => :post)
+ assert_recognizes(options[:options].merge(:action => 'show', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'edit', :id => '1', :format => 'xml'), :path => formatted_edit_member_path, :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'update', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :put)
+ assert_recognizes(options[:options].merge(:action => 'destroy', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :delete)
+
+ yield options[:options] if block_given?
+ end
+
+ # test named routes like foo_path and foos_path map to the correct options.
+ def assert_restful_named_routes_for(controller_name, singular_name = nil, options = {})
+ if singular_name.is_a?(Hash)
+ options = singular_name
+ singular_name = nil
+ end
+ singular_name ||= controller_name.to_s.singularize
+
+ options[:options] ||= {}
+ options[:options][:controller] = options[:controller] || controller_name.to_s
+
+ @controller = "#{options[:options][:controller].camelize}Controller".constantize.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ get :index, options[:options]
+ options[:options].delete :action
+
+ full_prefix = "/#{options[:path_prefix]}#{controller_name}"
+ name_prefix = options[:name_prefix]
+
+ assert_named_route "#{full_prefix}", "#{name_prefix}#{controller_name}_path", options[:options]
+ assert_named_route "#{full_prefix}.xml", "formatted_#{name_prefix}#{controller_name}_path", options[:options].merge( :format => 'xml')
+ assert_named_route "#{full_prefix}/1", "#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1')
+ assert_named_route "#{full_prefix}/1.xml", "formatted_#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1', :format => 'xml')
+
+ assert_named_route "#{full_prefix}/new", "new_#{name_prefix}#{singular_name}_path", options[:options]
+ assert_named_route "#{full_prefix}/new.xml", "formatted_new_#{name_prefix}#{singular_name}_path", options[:options].merge( :format => 'xml')
+ assert_named_route "#{full_prefix}/1/edit", "edit_#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1')
+ assert_named_route "#{full_prefix}/1/edit.xml", "formatted_edit_#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1', :format => 'xml')
+
+ yield options[:options] if block_given?
+ end
+
+ def assert_singleton_routes_for(singleton_name, options = {})
+ options[:options] ||= {}
+ options[:options][:controller] = options[:controller] || singleton_name.to_s.pluralize
+
+ full_path = "/#{options[:path_prefix]}#{singleton_name}"
+ new_path = "#{full_path}/new"
+ edit_path = "#{full_path}/edit"
+ formatted_edit_path = "#{full_path}/edit.xml"
+
+ with_options options[:options] do |controller|
+ controller.assert_routing full_path, :action => 'show'
+ controller.assert_routing new_path, :action => 'new'
+ controller.assert_routing edit_path, :action => 'edit'
+ controller.assert_routing "#{full_path}.xml", :action => 'show', :format => 'xml'
+ controller.assert_routing "#{new_path}.xml", :action => 'new', :format => 'xml'
+ controller.assert_routing formatted_edit_path, :action => 'edit', :format => 'xml'
+ end
+
+ assert_recognizes(options[:options].merge(:action => 'show'), :path => full_path, :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'new'), :path => new_path, :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'edit'), :path => edit_path, :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'create'), :path => full_path, :method => :post)
+ assert_recognizes(options[:options].merge(:action => 'update'), :path => full_path, :method => :put)
+ assert_recognizes(options[:options].merge(:action => 'destroy'), :path => full_path, :method => :delete)
+
+ assert_recognizes(options[:options].merge(:action => 'show', :format => 'xml'), :path => "#{full_path}.xml", :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'edit', :format => 'xml'), :path => formatted_edit_path, :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{full_path}.xml", :method => :post)
+ assert_recognizes(options[:options].merge(:action => 'update', :format => 'xml'), :path => "#{full_path}.xml", :method => :put)
+ assert_recognizes(options[:options].merge(:action => 'destroy', :format => 'xml'), :path => "#{full_path}.xml", :method => :delete)
+
+ yield options[:options] if block_given?
+ end
+
+ def assert_singleton_named_routes_for(singleton_name, options = {})
+ (options[:options] ||= {})[:controller] ||= singleton_name.to_s.pluralize
+ @controller = "#{options[:options][:controller].camelize}Controller".constantize.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ get :show, options[:options]
+ options[:options].delete :action
+
+ full_path = "/#{options[:path_prefix]}#{singleton_name}"
+ name_prefix = options[:name_prefix]
+
+ assert_named_route "#{full_path}", "#{name_prefix}#{singleton_name}_path", options[:options]
+ assert_named_route "#{full_path}.xml", "formatted_#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml')
+
+ assert_named_route "#{full_path}/new", "new_#{name_prefix}#{singleton_name}_path", options[:options]
+ assert_named_route "#{full_path}/new.xml", "formatted_new_#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml')
+ assert_named_route "#{full_path}/edit", "edit_#{name_prefix}#{singleton_name}_path", options[:options]
+ assert_named_route "#{full_path}/edit.xml", "formatted_edit_#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml')
+ end
+
+ def assert_named_route(expected, route, options)
+ actual = @controller.send(route, options) rescue $!.class.name
+ assert_equal expected, actual, "Error on route: #{route}(#{options.inspect})"
+ end
+
+ def assert_resource_methods(expected, resource, action_method, method)
+ assert_equal expected.length, resource.send("#{action_method}_methods")[method].size, "#{resource.send("#{action_method}_methods")[method].inspect}"
+ expected.each do |action|
+ assert resource.send("#{action_method}_methods")[method].include?(action),
+ "#{method} not in #{action_method} methods: #{resource.send("#{action_method}_methods")[method].inspect}"
+ end
+ end
+
+ def distinct_routes? (r1, r2)
+ if r1.conditions == r2.conditions and r1.requirements == r2.requirements then
+ if r1.segments.collect(&:to_s) == r2.segments.collect(&:to_s) then
+ return false
+ end
+ end
+ true
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/routing_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/routing_test.rb
new file mode 100644
index 000000000..281ef13b3
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/routing_test.rb
@@ -0,0 +1,2203 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+require "#{File.dirname(__FILE__)}/fake_controllers"
+require 'action_controller/routing'
+
+class MilestonesController < ActionController::Base
+ def index() head :ok end
+ alias_method :show, :index
+ def rescue_action(e) raise e end
+end
+
+RunTimeTests = ARGV.include? 'time'
+ROUTING = ActionController::Routing
+
+class ROUTING::RouteBuilder
+ attr_reader :warn_output
+
+ def warn(msg)
+ (@warn_output ||= []) << msg
+ end
+end
+
+# See RFC 3986, section 3.3 for allowed path characters.
+class UriReservedCharactersRoutingTest < Test::Unit::TestCase
+ def setup
+ ActionController::Routing.use_controllers! ['controller']
+ @set = ActionController::Routing::RouteSet.new
+ @set.draw do |map|
+ map.connect ':controller/:action/:variable'
+ end
+
+ safe, unsafe = %w(: @ & = + $ , ;), %w(^ / ? # [ ])
+ hex = unsafe.map { |char| '%' + char.unpack('H2').first.upcase }
+
+ @segment = "#{safe}#{unsafe}".freeze
+ @escaped = "#{safe}#{hex}".freeze
+ end
+
+ def test_route_generation_escapes_unsafe_path_characters
+ assert_equal "/contr#{@segment}oller/act#{@escaped}ion/var#{@escaped}iable",
+ @set.generate(:controller => "contr#{@segment}oller",
+ :action => "act#{@segment}ion",
+ :variable => "var#{@segment}iable")
+ end
+
+ def test_route_recognition_unescapes_path_components
+ options = { :controller => "controller",
+ :action => "act#{@segment}ion",
+ :variable => "var#{@segment}iable" }
+ assert_equal options, @set.recognize_path("/controller/act#{@escaped}ion/var#{@escaped}iable")
+ end
+end
+
+class LegacyRouteSetTests < Test::Unit::TestCase
+ attr_reader :rs
+ def setup
+ # These tests assume optimisation is on, so re-enable it.
+ ActionController::Base.optimise_named_routes = true
+
+ @rs = ::ActionController::Routing::RouteSet.new
+ @rs.draw {|m| m.connect ':controller/:action/:id' }
+
+ ActionController::Routing.use_controllers! %w(content admin/user admin/news_feed)
+ end
+
+ def test_default_setup
+ assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content"))
+ assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/content/list"))
+ assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/content/show/10"))
+
+ assert_equal({:controller => "admin/user", :action => 'show', :id => '10'}, rs.recognize_path("/admin/user/show/10"))
+
+ assert_equal '/admin/user/show/10', rs.generate(:controller => 'admin/user', :action => 'show', :id => 10)
+
+ assert_equal '/admin/user/show', rs.generate({:action => 'show'}, {:controller => 'admin/user', :action => 'list', :id => '10'})
+ assert_equal '/admin/user/list/10', rs.generate({}, {:controller => 'admin/user', :action => 'list', :id => '10'})
+
+ assert_equal '/admin/stuff', rs.generate({:controller => 'stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'})
+ assert_equal '/stuff', rs.generate({:controller => '/stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'})
+ end
+
+ def test_ignores_leading_slash
+ @rs.draw {|m| m.connect '/:controller/:action/:id'}
+ test_default_setup
+ end
+
+ def test_time_recognition
+ n = 10000
+ if RunTimeTests
+ GC.start
+ rectime = Benchmark.realtime do
+ n.times do
+ rs.recognize_path("content")
+ rs.recognize_path("content/list")
+ rs.recognize_path("content/show/10")
+ rs.recognize_path("admin/user")
+ rs.recognize_path("admin/user/list")
+ rs.recognize_path("admin/user/show/10")
+ end
+ end
+ puts "\n\nRecognition (RouteSet):"
+ per_url = rectime / (n * 6)
+ puts "#{per_url * 1000} ms/url"
+ puts "#{1 / per_url} url/s\n\n"
+ end
+ end
+ def test_time_generation
+ n = 5000
+ if RunTimeTests
+ GC.start
+ pairs = [
+ [{:controller => 'content', :action => 'index'}, {:controller => 'content', :action => 'show'}],
+ [{:controller => 'content'}, {:controller => 'content', :action => 'index'}],
+ [{:controller => 'content', :action => 'list'}, {:controller => 'content', :action => 'index'}],
+ [{:controller => 'content', :action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'}],
+ [{:controller => 'admin/user', :action => 'index'}, {:controller => 'admin/user', :action => 'show'}],
+ [{:controller => 'admin/user'}, {:controller => 'admin/user', :action => 'index'}],
+ [{:controller => 'admin/user', :action => 'list'}, {:controller => 'admin/user', :action => 'index'}],
+ [{:controller => 'admin/user', :action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'}],
+ ]
+ p = nil
+ gentime = Benchmark.realtime do
+ n.times do
+ pairs.each {|(a, b)| rs.generate(a, b)}
+ end
+ end
+
+ puts "\n\nGeneration (RouteSet): (#{(n * 8)} urls)"
+ per_url = gentime / (n * 8)
+ puts "#{per_url * 1000} ms/url"
+ puts "#{1 / per_url} url/s\n\n"
+ end
+ end
+
+ def test_route_with_colon_first
+ rs.draw do |map|
+ map.connect '/:controller/:action/:id', :action => 'index', :id => nil
+ map.connect ':url', :controller => 'tiny_url', :action => 'translate'
+ end
+ end
+
+ def test_route_with_regexp_for_controller
+ rs.draw do |map|
+ map.connect ':controller/:admintoken/:action/:id', :controller => /admin\/.+/
+ map.connect ':controller/:action/:id'
+ end
+ assert_equal({:controller => "admin/user", :admintoken => "foo", :action => "index"},
+ rs.recognize_path("/admin/user/foo"))
+ assert_equal({:controller => "content", :action => "foo"}, rs.recognize_path("/content/foo"))
+ assert_equal '/admin/user/foo', rs.generate(:controller => "admin/user", :admintoken => "foo", :action => "index")
+ assert_equal '/content/foo', rs.generate(:controller => "content", :action => "foo")
+ end
+
+ def test_route_with_regexp_and_dot
+ rs.draw do |map|
+ map.connect ':controller/:action/:file',
+ :controller => /admin|user/,
+ :action => /upload|download/,
+ :defaults => {:file => nil},
+ :requirements => {:file => %r{[^/]+(\.[^/]+)?}}
+ end
+ # Without a file extension
+ assert_equal '/user/download/file',
+ rs.generate(:controller => "user", :action => "download", :file => "file")
+ assert_equal(
+ {:controller => "user", :action => "download", :file => "file"},
+ rs.recognize_path("/user/download/file"))
+
+ # Now, let's try a file with an extension, really a dot (.)
+ assert_equal '/user/download/file.jpg',
+ rs.generate(
+ :controller => "user", :action => "download", :file => "file.jpg")
+ assert_equal(
+ {:controller => "user", :action => "download", :file => "file.jpg"},
+ rs.recognize_path("/user/download/file.jpg"))
+ end
+
+ def test_basic_named_route
+ rs.add_named_route :home, '', :controller => 'content', :action => 'list'
+ x = setup_for_named_route
+ assert_equal("http://named.route.test/",
+ x.send(:home_url))
+ end
+
+ def test_basic_named_route_with_relative_url_root
+ rs.add_named_route :home, '', :controller => 'content', :action => 'list'
+ x = setup_for_named_route
+ x.relative_url_root="/foo"
+ assert_equal("http://named.route.test/foo/",
+ x.send(:home_url))
+ assert_equal "/foo/", x.send(:home_path)
+ end
+
+ def test_named_route_with_option
+ rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page'
+ x = setup_for_named_route
+ assert_equal("http://named.route.test/page/new%20stuff",
+ x.send(:page_url, :title => 'new stuff'))
+ end
+
+ def test_named_route_with_default
+ rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page', :title => 'AboutPage'
+ x = setup_for_named_route
+ assert_equal("http://named.route.test/page/AboutRails",
+ x.send(:page_url, :title => "AboutRails"))
+
+ end
+
+ def test_named_route_with_nested_controller
+ rs.add_named_route :users, 'admin/user', :controller => 'admin/user', :action => 'index'
+ x = setup_for_named_route
+ assert_equal("http://named.route.test/admin/user",
+ x.send(:users_url))
+ end
+
+ uses_mocha "named route optimisation" do
+ def test_optimised_named_route_call_never_uses_url_for
+ rs.add_named_route :users, 'admin/user', :controller => '/admin/user', :action => 'index'
+ rs.add_named_route :user, 'admin/user/:id', :controller=>'/admin/user', :action=>'show'
+ x = setup_for_named_route
+ x.expects(:url_for).never
+ x.send(:users_url)
+ x.send(:users_path)
+ x.send(:user_url, 2, :foo=>"bar")
+ x.send(:user_path, 3, :bar=>"foo")
+ end
+
+ def test_optimised_named_route_with_host
+ rs.add_named_route :pages, 'pages', :controller => 'content', :action => 'show_page', :host => 'foo.com'
+ x = setup_for_named_route
+ x.expects(:url_for).with(:host => 'foo.com', :only_path => false, :controller => 'content', :action => 'show_page', :use_route => :pages).once
+ x.send(:pages_url)
+ end
+ end
+
+ def setup_for_named_route
+ klass = Class.new(MockController)
+ rs.install_helpers(klass)
+ klass.new(rs)
+ end
+
+ def test_named_route_without_hash
+ rs.draw do |map|
+ map.normal ':controller/:action/:id'
+ end
+ end
+
+ def test_named_route_root
+ rs.draw do |map|
+ map.root :controller => "hello"
+ end
+ x = setup_for_named_route
+ assert_equal("http://named.route.test/", x.send(:root_url))
+ assert_equal("/", x.send(:root_path))
+ end
+
+ def test_named_route_with_regexps
+ rs.draw do |map|
+ map.article 'page/:year/:month/:day/:title', :controller => 'page', :action => 'show',
+ :year => /\d+/, :month => /\d+/, :day => /\d+/
+ map.connect ':controller/:action/:id'
+ end
+ x = setup_for_named_route
+ # assert_equal(
+ # {:controller => 'page', :action => 'show', :title => 'hi', :use_route => :article, :only_path => false},
+ # x.send(:article_url, :title => 'hi')
+ # )
+ assert_equal(
+ "http://named.route.test/page/2005/6/10/hi",
+ x.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6)
+ )
+ end
+
+ def test_changing_controller
+ assert_equal '/admin/stuff/show/10', rs.generate(
+ {:controller => 'stuff', :action => 'show', :id => 10},
+ {:controller => 'admin/user', :action => 'index'}
+ )
+ end
+
+ def test_paths_escaped
+ rs.draw do |map|
+ map.path 'file/*path', :controller => 'content', :action => 'show_file'
+ map.connect ':controller/:action/:id'
+ end
+
+ # No + to space in URI escaping, only for query params.
+ results = rs.recognize_path "/file/hello+world/how+are+you%3F"
+ assert results, "Recognition should have succeeded"
+ assert_equal ['hello+world', 'how+are+you?'], results[:path]
+
+ # Use %20 for space instead.
+ results = rs.recognize_path "/file/hello%20world/how%20are%20you%3F"
+ assert results, "Recognition should have succeeded"
+ assert_equal ['hello world', 'how are you?'], results[:path]
+
+ results = rs.recognize_path "/file"
+ assert results, "Recognition should have succeeded"
+ assert_equal [], results[:path]
+ end
+
+ def test_paths_slashes_unescaped_with_ordered_parameters
+ rs.add_named_route :path, '/file/*path', :controller => 'content'
+
+ # No / to %2F in URI, only for query params.
+ x = setup_for_named_route
+ assert_equal("/file/hello/world", x.send(:path_path, 'hello/world'))
+ end
+
+ def test_non_controllers_cannot_be_matched
+ rs.draw do |map|
+ map.connect ':controller/:action/:id'
+ end
+ assert_raises(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") }
+ end
+
+ def test_paths_do_not_accept_defaults
+ assert_raises(ActionController::RoutingError) do
+ rs.draw do |map|
+ map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default)
+ map.connect ':controller/:action/:id'
+ end
+ end
+
+ rs.draw do |map|
+ map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => []
+ map.connect ':controller/:action/:id'
+ end
+ end
+
+ def test_should_list_options_diff_when_routing_requirements_dont_match
+ rs.draw do |map|
+ map.post 'post/:id', :controller=> 'post', :action=> 'show', :requirements => {:id => /\d+/}
+ end
+ exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post") }
+ assert_match /^post_url failed to generate/, exception.message
+ from_match = exception.message.match(/from \{[^\}]+\}/).to_s
+ assert_match /:bad_param=>"foo"/, from_match
+ assert_match /:action=>"show"/, from_match
+ assert_match /:controller=>"post"/, from_match
+
+ expected_match = exception.message.match(/expected: \{[^\}]+\}/).to_s
+ assert_no_match /:bad_param=>"foo"/, expected_match
+ assert_match /:action=>"show"/, expected_match
+ assert_match /:controller=>"post"/, expected_match
+
+ diff_match = exception.message.match(/diff: \{[^\}]+\}/).to_s
+ assert_match /:bad_param=>"foo"/, diff_match
+ assert_no_match /:action=>"show"/, diff_match
+ assert_no_match /:controller=>"post"/, diff_match
+ end
+
+ # this specifies the case where your formerly would get a very confusing error message with an empty diff
+ def test_should_have_better_error_message_when_options_diff_is_empty
+ rs.draw do |map|
+ map.content '/content/:query', :controller => 'content', :action => 'show'
+ end
+
+ exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'content', :action => 'show', :use_route => "content") }
+ assert_match %r[:action=>"show"], exception.message
+ assert_match %r[:controller=>"content"], exception.message
+ assert_match %r[you may have ambiguous routes, or you may need to supply additional parameters for this route], exception.message
+ assert_match %r[content_url has the following required parameters: \["content", :query\] - are they all satisfied?], exception.message
+ end
+
+ def test_dynamic_path_allowed
+ rs.draw do |map|
+ map.connect '*path', :controller => 'content', :action => 'show_file'
+ end
+
+ assert_equal '/pages/boo', rs.generate(:controller => 'content', :action => 'show_file', :path => %w(pages boo))
+ end
+
+ def test_dynamic_recall_paths_allowed
+ rs.draw do |map|
+ map.connect '*path', :controller => 'content', :action => 'show_file'
+ end
+
+ recall_path = ActionController::Routing::PathSegment::Result.new(%w(pages boo))
+ assert_equal '/pages/boo', rs.generate({}, :controller => 'content', :action => 'show_file', :path => recall_path)
+ end
+
+ def test_backwards
+ rs.draw do |map|
+ map.connect 'page/:id/:action', :controller => 'pages', :action => 'show'
+ map.connect ':controller/:action/:id'
+ end
+
+ assert_equal '/page/20', rs.generate({:id => 20}, {:controller => 'pages', :action => 'show'})
+ assert_equal '/page/20', rs.generate(:controller => 'pages', :id => 20, :action => 'show')
+ assert_equal '/pages/boo', rs.generate(:controller => 'pages', :action => 'boo')
+ end
+
+ def test_route_with_fixnum_default
+ rs.draw do |map|
+ map.connect 'page/:id', :controller => 'content', :action => 'show_page', :id => 1
+ map.connect ':controller/:action/:id'
+ end
+
+ assert_equal '/page', rs.generate(:controller => 'content', :action => 'show_page')
+ assert_equal '/page', rs.generate(:controller => 'content', :action => 'show_page', :id => 1)
+ assert_equal '/page', rs.generate(:controller => 'content', :action => 'show_page', :id => '1')
+ assert_equal '/page/10', rs.generate(:controller => 'content', :action => 'show_page', :id => 10)
+
+ assert_equal({:controller => "content", :action => 'show_page', :id => '1'}, rs.recognize_path("/page"))
+ assert_equal({:controller => "content", :action => 'show_page', :id => '1'}, rs.recognize_path("/page/1"))
+ assert_equal({:controller => "content", :action => 'show_page', :id => '10'}, rs.recognize_path("/page/10"))
+ end
+
+ # For newer revision
+ def test_route_with_text_default
+ rs.draw do |map|
+ map.connect 'page/:id', :controller => 'content', :action => 'show_page', :id => 1
+ map.connect ':controller/:action/:id'
+ end
+
+ assert_equal '/page/foo', rs.generate(:controller => 'content', :action => 'show_page', :id => 'foo')
+ assert_equal({:controller => "content", :action => 'show_page', :id => 'foo'}, rs.recognize_path("/page/foo"))
+
+ token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in russian
+ escaped_token = CGI::escape(token)
+
+ assert_equal '/page/' + escaped_token, rs.generate(:controller => 'content', :action => 'show_page', :id => token)
+ assert_equal({:controller => "content", :action => 'show_page', :id => token}, rs.recognize_path("/page/#{escaped_token}"))
+ end
+
+ def test_action_expiry
+ assert_equal '/content', rs.generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
+ end
+
+ def test_recognition_with_uppercase_controller_name
+ assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/Content"))
+ assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/ConTent/list"))
+ assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/CONTENT/show/10"))
+
+ # these used to work, before the routes rewrite, but support for this was pulled in the new version...
+ #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/NewsFeed"))
+ #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/News_Feed"))
+ end
+
+ def test_requirement_should_prevent_optional_id
+ rs.draw do |map|
+ map.post 'post/:id', :controller=> 'post', :action=> 'show', :requirements => {:id => /\d+/}
+ end
+
+ assert_equal '/post/10', rs.generate(:controller => 'post', :action => 'show', :id => 10)
+
+ assert_raises ActionController::RoutingError do
+ rs.generate(:controller => 'post', :action => 'show')
+ end
+ end
+
+ def test_both_requirement_and_optional
+ rs.draw do |map|
+ map.blog('test/:year', :controller => 'post', :action => 'show',
+ :defaults => { :year => nil },
+ :requirements => { :year => /\d{4}/ }
+ )
+ map.connect ':controller/:action/:id'
+ end
+
+ assert_equal '/test', rs.generate(:controller => 'post', :action => 'show')
+ assert_equal '/test', rs.generate(:controller => 'post', :action => 'show', :year => nil)
+
+ x = setup_for_named_route
+ assert_equal("http://named.route.test/test",
+ x.send(:blog_url))
+ end
+
+ def test_set_to_nil_forgets
+ rs.draw do |map|
+ map.connect 'pages/:year/:month/:day', :controller => 'content', :action => 'list_pages', :month => nil, :day => nil
+ map.connect ':controller/:action/:id'
+ end
+
+ assert_equal '/pages/2005',
+ rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005)
+ assert_equal '/pages/2005/6',
+ rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6)
+ assert_equal '/pages/2005/6/12',
+ rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6, :day => 12)
+
+ assert_equal '/pages/2005/6/4',
+ rs.generate({:day => 4}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'})
+
+ assert_equal '/pages/2005/6',
+ rs.generate({:day => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'})
+
+ assert_equal '/pages/2005',
+ rs.generate({:day => nil, :month => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'})
+ end
+
+ def test_url_with_no_action_specified
+ rs.draw do |map|
+ map.connect '', :controller => 'content'
+ map.connect ':controller/:action/:id'
+ end
+
+ assert_equal '/', rs.generate(:controller => 'content', :action => 'index')
+ assert_equal '/', rs.generate(:controller => 'content')
+ end
+
+ def test_named_url_with_no_action_specified
+ rs.draw do |map|
+ map.home '', :controller => 'content'
+ map.connect ':controller/:action/:id'
+ end
+
+ assert_equal '/', rs.generate(:controller => 'content', :action => 'index')
+ assert_equal '/', rs.generate(:controller => 'content')
+
+ x = setup_for_named_route
+ assert_equal("http://named.route.test/",
+ x.send(:home_url))
+ end
+
+ def test_url_generated_when_forgetting_action
+ [{:controller => 'content', :action => 'index'}, {:controller => 'content'}].each do |hash|
+ rs.draw do |map|
+ map.home '', hash
+ map.connect ':controller/:action/:id'
+ end
+ assert_equal '/', rs.generate({:action => nil}, {:controller => 'content', :action => 'hello'})
+ assert_equal '/', rs.generate({:controller => 'content'})
+ assert_equal '/content/hi', rs.generate({:controller => 'content', :action => 'hi'})
+ end
+ end
+
+ def test_named_route_method
+ rs.draw do |map|
+ map.categories 'categories', :controller => 'content', :action => 'categories'
+ map.connect ':controller/:action/:id'
+ end
+
+ assert_equal '/categories', rs.generate(:controller => 'content', :action => 'categories')
+ assert_equal '/content/hi', rs.generate({:controller => 'content', :action => 'hi'})
+ end
+
+ def test_named_routes_array
+ test_named_route_method
+ assert_equal [:categories], rs.named_routes.names
+ end
+
+ def test_nil_defaults
+ rs.draw do |map|
+ map.connect 'journal',
+ :controller => 'content',
+ :action => 'list_journal',
+ :date => nil, :user_id => nil
+ map.connect ':controller/:action/:id'
+ end
+
+ assert_equal '/journal', rs.generate(:controller => 'content', :action => 'list_journal', :date => nil, :user_id => nil)
+ end
+
+ def setup_request_method_routes_for(method)
+ @request = ActionController::TestRequest.new
+ @request.env["REQUEST_METHOD"] = method
+ @request.request_uri = "/match"
+
+ rs.draw do |r|
+ r.connect '/match', :controller => 'books', :action => 'get', :conditions => { :method => :get }
+ r.connect '/match', :controller => 'books', :action => 'post', :conditions => { :method => :post }
+ r.connect '/match', :controller => 'books', :action => 'put', :conditions => { :method => :put }
+ r.connect '/match', :controller => 'books', :action => 'delete', :conditions => { :method => :delete }
+ end
+ end
+
+ %w(GET POST PUT DELETE).each do |request_method|
+ define_method("test_request_method_recognized_with_#{request_method}") do
+ begin
+ Object.const_set(:BooksController, Class.new(ActionController::Base))
+
+ setup_request_method_routes_for(request_method)
+
+ assert_nothing_raised { rs.recognize(@request) }
+ assert_equal request_method.downcase, @request.path_parameters[:action]
+ ensure
+ Object.send(:remove_const, :BooksController) rescue nil
+ end
+ end
+ end
+
+ def test_subpath_recognized
+ Object.const_set(:SubpathBooksController, Class.new(ActionController::Base))
+
+ rs.draw do |r|
+ r.connect '/books/:id/edit', :controller => 'subpath_books', :action => 'edit'
+ r.connect '/items/:id/:action', :controller => 'subpath_books'
+ r.connect '/posts/new/:action', :controller => 'subpath_books'
+ r.connect '/posts/:id', :controller => 'subpath_books', :action => "show"
+ end
+
+ hash = rs.recognize_path "/books/17/edit"
+ assert_not_nil hash
+ assert_equal %w(subpath_books 17 edit), [hash[:controller], hash[:id], hash[:action]]
+
+ hash = rs.recognize_path "/items/3/complete"
+ assert_not_nil hash
+ assert_equal %w(subpath_books 3 complete), [hash[:controller], hash[:id], hash[:action]]
+
+ hash = rs.recognize_path "/posts/new/preview"
+ assert_not_nil hash
+ assert_equal %w(subpath_books preview), [hash[:controller], hash[:action]]
+
+ hash = rs.recognize_path "/posts/7"
+ assert_not_nil hash
+ assert_equal %w(subpath_books show 7), [hash[:controller], hash[:action], hash[:id]]
+ ensure
+ Object.send(:remove_const, :SubpathBooksController) rescue nil
+ end
+
+ def test_subpath_generated
+ Object.const_set(:SubpathBooksController, Class.new(ActionController::Base))
+
+ rs.draw do |r|
+ r.connect '/books/:id/edit', :controller => 'subpath_books', :action => 'edit'
+ r.connect '/items/:id/:action', :controller => 'subpath_books'
+ r.connect '/posts/new/:action', :controller => 'subpath_books'
+ end
+
+ assert_equal "/books/7/edit", rs.generate(:controller => "subpath_books", :id => 7, :action => "edit")
+ assert_equal "/items/15/complete", rs.generate(:controller => "subpath_books", :id => 15, :action => "complete")
+ assert_equal "/posts/new/preview", rs.generate(:controller => "subpath_books", :action => "preview")
+ ensure
+ Object.send(:remove_const, :SubpathBooksController) rescue nil
+ end
+
+ def test_failed_requirements_raises_exception_with_violated_requirements
+ rs.draw do |r|
+ r.foo_with_requirement 'foos/:id', :controller=>'foos', :requirements=>{:id=>/\d+/}
+ end
+
+ x = setup_for_named_route
+ assert_raises(ActionController::RoutingError) do
+ x.send(:foo_with_requirement_url, "I am Against the requirements")
+ end
+ end
+end
+
+class SegmentTest < Test::Unit::TestCase
+
+ def test_first_segment_should_interpolate_for_structure
+ s = ROUTING::Segment.new
+ def s.interpolation_statement(array) 'hello' end
+ assert_equal 'hello', s.continue_string_structure([])
+ end
+
+ def test_interpolation_statement
+ s = ROUTING::StaticSegment.new
+ s.value = "Hello"
+ assert_equal "Hello", eval(s.interpolation_statement([]))
+ assert_equal "HelloHello", eval(s.interpolation_statement([s]))
+
+ s2 = ROUTING::StaticSegment.new
+ s2.value = "-"
+ assert_equal "Hello-Hello", eval(s.interpolation_statement([s, s2]))
+
+ s3 = ROUTING::StaticSegment.new
+ s3.value = "World"
+ assert_equal "Hello-World", eval(s3.interpolation_statement([s, s2]))
+ end
+
+end
+
+class StaticSegmentTest < Test::Unit::TestCase
+
+ def test_interpolation_chunk_should_respect_raw
+ s = ROUTING::StaticSegment.new
+ s.value = 'Hello World'
+ assert ! s.raw?
+ assert_equal 'Hello%20World', s.interpolation_chunk
+
+ s.raw = true
+ assert s.raw?
+ assert_equal 'Hello World', s.interpolation_chunk
+ end
+
+ def test_regexp_chunk_should_escape_specials
+ s = ROUTING::StaticSegment.new
+
+ s.value = 'Hello*World'
+ assert_equal 'Hello\*World', s.regexp_chunk
+
+ s.value = 'HelloWorld'
+ assert_equal 'HelloWorld', s.regexp_chunk
+ end
+
+ def test_regexp_chunk_should_add_question_mark_for_optionals
+ s = ROUTING::StaticSegment.new
+ s.value = "/"
+ s.is_optional = true
+ assert_equal "/?", s.regexp_chunk
+
+ s.value = "hello"
+ assert_equal "(?:hello)?", s.regexp_chunk
+ end
+
+end
+
+class DynamicSegmentTest < Test::Unit::TestCase
+
+ def segment
+ unless @segment
+ @segment = ROUTING::DynamicSegment.new
+ @segment.key = :a
+ end
+ @segment
+ end
+
+ def test_extract_value
+ s = ROUTING::DynamicSegment.new
+ s.key = :a
+
+ hash = {:a => '10', :b => '20'}
+ assert_equal '10', eval(s.extract_value)
+
+ hash = {:b => '20'}
+ assert_equal nil, eval(s.extract_value)
+
+ s.default = '20'
+ assert_equal '20', eval(s.extract_value)
+ end
+
+ def test_default_local_name
+ assert_equal 'a_value', segment.local_name,
+ "Unexpected name -- all value_check tests will fail!"
+ end
+
+ def test_presence_value_check
+ a_value = 10
+ assert eval(segment.value_check)
+ end
+
+ def test_regexp_value_check_rejects_nil
+ segment.regexp = /\d+/
+ a_value = nil
+ assert ! eval(segment.value_check)
+ end
+
+ def test_optional_regexp_value_check_should_accept_nil
+ segment.regexp = /\d+/
+ segment.is_optional = true
+ a_value = nil
+ assert eval(segment.value_check)
+ end
+
+ def test_regexp_value_check_rejects_no_match
+ segment.regexp = /\d+/
+
+ a_value = "Hello20World"
+ assert ! eval(segment.value_check)
+
+ a_value = "20Hi"
+ assert ! eval(segment.value_check)
+ end
+
+ def test_regexp_value_check_accepts_match
+ segment.regexp = /\d+/
+
+ a_value = "30"
+ assert eval(segment.value_check)
+ end
+
+ def test_value_check_fails_on_nil
+ a_value = nil
+ assert ! eval(segment.value_check)
+ end
+
+ def test_optional_value_needs_no_check
+ segment.is_optional = true
+ a_value = nil
+ assert_equal nil, segment.value_check
+ end
+
+ def test_regexp_value_check_should_accept_match_with_default
+ segment.regexp = /\d+/
+ segment.default = '200'
+
+ a_value = '100'
+ assert eval(segment.value_check)
+ end
+
+ def test_expiry_should_not_trigger_once_expired
+ expired = true
+ hash = merged = {:a => 2, :b => 3}
+ options = {:b => 3}
+ expire_on = Hash.new { raise 'No!!!' }
+
+ eval(segment.expiry_statement)
+ rescue RuntimeError
+ flunk "Expiry check should not have occurred!"
+ end
+
+ def test_expiry_should_occur_according_to_expire_on
+ expired = false
+ hash = merged = {:a => 2, :b => 3}
+ options = {:b => 3}
+
+ expire_on = {:b => true, :a => false}
+ eval(segment.expiry_statement)
+ assert !expired
+ assert_equal({:a => 2, :b => 3}, hash)
+
+ expire_on = {:b => true, :a => true}
+ eval(segment.expiry_statement)
+ assert expired
+ assert_equal({:b => 3}, hash)
+ end
+
+ def test_extraction_code_should_return_on_nil
+ hash = merged = {:b => 3}
+ options = {:b => 3}
+ a_value = nil
+
+ # Local jump because of return inside eval.
+ assert_raises(LocalJumpError) { eval(segment.extraction_code) }
+ end
+
+ def test_extraction_code_should_return_on_mismatch
+ segment.regexp = /\d+/
+ hash = merged = {:a => 'Hi', :b => '3'}
+ options = {:b => '3'}
+ a_value = nil
+
+ # Local jump because of return inside eval.
+ assert_raises(LocalJumpError) { eval(segment.extraction_code) }
+ end
+
+ def test_extraction_code_should_accept_value_and_set_local
+ hash = merged = {:a => 'Hi', :b => '3'}
+ options = {:b => '3'}
+ a_value = nil
+ expired = true
+
+ eval(segment.extraction_code)
+ assert_equal 'Hi', a_value
+ end
+
+ def test_extraction_should_work_without_value_check
+ segment.default = 'hi'
+ hash = merged = {:b => '3'}
+ options = {:b => '3'}
+ a_value = nil
+ expired = true
+
+ eval(segment.extraction_code)
+ assert_equal 'hi', a_value
+ end
+
+ def test_extraction_code_should_perform_expiry
+ expired = false
+ hash = merged = {:a => 'Hi', :b => '3'}
+ options = {:b => '3'}
+ expire_on = {:a => true}
+ a_value = nil
+
+ eval(segment.extraction_code)
+ assert_equal 'Hi', a_value
+ assert expired
+ assert_equal options, hash
+ end
+
+ def test_interpolation_chunk_should_replace_value
+ a_value = 'Hi'
+ assert_equal a_value, eval(%("#{segment.interpolation_chunk}"))
+ end
+
+ def test_interpolation_chunk_should_accept_nil
+ a_value = nil
+ assert_equal '', eval(%("#{segment.interpolation_chunk('a_value')}"))
+ end
+
+ def test_value_regexp_should_be_nil_without_regexp
+ assert_equal nil, segment.value_regexp
+ end
+
+ def test_value_regexp_should_match_exacly
+ segment.regexp = /\d+/
+ assert_no_match segment.value_regexp, "Hello 10 World"
+ assert_no_match segment.value_regexp, "Hello 10"
+ assert_no_match segment.value_regexp, "10 World"
+ assert_match segment.value_regexp, "10"
+ end
+
+ def test_regexp_chunk_should_return_string
+ segment.regexp = /\d+/
+ assert_kind_of String, segment.regexp_chunk
+ end
+
+ def test_build_pattern_non_optional_with_no_captures
+ # Non optional
+ a_segment = ROUTING::DynamicSegment.new
+ a_segment.regexp = /\d+/ #number_of_captures is 0
+ assert_equal "(\\d+)stuff", a_segment.build_pattern('stuff')
+ end
+
+ def test_build_pattern_non_optional_with_captures
+ # Non optional
+ a_segment = ROUTING::DynamicSegment.new
+ a_segment.regexp = /(\d+)(.*?)/ #number_of_captures is 2
+ assert_equal "((\\d+)(.*?))stuff", a_segment.build_pattern('stuff')
+ end
+
+ def test_optionality_implied
+ a_segment = ROUTING::DynamicSegment.new
+ a_segment.key = :id
+ assert a_segment.optionality_implied?
+
+ a_segment.key = :action
+ assert a_segment.optionality_implied?
+ end
+end
+
+class ControllerSegmentTest < Test::Unit::TestCase
+
+ def test_regexp_should_only_match_possible_controllers
+ ActionController::Routing.with_controllers %w(admin/accounts admin/users account pages) do
+ cs = ROUTING::ControllerSegment.new :controller
+ regexp = %r{\A#{cs.regexp_chunk}\Z}
+
+ ActionController::Routing.possible_controllers.each do |name|
+ assert_match regexp, name
+ assert_no_match regexp, "#{name}_fake"
+
+ match = regexp.match name
+ assert_equal name, match[1]
+ end
+ end
+ end
+
+end
+
+uses_mocha 'RouteTest' do
+
+ class MockController
+ attr_accessor :routes
+
+ def initialize(routes)
+ self.routes = routes
+ end
+
+ def url_for(options)
+ only_path = options.delete(:only_path)
+
+ port = options.delete(:port) || 80
+ port_string = port == 80 ? '' : ":#{port}"
+
+ host = options.delete(:host) || "named.route.test"
+ anchor = "##{options.delete(:anchor)}" if options.key?(:anchor)
+
+ path = routes.generate(options)
+
+ only_path ? "#{path}#{anchor}" : "http://#{host}#{port_string}#{path}#{anchor}"
+ end
+
+ def request
+ @request ||= MockRequest.new(:host => "named.route.test", :method => :get)
+ end
+
+ def relative_url_root=(value)
+ request.relative_url_root=value
+ end
+ end
+
+ class MockRequest
+ attr_accessor :path, :path_parameters, :host, :subdomains, :domain,
+ :method, :relative_url_root
+
+ def initialize(values={})
+ values.each { |key, value| send("#{key}=", value) }
+ if values[:host]
+ subdomain, self.domain = values[:host].split(/\./, 2)
+ self.subdomains = [subdomain]
+ end
+ end
+
+ def protocol
+ "http://"
+ end
+
+ def host_with_port
+ (subdomains * '.') + '.' + domain
+ end
+ end
+
+class RouteTest < Test::Unit::TestCase
+
+ def setup
+ @route = ROUTING::Route.new
+ end
+
+ def slash_segment(is_optional = false)
+ returning ROUTING::DividerSegment.new('/') do |s|
+ s.is_optional = is_optional
+ end
+ end
+
+ def default_route
+ unless defined?(@default_route)
+ @default_route = ROUTING::Route.new
+
+ @default_route.segments << (s = ROUTING::StaticSegment.new)
+ s.value = '/'
+ s.raw = true
+
+ @default_route.segments << (s = ROUTING::DynamicSegment.new)
+ s.key = :controller
+
+ @default_route.segments << slash_segment(:optional)
+ @default_route.segments << (s = ROUTING::DynamicSegment.new)
+ s.key = :action
+ s.default = 'index'
+ s.is_optional = true
+
+ @default_route.segments << slash_segment(:optional)
+ @default_route.segments << (s = ROUTING::DynamicSegment.new)
+ s.key = :id
+ s.is_optional = true
+
+ @default_route.segments << slash_segment(:optional)
+ end
+ @default_route
+ end
+
+ def test_default_route_recognition
+ expected = {:controller => 'accounts', :action => 'show', :id => '10'}
+ assert_equal expected, default_route.recognize('/accounts/show/10')
+ assert_equal expected, default_route.recognize('/accounts/show/10/')
+
+ expected[:id] = 'jamis'
+ assert_equal expected, default_route.recognize('/accounts/show/jamis/')
+
+ expected.delete :id
+ assert_equal expected, default_route.recognize('/accounts/show')
+ assert_equal expected, default_route.recognize('/accounts/show/')
+
+ expected[:action] = 'index'
+ assert_equal expected, default_route.recognize('/accounts/')
+ assert_equal expected, default_route.recognize('/accounts')
+
+ assert_equal nil, default_route.recognize('/')
+ assert_equal nil, default_route.recognize('/accounts/how/goood/it/is/to/be/free')
+ end
+
+ def test_default_route_should_omit_default_action
+ o = {:controller => 'accounts', :action => 'index'}
+ assert_equal '/accounts', default_route.generate(o, o, {})
+ end
+
+ def test_default_route_should_include_default_action_when_id_present
+ o = {:controller => 'accounts', :action => 'index', :id => '20'}
+ assert_equal '/accounts/index/20', default_route.generate(o, o, {})
+ end
+
+ def test_default_route_should_work_with_action_but_no_id
+ o = {:controller => 'accounts', :action => 'list_all'}
+ assert_equal '/accounts/list_all', default_route.generate(o, o, {})
+ end
+
+ def test_default_route_should_uri_escape_pluses
+ expected = { :controller => 'accounts', :action => 'show', :id => 'hello world' }
+ assert_equal expected, default_route.recognize('/accounts/show/hello world')
+ assert_equal expected, default_route.recognize('/accounts/show/hello%20world')
+ assert_equal '/accounts/show/hello%20world', default_route.generate(expected, expected, {})
+
+ expected[:id] = 'hello+world'
+ assert_equal expected, default_route.recognize('/accounts/show/hello+world')
+ assert_equal expected, default_route.recognize('/accounts/show/hello%2Bworld')
+ assert_equal '/accounts/show/hello+world', default_route.generate(expected, expected, {})
+ end
+
+ def test_matches_controller_and_action
+ # requirement_for should only be called for the action and controller _once_
+ @route.expects(:requirement_for).with(:controller).times(1).returns('pages')
+ @route.expects(:requirement_for).with(:action).times(1).returns('show')
+
+ @route.requirements = {:controller => 'pages', :action => 'show'}
+ assert @route.matches_controller_and_action?('pages', 'show')
+ assert !@route.matches_controller_and_action?('not_pages', 'show')
+ assert !@route.matches_controller_and_action?('pages', 'not_show')
+ end
+
+ def test_parameter_shell
+ page_url = ROUTING::Route.new
+ page_url.requirements = {:controller => 'pages', :action => 'show', :id => /\d+/}
+ assert_equal({:controller => 'pages', :action => 'show'}, page_url.parameter_shell)
+ end
+
+ def test_defaults
+ route = ROUTING::RouteBuilder.new.build '/users/:id.:format', :controller => "users", :action => "show", :format => "html"
+ assert_equal(
+ { :controller => "users", :action => "show", :format => "html" },
+ route.defaults)
+ end
+
+ def test_builder_complains_without_controller
+ assert_raises(ArgumentError) do
+ ROUTING::RouteBuilder.new.build '/contact', :contoller => "contact", :action => "index"
+ end
+ end
+
+ def test_significant_keys_for_default_route
+ keys = default_route.significant_keys.sort_by {|k| k.to_s }
+ assert_equal [:action, :controller, :id], keys
+ end
+
+ def test_significant_keys
+ user_url = ROUTING::Route.new
+ user_url.segments << (s = ROUTING::StaticSegment.new)
+ s.value = '/'
+ s.raw = true
+
+ user_url.segments << (s = ROUTING::StaticSegment.new)
+ s.value = 'user'
+
+ user_url.segments << (s = ROUTING::StaticSegment.new)
+ s.value = '/'
+ s.raw = true
+ s.is_optional = true
+
+ user_url.segments << (s = ROUTING::DynamicSegment.new)
+ s.key = :user
+
+ user_url.segments << (s = ROUTING::StaticSegment.new)
+ s.value = '/'
+ s.raw = true
+ s.is_optional = true
+
+ user_url.requirements = {:controller => 'users', :action => 'show'}
+
+ keys = user_url.significant_keys.sort_by { |k| k.to_s }
+ assert_equal [:action, :controller, :user], keys
+ end
+
+ def test_build_empty_query_string
+ assert_equal '', @route.build_query_string({})
+ end
+
+ def test_build_query_string_with_nil_value
+ assert_equal '', @route.build_query_string({:x => nil})
+ end
+
+ def test_simple_build_query_string
+ assert_equal '?x=1&y=2', order_query_string(@route.build_query_string(:x => '1', :y => '2'))
+ end
+
+ def test_convert_ints_build_query_string
+ assert_equal '?x=1&y=2', order_query_string(@route.build_query_string(:x => 1, :y => 2))
+ end
+
+ def test_escape_spaces_build_query_string
+ assert_equal '?x=hello+world&y=goodbye+world', order_query_string(@route.build_query_string(:x => 'hello world', :y => 'goodbye world'))
+ end
+
+ def test_expand_array_build_query_string
+ assert_equal '?x%5B%5D=1&x%5B%5D=2', order_query_string(@route.build_query_string(:x => [1, 2]))
+ end
+
+ def test_escape_spaces_build_query_string_selected_keys
+ assert_equal '?x=hello+world', order_query_string(@route.build_query_string({:x => 'hello world', :y => 'goodbye world'}, [:x]))
+ end
+
+ private
+ def order_query_string(qs)
+ '?' + qs[1..-1].split('&').sort.join('&')
+ end
+end
+
+end # uses_mocha
+
+class RouteBuilderTest < Test::Unit::TestCase
+
+ def builder
+ @builder ||= ROUTING::RouteBuilder.new
+ end
+
+ def build(path, options)
+ builder.build(path, options)
+ end
+
+ def test_options_should_not_be_modified
+ requirements1 = { :id => /\w+/, :controller => /(?:[a-z](?:-?[a-z]+)*)/ }
+ requirements2 = requirements1.dup
+
+ assert_equal requirements1, requirements2
+
+ with_options(:controller => 'folder',
+ :requirements => requirements2) do |m|
+ m.build 'folders/new', :action => 'new'
+ end
+
+ assert_equal requirements1, requirements2
+ end
+
+ def test_segment_for_static
+ segment, rest = builder.segment_for 'ulysses'
+ assert_equal '', rest
+ assert_kind_of ROUTING::StaticSegment, segment
+ assert_equal 'ulysses', segment.value
+ end
+
+ def test_segment_for_action
+ segment, rest = builder.segment_for ':action'
+ assert_equal '', rest
+ assert_kind_of ROUTING::DynamicSegment, segment
+ assert_equal :action, segment.key
+ assert_equal 'index', segment.default
+ end
+
+ def test_segment_for_dynamic
+ segment, rest = builder.segment_for ':login'
+ assert_equal '', rest
+ assert_kind_of ROUTING::DynamicSegment, segment
+ assert_equal :login, segment.key
+ assert_equal nil, segment.default
+ assert ! segment.optional?
+ end
+
+ def test_segment_for_with_rest
+ segment, rest = builder.segment_for ':login/:action'
+ assert_equal :login, segment.key
+ assert_equal '/:action', rest
+ segment, rest = builder.segment_for rest
+ assert_equal '/', segment.value
+ assert_equal ':action', rest
+ segment, rest = builder.segment_for rest
+ assert_equal :action, segment.key
+ assert_equal '', rest
+ end
+
+ def test_segments_for
+ segments = builder.segments_for_route_path '/:controller/:action/:id'
+
+ assert_kind_of ROUTING::DividerSegment, segments[0]
+ assert_equal '/', segments[2].value
+
+ assert_kind_of ROUTING::DynamicSegment, segments[1]
+ assert_equal :controller, segments[1].key
+
+ assert_kind_of ROUTING::DividerSegment, segments[2]
+ assert_equal '/', segments[2].value
+
+ assert_kind_of ROUTING::DynamicSegment, segments[3]
+ assert_equal :action, segments[3].key
+
+ assert_kind_of ROUTING::DividerSegment, segments[4]
+ assert_equal '/', segments[4].value
+
+ assert_kind_of ROUTING::DynamicSegment, segments[5]
+ assert_equal :id, segments[5].key
+ end
+
+ def test_segment_for_action
+ s, r = builder.segment_for(':action/something/else')
+ assert_equal '/something/else', r
+ assert_equal :action, s.key
+ end
+
+ def test_action_default_should_not_trigger_on_prefix
+ s, r = builder.segment_for ':action_name/something/else'
+ assert_equal '/something/else', r
+ assert_equal :action_name, s.key
+ assert_equal nil, s.default
+ end
+
+ def test_divide_route_options
+ segments = builder.segments_for_route_path '/cars/:action/:person/:car/'
+ defaults, requirements = builder.divide_route_options(segments,
+ :action => 'buy', :person => /\w+/, :car => /\w+/,
+ :defaults => {:person => nil, :car => nil}
+ )
+
+ assert_equal({:action => 'buy', :person => nil, :car => nil}, defaults)
+ assert_equal({:person => /\w+/, :car => /\w+/}, requirements)
+ end
+
+ def test_assign_route_options
+ segments = builder.segments_for_route_path '/cars/:action/:person/:car/'
+ defaults = {:action => 'buy', :person => nil, :car => nil}
+ requirements = {:person => /\w+/, :car => /\w+/}
+
+ route_requirements = builder.assign_route_options(segments, defaults, requirements)
+ assert_equal({}, route_requirements)
+
+ assert_equal :action, segments[3].key
+ assert_equal 'buy', segments[3].default
+
+ assert_equal :person, segments[5].key
+ assert_equal %r/\w+/, segments[5].regexp
+ assert segments[5].optional?
+
+ assert_equal :car, segments[7].key
+ assert_equal %r/\w+/, segments[7].regexp
+ assert segments[7].optional?
+ end
+
+ def test_assign_route_options_with_anchor_chars
+ segments = builder.segments_for_route_path '/cars/:action/:person/:car/'
+ defaults = {:action => 'buy', :person => nil, :car => nil}
+ requirements = {:person => /\w+/, :car => /^\w+$/}
+
+ assert_raises ArgumentError do
+ route_requirements = builder.assign_route_options(segments, defaults, requirements)
+ end
+
+ requirements[:car] = /[^\/]+/
+ route_requirements = builder.assign_route_options(segments, defaults, requirements)
+ end
+
+
+ def test_optional_segments_preceding_required_segments
+ segments = builder.segments_for_route_path '/cars/:action/:person/:car/'
+ defaults = {:action => 'buy', :person => nil, :car => "model-t"}
+ assert builder.assign_route_options(segments, defaults, {}).empty?
+
+ 0.upto(1) { |i| assert !segments[i].optional?, "segment #{i} is optional and it shouldn't be" }
+ assert segments[2].optional?
+
+ assert_equal nil, builder.warn_output # should only warn on the :person segment
+ end
+
+ def test_segmentation_of_dot_path
+ segments = builder.segments_for_route_path '/books/:action.rss'
+ assert builder.assign_route_options(segments, {}, {}).empty?
+ assert_equal 6, segments.length # "/", "books", "/", ":action", ".", "rss"
+ assert !segments.any? { |seg| seg.optional? }
+ end
+
+ def test_segmentation_of_dynamic_dot_path
+ segments = builder.segments_for_route_path '/books/:action.:format'
+ assert builder.assign_route_options(segments, {}, {}).empty?
+ assert_equal 6, segments.length # "/", "books", "/", ":action", ".", ":format"
+ assert !segments.any? { |seg| seg.optional? }
+ assert_kind_of ROUTING::DynamicSegment, segments.last
+ end
+
+ def test_assignment_of_default_options
+ segments = builder.segments_for_route_path '/:controller/:action/:id/'
+ action, id = segments[-4], segments[-2]
+
+ assert_equal :action, action.key
+ assert_equal :id, id.key
+ assert ! action.optional?
+ assert ! id.optional?
+
+ builder.assign_default_route_options(segments)
+
+ assert_equal 'index', action.default
+ assert action.optional?
+ assert id.optional?
+ end
+
+ def test_assignment_of_default_options_respects_existing_defaults
+ segments = builder.segments_for_route_path '/:controller/:action/:id/'
+ action, id = segments[-4], segments[-2]
+
+ assert_equal :action, action.key
+ assert_equal :id, id.key
+ action.default = 'show'
+ action.is_optional = true
+
+ id.default = 'Welcome'
+ id.is_optional = true
+
+ builder.assign_default_route_options(segments)
+
+ assert_equal 'show', action.default
+ assert action.optional?
+ assert_equal 'Welcome', id.default
+ assert id.optional?
+ end
+
+ def test_assignment_of_default_options_respects_regexps
+ segments = builder.segments_for_route_path '/:controller/:action/:id/'
+ action = segments[-4]
+
+ assert_equal :action, action.key
+ action.regexp = /show|in/ # Use 'in' to check partial matches
+
+ builder.assign_default_route_options(segments)
+
+ assert_equal nil, action.default
+ assert ! action.optional?
+ end
+
+ def test_assignment_of_is_optional_when_default
+ segments = builder.segments_for_route_path '/books/:action.rss'
+ assert_equal segments[3].key, :action
+ segments[3].default = 'changes'
+ builder.ensure_required_segments(segments)
+ assert ! segments[3].optional?
+ end
+
+ def test_is_optional_is_assigned_to_default_segments
+ segments = builder.segments_for_route_path '/books/:action'
+ builder.assign_route_options(segments, {:action => 'index'}, {})
+
+ assert_equal segments[3].key, :action
+ assert segments[3].optional?
+ assert_kind_of ROUTING::DividerSegment, segments[2]
+ assert segments[2].optional?
+ end
+
+ # XXX is optional not being set right?
+ # /blah/:defaulted_segment <-- is the second slash optional? it should be.
+
+ def test_route_build
+ ActionController::Routing.with_controllers %w(users pages) do
+ r = builder.build '/:controller/:action/:id/', :action => nil
+
+ [0, 2, 4].each do |i|
+ assert_kind_of ROUTING::DividerSegment, r.segments[i]
+ assert_equal '/', r.segments[i].value
+ assert r.segments[i].optional? if i > 1
+ end
+
+ assert_kind_of ROUTING::DynamicSegment, r.segments[1]
+ assert_equal :controller, r.segments[1].key
+ assert_equal nil, r.segments[1].default
+
+ assert_kind_of ROUTING::DynamicSegment, r.segments[3]
+ assert_equal :action, r.segments[3].key
+ assert_equal 'index', r.segments[3].default
+
+ assert_kind_of ROUTING::DynamicSegment, r.segments[5]
+ assert_equal :id, r.segments[5].key
+ assert r.segments[5].optional?
+ end
+ end
+
+ def test_slashes_are_implied
+ routes = [
+ builder.build('/:controller/:action/:id/', :action => nil),
+ builder.build('/:controller/:action/:id', :action => nil),
+ builder.build(':controller/:action/:id', :action => nil),
+ builder.build('/:controller/:action/:id/', :action => nil)
+ ]
+ expected = routes.first.segments.length
+ routes.each_with_index do |route, i|
+ found = route.segments.length
+ assert_equal expected, found, "Route #{i + 1} has #{found} segments, expected #{expected}"
+ end
+ end
+
+end
+
+
+
+
+class RouteSetTest < Test::Unit::TestCase
+
+ def set
+ @set ||= ROUTING::RouteSet.new
+ end
+
+ def request
+ @request ||= MockRequest.new(:host => "named.routes.test", :method => :get)
+ end
+
+ def test_generate_extras
+ set.draw { |m| m.connect ':controller/:action/:id' }
+ path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
+ assert_equal "/foo/bar/15", path
+ assert_equal %w(that this), extras.map(&:to_s).sort
+ end
+
+ def test_extra_keys
+ set.draw { |m| m.connect ':controller/:action/:id' }
+ extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
+ assert_equal %w(that this), extras.map(&:to_s).sort
+ end
+
+ def test_generate_extras_not_first
+ set.draw do |map|
+ map.connect ':controller/:action/:id.:format'
+ map.connect ':controller/:action/:id'
+ end
+ path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
+ assert_equal "/foo/bar/15", path
+ assert_equal %w(that this), extras.map(&:to_s).sort
+ end
+
+ def test_generate_not_first
+ set.draw do |map|
+ map.connect ':controller/:action/:id.:format'
+ map.connect ':controller/:action/:id'
+ end
+ assert_equal "/foo/bar/15?this=hello", set.generate(:controller => "foo", :action => "bar", :id => 15, :this => "hello")
+ end
+
+ def test_extra_keys_not_first
+ set.draw do |map|
+ map.connect ':controller/:action/:id.:format'
+ map.connect ':controller/:action/:id'
+ end
+ extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
+ assert_equal %w(that this), extras.map(&:to_s).sort
+ end
+
+ def test_draw
+ assert_equal 0, set.routes.size
+ set.draw do |map|
+ map.connect '/hello/world', :controller => 'a', :action => 'b'
+ end
+ assert_equal 1, set.routes.size
+ end
+
+ def test_named_draw
+ assert_equal 0, set.routes.size
+ set.draw do |map|
+ map.hello '/hello/world', :controller => 'a', :action => 'b'
+ end
+ assert_equal 1, set.routes.size
+ assert_equal set.routes.first, set.named_routes[:hello]
+ end
+
+ def test_later_named_routes_take_precedence
+ set.draw do |map|
+ map.hello '/hello/world', :controller => 'a', :action => 'b'
+ map.hello '/hello', :controller => 'a', :action => 'b'
+ end
+ assert_equal set.routes.last, set.named_routes[:hello]
+ end
+
+ def setup_named_route_test
+ set.draw do |map|
+ map.show '/people/:id', :controller => 'people', :action => 'show'
+ map.index '/people', :controller => 'people', :action => 'index'
+ map.multi '/people/go/:foo/:bar/joe/:id', :controller => 'people', :action => 'multi'
+ map.users '/admin/users', :controller => 'admin/users', :action => 'index'
+ end
+
+ klass = Class.new(MockController)
+ set.install_helpers(klass)
+ klass.new(set)
+ end
+
+ def test_named_route_hash_access_method
+ controller = setup_named_route_test
+
+ assert_equal(
+ { :controller => 'people', :action => 'show', :id => 5, :use_route => :show, :only_path => false },
+ controller.send(:hash_for_show_url, :id => 5))
+
+ assert_equal(
+ { :controller => 'people', :action => 'index', :use_route => :index, :only_path => false },
+ controller.send(:hash_for_index_url))
+
+ assert_equal(
+ { :controller => 'people', :action => 'show', :id => 5, :use_route => :show, :only_path => true },
+ controller.send(:hash_for_show_path, :id => 5)
+ )
+ end
+
+ def test_named_route_url_method
+ controller = setup_named_route_test
+
+ assert_equal "http://named.route.test/people/5", controller.send(:show_url, :id => 5)
+ assert_equal "/people/5", controller.send(:show_path, :id => 5)
+
+ assert_equal "http://named.route.test/people", controller.send(:index_url)
+ assert_equal "/people", controller.send(:index_path)
+
+ assert_equal "http://named.route.test/admin/users", controller.send(:users_url)
+ assert_equal '/admin/users', controller.send(:users_path)
+ assert_equal '/admin/users', set.generate(controller.send(:hash_for_users_url), {:controller => 'users', :action => 'index'})
+ end
+
+ def test_named_route_url_method_with_anchor
+ controller = setup_named_route_test
+
+ assert_equal "http://named.route.test/people/5#location", controller.send(:show_url, :id => 5, :anchor => 'location')
+ assert_equal "/people/5#location", controller.send(:show_path, :id => 5, :anchor => 'location')
+
+ assert_equal "http://named.route.test/people#location", controller.send(:index_url, :anchor => 'location')
+ assert_equal "/people#location", controller.send(:index_path, :anchor => 'location')
+
+ assert_equal "http://named.route.test/admin/users#location", controller.send(:users_url, :anchor => 'location')
+ assert_equal '/admin/users#location', controller.send(:users_path, :anchor => 'location')
+
+ assert_equal "http://named.route.test/people/go/7/hello/joe/5#location",
+ controller.send(:multi_url, 7, "hello", 5, :anchor => 'location')
+
+ assert_equal "http://named.route.test/people/go/7/hello/joe/5?baz=bar#location",
+ controller.send(:multi_url, 7, "hello", 5, :baz => "bar", :anchor => 'location')
+
+ assert_equal "http://named.route.test/people?baz=bar#location",
+ controller.send(:index_url, :baz => "bar", :anchor => 'location')
+ end
+
+ def test_named_route_url_method_with_port
+ controller = setup_named_route_test
+ assert_equal "http://named.route.test:8080/people/5", controller.send(:show_url, 5, :port=>8080)
+ end
+
+ def test_named_route_url_method_with_host
+ controller = setup_named_route_test
+ assert_equal "http://some.example.com/people/5", controller.send(:show_url, 5, :host=>"some.example.com")
+ end
+
+
+ def test_named_route_url_method_with_ordered_parameters
+ controller = setup_named_route_test
+ assert_equal "http://named.route.test/people/go/7/hello/joe/5",
+ controller.send(:multi_url, 7, "hello", 5)
+ end
+
+ def test_named_route_url_method_with_ordered_parameters_and_hash
+ controller = setup_named_route_test
+ assert_equal "http://named.route.test/people/go/7/hello/joe/5?baz=bar",
+ controller.send(:multi_url, 7, "hello", 5, :baz => "bar")
+ end
+
+ def test_named_route_url_method_with_no_positional_arguments
+ controller = setup_named_route_test
+ assert_equal "http://named.route.test/people?baz=bar",
+ controller.send(:index_url, :baz => "bar")
+ end
+
+ def test_draw_default_route
+ ActionController::Routing.with_controllers(['users']) do
+ set.draw do |map|
+ map.connect '/:controller/:action/:id'
+ end
+
+ assert_equal 1, set.routes.size
+ route = set.routes.first
+
+ assert route.segments.last.optional?
+
+ assert_equal '/users/show/10', set.generate(:controller => 'users', :action => 'show', :id => 10)
+ assert_equal '/users/index/10', set.generate(:controller => 'users', :id => 10)
+
+ assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10'))
+ assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10/'))
+ end
+ end
+
+ def test_draw_default_route_with_default_controller
+ ActionController::Routing.with_controllers(['users']) do
+ set.draw do |map|
+ map.connect '/:controller/:action/:id', :controller => 'users'
+ end
+ assert_equal({:controller => 'users', :action => 'index'}, set.recognize_path('/'))
+ end
+ end
+
+ def test_route_with_parameter_shell
+ ActionController::Routing.with_controllers(['users', 'pages']) do
+ set.draw do |map|
+ map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
+ map.connect '/:controller/:action/:id'
+ end
+
+ assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages'))
+ assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages/index'))
+ assert_equal({:controller => 'pages', :action => 'list'}, set.recognize_path('/pages/list'))
+
+ assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/pages/show/10'))
+ assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10'))
+ end
+ end
+
+ def test_route_requirements_with_anchor_chars_are_invalid
+ assert_raises ArgumentError do
+ set.draw do |map|
+ map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /^\d+/
+ end
+ end
+ assert_raises ArgumentError do
+ set.draw do |map|
+ map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\A\d+/
+ end
+ end
+ assert_raises ArgumentError do
+ set.draw do |map|
+ map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+$/
+ end
+ end
+ assert_raises ArgumentError do
+ set.draw do |map|
+ map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\Z/
+ end
+ end
+ assert_raises ArgumentError do
+ set.draw do |map|
+ map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\z/
+ end
+ end
+ assert_nothing_raised do
+ set.draw do |map|
+ map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/, :name => /^(david|jamis)/
+ end
+ assert_raises ActionController::RoutingError do
+ set.generate :controller => 'pages', :action => 'show', :id => 10
+ end
+ end
+ end
+
+ def test_non_path_route_requirements_match_all
+ set.draw do |map|
+ map.connect 'page/37s', :controller => 'pages', :action => 'show', :name => /(jamis|david)/
+ end
+ assert_equal '/page/37s', set.generate(:controller => 'pages', :action => 'show', :name => 'jamis')
+ assert_raises ActionController::RoutingError do
+ set.generate(:controller => 'pages', :action => 'show', :name => 'not_jamis')
+ end
+ assert_raises ActionController::RoutingError do
+ set.generate(:controller => 'pages', :action => 'show', :name => 'nor_jamis_and_david')
+ end
+ end
+
+ def test_recognize_with_encoded_id_and_regex
+ set.draw do |map|
+ map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /[a-zA-Z0-9\+]+/
+ end
+
+ assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10'))
+ assert_equal({:controller => 'pages', :action => 'show', :id => 'hello+world'}, set.recognize_path('/page/hello+world'))
+ end
+
+ def test_recognize_with_conditions
+ Object.const_set(:PeopleController, Class.new)
+
+ set.draw do |map|
+ map.with_options(:controller => "people") do |people|
+ people.people "/people", :action => "index", :conditions => { :method => :get }
+ people.connect "/people", :action => "create", :conditions => { :method => :post }
+ people.person "/people/:id", :action => "show", :conditions => { :method => :get }
+ people.connect "/people/:id", :action => "update", :conditions => { :method => :put }
+ people.connect "/people/:id", :action => "destroy", :conditions => { :method => :delete }
+ end
+ end
+
+ request.path = "/people"
+ request.method = :get
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("index", request.path_parameters[:action])
+
+ request.method = :post
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("create", request.path_parameters[:action])
+
+ request.method = :put
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("update", request.path_parameters[:action])
+
+ begin
+ request.method = :bacon
+ set.recognize(request)
+ flunk 'Should have raised NotImplemented'
+ rescue ActionController::NotImplemented => e
+ assert_equal [:get, :post, :put, :delete], e.allowed_methods
+ end
+
+ request.path = "/people/5"
+ request.method = :get
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("show", request.path_parameters[:action])
+ assert_equal("5", request.path_parameters[:id])
+
+ request.method = :put
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("update", request.path_parameters[:action])
+ assert_equal("5", request.path_parameters[:id])
+
+ request.method = :delete
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("destroy", request.path_parameters[:action])
+ assert_equal("5", request.path_parameters[:id])
+
+ begin
+ request.method = :post
+ set.recognize(request)
+ flunk 'Should have raised MethodNotAllowed'
+ rescue ActionController::MethodNotAllowed => e
+ assert_equal [:get, :put, :delete], e.allowed_methods
+ end
+
+ ensure
+ Object.send(:remove_const, :PeopleController)
+ end
+
+ def test_typo_recognition
+ Object.const_set(:ArticlesController, Class.new)
+
+ set.draw do |map|
+ map.connect 'articles/:year/:month/:day/:title',
+ :controller => 'articles', :action => 'permalink',
+ :year => /\d{4}/, :day => /\d{1,2}/, :month => /\d{1,2}/
+ end
+
+ request.path = "/articles/2005/11/05/a-very-interesting-article"
+ request.method = :get
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("permalink", request.path_parameters[:action])
+ assert_equal("2005", request.path_parameters[:year])
+ assert_equal("11", request.path_parameters[:month])
+ assert_equal("05", request.path_parameters[:day])
+ assert_equal("a-very-interesting-article", request.path_parameters[:title])
+
+ ensure
+ Object.send(:remove_const, :ArticlesController)
+ end
+
+ def test_routing_traversal_does_not_load_extra_classes
+ assert !Object.const_defined?("Profiler__"), "Profiler should not be loaded"
+ set.draw do |map|
+ map.connect '/profile', :controller => 'profile'
+ end
+
+ request.path = '/profile'
+
+ set.recognize(request) rescue nil
+
+ assert !Object.const_defined?("Profiler__"), "Profiler should not be loaded"
+ end
+
+ def test_recognize_with_conditions_and_format
+ Object.const_set(:PeopleController, Class.new)
+
+ set.draw do |map|
+ map.with_options(:controller => "people") do |people|
+ people.person "/people/:id", :action => "show", :conditions => { :method => :get }
+ people.connect "/people/:id", :action => "update", :conditions => { :method => :put }
+ people.connect "/people/:id.:_format", :action => "show", :conditions => { :method => :get }
+ end
+ end
+
+ request.path = "/people/5"
+ request.method = :get
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("show", request.path_parameters[:action])
+ assert_equal("5", request.path_parameters[:id])
+
+ request.method = :put
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("update", request.path_parameters[:action])
+
+ request.path = "/people/5.png"
+ request.method = :get
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("show", request.path_parameters[:action])
+ assert_equal("5", request.path_parameters[:id])
+ assert_equal("png", request.path_parameters[:_format])
+ ensure
+ Object.send(:remove_const, :PeopleController)
+ end
+
+ def test_generate_with_default_action
+ set.draw do |map|
+ map.connect "/people", :controller => "people"
+ map.connect "/people/list", :controller => "people", :action => "list"
+ end
+
+ url = set.generate(:controller => "people", :action => "list")
+ assert_equal "/people/list", url
+ end
+
+ def test_root_map
+ Object.const_set(:PeopleController, Class.new)
+
+ set.draw { |map| map.root :controller => "people" }
+
+ request.path = ""
+ request.method = :get
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("people", request.path_parameters[:controller])
+ assert_equal("index", request.path_parameters[:action])
+ ensure
+ Object.send(:remove_const, :PeopleController)
+ end
+
+
+ def test_namespace
+ Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) })
+
+ set.draw do |map|
+
+ map.namespace 'api' do |api|
+ api.route 'inventory', :controller => "products", :action => 'inventory'
+ end
+
+ end
+
+ request.path = "/api/inventory"
+ request.method = :get
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("api/products", request.path_parameters[:controller])
+ assert_equal("inventory", request.path_parameters[:action])
+ ensure
+ Object.send(:remove_const, :Api)
+ end
+
+
+ def test_namespaced_root_map
+ Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) })
+
+ set.draw do |map|
+
+ map.namespace 'api' do |api|
+ api.root :controller => "products"
+ end
+
+ end
+
+ request.path = "/api"
+ request.method = :get
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("api/products", request.path_parameters[:controller])
+ assert_equal("index", request.path_parameters[:action])
+ ensure
+ Object.send(:remove_const, :Api)
+ end
+
+ def test_generate_finds_best_fit
+ set.draw do |map|
+ map.connect "/people", :controller => "people", :action => "index"
+ map.connect "/ws/people", :controller => "people", :action => "index", :ws => true
+ end
+
+ url = set.generate(:controller => "people", :action => "index", :ws => true)
+ assert_equal "/ws/people", url
+ end
+
+ def test_generate_changes_controller_module
+ set.draw { |map| map.connect ':controller/:action/:id' }
+ current = { :controller => "bling/bloop", :action => "bap", :id => 9 }
+ url = set.generate({:controller => "foo/bar", :action => "baz", :id => 7}, current)
+ assert_equal "/foo/bar/baz/7", url
+ end
+
+ def test_id_is_not_impossibly_sticky
+ set.draw do |map|
+ map.connect 'foo/:number', :controller => "people", :action => "index"
+ map.connect ':controller/:action/:id'
+ end
+
+ url = set.generate({:controller => "people", :action => "index", :number => 3},
+ {:controller => "people", :action => "index", :id => "21"})
+ assert_equal "/foo/3", url
+ end
+
+ def test_id_is_sticky_when_it_ought_to_be
+ set.draw do |map|
+ map.connect ':controller/:id/:action'
+ end
+
+ url = set.generate({:action => "destroy"}, {:controller => "people", :action => "show", :id => "7"})
+ assert_equal "/people/7/destroy", url
+ end
+
+ def test_use_static_path_when_possible
+ set.draw do |map|
+ map.connect 'about', :controller => "welcome", :action => "about"
+ map.connect ':controller/:action/:id'
+ end
+
+ url = set.generate({:controller => "welcome", :action => "about"},
+ {:controller => "welcome", :action => "get", :id => "7"})
+ assert_equal "/about", url
+ end
+
+ def test_generate
+ set.draw { |map| map.connect ':controller/:action/:id' }
+
+ args = { :controller => "foo", :action => "bar", :id => "7", :x => "y" }
+ assert_equal "/foo/bar/7?x=y", set.generate(args)
+ assert_equal ["/foo/bar/7", [:x]], set.generate_extras(args)
+ assert_equal [:x], set.extra_keys(args)
+ end
+
+ def test_named_routes_are_never_relative_to_modules
+ set.draw do |map|
+ map.connect "/connection/manage/:action", :controller => 'connection/manage'
+ map.connect "/connection/connection", :controller => "connection/connection"
+ map.family_connection "/connection", :controller => "connection"
+ end
+
+ url = set.generate({:controller => "connection"}, {:controller => 'connection/manage'})
+ assert_equal "/connection/connection", url
+
+ url = set.generate({:use_route => :family_connection, :controller => "connection"}, {:controller => 'connection/manage'})
+ assert_equal "/connection", url
+ end
+
+ def test_action_left_off_when_id_is_recalled
+ set.draw do |map|
+ map.connect ':controller/:action/:id'
+ end
+ assert_equal '/post', set.generate(
+ {:controller => 'post', :action => 'index'},
+ {:controller => 'post', :action => 'show', :id => '10'}
+ )
+ end
+
+ def test_query_params_will_be_shown_when_recalled
+ set.draw do |map|
+ map.connect 'show_post/:parameter', :controller => 'post', :action => 'show'
+ map.connect ':controller/:action/:id'
+ end
+ assert_equal '/post/edit?parameter=1', set.generate(
+ {:action => 'edit', :parameter => 1},
+ {:controller => 'post', :action => 'show', :parameter => 1}
+ )
+ end
+
+ def test_expiry_determination_should_consider_values_with_to_param
+ set.draw { |map| map.connect 'projects/:project_id/:controller/:action' }
+ assert_equal '/projects/1/post/show', set.generate(
+ {:action => 'show', :project_id => 1},
+ {:controller => 'post', :action => 'show', :project_id => '1'})
+ end
+
+ def test_generate_all
+ set.draw do |map|
+ map.connect 'show_post/:id', :controller => 'post', :action => 'show'
+ map.connect ':controller/:action/:id'
+ end
+ all = set.generate(
+ {:action => 'show', :id => 10, :generate_all => true},
+ {:controller => 'post', :action => 'show'}
+ )
+ assert_equal 2, all.length
+ assert_equal '/show_post/10', all.first
+ assert_equal '/post/show/10', all.last
+ end
+
+ def test_named_route_in_nested_resource
+ set.draw do |map|
+ map.resources :projects do |project|
+ project.milestones 'milestones', :controller => 'milestones', :action => 'index'
+ end
+ end
+
+ request.path = "/projects/1/milestones"
+ request.method = :get
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("milestones", request.path_parameters[:controller])
+ assert_equal("index", request.path_parameters[:action])
+ end
+
+ def test_setting_root_in_namespace_using_symbol
+ assert_nothing_raised do
+ set.draw do |map|
+ map.namespace :admin do |admin|
+ admin.root :controller => 'home'
+ end
+ end
+ end
+ end
+
+ def test_setting_root_in_namespace_using_string
+ assert_nothing_raised do
+ set.draw do |map|
+ map.namespace 'admin' do |admin|
+ admin.root :controller => 'home'
+ end
+ end
+ end
+ end
+
+end
+
+class RoutingTest < Test::Unit::TestCase
+
+ def test_possible_controllers
+ true_controller_paths = ActionController::Routing.controller_paths
+
+ ActionController::Routing.use_controllers! nil
+
+ silence_warnings do
+ Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + '/controller_fixtures')
+ end
+
+ ActionController::Routing.controller_paths = [
+ RAILS_ROOT, RAILS_ROOT + '/app/controllers', RAILS_ROOT + '/vendor/plugins/bad_plugin/lib'
+ ]
+
+ assert_equal ["admin/user", "plugin", "user"], ActionController::Routing.possible_controllers.sort
+ ensure
+ if true_controller_paths
+ ActionController::Routing.controller_paths = true_controller_paths
+ end
+ ActionController::Routing.use_controllers! nil
+ Object.send(:remove_const, :RAILS_ROOT) rescue nil
+ end
+
+ def test_possible_controllers_are_reset_on_each_load
+ true_possible_controllers = ActionController::Routing.possible_controllers
+ true_controller_paths = ActionController::Routing.controller_paths
+
+ ActionController::Routing.use_controllers! nil
+ root = File.dirname(__FILE__) + '/controller_fixtures'
+
+ ActionController::Routing.controller_paths = []
+ assert_equal [], ActionController::Routing.possible_controllers
+
+ ActionController::Routing::Routes.load!
+ ActionController::Routing.controller_paths = [
+ root, root + '/app/controllers', root + '/vendor/plugins/bad_plugin/lib'
+ ]
+
+ assert_equal ["admin/user", "plugin", "user"], ActionController::Routing.possible_controllers.sort
+ ensure
+ ActionController::Routing.controller_paths = true_controller_paths
+ ActionController::Routing.use_controllers! true_possible_controllers
+ Object.send(:remove_const, :RAILS_ROOT) rescue nil
+
+ ActionController::Routing::Routes.clear!
+ ActionController::Routing::Routes.load_routes!
+ end
+
+ def test_with_controllers
+ c = %w(admin/accounts admin/users account pages)
+ ActionController::Routing.with_controllers c do
+ assert_equal c, ActionController::Routing.possible_controllers
+ end
+ end
+
+ def test_normalize_unix_paths
+ load_paths = %w(. config/../app/controllers config/../app//helpers script/../config/../vendor/rails/actionpack/lib vendor/rails/railties/builtin/rails_info app/models lib script/../config/../foo/bar/../../app/models)
+ paths = ActionController::Routing.normalize_paths(load_paths)
+ assert_equal %w(vendor/rails/railties/builtin/rails_info vendor/rails/actionpack/lib app/controllers app/helpers app/models lib .), paths
+ end
+
+ def test_normalize_windows_paths
+ load_paths = %w(. config\\..\\app\\controllers config\\..\\app\\\\helpers script\\..\\config\\..\\vendor\\rails\\actionpack\\lib vendor\\rails\\railties\\builtin\\rails_info app\\models lib script\\..\\config\\..\\foo\\bar\\..\\..\\app\\models)
+ paths = ActionController::Routing.normalize_paths(load_paths)
+ assert_equal %w(vendor\\rails\\railties\\builtin\\rails_info vendor\\rails\\actionpack\\lib app\\controllers app\\helpers app\\models lib .), paths
+ end
+
+ def test_routing_helper_module
+ assert_kind_of Module, ActionController::Routing::Helpers
+
+ h = ActionController::Routing::Helpers
+ c = Class.new
+ assert ! c.ancestors.include?(h)
+ ActionController::Routing::Routes.install_helpers c
+ assert c.ancestors.include?(h)
+ end
+
+end
+
+uses_mocha 'route loading' do
+ class RouteLoadingTest < Test::Unit::TestCase
+
+ def setup
+ routes.instance_variable_set '@routes_last_modified', nil
+ silence_warnings { Object.const_set :RAILS_ROOT, '.' }
+
+ @stat = stub_everything
+ end
+
+ def teardown
+ Object.send :remove_const, :RAILS_ROOT
+ end
+
+ def test_load
+ File.expects(:stat).returns(@stat)
+ routes.expects(:load).with(regexp_matches(/routes\.rb$/))
+
+ routes.reload
+ end
+
+ def test_no_reload_when_not_modified
+ @stat.expects(:mtime).times(2).returns(1)
+ File.expects(:stat).times(2).returns(@stat)
+ routes.expects(:load).with(regexp_matches(/routes\.rb$/)).at_most_once
+
+ 2.times { routes.reload }
+ end
+
+ def test_reload_when_modified
+ @stat.expects(:mtime).at_least(2).returns(1, 2)
+ File.expects(:stat).at_least(2).returns(@stat)
+ routes.expects(:load).with(regexp_matches(/routes\.rb$/)).times(2)
+
+ 2.times { routes.reload }
+ end
+
+ def test_bang_forces_reload
+ @stat.expects(:mtime).at_least(2).returns(1)
+ File.expects(:stat).at_least(2).returns(@stat)
+ routes.expects(:load).with(regexp_matches(/routes\.rb$/)).times(2)
+
+ 2.times { routes.reload! }
+ end
+
+ def test_adding_inflections_forces_reload
+ Inflector::Inflections.instance.expects(:uncountable).with('equipment')
+ routes.expects(:reload!)
+
+ Inflector.inflections { |inflect| inflect.uncountable('equipment') }
+ end
+
+ private
+ def routes
+ ActionController::Routing::Routes
+ end
+
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/selector_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/selector_test.rb
new file mode 100644
index 000000000..ca106ba23
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/selector_test.rb
@@ -0,0 +1,628 @@
+#--
+# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
+# Under MIT and/or CC By license.
+#++
+
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+require "#{File.dirname(__FILE__)}/fake_controllers"
+
+class SelectorTest < Test::Unit::TestCase
+ #
+ # Basic selector: element, id, class, attributes.
+ #
+
+ def test_element
+ parse(%Q{<div id="1"></div><p></p><div id="2"></div>})
+ # Match element by name.
+ select("div")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "2", @matches[1].attributes["id"]
+ # Not case sensitive.
+ select("DIV")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "2", @matches[1].attributes["id"]
+ # Universal match (all elements).
+ select("*")
+ assert_equal 3, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal nil, @matches[1].attributes["id"]
+ assert_equal "2", @matches[2].attributes["id"]
+ end
+
+
+ def test_identifier
+ parse(%Q{<div id="1"></div><p></p><div id="2"></div>})
+ # Match element by ID.
+ select("div#1")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ # Match element by ID, substitute value.
+ select("div#?", 2)
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Element name does not match ID.
+ select("p#?", 2)
+ assert_equal 0, @matches.size
+ # Use regular expression.
+ select("#?", /\d/)
+ assert_equal 2, @matches.size
+ end
+
+
+ def test_class_name
+ parse(%Q{<div id="1" class=" foo "></div><p id="2" class=" foo bar "></p><div id="3" class="bar"></div>})
+ # Match element with specified class.
+ select("div.foo")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ # Match any element with specified class.
+ select("*.foo")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "2", @matches[1].attributes["id"]
+ # Match elements with other class.
+ select("*.bar")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ # Match only element with both class names.
+ select("*.bar.foo")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ end
+
+
+ def test_attribute
+ parse(%Q{<div id="1"></div><p id="2" title="" bar="foo"></p><div id="3" title="foo"></div>})
+ # Match element with attribute.
+ select("div[title]")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ # Match any element with attribute.
+ select("*[title]")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ # Match element with attribute value.
+ select("*[title=foo]")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ # Match element with attribute and attribute value.
+ select("[bar=foo][title]")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Not case sensitive.
+ select("[BAR=foo][TiTle]")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ end
+
+
+ def test_attribute_quoted
+ parse(%Q{<div id="1" title="foo"></div><div id="2" title="bar"></div><div id="3" title=" bar "></div>})
+ # Match without quotes.
+ select("[title = bar]")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Match with single quotes.
+ select("[title = 'bar' ]")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Match with double quotes.
+ select("[title = \"bar\" ]")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Match with spaces.
+ select("[title = \" bar \" ]")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ end
+
+
+ def test_attribute_equality
+ parse(%Q{<div id="1" title="foo bar"></div><div id="2" title="barbaz"></div>})
+ # Match (fail) complete value.
+ select("[title=bar]")
+ assert_equal 0, @matches.size
+ # Match space-separate word.
+ select("[title~=foo]")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ select("[title~=bar]")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ # Match beginning of value.
+ select("[title^=ba]")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Match end of value.
+ select("[title$=ar]")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ # Match text in value.
+ select("[title*=bar]")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "2", @matches[1].attributes["id"]
+ # Match first space separated word.
+ select("[title|=foo]")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ select("[title|=bar]")
+ assert_equal 0, @matches.size
+ end
+
+
+ #
+ # Selector composition: groups, sibling, children
+ #
+
+
+ def test_selector_group
+ parse(%Q{<h1 id="1"></h1><h2 id="2"></h2><h3 id="3"></h3>})
+ # Simple group selector.
+ select("h1,h3")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ select("h1 , h3")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ # Complex group selector.
+ parse(%Q{<h1 id="1"><a href="foo"></a></h1><h2 id="2"><a href="bar"></a></h2><h3 id="2"><a href="baz"></a></h3>})
+ select("h1 a, h3 a")
+ assert_equal 2, @matches.size
+ assert_equal "foo", @matches[0].attributes["href"]
+ assert_equal "baz", @matches[1].attributes["href"]
+ # And now for the three selector challenge.
+ parse(%Q{<h1 id="1"><a href="foo"></a></h1><h2 id="2"><a href="bar"></a></h2><h3 id="2"><a href="baz"></a></h3>})
+ select("h1 a, h2 a, h3 a")
+ assert_equal 3, @matches.size
+ assert_equal "foo", @matches[0].attributes["href"]
+ assert_equal "bar", @matches[1].attributes["href"]
+ assert_equal "baz", @matches[2].attributes["href"]
+ end
+
+
+ def test_sibling_selector
+ parse(%Q{<h1 id="1"></h1><h2 id="2"></h2><h3 id="3"></h3>})
+ # Test next sibling.
+ select("h1+*")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ select("h1+h2")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ select("h1+h3")
+ assert_equal 0, @matches.size
+ select("*+h3")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ # Test any sibling.
+ select("h1~*")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ select("h2~*")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ end
+
+
+ def test_children_selector
+ parse(%Q{<div><p id="1"><span id="2"></span></p></div><div><p id="3"><span id="4" class="foo"></span></p></div>})
+ # Test child selector.
+ select("div>p")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ select("div>span")
+ assert_equal 0, @matches.size
+ select("div>p#3")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ select("div>p>span")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ # Test descendant selector.
+ select("div p")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ select("div span")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ select("div *#3")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ select("div *#4")
+ assert_equal 1, @matches.size
+ assert_equal "4", @matches[0].attributes["id"]
+ # This is here because it failed before when whitespaces
+ # were not properly stripped.
+ select("div .foo")
+ assert_equal 1, @matches.size
+ assert_equal "4", @matches[0].attributes["id"]
+ end
+
+
+ #
+ # Pseudo selectors: root, nth-child, empty, content, etc
+ #
+
+
+ def test_root_selector
+ parse(%Q{<div id="1"><div id="2"></div></div>})
+ # Can only find element if it's root.
+ select(":root")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ select("#1:root")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ select("#2:root")
+ assert_equal 0, @matches.size
+ # Opposite for nth-child.
+ select("#1:nth-child(1)")
+ assert_equal 0, @matches.size
+ end
+
+
+ def test_nth_child_odd_even
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # Test odd nth children.
+ select("tr:nth-child(odd)")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ # Test even nth children.
+ select("tr:nth-child(even)")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ end
+
+
+ def test_nth_child_a_is_zero
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # Test the third child.
+ select("tr:nth-child(0n+3)")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ # Same but an can be omitted when zero.
+ select("tr:nth-child(3)")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ # Second element (but not every second element).
+ select("tr:nth-child(0n+2)")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Before first and past last returns nothing.:
+ assert_raises(ArgumentError) { select("tr:nth-child(-1)") }
+ select("tr:nth-child(0)")
+ assert_equal 0, @matches.size
+ select("tr:nth-child(5)")
+ assert_equal 0, @matches.size
+ end
+
+
+ def test_nth_child_a_is_one
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # a is group of one, pick every element in group.
+ select("tr:nth-child(1n+0)")
+ assert_equal 4, @matches.size
+ # Same but a can be omitted when one.
+ select("tr:nth-child(n+0)")
+ assert_equal 4, @matches.size
+ # Same but b can be omitted when zero.
+ select("tr:nth-child(n)")
+ assert_equal 4, @matches.size
+ end
+
+
+ def test_nth_child_b_is_zero
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # If b is zero, pick the n-th element (here each one).
+ select("tr:nth-child(n+0)")
+ assert_equal 4, @matches.size
+ # If b is zero, pick the n-th element (here every second).
+ select("tr:nth-child(2n+0)")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ # If a and b are both zero, no element selected.
+ select("tr:nth-child(0n+0)")
+ assert_equal 0, @matches.size
+ select("tr:nth-child(0)")
+ assert_equal 0, @matches.size
+ end
+
+
+ def test_nth_child_a_is_negative
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # Since a is -1, picks the first three elements.
+ select("tr:nth-child(-n+3)")
+ assert_equal 3, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "2", @matches[1].attributes["id"]
+ assert_equal "3", @matches[2].attributes["id"]
+ # Since a is -2, picks the first in every second of first four elements.
+ select("tr:nth-child(-2n+3)")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ # Since a is -2, picks the first in every second of first three elements.
+ select("tr:nth-child(-2n+2)")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ end
+
+
+ def test_nth_child_b_is_negative
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # Select last of four.
+ select("tr:nth-child(4n-1)")
+ assert_equal 1, @matches.size
+ assert_equal "4", @matches[0].attributes["id"]
+ # Select first of four.
+ select("tr:nth-child(4n-4)")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ # Select last of every second.
+ select("tr:nth-child(2n-1)")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ # Select nothing since an+b always < 0
+ select("tr:nth-child(-1n-1)")
+ assert_equal 0, @matches.size
+ end
+
+
+ def test_nth_child_substitution_values
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # Test with ?n?.
+ select("tr:nth-child(?n?)", 2, 1)
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ select("tr:nth-child(?n?)", 2, 2)
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ select("tr:nth-child(?n?)", 4, 2)
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Test with ? (b only).
+ select("tr:nth-child(?)", 3)
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ select("tr:nth-child(?)", 5)
+ assert_equal 0, @matches.size
+ end
+
+
+ def test_nth_last_child
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # Last two elements.
+ select("tr:nth-last-child(-n+2)")
+ assert_equal 2, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ # All old elements counting from last one.
+ select("tr:nth-last-child(odd)")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ end
+
+
+ def test_nth_of_type
+ parse(%Q{<table><thead></thead><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # First two elements.
+ select("tr:nth-of-type(-n+2)")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "2", @matches[1].attributes["id"]
+ # All old elements counting from last one.
+ select("tr:nth-last-of-type(odd)")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ end
+
+
+ def test_first_and_last
+ parse(%Q{<table><thead></thead><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # First child.
+ select("tr:first-child")
+ assert_equal 0, @matches.size
+ select(":first-child")
+ assert_equal 1, @matches.size
+ assert_equal "thead", @matches[0].name
+ # First of type.
+ select("tr:first-of-type")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ select("thead:first-of-type")
+ assert_equal 1, @matches.size
+ assert_equal "thead", @matches[0].name
+ select("div:first-of-type")
+ assert_equal 0, @matches.size
+ # Last child.
+ select("tr:last-child")
+ assert_equal 1, @matches.size
+ assert_equal "4", @matches[0].attributes["id"]
+ # Last of type.
+ select("tr:last-of-type")
+ assert_equal 1, @matches.size
+ assert_equal "4", @matches[0].attributes["id"]
+ select("thead:last-of-type")
+ assert_equal 1, @matches.size
+ assert_equal "thead", @matches[0].name
+ select("div:last-of-type")
+ assert_equal 0, @matches.size
+ end
+
+
+ def test_first_and_last
+ # Only child.
+ parse(%Q{<table><tr></tr></table>})
+ select("table:only-child")
+ assert_equal 0, @matches.size
+ select("tr:only-child")
+ assert_equal 1, @matches.size
+ assert_equal "tr", @matches[0].name
+ parse(%Q{<table><tr></tr><tr></tr></table>})
+ select("tr:only-child")
+ assert_equal 0, @matches.size
+ # Only of type.
+ parse(%Q{<table><thead></thead><tr></tr><tr></tr></table>})
+ select("thead:only-of-type")
+ assert_equal 1, @matches.size
+ assert_equal "thead", @matches[0].name
+ select("td:only-of-type")
+ assert_equal 0, @matches.size
+ end
+
+
+ def test_empty
+ parse(%Q{<table><tr></tr></table>})
+ select("table:empty")
+ assert_equal 0, @matches.size
+ select("tr:empty")
+ assert_equal 1, @matches.size
+ parse(%Q{<div> </div>})
+ select("div:empty")
+ assert_equal 1, @matches.size
+ end
+
+
+ def test_content
+ parse(%Q{<div> </div>})
+ select("div:content()")
+ assert_equal 1, @matches.size
+ parse(%Q{<div>something </div>})
+ select("div:content()")
+ assert_equal 0, @matches.size
+ select("div:content(something)")
+ assert_equal 1, @matches.size
+ select("div:content( 'something' )")
+ assert_equal 1, @matches.size
+ select("div:content( \"something\" )")
+ assert_equal 1, @matches.size
+ select("div:content(?)", "something")
+ assert_equal 1, @matches.size
+ select("div:content(?)", /something/)
+ assert_equal 1, @matches.size
+ end
+
+
+ #
+ # Test negation.
+ #
+
+
+ def test_element_negation
+ parse(%Q{<p></p><div></div>})
+ select("*")
+ assert_equal 2, @matches.size
+ select("*:not(p)")
+ assert_equal 1, @matches.size
+ assert_equal "div", @matches[0].name
+ select("*:not(div)")
+ assert_equal 1, @matches.size
+ assert_equal "p", @matches[0].name
+ select("*:not(span)")
+ assert_equal 2, @matches.size
+ end
+
+
+ def test_id_negation
+ parse(%Q{<p id="1"></p><p id="2"></p>})
+ select("p")
+ assert_equal 2, @matches.size
+ select(":not(#1)")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ select(":not(#2)")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ end
+
+
+ def test_class_name_negation
+ parse(%Q{<p class="foo"></p><p class="bar"></p>})
+ select("p")
+ assert_equal 2, @matches.size
+ select(":not(.foo)")
+ assert_equal 1, @matches.size
+ assert_equal "bar", @matches[0].attributes["class"]
+ select(":not(.bar)")
+ assert_equal 1, @matches.size
+ assert_equal "foo", @matches[0].attributes["class"]
+ end
+
+
+ def test_attribute_negation
+ parse(%Q{<p title="foo"></p><p title="bar"></p>})
+ select("p")
+ assert_equal 2, @matches.size
+ select(":not([title=foo])")
+ assert_equal 1, @matches.size
+ assert_equal "bar", @matches[0].attributes["title"]
+ select(":not([title=bar])")
+ assert_equal 1, @matches.size
+ assert_equal "foo", @matches[0].attributes["title"]
+ end
+
+
+ def test_pseudo_class_negation
+ parse(%Q{<div><p id="1"></p><p id="2"></p></div>})
+ select("p")
+ assert_equal 2, @matches.size
+ select("p:not(:first-child)")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ select("p:not(:nth-child(2))")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ end
+
+
+ def test_negation_details
+ parse(%Q{<p id="1"></p><p id="2"></p><p id="3"></p>})
+ assert_raises(ArgumentError) { select(":not(") }
+ assert_raises(ArgumentError) { select(":not(:not())") }
+ select("p:not(#1):not(#3)")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ end
+
+
+ def test_select_from_element
+ parse(%Q{<div><p id="1"></p><p id="2"></p></div>})
+ select("div")
+ @matches = @matches[0].select("p")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "2", @matches[1].attributes["id"]
+ end
+
+
+protected
+
+ def parse(html)
+ @html = HTML::Document.new(html).root
+ end
+
+ def select(*selector)
+ @matches = HTML.selector(*selector).select(@html)
+ end
+
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/send_file_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/send_file_test.rb
new file mode 100644
index 000000000..2d876c1bb
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/send_file_test.rb
@@ -0,0 +1,127 @@
+require File.join(File.dirname(__FILE__), '..', 'abstract_unit')
+
+
+module TestFileUtils
+ def file_name() File.basename(__FILE__) end
+ def file_path() File.expand_path(__FILE__) end
+ def file_data() File.open(file_path, 'rb') { |f| f.read } end
+end
+
+
+class SendFileController < ActionController::Base
+ include TestFileUtils
+ layout "layouts/standard" # to make sure layouts don't interfere
+
+ attr_writer :options
+ def options() @options ||= {} end
+
+ def file() send_file(file_path, options) end
+ def data() send_data(file_data, options) end
+
+ def rescue_action(e) raise end
+end
+
+SendFileController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
+
+class SendFileTest < Test::Unit::TestCase
+ include TestFileUtils
+
+ Mime::Type.register "image/png", :png unless defined? Mime::PNG
+
+ def setup
+ @controller = SendFileController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_file_nostream
+ @controller.options = { :stream => false }
+ response = nil
+ assert_nothing_raised { response = process('file') }
+ assert_not_nil response
+ assert_kind_of String, response.body
+ assert_equal file_data, response.body
+ end
+
+ def test_file_stream
+ response = nil
+ assert_nothing_raised { response = process('file') }
+ assert_not_nil response
+ assert_kind_of Proc, response.body
+
+ require 'stringio'
+ output = StringIO.new
+ output.binmode
+ assert_nothing_raised { response.body.call(response, output) }
+ assert_equal file_data, output.string
+ end
+
+ def test_file_url_based_filename
+ @controller.options = { :url_based_filename => true }
+ response = nil
+ assert_nothing_raised { response = process('file') }
+ assert_not_nil response
+ assert_equal "attachment", response.headers["Content-Disposition"]
+ end
+
+ def test_data
+ response = nil
+ assert_nothing_raised { response = process('data') }
+ assert_not_nil response
+
+ assert_kind_of String, response.body
+ assert_equal file_data, response.body
+ end
+
+ def test_headers_after_send_shouldnt_include_charset
+ response = process('data')
+ assert_equal "application/octet-stream", response.content_type
+
+ response = process('file')
+ assert_equal "application/octet-stream", response.content_type
+ end
+
+ # Test that send_file_headers! is setting the correct HTTP headers.
+ def test_send_file_headers!
+ options = {
+ :length => 1,
+ :type => Mime::PNG,
+ :disposition => 'disposition',
+ :filename => 'filename'
+ }
+
+ # Do it a few times: the resulting headers should be identical
+ # no matter how many times you send with the same options.
+ # Test resolving Ticket #458.
+ @controller.headers = {}
+ @controller.send(:send_file_headers!, options)
+ @controller.send(:send_file_headers!, options)
+ @controller.send(:send_file_headers!, options)
+
+ h = @controller.headers
+ assert_equal 1, h['Content-Length']
+ assert_equal 'image/png', h['Content-Type']
+ assert_equal 'disposition; filename="filename"', h['Content-Disposition']
+ assert_equal 'binary', h['Content-Transfer-Encoding']
+
+ # test overriding Cache-Control: no-cache header to fix IE open/save dialog
+ @controller.headers = { 'Cache-Control' => 'no-cache' }
+ @controller.send(:send_file_headers!, options)
+ h = @controller.headers
+ assert_equal 'private', h['Cache-Control']
+ end
+
+ %w(file data).each do |method|
+ define_method "test_send_#{method}_status" do
+ @controller.options = { :stream => false, :status => 500 }
+ assert_nothing_raised { assert_not_nil process(method) }
+ assert_equal '500 Internal Server Error', @response.headers['Status']
+ end
+
+ define_method "test_default_send_#{method}_status" do
+ @controller.options = { :stream => false }
+ assert_nothing_raised { assert_not_nil process(method) }
+ assert_equal ActionController::Base::DEFAULT_RENDER_STATUS_CODE, @response.headers['Status']
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/session/cookie_store_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/session/cookie_store_test.rb
new file mode 100755
index 000000000..b2655c72d
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/session/cookie_store_test.rb
@@ -0,0 +1,246 @@
+require "#{File.dirname(__FILE__)}/../../abstract_unit"
+require 'action_controller/cgi_process'
+require 'action_controller/cgi_ext'
+
+require 'stringio'
+
+
+class CGI::Session::CookieStore
+ def ensure_secret_secure_with_test_hax(secret)
+ if secret == CookieStoreTest.default_session_options['secret']
+ return true
+ else
+ ensure_secret_secure_without_test_hax(secret)
+ end
+ end
+ alias_method_chain :ensure_secret_secure, :test_hax
+end
+
+
+# Expose for tests.
+class CGI
+ attr_reader :output_cookies, :output_hidden
+
+ class Session
+ attr_reader :dbman
+
+ class CookieStore
+ attr_reader :data, :original, :cookie_options
+ end
+ end
+end
+
+class CookieStoreTest < Test::Unit::TestCase
+ def self.default_session_options
+ { 'database_manager' => CGI::Session::CookieStore,
+ 'session_key' => '_myapp_session',
+ 'secret' => 'Keep it secret; keep it safe.',
+ 'no_cookies' => true,
+ 'no_hidden' => true }
+ end
+
+ def self.cookies
+ { :empty => ['BAgw--0686dcaccc01040f4bd4f35fe160afe9bc04c330', {}],
+ :a_one => ['BAh7BiIGYWkG--5689059497d7f122a7119f171aef81dcfd807fec', { 'a' => 1 }],
+ :typical => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7BiILbm90aWNlIgxIZXkgbm93--9d20154623b9eeea05c62ab819be0e2483238759', { 'user_id' => 123, 'flash' => { 'notice' => 'Hey now' }}],
+ :flashed => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA%3D%3D--bf9785a666d3c4ac09f7fe3353496b437546cfbf', { 'user_id' => 123, 'flash' => {} }] }
+ end
+
+ def setup
+ ENV.delete('HTTP_COOKIE')
+ end
+
+ def test_raises_argument_error_if_missing_session_key
+ [nil, ''].each do |blank|
+ assert_raise(ArgumentError, blank.inspect) { new_session 'session_key' => blank }
+ end
+ end
+
+ def test_raises_argument_error_if_missing_secret
+ [nil, ''].each do |blank|
+ assert_raise(ArgumentError, blank.inspect) { new_session 'secret' => blank }
+ end
+ end
+
+ def test_raises_argument_error_if_secret_is_probably_insecure
+ ["password", "secret", "12345678901234567890123456789"].each do |blank|
+ assert_raise(ArgumentError, blank.inspect) { new_session 'secret' => blank }
+ end
+ end
+
+ def test_reconfigures_session_to_omit_id_cookie_and_hidden_field
+ new_session do |session|
+ assert_equal true, @options['no_hidden']
+ assert_equal true, @options['no_cookies']
+ end
+ end
+
+ def test_restore_unmarshals_missing_cookie_as_empty_hash
+ new_session do |session|
+ assert_nil session.dbman.data
+ assert_nil session['test']
+ assert_equal Hash.new, session.dbman.data
+ end
+ end
+
+ def test_restore_unmarshals_good_cookies
+ cookies(:empty, :a_one, :typical).each do |value, expected|
+ set_cookie! value
+ new_session do |session|
+ assert_nil session['lazy loads the data hash']
+ assert_equal expected, session.dbman.data
+ end
+ end
+ end
+
+ def test_restore_deletes_tampered_cookies
+ set_cookie! 'a--b'
+ new_session do |session|
+ assert_raise(CGI::Session::CookieStore::TamperedWithCookie) { session['fail'] }
+ assert_cookie_deleted session
+ end
+ end
+
+ def test_close_doesnt_write_cookie_if_data_is_blank
+ new_session do |session|
+ assert_no_cookies session
+ session.close
+ assert_no_cookies session
+ end
+ end
+
+ def test_close_doesnt_write_cookie_if_data_is_unchanged
+ set_cookie! cookie_value(:typical)
+ new_session do |session|
+ assert_no_cookies session
+ session['user_id'] = session['user_id']
+ session.close
+ assert_no_cookies session
+ end
+ end
+
+ def test_close_raises_when_data_overflows
+ set_cookie! cookie_value(:empty)
+ new_session do |session|
+ session['overflow'] = 'bye!' * 1024
+ assert_raise(CGI::Session::CookieStore::CookieOverflow) { session.close }
+ assert_no_cookies session
+ end
+ end
+
+ def test_close_marshals_and_writes_cookie
+ set_cookie! cookie_value(:typical)
+ new_session do |session|
+ assert_no_cookies session
+ session['flash'] = {}
+ assert_no_cookies session
+ session.close
+ assert_equal 1, session.cgi.output_cookies.size
+ cookie = session.cgi.output_cookies.first
+ assert_cookie cookie, cookie_value(:flashed)
+ end
+ end
+
+ def test_delete_writes_expired_empty_cookie_and_sets_data_to_nil
+ set_cookie! cookie_value(:typical)
+ new_session do |session|
+ assert_no_cookies session
+ session.delete
+ assert_cookie_deleted session
+
+ # @data is set to nil so #close doesn't send another cookie.
+ session.close
+ assert_cookie_deleted session
+ end
+ end
+
+ def test_new_session_doesnt_reuse_deleted_cookie_data
+ set_cookie! cookie_value(:typical)
+
+ new_session do |session|
+ assert_not_nil session['user_id']
+ session.delete
+
+ # Start a new session using the same CGI instance.
+ post_delete_session = CGI::Session.new(session.cgi, self.class.default_session_options)
+ assert_nil post_delete_session['user_id']
+ end
+ end
+
+ private
+ def assert_no_cookies(session)
+ assert_nil session.cgi.output_cookies, session.cgi.output_cookies.inspect
+ end
+
+ def assert_cookie_deleted(session, message = 'Expected session deletion cookie to be set')
+ assert_equal 1, session.cgi.output_cookies.size
+ cookie = session.cgi.output_cookies.first
+ assert_cookie cookie, nil, 1.year.ago.to_date, message
+ end
+
+ def assert_cookie(cookie, value = nil, expires = nil, message = nil)
+ assert_equal '_myapp_session', cookie.name, message
+ assert_equal [value].compact, cookie.value, message
+ assert_equal expires, cookie.expires ? cookie.expires.to_date : cookie.expires, message
+ end
+
+
+ def cookies(*which)
+ self.class.cookies.values_at(*which)
+ end
+
+ def cookie_value(which)
+ self.class.cookies[which].first
+ end
+
+ def set_cookie!(value)
+ ENV['HTTP_COOKIE'] = "_myapp_session=#{value}"
+ end
+
+ def new_session(options = {})
+ with_cgi do |cgi|
+ assert_nil cgi.output_hidden, "Output hidden params should be empty: #{cgi.output_hidden.inspect}"
+ assert_nil cgi.output_cookies, "Output cookies should be empty: #{cgi.output_cookies.inspect}"
+
+ @options = self.class.default_session_options.merge(options)
+ session = CGI::Session.new(cgi, @options)
+
+ assert_nil cgi.output_hidden, "Output hidden params should be empty: #{cgi.output_hidden.inspect}"
+ assert_nil cgi.output_cookies, "Output cookies should be empty: #{cgi.output_cookies.inspect}"
+
+ yield session if block_given?
+ session
+ end
+ end
+
+ def with_cgi
+ ENV['REQUEST_METHOD'] = 'GET'
+ ENV['HTTP_HOST'] = 'example.com'
+ ENV['QUERY_STRING'] = ''
+
+ cgi = CGI.new('query', StringIO.new(''))
+ yield cgi if block_given?
+ cgi
+ end
+end
+
+
+class CookieStoreWithBlockAsSecretTest < CookieStoreTest
+ def self.default_session_options
+ CookieStoreTest.default_session_options.merge 'secret' => Proc.new { 'Keep it secret; keep it safe.' }
+ end
+end
+
+
+class CookieStoreWithMD5DigestTest < CookieStoreTest
+ def self.default_session_options
+ CookieStoreTest.default_session_options.merge 'digest' => 'MD5'
+ end
+
+ def self.cookies
+ { :empty => ['BAgw--0415cc0be9579b14afc22ee2d341aa21', {}],
+ :a_one => ['BAh7BiIGYWkG--5a0ed962089cc6600ff44168a5d59bc8', { 'a' => 1 }],
+ :typical => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7BiILbm90aWNlIgxIZXkgbm93--f426763f6ef435b3738b493600db8d64', { 'user_id' => 123, 'flash' => { 'notice' => 'Hey now' }}],
+ :flashed => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA%3D%3D--0af9156650dab044a53a91a4ddec2c51', { 'user_id' => 123, 'flash' => {} }] }
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/session/mem_cache_store_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/session/mem_cache_store_test.rb
new file mode 100644
index 000000000..3afb7a8ea
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/session/mem_cache_store_test.rb
@@ -0,0 +1,182 @@
+require "#{File.dirname(__FILE__)}/../../abstract_unit"
+require 'action_controller/cgi_process'
+require 'action_controller/cgi_ext'
+
+
+class CGI::Session
+ def cache
+ dbman.instance_variable_get(:@cache)
+ end
+end
+
+
+uses_mocha 'MemCacheStore tests' do
+if defined? MemCache::MemCacheError
+
+class MemCacheStoreTest < Test::Unit::TestCase
+ SESSION_KEY_RE = /^session:[0-9a-z]+/
+ CONN_TEST_KEY = 'connection_test'
+ MULTI_TEST_KEY = '0123456789'
+ TEST_DATA = 'Hello test'
+
+ def self.get_mem_cache_if_available
+ begin
+ require 'memcache'
+ cache = MemCache.new('127.0.0.1')
+ # Test availability of the connection
+ cache.set(CONN_TEST_KEY, 1)
+ unless cache.get(CONN_TEST_KEY) == 1
+ puts 'Warning: memcache server available but corrupted.'
+ return nil
+ end
+ rescue LoadError, MemCache::MemCacheError
+ return nil
+ end
+ return cache
+ end
+
+ CACHE = get_mem_cache_if_available
+
+
+ def test_initialization
+ assert_raise(ArgumentError) { new_session('session_id' => '!invalid_id') }
+ new_session do |s|
+ assert_equal Hash.new, s.cache.get('session:' + s.session_id)
+ end
+ end
+
+
+ def test_storage
+ d = rand(0xffff)
+ new_session do |s|
+ session_key = 'session:' + s.session_id
+ unless CACHE
+ s.cache.expects(:get).with(session_key) \
+ .returns(:test => d)
+ s.cache.expects(:set).with(session_key,
+ has_entry(:test, d),
+ 0)
+ end
+ s[:test] = d
+ s.close
+ assert_equal d, s.cache.get(session_key)[:test]
+ assert_equal d, s[:test]
+ end
+ end
+
+
+ def test_deletion
+ new_session do |s|
+ session_key = 'session:' + s.session_id
+ unless CACHE
+ s.cache.expects(:delete)
+ s.cache.expects(:get).with(session_key) \
+ .returns(nil)
+ end
+ s[:test] = rand(0xffff)
+ s.delete
+ assert_nil s.cache.get(session_key)
+ end
+ end
+
+
+ def test_other_session_retrieval
+ new_session do |sa|
+ unless CACHE
+ sa.cache.expects(:set).with('session:' + sa.session_id,
+ has_entry(:test, TEST_DATA),
+ 0)
+ end
+ sa[:test] = TEST_DATA
+ sa.close
+ new_session('session_id' => sa.session_id) do |sb|
+ unless CACHE
+ sb.cache.expects(:[]).with('session:' + sb.session_id) \
+ .returns(:test => TEST_DATA)
+ end
+ assert_equal(TEST_DATA, sb[:test])
+ end
+ end
+ end
+
+
+ def test_multiple_sessions
+ s_slots = Array.new(10)
+ operation = :write
+ last_data = nil
+ reads = writes = 0
+ 50.times do
+ current = rand(10)
+ s_slots[current] ||= new_session('session_id' => MULTI_TEST_KEY,
+ 'new_session' => true)
+ s = s_slots[current]
+ case operation
+ when :write
+ last_data = rand(0xffff)
+ unless CACHE
+ s.cache.expects(:set).with('session:' + MULTI_TEST_KEY,
+ { :test => last_data },
+ 0)
+ end
+ s[:test] = last_data
+ s.close
+ writes += 1
+ when :read
+ # Make CGI::Session#[] think there was no data retrieval yet.
+ # Normally, the session caches the data during its lifetime.
+ s.instance_variable_set(:@data, nil)
+ unless CACHE
+ s.cache.expects(:[]).with('session:' + MULTI_TEST_KEY) \
+ .returns(:test => last_data)
+ end
+ d = s[:test]
+ assert_equal(last_data, d, "OK reads: #{reads}, OK writes: #{writes}")
+ reads += 1
+ end
+ operation = rand(5) == 0 ? :write : :read
+ end
+ end
+
+
+
+ private
+ def obtain_session_options
+ options = { 'database_manager' => CGI::Session::MemCacheStore,
+ 'session_key' => '_test_app_session'
+ }
+ # if don't have running memcache server we use mock instead
+ unless CACHE
+ options['cache'] = c = mock
+ c.stubs(:[]).with(regexp_matches(SESSION_KEY_RE))
+ c.stubs(:get).with(regexp_matches(SESSION_KEY_RE)) \
+ .returns(Hash.new)
+ c.stubs(:add).with(regexp_matches(SESSION_KEY_RE),
+ instance_of(Hash),
+ 0)
+ end
+ options
+ end
+
+
+ def new_session(options = {})
+ with_cgi do |cgi|
+ @options = obtain_session_options.merge(options)
+ session = CGI::Session.new(cgi, @options)
+ yield session if block_given?
+ return session
+ end
+ end
+
+ def with_cgi
+ ENV['REQUEST_METHOD'] = 'GET'
+ ENV['HTTP_HOST'] = 'example.com'
+ ENV['QUERY_STRING'] = ''
+
+ cgi = CGI.new('query', StringIO.new(''))
+ yield cgi if block_given?
+ cgi
+ end
+end
+
+end # defined? MemCache
+end # uses_mocha
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/session_fixation_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/session_fixation_test.rb
new file mode 100644
index 000000000..34a7aa2d0
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/session_fixation_test.rb
@@ -0,0 +1,89 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+
+class SessionFixationTest < Test::Unit::TestCase
+ class MockCGI < CGI #:nodoc:
+ attr_accessor :stdoutput, :env_table
+
+ def initialize(env, data = '')
+ self.env_table = env
+ self.stdoutput = StringIO.new
+ super(nil, StringIO.new(data))
+ end
+ end
+
+ class TestController < ActionController::Base
+ session :session_key => '_myapp_session_id', :secret => CGI::Session.generate_unique_id, :except => :default_session_key
+ session :cookie_only => false, :only => :allow_session_fixation
+
+ def default_session_key
+ render :text => "default_session_key"
+ end
+
+ def custom_session_key
+ render :text => "custom_session_key: #{params[:id]}"
+ end
+
+ def allow_session_fixation
+ render :text => "allow_session_fixation"
+ end
+
+ def rescue_action(e) raise end
+ end
+
+ def setup
+ @controller = TestController.new
+ end
+
+ def test_should_be_able_to_make_a_successful_request
+ cgi = mock_cgi_for_request_to(:custom_session_key, :id => 1)
+
+ assert_nothing_raised do
+ @controller.send(:process, ActionController::CgiRequest.new(cgi, {}), ActionController::CgiResponse.new(cgi))
+ end
+ assert_equal 'custom_session_key: 1', @controller.response.body
+ assert_not_nil @controller.session
+ end
+
+ def test_should_catch_session_fixation_attempt
+ cgi = mock_cgi_for_request_to(:custom_session_key, :_myapp_session_id => 42)
+
+ assert_raises ActionController::CgiRequest::SessionFixationAttempt do
+ @controller.send(:process, ActionController::CgiRequest.new(cgi, {}), ActionController::CgiResponse.new(cgi))
+ end
+ assert_nil @controller.session
+ end
+
+ def test_should_not_catch_session_fixation_attempt_when_cookie_only_setting_is_disabled
+ cgi = mock_cgi_for_request_to(:allow_session_fixation, :_myapp_session_id => 42)
+
+ assert_nothing_raised do
+ @controller.send(:process, ActionController::CgiRequest.new(cgi, {}), ActionController::CgiResponse.new(cgi))
+ end
+ assert ! @controller.response.body.blank?
+ assert_not_nil @controller.session
+ end
+
+ def test_should_catch_session_fixation_attempt_with_default_session_key
+ ActionController::Base.session_store = :p_store # using the default session_key is not possible with cookie store
+ cgi = mock_cgi_for_request_to(:default_session_key, :_session_id => 42)
+
+ assert_raises ActionController::CgiRequest::SessionFixationAttempt do
+ @controller.send(:process, ActionController::CgiRequest.new(cgi, {}), ActionController::CgiResponse.new(cgi))
+ end
+ assert @controller.response.body.blank?
+ assert_nil @controller.session
+ end
+
+private
+
+ def mock_cgi_for_request_to(action, params = {})
+ MockCGI.new({
+ "REQUEST_METHOD" => "GET",
+ "QUERY_STRING" => "action=#{action}&#{params.to_query}",
+ "REQUEST_URI" => "/",
+ "SERVER_PORT" => "80",
+ "HTTP_HOST" => "testdomain.com" }, '')
+ end
+
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/session_management_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/session_management_test.rb
new file mode 100644
index 000000000..44fb93104
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/session_management_test.rb
@@ -0,0 +1,156 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class SessionManagementTest < Test::Unit::TestCase
+ class SessionOffController < ActionController::Base
+ session :off
+
+ def show
+ render :text => "done"
+ end
+
+ def tell
+ render :text => "done"
+ end
+ end
+
+ class TestController < ActionController::Base
+ session :off, :only => :show
+ session :session_secure => true, :except => :show
+ session :off, :only => :conditional,
+ :if => Proc.new { |r| r.parameters[:ws] }
+
+ def show
+ render :text => "done"
+ end
+
+ def tell
+ render :text => "done"
+ end
+
+ def conditional
+ render :text => ">>>#{params[:ws]}<<<"
+ end
+ end
+
+ class SpecializedController < SessionOffController
+ session :disabled => false, :only => :something
+
+ def something
+ render :text => "done"
+ end
+
+ def another
+ render :text => "done"
+ end
+ end
+
+ class AssociationCachingTestController < ActionController::Base
+ class ObjectWithAssociationCache
+ def initialize
+ @cached_associations = false
+ end
+
+ def fetch_associations
+ @cached_associations = true
+ end
+
+ def clear_association_cache
+ @cached_associations = false
+ end
+
+ def has_cached_associations?
+ @cached_associations
+ end
+ end
+
+ def show
+ session[:object] = ObjectWithAssociationCache.new
+ session[:object].fetch_associations
+ if session[:object].has_cached_associations?
+ render :text => "has cached associations"
+ else
+ render :text => "does not have cached associations"
+ end
+ end
+
+ def tell
+ if session[:object]
+ if session[:object].has_cached_associations?
+ render :text => "has cached associations"
+ else
+ render :text => "does not have cached associations"
+ end
+ else
+ render :text => "there is no object"
+ end
+ end
+ end
+
+
+ def setup
+ @request, @response = ActionController::TestRequest.new,
+ ActionController::TestResponse.new
+ end
+
+ def test_session_off_globally
+ @controller = SessionOffController.new
+ get :show
+ assert_equal false, @request.session_options
+ get :tell
+ assert_equal false, @request.session_options
+ end
+
+ def test_session_off_conditionally
+ @controller = TestController.new
+ get :show
+ assert_equal false, @request.session_options
+ get :tell
+ assert_instance_of Hash, @request.session_options
+ assert @request.session_options[:session_secure]
+ end
+
+ def test_controller_specialization_overrides_settings
+ @controller = SpecializedController.new
+ get :something
+ assert_instance_of Hash, @request.session_options
+ get :another
+ assert_equal false, @request.session_options
+ end
+
+ def test_session_off_with_if
+ @controller = TestController.new
+ get :conditional
+ assert_instance_of Hash, @request.session_options
+ get :conditional, :ws => "ws"
+ assert_equal false, @request.session_options
+ end
+
+ def test_session_store_setting
+ ActionController::Base.session_store = :drb_store
+ assert_equal CGI::Session::DRbStore, ActionController::Base.session_store
+
+ if Object.const_defined?(:ActiveRecord)
+ ActionController::Base.session_store = :active_record_store
+ assert_equal CGI::Session::ActiveRecordStore, ActionController::Base.session_store
+ end
+ end
+
+ def test_process_cleanup_with_session_management_support
+ @controller = AssociationCachingTestController.new
+ get :show
+ assert_equal "has cached associations", @response.body
+ get :tell
+ assert_equal "does not have cached associations", @response.body
+ end
+
+ def test_session_is_enabled
+ @controller = TestController.new
+ get :show
+ assert_nothing_raised do
+ assert_equal false, @controller.session_enabled?
+ end
+
+ get :tell
+ assert @controller.session_enabled?
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/test_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/test_test.rb
new file mode 100644
index 000000000..11e48da91
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/test_test.rb
@@ -0,0 +1,623 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+require "#{File.dirname(__FILE__)}/fake_controllers"
+require "action_controller/test_case"
+
+class TestTest < Test::Unit::TestCase
+ class TestController < ActionController::Base
+ def set_flash
+ flash["test"] = ">#{flash["test"]}<"
+ render :text => 'ignore me'
+ end
+
+ def render_raw_post
+ raise Test::Unit::AssertionFailedError, "#raw_post is blank" if request.raw_post.blank?
+ render :text => request.raw_post
+ end
+
+ def render_body
+ render :text => request.body.read
+ end
+
+ def test_params
+ render :text => params.inspect
+ end
+
+ def test_uri
+ render :text => request.request_uri
+ end
+
+ def test_query_string
+ render :text => request.query_string
+ end
+
+ def test_html_output
+ render :text => <<HTML
+<html>
+ <body>
+ <a href="/"><img src="/images/button.png" /></a>
+ <div id="foo">
+ <ul>
+ <li class="item">hello</li>
+ <li class="item">goodbye</li>
+ </ul>
+ </div>
+ <div id="bar">
+ <form action="/somewhere">
+ Name: <input type="text" name="person[name]" id="person_name" />
+ </form>
+ </div>
+ </body>
+</html>
+HTML
+ end
+
+ def test_xml_output
+ response.content_type = "application/xml"
+ render :text => <<XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <area>area is an empty tag in HTML, raising an error if not in xml mode</area>
+</root>
+XML
+ end
+
+ def test_only_one_param
+ render :text => (params[:left] && params[:right]) ? "EEP, Both here!" : "OK"
+ end
+
+ def test_remote_addr
+ render :text => (request.remote_addr || "not specified")
+ end
+
+ def test_file_upload
+ render :text => params[:file].size
+ end
+
+ def test_send_file
+ send_file(File.expand_path(__FILE__))
+ end
+
+ def redirect_to_same_controller
+ redirect_to :controller => 'test', :action => 'test_uri', :id => 5
+ end
+
+ def redirect_to_different_controller
+ redirect_to :controller => 'fail', :id => 5
+ end
+
+ def create
+ head :created, :location => 'created resource'
+ end
+
+ private
+ def rescue_action(e)
+ raise e
+ end
+
+ def generate_url(opts)
+ url_for(opts.merge(:action => "test_uri"))
+ end
+ end
+
+ def setup
+ @controller = TestController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ ActionController::Routing::Routes.reload
+ ActionController::Routing.use_controllers! %w(content admin/user test_test/test)
+ end
+
+ def teardown
+ ActionController::Routing::Routes.reload
+ end
+
+ def test_raw_post_handling
+ params = {:page => {:name => 'page name'}, 'some key' => 123}
+ post :render_raw_post, params.dup
+
+ assert_equal params.to_query, @response.body
+ end
+
+ def test_body_stream
+ params = { :page => { :name => 'page name' }, 'some key' => 123 }
+
+ post :render_body, params.dup
+
+ assert_equal params.to_query, @response.body
+ end
+
+ def test_process_without_flash
+ process :set_flash
+ assert_equal '><', flash['test']
+ end
+
+ def test_process_with_flash
+ process :set_flash, nil, nil, { "test" => "value" }
+ assert_equal '>value<', flash['test']
+ end
+
+ def test_process_with_request_uri_with_no_params
+ process :test_uri
+ assert_equal "/test_test/test/test_uri", @response.body
+ end
+
+ def test_process_with_request_uri_with_params
+ process :test_uri, :id => 7
+ assert_equal "/test_test/test/test_uri/7", @response.body
+ end
+
+ def test_process_with_request_uri_with_params_with_explicit_uri
+ @request.set_REQUEST_URI "/explicit/uri"
+ process :test_uri, :id => 7
+ assert_equal "/explicit/uri", @response.body
+ end
+
+ def test_process_with_query_string
+ process :test_query_string, :q => 'test'
+ assert_equal "q=test", @response.body
+ end
+
+ def test_process_with_query_string_with_explicit_uri
+ @request.set_REQUEST_URI "/explicit/uri?q=test?extra=question"
+ process :test_query_string
+ assert_equal "q=test?extra=question", @response.body
+ end
+
+ def test_multiple_calls
+ process :test_only_one_param, :left => true
+ assert_equal "OK", @response.body
+ process :test_only_one_param, :right => true
+ assert_equal "OK", @response.body
+ end
+
+ def test_assert_tag_tag
+ process :test_html_output
+
+ # there is a 'form' tag
+ assert_tag :tag => 'form'
+ # there is not an 'hr' tag
+ assert_no_tag :tag => 'hr'
+ end
+
+ def test_assert_tag_attributes
+ process :test_html_output
+
+ # there is a tag with an 'id' of 'bar'
+ assert_tag :attributes => { :id => "bar" }
+ # there is no tag with a 'name' of 'baz'
+ assert_no_tag :attributes => { :name => "baz" }
+ end
+
+ def test_assert_tag_parent
+ process :test_html_output
+
+ # there is a tag with a parent 'form' tag
+ assert_tag :parent => { :tag => "form" }
+ # there is no tag with a parent of 'input'
+ assert_no_tag :parent => { :tag => "input" }
+ end
+
+ def test_assert_tag_child
+ process :test_html_output
+
+ # there is a tag with a child 'input' tag
+ assert_tag :child => { :tag => "input" }
+ # there is no tag with a child 'strong' tag
+ assert_no_tag :child => { :tag => "strong" }
+ end
+
+ def test_assert_tag_ancestor
+ process :test_html_output
+
+ # there is a 'li' tag with an ancestor having an id of 'foo'
+ assert_tag :ancestor => { :attributes => { :id => "foo" } }, :tag => "li"
+ # there is no tag of any kind with an ancestor having an href matching 'foo'
+ assert_no_tag :ancestor => { :attributes => { :href => /foo/ } }
+ end
+
+ def test_assert_tag_descendant
+ process :test_html_output
+
+ # there is a tag with a descendant 'li' tag
+ assert_tag :descendant => { :tag => "li" }
+ # there is no tag with a descendant 'html' tag
+ assert_no_tag :descendant => { :tag => "html" }
+ end
+
+ def test_assert_tag_sibling
+ process :test_html_output
+
+ # there is a tag with a sibling of class 'item'
+ assert_tag :sibling => { :attributes => { :class => "item" } }
+ # there is no tag with a sibling 'ul' tag
+ assert_no_tag :sibling => { :tag => "ul" }
+ end
+
+ def test_assert_tag_after
+ process :test_html_output
+
+ # there is a tag following a sibling 'div' tag
+ assert_tag :after => { :tag => "div" }
+ # there is no tag following a sibling tag with id 'bar'
+ assert_no_tag :after => { :attributes => { :id => "bar" } }
+ end
+
+ def test_assert_tag_before
+ process :test_html_output
+
+ # there is a tag preceding a tag with id 'bar'
+ assert_tag :before => { :attributes => { :id => "bar" } }
+ # there is no tag preceding a 'form' tag
+ assert_no_tag :before => { :tag => "form" }
+ end
+
+ def test_assert_tag_children_count
+ process :test_html_output
+
+ # there is a tag with 2 children
+ assert_tag :children => { :count => 2 }
+ # in particular, there is a <ul> tag with two children (a nameless pair of <li>s)
+ assert_tag :tag => 'ul', :children => { :count => 2 }
+ # there is no tag with 4 children
+ assert_no_tag :children => { :count => 4 }
+ end
+
+ def test_assert_tag_children_less_than
+ process :test_html_output
+
+ # there is a tag with less than 5 children
+ assert_tag :children => { :less_than => 5 }
+ # there is no 'ul' tag with less than 2 children
+ assert_no_tag :children => { :less_than => 2 }, :tag => "ul"
+ end
+
+ def test_assert_tag_children_greater_than
+ process :test_html_output
+
+ # there is a 'body' tag with more than 1 children
+ assert_tag :children => { :greater_than => 1 }, :tag => "body"
+ # there is no tag with more than 10 children
+ assert_no_tag :children => { :greater_than => 10 }
+ end
+
+ def test_assert_tag_children_only
+ process :test_html_output
+
+ # there is a tag containing only one child with an id of 'foo'
+ assert_tag :children => { :count => 1,
+ :only => { :attributes => { :id => "foo" } } }
+ # there is no tag containing only one 'li' child
+ assert_no_tag :children => { :count => 1, :only => { :tag => "li" } }
+ end
+
+ def test_assert_tag_content
+ process :test_html_output
+
+ # the output contains the string "Name"
+ assert_tag :content => /Name/
+ # the output does not contain the string "test"
+ assert_no_tag :content => /test/
+ end
+
+ def test_assert_tag_multiple
+ process :test_html_output
+
+ # there is a 'div', id='bar', with an immediate child whose 'action'
+ # attribute matches the regexp /somewhere/.
+ assert_tag :tag => "div", :attributes => { :id => "bar" },
+ :child => { :attributes => { :action => /somewhere/ } }
+
+ # there is no 'div', id='foo', with a 'ul' child with more than
+ # 2 "li" children.
+ assert_no_tag :tag => "div", :attributes => { :id => "foo" },
+ :child => {
+ :tag => "ul",
+ :children => { :greater_than => 2,
+ :only => { :tag => "li" } } }
+ end
+
+ def test_assert_tag_children_without_content
+ process :test_html_output
+
+ # there is a form tag with an 'input' child which is a self closing tag
+ assert_tag :tag => "form",
+ :children => { :count => 1,
+ :only => { :tag => "input" } }
+
+ # the body tag has an 'a' child which in turn has an 'img' child
+ assert_tag :tag => "body",
+ :children => { :count => 1,
+ :only => { :tag => "a",
+ :children => { :count => 1,
+ :only => { :tag => "img" } } } }
+ end
+
+ def test_should_not_impose_childless_html_tags_in_xml
+ process :test_xml_output
+
+ begin
+ $stderr = StringIO.new
+ assert_select 'area' #This will cause a warning if content is processed as HTML
+ $stderr.rewind && err = $stderr.read
+ ensure
+ $stderr = STDERR
+ end
+
+ assert err.empty?
+ end
+
+ def test_assert_tag_attribute_matching
+ @response.body = '<input type="text" name="my_name">'
+ assert_tag :tag => 'input',
+ :attributes => { :name => /my/, :type => 'text' }
+ assert_no_tag :tag => 'input',
+ :attributes => { :name => 'my', :type => 'text' }
+ assert_no_tag :tag => 'input',
+ :attributes => { :name => /^my$/, :type => 'text' }
+ end
+
+ def test_assert_tag_content_matching
+ @response.body = "<p>hello world</p>"
+ assert_tag :tag => "p", :content => "hello world"
+ assert_tag :tag => "p", :content => /hello/
+ assert_no_tag :tag => "p", :content => "hello"
+ end
+
+ def test_assert_generates
+ assert_generates 'controller/action/5', :controller => 'controller', :action => 'action', :id => '5'
+ assert_generates 'controller/action/7', {:id => "7"}, {:controller => "controller", :action => "action"}
+ assert_generates 'controller/action/5', {:controller => "controller", :action => "action", :id => "5", :name => "bob"}, {}, {:name => "bob"}
+ assert_generates 'controller/action/7', {:id => "7", :name => "bob"}, {:controller => "controller", :action => "action"}, {:name => "bob"}
+ assert_generates 'controller/action/7', {:id => "7"}, {:controller => "controller", :action => "action", :name => "bob"}, {}
+ end
+
+ def test_assert_routing
+ assert_routing 'content', :controller => 'content', :action => 'index'
+ end
+
+ def test_assert_routing_in_module
+ assert_routing 'admin/user', :controller => 'admin/user', :action => 'index'
+ end
+
+ def test_params_passing
+ get :test_params, :page => {:name => "Page name", :month => '4', :year => '2004', :day => '6'}
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {'controller' => 'test_test/test', 'action' => 'test_params',
+ 'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}},
+ parsed_params
+ )
+ end
+
+ def test_id_converted_to_string
+ get :test_params, :id => 20, :foo => Object.new
+ assert_kind_of String, @request.path_parameters['id']
+ end
+
+ def test_array_path_parameter_handled_properly
+ with_routing do |set|
+ set.draw do |map|
+ map.connect 'file/*path', :controller => 'test_test/test', :action => 'test_params'
+ map.connect ':controller/:action/:id'
+ end
+
+ get :test_params, :path => ['hello', 'world']
+ assert_equal ['hello', 'world'], @request.path_parameters['path']
+ assert_equal 'hello/world', @request.path_parameters['path'].to_s
+ end
+ end
+
+ def test_assert_realistic_path_parameters
+ get :test_params, :id => 20, :foo => Object.new
+
+ # All elements of path_parameters should use string keys
+ @request.path_parameters.keys.each do |key|
+ assert_kind_of String, key
+ end
+ end
+
+ def test_with_routing_places_routes_back
+ assert ActionController::Routing::Routes
+ routes_id = ActionController::Routing::Routes.object_id
+
+ begin
+ with_routing { raise 'fail' }
+ fail 'Should not be here.'
+ rescue RuntimeError
+ end
+
+ assert ActionController::Routing::Routes
+ assert_equal routes_id, ActionController::Routing::Routes.object_id
+ end
+
+ def test_remote_addr
+ get :test_remote_addr
+ assert_equal "0.0.0.0", @response.body
+
+ @request.remote_addr = "192.0.0.1"
+ get :test_remote_addr
+ assert_equal "192.0.0.1", @response.body
+ end
+
+ def test_header_properly_reset_after_remote_http_request
+ xhr :get, :test_params
+ assert_nil @request.env['HTTP_X_REQUESTED_WITH']
+ end
+
+ def test_header_properly_reset_after_get_request
+ get :test_params
+ @request.recycle!
+ assert_nil @request.instance_variable_get("@request_method")
+ end
+
+ %w(controller response request).each do |variable|
+ %w(get post put delete head process).each do |method|
+ define_method("test_#{variable}_missing_for_#{method}_raises_error") do
+ remove_instance_variable "@#{variable}"
+ begin
+ send(method, :test_remote_addr)
+ assert false, "expected RuntimeError, got nothing"
+ rescue RuntimeError => error
+ assert true
+ assert_match %r{@#{variable} is nil}, error.message
+ rescue => error
+ assert false, "expected RuntimeError, got #{error.class}"
+ end
+ end
+ end
+ end
+
+ FILES_DIR = File.dirname(__FILE__) + '/../fixtures/multipart'
+
+ def test_test_uploaded_file
+ filename = 'mona_lisa.jpg'
+ path = "#{FILES_DIR}/#{filename}"
+ content_type = 'image/png'
+
+ file = ActionController::TestUploadedFile.new(path, content_type)
+ assert_equal filename, file.original_filename
+ assert_equal content_type, file.content_type
+ assert_equal file.path, file.local_path
+ assert_equal File.read(path), file.read
+ end
+
+ def test_test_uploaded_file_with_binary
+ filename = 'mona_lisa.jpg'
+ path = "#{FILES_DIR}/#{filename}"
+ content_type = 'image/png'
+
+ binary_uploaded_file = ActionController::TestUploadedFile.new(path, content_type, :binary)
+ assert_equal File.open(path, 'rb').read, binary_uploaded_file.read
+
+ plain_uploaded_file = ActionController::TestUploadedFile.new(path, content_type)
+ assert_equal File.open(path, 'r').read, plain_uploaded_file.read
+ end
+
+ def test_fixture_file_upload_with_binary
+ filename = 'mona_lisa.jpg'
+ path = "#{FILES_DIR}/#{filename}"
+ content_type = 'image/jpg'
+
+ binary_file_upload = fixture_file_upload(path, content_type, :binary)
+ assert_equal File.open(path, 'rb').read, binary_file_upload.read
+
+ plain_file_upload = fixture_file_upload(path, content_type)
+ assert_equal File.open(path, 'r').read, plain_file_upload.read
+ end
+
+ def test_fixture_file_upload
+ post :test_file_upload, :file => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")
+ assert_equal '159528', @response.body
+ end
+
+ def test_test_uploaded_file_exception_when_file_doesnt_exist
+ assert_raise(RuntimeError) { ActionController::TestUploadedFile.new('non_existent_file') }
+ end
+
+ def test_assert_follow_redirect_to_same_controller
+ with_foo_routing do |set|
+ get :redirect_to_same_controller
+ assert_response :redirect
+ assert_redirected_to :controller => 'test_test/test', :action => 'test_uri', :id => 5
+ assert_nothing_raised { follow_redirect }
+ end
+ end
+
+ def test_assert_follow_redirect_to_different_controller
+ with_foo_routing do |set|
+ get :redirect_to_different_controller
+ assert_response :redirect
+ assert_redirected_to :controller => 'fail', :id => 5
+ assert_raise(RuntimeError) { follow_redirect }
+ end
+ end
+
+ def test_redirect_url_only_cares_about_location_header
+ get :create
+ assert_response :created
+
+ # Redirect url doesn't care that it wasn't a :redirect response.
+ assert_equal 'created resource', @response.redirect_url
+ assert_equal @response.redirect_url, redirect_to_url
+
+ # Must be a :redirect response.
+ assert_raise(Test::Unit::AssertionFailedError) do
+ assert_redirected_to 'created resource'
+ end
+ end
+
+ def test_binary_content_works_with_send_file
+ get :test_send_file
+ assert_nothing_raised(NoMethodError) { @response.binary_content }
+ end
+
+ protected
+ def with_foo_routing
+ with_routing do |set|
+ set.draw do |map|
+ map.generate_url 'foo', :controller => 'test'
+ map.connect ':controller/:action/:id'
+ end
+ yield set
+ end
+ end
+end
+
+
+class CleanBacktraceTest < Test::Unit::TestCase
+ def test_should_reraise_the_same_object
+ exception = Test::Unit::AssertionFailedError.new('message')
+ clean_backtrace { raise exception }
+ rescue => caught
+ assert_equal exception.object_id, caught.object_id
+ assert_equal exception.message, caught.message
+ end
+
+ def test_should_clean_assertion_lines_from_backtrace
+ path = File.expand_path("#{File.dirname(__FILE__)}/../../lib/action_controller")
+ exception = Test::Unit::AssertionFailedError.new('message')
+ exception.set_backtrace ["#{path}/abc", "#{path}/assertions/def"]
+ clean_backtrace { raise exception }
+ rescue => caught
+ assert_equal ["#{path}/abc"], caught.backtrace
+ end
+
+ def test_should_only_clean_assertion_failure_errors
+ clean_backtrace do
+ raise "can't touch this", [File.expand_path("#{File.dirname(__FILE__)}/../../lib/action_controller/assertions/abc")]
+ end
+ rescue => caught
+ assert !caught.backtrace.empty?
+ end
+end
+
+class InferringClassNameTest < Test::Unit::TestCase
+ def test_determine_controller_class
+ assert_equal ContentController, determine_class("ContentControllerTest")
+ end
+
+ def test_determine_controller_class_with_nonsense_name
+ assert_raises ActionController::NonInferrableControllerError do
+ determine_class("HelloGoodBye")
+ end
+ end
+
+ def test_determine_controller_class_with_sensible_name_where_no_controller_exists
+ assert_raises ActionController::NonInferrableControllerError do
+ determine_class("NoControllerWithThisNameTest")
+ end
+ end
+
+ private
+ def determine_class(name)
+ ActionController::TestCase.determine_default_controller_class(name)
+ end
+end
+
+class CrazyNameTest < ActionController::TestCase
+ tests ContentController
+ def test_controller_class_can_be_set_manually_not_just_inferred
+ assert_equal ContentController, self.class.controller_class
+ end
+end
+
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/url_rewriter_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/url_rewriter_test.rb
new file mode 100644
index 000000000..75497c832
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/url_rewriter_test.rb
@@ -0,0 +1,246 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+ActionController::UrlRewriter
+
+class UrlRewriterTests < Test::Unit::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @params = {}
+ @rewriter = ActionController::UrlRewriter.new(@request, @params)
+ end
+
+ def test_port
+ assert_equal('http://test.host:1271/c/a/i',
+ @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :port => 1271)
+ )
+ end
+
+ def test_protocol_with_and_without_separator
+ assert_equal('https://test.host/c/a/i',
+ @rewriter.rewrite(:protocol => 'https', :controller => 'c', :action => 'a', :id => 'i')
+ )
+
+ assert_equal('https://test.host/c/a/i',
+ @rewriter.rewrite(:protocol => 'https://', :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_user_name_and_password
+ assert_equal(
+ 'http://david:secret@test.host/c/a/i',
+ @rewriter.rewrite(:user => "david", :password => "secret", :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_user_name_and_password_with_escape_codes
+ assert_equal(
+ 'http://openid.aol.com%2Fnextangler:one+two%3F@test.host/c/a/i',
+ @rewriter.rewrite(:user => "openid.aol.com/nextangler", :password => "one two?", :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_anchor
+ assert_equal(
+ 'http://test.host/c/a/i#anchor',
+ @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor')
+ )
+ end
+
+ def test_overwrite_params
+ @params[:controller] = 'hi'
+ @params[:action] = 'bye'
+ @params[:id] = '2'
+
+ assert_equal '/hi/hi/2', @rewriter.rewrite(:only_path => true, :overwrite_params => {:action => 'hi'})
+ u = @rewriter.rewrite(:only_path => false, :overwrite_params => {:action => 'hi'})
+ assert_match %r(/hi/hi/2$), u
+ end
+
+ def test_overwrite_removes_original
+ @params[:controller] = 'search'
+ @params[:action] = 'list'
+ @params[:list_page] = 1
+
+ assert_equal '/search/list?list_page=2', @rewriter.rewrite(:only_path => true, :overwrite_params => {"list_page" => 2})
+ u = @rewriter.rewrite(:only_path => false, :overwrite_params => {:list_page => 2})
+ assert_equal 'http://test.host/search/list?list_page=2', u
+ end
+
+ def test_to_str
+ @params[:controller] = 'hi'
+ @params[:action] = 'bye'
+ @request.parameters[:id] = '2'
+
+ assert_equal 'http://, test.host, /, hi, bye, {"id"=>"2"}', @rewriter.to_str
+ end
+
+ def test_trailing_slash
+ options = {:controller => 'foo', :action => 'bar', :id => '3', :only_path => true}
+ assert_equal '/foo/bar/3', @rewriter.rewrite(options)
+ assert_equal '/foo/bar/3?query=string', @rewriter.rewrite(options.merge({:query => 'string'}))
+ options.update({:trailing_slash => true})
+ assert_equal '/foo/bar/3/', @rewriter.rewrite(options)
+ options.update({:query => 'string'})
+ assert_equal '/foo/bar/3/?query=string', @rewriter.rewrite(options)
+ end
+end
+
+class UrlWriterTests < Test::Unit::TestCase
+
+ class W
+ include ActionController::UrlWriter
+ end
+
+ def teardown
+ W.default_url_options.clear
+ end
+
+ def add_host!
+ W.default_url_options[:host] = 'www.basecamphq.com'
+ end
+
+ def test_exception_is_thrown_without_host
+ assert_raises RuntimeError do
+ W.new.url_for :controller => 'c', :action => 'a', :id => 'i'
+ end
+ end
+
+ def test_anchor
+ assert_equal('/c/a#anchor',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => 'anchor')
+ )
+ end
+
+ def test_default_host
+ add_host!
+ assert_equal('http://www.basecamphq.com/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_host_may_be_overridden
+ add_host!
+ assert_equal('http://37signals.basecamphq.com/c/a/i',
+ W.new.url_for(:host => '37signals.basecamphq.com', :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_port
+ add_host!
+ assert_equal('http://www.basecamphq.com:3000/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :port => 3000)
+ )
+ end
+
+ def test_protocol
+ add_host!
+ assert_equal('https://www.basecamphq.com/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
+ )
+ end
+
+ def test_protocol_with_and_without_separator
+ add_host!
+ assert_equal('https://www.basecamphq.com/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
+ )
+ assert_equal('https://www.basecamphq.com/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https://')
+ )
+ end
+
+ def test_named_route
+ ActionController::Routing::Routes.draw do |map|
+ map.no_args '/this/is/verbose', :controller => 'home', :action => 'index'
+ map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index'
+ map.connect ':controller/:action/:id'
+ end
+
+ # We need to create a new class in order to install the new named route.
+ kls = Class.new { include ActionController::UrlWriter }
+ controller = kls.new
+ assert controller.respond_to?(:home_url)
+ assert_equal 'http://www.basecamphq.com/home/sweet/home/again',
+ controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again')
+
+ assert_equal("/home/sweet/home/alabama", controller.send(:home_path, :user => 'alabama', :host => 'unused'))
+ assert_equal("http://www.basecamphq.com/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'www.basecamphq.com'))
+ assert_equal("http://www.basecamphq.com/this/is/verbose", controller.send(:no_args_url, :host=>'www.basecamphq.com'))
+ ensure
+ ActionController::Routing::Routes.load!
+ end
+
+ def test_only_path
+ ActionController::Routing::Routes.draw do |map|
+ map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index'
+ map.connect ':controller/:action/:id'
+ end
+
+ # We need to create a new class in order to install the new named route.
+ kls = Class.new { include ActionController::UrlWriter }
+ controller = kls.new
+ assert controller.respond_to?(:home_url)
+ assert_equal '/brave/new/world',
+ controller.send(:url_for, :controller => 'brave', :action => 'new', :id => 'world', :only_path => true)
+
+ assert_equal("/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'unused', :only_path => true))
+ assert_equal("/home/sweet/home/alabama", controller.send(:home_path, 'alabama'))
+ ensure
+ ActionController::Routing::Routes.load!
+ end
+
+ def test_one_parameter
+ assert_equal('/c/a?param=val',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :param => 'val')
+ )
+ end
+
+ def test_two_parameters
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :p1 => 'X1', :p2 => 'Y2')
+ params = extract_params(url)
+ assert_equal params[0], { :p1 => 'X1' }.to_query
+ assert_equal params[1], { :p2 => 'Y2' }.to_query
+ end
+
+ def test_hash_parameter
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:name => 'Bob', :category => 'prof'})
+ params = extract_params(url)
+ assert_equal params[0], { 'query[category]' => 'prof' }.to_query
+ assert_equal params[1], { 'query[name]' => 'Bob' }.to_query
+ end
+
+ def test_array_parameter
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => ['Bob', 'prof'])
+ params = extract_params(url)
+ assert_equal params[0], { 'query[]' => 'Bob' }.to_query
+ assert_equal params[1], { 'query[]' => 'prof' }.to_query
+ end
+
+ def test_hash_recursive_parameters
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:person => {:name => 'Bob', :position => 'prof'}, :hobby => 'piercing'})
+ params = extract_params(url)
+ assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
+ assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
+ assert_equal params[2], { 'query[person][position]' => 'prof' }.to_query
+ end
+
+ def test_hash_recursive_and_array_parameters
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => 101, :query => {:person => {:name => 'Bob', :position => ['prof', 'art director']}, :hobby => 'piercing'})
+ assert_match %r(^/c/a/101), url
+ params = extract_params(url)
+ assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
+ assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
+ assert_equal params[2], { 'query[person][position][]' => 'prof' }.to_query
+ assert_equal params[3], { 'query[person][position][]' => 'art director' }.to_query
+ end
+
+ def test_path_generation_for_symbol_parameter_keys
+ assert_generates("/image", :controller=> :image)
+ end
+
+ private
+ def extract_params(url)
+ url.split('?', 2).last.split('&')
+ end
+
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/verification_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/verification_test.rb
new file mode 100644
index 000000000..e61bd5ccc
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/verification_test.rb
@@ -0,0 +1,253 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class VerificationTest < Test::Unit::TestCase
+ class TestController < ActionController::Base
+ verify :only => :guarded_one, :params => "one",
+ :add_flash => { :error => 'unguarded' },
+ :redirect_to => { :action => "unguarded" }
+
+ verify :only => :guarded_two, :params => %w( one two ),
+ :redirect_to => { :action => "unguarded" }
+
+ verify :only => :guarded_with_flash, :params => "one",
+ :add_flash => { :notice => "prereqs failed" },
+ :redirect_to => { :action => "unguarded" }
+
+ verify :only => :guarded_in_session, :session => "one",
+ :redirect_to => { :action => "unguarded" }
+
+ verify :only => [:multi_one, :multi_two], :session => %w( one two ),
+ :redirect_to => { :action => "unguarded" }
+
+ verify :only => :guarded_by_method, :method => :post,
+ :redirect_to => { :action => "unguarded" }
+
+ verify :only => :guarded_by_xhr, :xhr => true,
+ :redirect_to => { :action => "unguarded" }
+
+ verify :only => :guarded_by_not_xhr, :xhr => false,
+ :redirect_to => { :action => "unguarded" }
+
+ before_filter :unconditional_redirect, :only => :two_redirects
+ verify :only => :two_redirects, :method => :post,
+ :redirect_to => { :action => "unguarded" }
+
+ verify :only => :must_be_post, :method => :post, :render => { :status => 405, :text => "Must be post" }, :add_headers => { "Allow" => "POST" }
+
+ verify :only => :guarded_one_for_named_route_test, :params => "one",
+ :redirect_to => :foo_url
+
+ verify :only => :no_default_action, :params => "santa"
+
+ def guarded_one
+ render :text => "#{params[:one]}"
+ end
+
+ def guarded_one_for_named_route_test
+ render :text => "#{params[:one]}"
+ end
+
+ def guarded_with_flash
+ render :text => "#{params[:one]}"
+ end
+
+ def guarded_two
+ render :text => "#{params[:one]}:#{params[:two]}"
+ end
+
+ def guarded_in_session
+ render :text => "#{session["one"]}"
+ end
+
+ def multi_one
+ render :text => "#{session["one"]}:#{session["two"]}"
+ end
+
+ def multi_two
+ render :text => "#{session["two"]}:#{session["one"]}"
+ end
+
+ def guarded_by_method
+ render :text => "#{request.method}"
+ end
+
+ def guarded_by_xhr
+ render :text => "#{request.xhr?}"
+ end
+
+ def guarded_by_not_xhr
+ render :text => "#{request.xhr?}"
+ end
+
+ def unguarded
+ render :text => "#{params[:one]}"
+ end
+
+ def two_redirects
+ render :nothing => true
+ end
+
+ def must_be_post
+ render :text => "Was a post!"
+ end
+
+ def no_default_action
+ # Will never run
+ end
+
+ protected
+ def rescue_action(e) raise end
+
+ def unconditional_redirect
+ redirect_to :action => "unguarded"
+ end
+ end
+
+ def setup
+ @controller = TestController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ ActionController::Routing::Routes.add_named_route :foo, '/foo', :controller => 'test', :action => 'foo'
+ end
+
+ def test_no_deprecation_warning_for_named_route
+ assert_not_deprecated do
+ get :guarded_one_for_named_route_test, :two => "not one"
+ assert_redirected_to '/foo'
+ end
+ end
+
+ def test_guarded_one_with_prereqs
+ get :guarded_one, :one => "here"
+ assert_equal "here", @response.body
+ end
+
+ def test_guarded_one_without_prereqs
+ get :guarded_one
+ assert_redirected_to :action => "unguarded"
+ assert_equal 'unguarded', flash[:error]
+ end
+
+ def test_guarded_with_flash_with_prereqs
+ get :guarded_with_flash, :one => "here"
+ assert_equal "here", @response.body
+ assert flash.empty?
+ end
+
+ def test_guarded_with_flash_without_prereqs
+ get :guarded_with_flash
+ assert_redirected_to :action => "unguarded"
+ assert_equal "prereqs failed", flash[:notice]
+ end
+
+ def test_guarded_two_with_prereqs
+ get :guarded_two, :one => "here", :two => "there"
+ assert_equal "here:there", @response.body
+ end
+
+ def test_guarded_two_without_prereqs_one
+ get :guarded_two, :two => "there"
+ assert_redirected_to :action => "unguarded"
+ end
+
+ def test_guarded_two_without_prereqs_two
+ get :guarded_two, :one => "here"
+ assert_redirected_to :action => "unguarded"
+ end
+
+ def test_guarded_two_without_prereqs_both
+ get :guarded_two
+ assert_redirected_to :action => "unguarded"
+ end
+
+ def test_unguarded_with_params
+ get :unguarded, :one => "here"
+ assert_equal "here", @response.body
+ end
+
+ def test_unguarded_without_params
+ get :unguarded
+ assert_equal "", @response.body
+ end
+
+ def test_guarded_in_session_with_prereqs
+ get :guarded_in_session, {}, "one" => "here"
+ assert_equal "here", @response.body
+ end
+
+ def test_guarded_in_session_without_prereqs
+ get :guarded_in_session
+ assert_redirected_to :action => "unguarded"
+ end
+
+ def test_multi_one_with_prereqs
+ get :multi_one, {}, "one" => "here", "two" => "there"
+ assert_equal "here:there", @response.body
+ end
+
+ def test_multi_one_without_prereqs
+ get :multi_one
+ assert_redirected_to :action => "unguarded"
+ end
+
+ def test_multi_two_with_prereqs
+ get :multi_two, {}, "one" => "here", "two" => "there"
+ assert_equal "there:here", @response.body
+ end
+
+ def test_multi_two_without_prereqs
+ get :multi_two
+ assert_redirected_to :action => "unguarded"
+ end
+
+ def test_guarded_by_method_with_prereqs
+ post :guarded_by_method
+ assert_equal "post", @response.body
+ end
+
+ def test_guarded_by_method_without_prereqs
+ get :guarded_by_method
+ assert_redirected_to :action => "unguarded"
+ end
+
+ def test_guarded_by_xhr_with_prereqs
+ xhr :post, :guarded_by_xhr
+ assert_equal "true", @response.body
+ end
+
+ def test_guarded_by_xhr_without_prereqs
+ get :guarded_by_xhr
+ assert_redirected_to :action => "unguarded"
+ end
+
+ def test_guarded_by_not_xhr_with_prereqs
+ get :guarded_by_not_xhr
+ assert_equal "false", @response.body
+ end
+
+ def test_guarded_by_not_xhr_without_prereqs
+ xhr :post, :guarded_by_not_xhr
+ assert_redirected_to :action => "unguarded"
+ end
+
+ def test_guarded_post_and_calls_render_succeeds
+ post :must_be_post
+ assert_equal "Was a post!", @response.body
+ end
+
+ def test_default_failure_should_be_a_bad_request
+ post :no_default_action
+ assert_response :bad_request
+ end
+
+ def test_guarded_post_and_calls_render_fails_and_sets_allow_header
+ get :must_be_post
+ assert_response 405
+ assert_equal "Must be post", @response.body
+ assert_equal "POST", @response.headers["Allow"]
+ end
+
+ def test_second_redirect
+ assert_nothing_raised { get :two_redirects }
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/view_paths_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/view_paths_test.rb
new file mode 100644
index 000000000..e29e2b9f3
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/view_paths_test.rb
@@ -0,0 +1,137 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class ViewLoadPathsTest < Test::Unit::TestCase
+
+ LOAD_PATH_ROOT = File.join(File.dirname(__FILE__), '..', 'fixtures')
+
+ ActionController::Base.view_paths = [ LOAD_PATH_ROOT ]
+
+ class TestController < ActionController::Base
+ def self.controller_path() "test" end
+ def rescue_action(e) raise end
+
+ before_filter :add_view_path, :only => :hello_world_at_request_time
+
+ def hello_world() end
+ def hello_world_at_request_time() render(:action => 'hello_world') end
+ private
+ def add_view_path
+ self.class.view_paths.unshift "#{LOAD_PATH_ROOT}/override"
+ end
+ end
+
+ class Test::SubController < ActionController::Base
+ layout 'test/sub'
+ def hello_world; render(:template => 'test/hello_world'); end
+ end
+
+ def setup
+ TestController.view_paths = nil
+ ActionView::Base.cache_template_extensions = false
+ @controller = TestController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ # Track the last warning.
+ @old_behavior = ActiveSupport::Deprecation.behavior
+ @last_message = nil
+ ActiveSupport::Deprecation.behavior = Proc.new { |message, callback| @last_message = message }
+ end
+
+ def teardown
+ ActiveSupport::Deprecation.behavior = @old_behavior
+ ActionView::Base.cache_template_extensions = true
+ end
+
+ def test_template_load_path_was_set_correctly
+ assert_equal [ LOAD_PATH_ROOT ], @controller.view_paths
+ end
+
+ def test_controller_appends_view_path_correctly
+ TestController.append_view_path 'foo'
+ assert_equal [LOAD_PATH_ROOT, 'foo'], @controller.view_paths
+
+ TestController.append_view_path(%w(bar baz))
+ assert_equal [LOAD_PATH_ROOT, 'foo', 'bar', 'baz'], @controller.view_paths
+ end
+
+ def test_controller_prepends_view_path_correctly
+ TestController.prepend_view_path 'baz'
+ assert_equal ['baz', LOAD_PATH_ROOT], @controller.view_paths
+
+ TestController.prepend_view_path(%w(foo bar))
+ assert_equal ['foo', 'bar', 'baz', LOAD_PATH_ROOT], @controller.view_paths
+ end
+
+ def test_template_appends_view_path_correctly
+ @controller.instance_variable_set :@template, ActionView::Base.new(TestController.view_paths, {}, @controller)
+ class_view_paths = TestController.view_paths
+
+ @controller.append_view_path 'foo'
+ assert_equal [LOAD_PATH_ROOT, 'foo'], @controller.view_paths
+
+ @controller.append_view_path(%w(bar baz))
+ assert_equal [LOAD_PATH_ROOT, 'foo', 'bar', 'baz'], @controller.view_paths
+ assert_equal class_view_paths, TestController.view_paths
+ end
+
+ def test_template_prepends_view_path_correctly
+ @controller.instance_variable_set :@template, ActionView::Base.new(TestController.view_paths, {}, @controller)
+ class_view_paths = TestController.view_paths
+
+ @controller.prepend_view_path 'baz'
+ assert_equal ['baz', LOAD_PATH_ROOT], @controller.view_paths
+
+ @controller.prepend_view_path(%w(foo bar))
+ assert_equal ['foo', 'bar', 'baz', LOAD_PATH_ROOT], @controller.view_paths
+ assert_equal class_view_paths, TestController.view_paths
+ end
+
+ def test_view_paths
+ get :hello_world
+ assert_response :success
+ assert_equal "Hello world!", @response.body
+ end
+
+ def test_view_paths_override
+ TestController.view_paths.unshift "#{LOAD_PATH_ROOT}/override"
+ get :hello_world
+ assert_response :success
+ assert_equal "Hello overridden world!", @response.body
+ end
+
+ def test_view_paths_override_for_layouts_in_controllers_with_a_module
+ @controller = Test::SubController.new
+ Test::SubController.view_paths = [ "#{LOAD_PATH_ROOT}/override", LOAD_PATH_ROOT, "#{LOAD_PATH_ROOT}/override2" ]
+ get :hello_world
+ assert_response :success
+ assert_equal "layout: Hello overridden world!", @response.body
+ end
+
+ def test_view_paths_override_at_request_time
+ get :hello_world_at_request_time
+ assert_response :success
+ assert_equal "Hello overridden world!", @response.body
+ end
+
+ def test_inheritance
+ original_load_paths = ActionController::Base.view_paths
+
+ self.class.class_eval %{
+ class A < ActionController::Base; end
+ class B < A; end
+ class C < ActionController::Base; end
+ }
+
+ A.view_paths = [ 'a/path' ]
+
+ assert_equal [ 'a/path' ], A.view_paths
+ assert_equal A.view_paths, B.view_paths
+ assert_equal original_load_paths, C.view_paths
+
+ C.view_paths = []
+ assert_nothing_raised { C.view_paths << 'c/path' }
+ assert_equal ['c/path'], C.view_paths
+ end
+
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/controller/webservice_test.rb b/vendor/rails-2.0.2/actionpack/test/controller/webservice_test.rb
new file mode 100644
index 000000000..d89de5c8f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/controller/webservice_test.rb
@@ -0,0 +1,184 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class WebServiceTest < Test::Unit::TestCase
+ class MockCGI < CGI #:nodoc:
+ attr_accessor :stdoutput, :env_table
+
+ def initialize(env, data = '')
+ self.env_table = env
+ self.stdoutput = StringIO.new
+ super(nil, StringIO.new(data))
+ end
+ end
+
+ class TestController < ActionController::Base
+ session :off
+
+ def assign_parameters
+ if params[:full]
+ render :text => dump_params_keys
+ else
+ render :text => (params.keys - ['controller', 'action']).sort.join(", ")
+ end
+ end
+
+ def dump_params_keys(hash=params)
+ hash.keys.sort.inject("") do |s, k|
+ value = hash[k]
+ value = Hash === value ? "(#{dump_params_keys(value)})" : ""
+ s << ", " unless s.empty?
+ s << "#{k}#{value}"
+ end
+ end
+
+ def rescue_action(e) raise end
+ end
+
+ def setup
+ @controller = TestController.new
+ @default_param_parsers = ActionController::Base.param_parsers.dup
+ end
+
+ def teardown
+ ActionController::Base.param_parsers = @default_param_parsers
+ end
+
+ def test_check_parameters
+ process('GET')
+ assert_equal '', @controller.response.body
+ end
+
+ def test_post_xml
+ process('POST', 'application/xml', '<entry attributed="true"><summary>content...</summary></entry>')
+
+ assert_equal 'entry', @controller.response.body
+ assert @controller.params.has_key?(:entry)
+ assert_equal 'content...', @controller.params["entry"]['summary']
+ assert_equal 'true', @controller.params["entry"]['attributed']
+ end
+
+ def test_put_xml
+ process('PUT', 'application/xml', '<entry attributed="true"><summary>content...</summary></entry>')
+
+ assert_equal 'entry', @controller.response.body
+ assert @controller.params.has_key?(:entry)
+ assert_equal 'content...', @controller.params["entry"]['summary']
+ assert_equal 'true', @controller.params["entry"]['attributed']
+ end
+
+ def test_register_and_use_yaml
+ ActionController::Base.param_parsers[Mime::YAML] = Proc.new { |d| YAML.load(d) }
+ process('POST', 'application/x-yaml', {"entry" => "loaded from yaml"}.to_yaml)
+ assert_equal 'entry', @controller.response.body
+ assert @controller.params.has_key?(:entry)
+ assert_equal 'loaded from yaml', @controller.params["entry"]
+ end
+
+ def test_register_and_use_yaml_as_symbol
+ ActionController::Base.param_parsers[Mime::YAML] = :yaml
+ process('POST', 'application/x-yaml', {"entry" => "loaded from yaml"}.to_yaml)
+ assert_equal 'entry', @controller.response.body
+ assert @controller.params.has_key?(:entry)
+ assert_equal 'loaded from yaml', @controller.params["entry"]
+ end
+
+ def test_register_and_use_xml_simple
+ ActionController::Base.param_parsers[Mime::XML] = Proc.new { |data| XmlSimple.xml_in(data, 'ForceArray' => false) }
+ process('POST', 'application/xml', '<request><summary>content...</summary><title>SimpleXml</title></request>' )
+ assert_equal 'summary, title', @controller.response.body
+ assert @controller.params.has_key?(:summary)
+ assert @controller.params.has_key?(:title)
+ assert_equal 'content...', @controller.params["summary"]
+ assert_equal 'SimpleXml', @controller.params["title"]
+ end
+
+ def test_use_xml_ximple_with_empty_request
+ ActionController::Base.param_parsers[Mime::XML] = :xml_simple
+ assert_nothing_raised { process('POST', 'application/xml', "") }
+ assert_equal "", @controller.response.body
+ end
+
+ def test_dasherized_keys_as_xml
+ ActionController::Base.param_parsers[Mime::XML] = :xml_simple
+ process('POST', 'application/xml', "<first-key>\n<sub-key>...</sub-key>\n</first-key>", true)
+ assert_equal 'action, controller, first_key(sub_key), full', @controller.response.body
+ assert_equal "...", @controller.params[:first_key][:sub_key]
+ end
+
+ def test_typecast_as_xml
+ ActionController::Base.param_parsers[Mime::XML] = :xml_simple
+ process('POST', 'application/xml', <<-XML)
+ <data>
+ <a type="integer">15</a>
+ <b type="boolean">false</b>
+ <c type="boolean">true</c>
+ <d type="date">2005-03-17</d>
+ <e type="datetime">2005-03-17T21:41:07Z</e>
+ <f>unparsed</f>
+ <g type="integer">1</g>
+ <g>hello</g>
+ <g type="date">1974-07-25</g>
+ </data>
+ XML
+ params = @controller.params
+ assert_equal 15, params[:data][:a]
+ assert_equal false, params[:data][:b]
+ assert_equal true, params[:data][:c]
+ assert_equal Date.new(2005,3,17), params[:data][:d]
+ assert_equal Time.utc(2005,3,17,21,41,7), params[:data][:e]
+ assert_equal "unparsed", params[:data][:f]
+ assert_equal [1, "hello", Date.new(1974,7,25)], params[:data][:g]
+ end
+
+ def test_entities_unescaped_as_xml_simple
+ ActionController::Base.param_parsers[Mime::XML] = :xml_simple
+ process('POST', 'application/xml', <<-XML)
+ <data>&lt;foo &quot;bar&apos;s&quot; &amp; friends&gt;</data>
+ XML
+ assert_equal %(<foo "bar's" & friends>), @controller.params[:data]
+ end
+
+ def test_typecast_as_yaml
+ ActionController::Base.param_parsers[Mime::YAML] = :yaml
+ process('POST', 'application/x-yaml', <<-YAML)
+ ---
+ data:
+ a: 15
+ b: false
+ c: true
+ d: 2005-03-17
+ e: 2005-03-17T21:41:07Z
+ f: unparsed
+ g:
+ - 1
+ - hello
+ - 1974-07-25
+ YAML
+ params = @controller.params
+ assert_equal 15, params[:data][:a]
+ assert_equal false, params[:data][:b]
+ assert_equal true, params[:data][:c]
+ assert_equal Date.new(2005,3,17), params[:data][:d]
+ assert_equal Time.utc(2005,3,17,21,41,7), params[:data][:e]
+ assert_equal "unparsed", params[:data][:f]
+ assert_equal [1, "hello", Date.new(1974,7,25)], params[:data][:g]
+ end
+
+ private
+
+ def process(verb, content_type = 'application/x-www-form-urlencoded', data = '', full=false)
+
+ cgi = MockCGI.new({
+ 'REQUEST_METHOD' => verb,
+ 'CONTENT_TYPE' => content_type,
+ 'QUERY_STRING' => "action=assign_parameters&controller=webservicetest/test#{"&full=1" if full}",
+ "REQUEST_URI" => "/",
+ "HTTP_HOST" => 'testdomain.com',
+ "CONTENT_LENGTH" => data.size,
+ "SERVER_PORT" => "80",
+ "HTTPS" => "off"}, data)
+
+ @controller.send(:process, ActionController::CgiRequest.new(cgi, {}), ActionController::CgiResponse.new(cgi))
+ end
+
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/addresses/list.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/addresses/list.erb
new file mode 100644
index 000000000..c75e01eec
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/addresses/list.erb
@@ -0,0 +1 @@
+We only need to get this far!
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/companies.yml b/vendor/rails-2.0.2/actionpack/test/fixtures/companies.yml
new file mode 100644
index 000000000..707f72abc
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/companies.yml
@@ -0,0 +1,24 @@
+thirty_seven_signals:
+ id: 1
+ name: 37Signals
+ rating: 4
+
+TextDrive:
+ id: 2
+ name: TextDrive
+ rating: 4
+
+PlanetArgon:
+ id: 3
+ name: Planet Argon
+ rating: 4
+
+Google:
+ id: 4
+ name: Google
+ rating: 4
+
+Ionist:
+ id: 5
+ name: Ioni.st
+ rating: 4 \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/company.rb b/vendor/rails-2.0.2/actionpack/test/fixtures/company.rb
new file mode 100644
index 000000000..0d1c29b90
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/company.rb
@@ -0,0 +1,9 @@
+class Company < ActiveRecord::Base
+ attr_protected :rating
+ set_sequence_name :companies_nonstd_seq
+
+ validates_presence_of :name
+ def validate
+ errors.add('rating', 'rating should not be 2') if rating == 2
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml b/vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml
new file mode 100644
index 000000000..25dc74688
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml
@@ -0,0 +1 @@
+<hello>world</hello> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_for_rhtml.rhtml b/vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_for_rhtml.rhtml
new file mode 100644
index 000000000..c7926d48b
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_for_rhtml.rhtml
@@ -0,0 +1 @@
+<%= 'hello world!' %> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_for_rjs.rjs b/vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_for_rjs.rjs
new file mode 100644
index 000000000..8d614d04a
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_for_rjs.rjs
@@ -0,0 +1 @@
+page.alert 'hello world!' \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_for_rxml.rxml b/vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_for_rxml.rxml
new file mode 100644
index 000000000..598d62e2f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/content_type/render_default_for_rxml.rxml
@@ -0,0 +1 @@
+xml.p "Hello world!" \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/db_definitions/sqlite.sql b/vendor/rails-2.0.2/actionpack/test/fixtures/db_definitions/sqlite.sql
new file mode 100644
index 000000000..358c2bbb0
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/db_definitions/sqlite.sql
@@ -0,0 +1,43 @@
+CREATE TABLE 'companies' (
+ 'id' INTEGER PRIMARY KEY NOT NULL,
+ 'name' TEXT DEFAULT NULL,
+ 'rating' INTEGER DEFAULT 1
+);
+
+CREATE TABLE 'replies' (
+ 'id' INTEGER PRIMARY KEY NOT NULL,
+ 'content' text,
+ 'created_at' datetime,
+ 'updated_at' datetime,
+ 'topic_id' integer,
+ 'developer_id' integer
+);
+
+CREATE TABLE 'topics' (
+ 'id' INTEGER PRIMARY KEY NOT NULL,
+ 'title' varchar(255),
+ 'subtitle' varchar(255),
+ 'content' text,
+ 'created_at' datetime,
+ 'updated_at' datetime
+);
+
+CREATE TABLE 'developers' (
+ 'id' INTEGER PRIMARY KEY NOT NULL,
+ 'name' TEXT DEFAULT NULL,
+ 'salary' INTEGER DEFAULT 70000,
+ 'created_at' DATETIME DEFAULT NULL,
+ 'updated_at' DATETIME DEFAULT NULL
+);
+
+CREATE TABLE 'projects' (
+ 'id' INTEGER PRIMARY KEY NOT NULL,
+ 'name' TEXT DEFAULT NULL
+);
+
+CREATE TABLE 'developers_projects' (
+ 'developer_id' INTEGER NOT NULL,
+ 'project_id' INTEGER NOT NULL,
+ 'joined_on' DATE DEFAULT NULL,
+ 'access_level' INTEGER DEFAULT 1
+);
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/developer.rb b/vendor/rails-2.0.2/actionpack/test/fixtures/developer.rb
new file mode 100644
index 000000000..c70eda34c
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/developer.rb
@@ -0,0 +1,9 @@
+class Developer < ActiveRecord::Base
+ has_and_belongs_to_many :projects
+ has_many :replies
+ has_many :topics, :through => :replies
+end
+
+class DeVeLoPeR < ActiveRecord::Base
+ set_table_name "developers"
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/developers.yml b/vendor/rails-2.0.2/actionpack/test/fixtures/developers.yml
new file mode 100644
index 000000000..308bf75de
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/developers.yml
@@ -0,0 +1,21 @@
+david:
+ id: 1
+ name: David
+ salary: 80000
+
+jamis:
+ id: 2
+ name: Jamis
+ salary: 150000
+
+<% for digit in 3..10 %>
+dev_<%= digit %>:
+ id: <%= digit %>
+ name: fixture_<%= digit %>
+ salary: 100000
+<% end %>
+
+poor_jamis:
+ id: 11
+ name: Jamis
+ salary: 9000 \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/developers_projects.yml b/vendor/rails-2.0.2/actionpack/test/fixtures/developers_projects.yml
new file mode 100644
index 000000000..cee359c7c
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/developers_projects.yml
@@ -0,0 +1,13 @@
+david_action_controller:
+ developer_id: 1
+ project_id: 2
+ joined_on: 2004-10-10
+
+david_active_record:
+ developer_id: 1
+ project_id: 1
+ joined_on: 2004-10-10
+
+jamis_active_record:
+ developer_id: 2
+ project_id: 1 \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/fun/games/hello_world.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/fun/games/hello_world.erb
new file mode 100644
index 000000000..1ebfbe253
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/fun/games/hello_world.erb
@@ -0,0 +1 @@
+Living in a nested world \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/helpers/abc_helper.rb b/vendor/rails-2.0.2/actionpack/test/fixtures/helpers/abc_helper.rb
new file mode 100644
index 000000000..7104ff373
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/helpers/abc_helper.rb
@@ -0,0 +1,5 @@
+module AbcHelper
+ def bare_a() end
+ def bare_b() end
+ def bare_c() end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/helpers/fun/games_helper.rb b/vendor/rails-2.0.2/actionpack/test/fixtures/helpers/fun/games_helper.rb
new file mode 100644
index 000000000..bf60d9db0
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/helpers/fun/games_helper.rb
@@ -0,0 +1,3 @@
+module Fun::GamesHelper
+ def stratego() "Iz guuut!" end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/helpers/fun/pdf_helper.rb b/vendor/rails-2.0.2/actionpack/test/fixtures/helpers/fun/pdf_helper.rb
new file mode 100644
index 000000000..c4aea5a3f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/helpers/fun/pdf_helper.rb
@@ -0,0 +1,3 @@
+module Fun::PdfHelper
+ def foobar() 'baz' end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/alt/hello.rhtml b/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/alt/hello.rhtml
new file mode 100644
index 000000000..fcda6cf97
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/alt/hello.rhtml
@@ -0,0 +1 @@
+alt/hello.rhtml
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml b/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml
new file mode 100644
index 000000000..5f86a7de4
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml
@@ -0,0 +1 @@
+controller_name_space/nested.rhtml <%= yield %> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/item.rhtml b/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/item.rhtml
new file mode 100644
index 000000000..1bc7cbda0
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/item.rhtml
@@ -0,0 +1 @@
+item.rhtml <%= yield %> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/layout_test.rhtml b/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/layout_test.rhtml
new file mode 100644
index 000000000..c0f2642b4
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/layout_test.rhtml
@@ -0,0 +1 @@
+layout_test.rhtml <%= yield %> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb
new file mode 100644
index 000000000..3b65e54f9
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb
@@ -0,0 +1 @@
+multiple_extensions.html.erb <%= yield %>
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/third_party_template_library.mab b/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/third_party_template_library.mab
new file mode 100644
index 000000000..018abfb0a
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/layouts/third_party_template_library.mab
@@ -0,0 +1 @@
+Mab \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/views/hello.rhtml b/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/views/hello.rhtml
new file mode 100644
index 000000000..bbccf0913
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/layout_tests/views/hello.rhtml
@@ -0,0 +1 @@
+hello.rhtml \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/layouts/builder.builder b/vendor/rails-2.0.2/actionpack/test/fixtures/layouts/builder.builder
new file mode 100644
index 000000000..729af4b8b
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/layouts/builder.builder
@@ -0,0 +1,3 @@
+xml.wrapper do
+ xml << @content_for_layout
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/layouts/standard.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/layouts/standard.erb
new file mode 100644
index 000000000..368764e6f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/layouts/standard.erb
@@ -0,0 +1 @@
+<html><%= @content_for_layout %><%= @variable_for_layout %></html> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/layouts/talk_from_action.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/layouts/talk_from_action.erb
new file mode 100644
index 000000000..187aab07a
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/layouts/talk_from_action.erb
@@ -0,0 +1,2 @@
+<title><%= @title || @content_for_title %></title>
+<%= @content_for_layout -%> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/layouts/yield.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/layouts/yield.erb
new file mode 100644
index 000000000..482dc9022
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/layouts/yield.erb
@@ -0,0 +1,2 @@
+<title><%= yield :title %></title>
+<%= yield %>
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/binary_file b/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/binary_file
new file mode 100644
index 000000000..556187ac1
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/binary_file
Binary files differ
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/bracketed_param b/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/bracketed_param
new file mode 100644
index 000000000..096bd8a19
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/bracketed_param
@@ -0,0 +1,5 @@
+--AaB03x
+Content-Disposition: form-data; name="foo[baz]"
+
+bar
+--AaB03x--
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/large_text_file b/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/large_text_file
new file mode 100644
index 000000000..7f97fb1d7
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/large_text_file
@@ -0,0 +1,10 @@
+--AaB03x
+Content-Disposition: form-data; name="foo"
+
+bar
+--AaB03x
+Content-Disposition: form-data; name="file"; filename="file.txt"
+Content-Type: text/plain
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+--AaB03x--
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/mixed_files b/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/mixed_files
new file mode 100644
index 000000000..5eba7a6b4
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/mixed_files
Binary files differ
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/mona_lisa.jpg b/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/mona_lisa.jpg
new file mode 100644
index 000000000..5cf3bef3d
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/mona_lisa.jpg
Binary files differ
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/single_parameter b/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/single_parameter
new file mode 100644
index 000000000..8962c3543
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/single_parameter
@@ -0,0 +1,5 @@
+--AaB03x
+Content-Disposition: form-data; name="foo"
+
+bar
+--AaB03x--
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/text_file b/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/text_file
new file mode 100644
index 000000000..e0367d68c
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/multipart/text_file
@@ -0,0 +1,10 @@
+--AaB03x
+Content-Disposition: form-data; name="foo"
+
+bar
+--AaB03x
+Content-Disposition: form-data; name="file"; filename="file.txt"
+Content-Type: text/plain
+
+contents
+--AaB03x--
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/override/test/hello_world.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/override/test/hello_world.erb
new file mode 100644
index 000000000..3e308d3d8
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/override/test/hello_world.erb
@@ -0,0 +1 @@
+Hello overridden world! \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/override2/layouts/test/sub.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/override2/layouts/test/sub.erb
new file mode 100644
index 000000000..3863d5a8e
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/override2/layouts/test/sub.erb
@@ -0,0 +1 @@
+layout: <%= yield %> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/layouts/post.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/layouts/post.html.erb
new file mode 100644
index 000000000..c6c1a586d
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/layouts/post.html.erb
@@ -0,0 +1 @@
+<html><div id="html"><%= yield %></div></html> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/layouts/super_post.iphone.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/layouts/super_post.iphone.erb
new file mode 100644
index 000000000..db0e43694
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/layouts/super_post.iphone.erb
@@ -0,0 +1 @@
+<html><div id="super_iphone"><%= yield %></div></html> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/post/index.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/post/index.html.erb
new file mode 100644
index 000000000..b349b2561
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/post/index.html.erb
@@ -0,0 +1 @@
+Hello Firefox \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/post/index.iphone.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/post/index.iphone.erb
new file mode 100644
index 000000000..d741e4435
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/post/index.iphone.erb
@@ -0,0 +1 @@
+Hello iPhone \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/super_post/index.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/super_post/index.html.erb
new file mode 100644
index 000000000..7fc2eb190
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/super_post/index.html.erb
@@ -0,0 +1 @@
+Super Firefox \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/super_post/index.iphone.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/super_post/index.iphone.erb
new file mode 100644
index 000000000..99063a8d8
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/post_test/super_post/index.iphone.erb
@@ -0,0 +1 @@
+Super iPhone \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/project.rb b/vendor/rails-2.0.2/actionpack/test/fixtures/project.rb
new file mode 100644
index 000000000..2b53d39ed
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/project.rb
@@ -0,0 +1,3 @@
+class Project < ActiveRecord::Base
+ has_and_belongs_to_many :developers, :uniq => true
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/projects.yml b/vendor/rails-2.0.2/actionpack/test/fixtures/projects.yml
new file mode 100644
index 000000000..02800c782
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/projects.yml
@@ -0,0 +1,7 @@
+action_controller:
+ id: 2
+ name: Active Controller
+
+active_record:
+ id: 1
+ name: Active Record
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/public/404.html b/vendor/rails-2.0.2/actionpack/test/fixtures/public/404.html
new file mode 100644
index 000000000..497397cce
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/public/404.html
@@ -0,0 +1 @@
+404 error fixture
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/public/500.html b/vendor/rails-2.0.2/actionpack/test/fixtures/public/500.html
new file mode 100644
index 000000000..7c66c7a94
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/public/500.html
@@ -0,0 +1 @@
+500 error fixture
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/public/images/rails.png b/vendor/rails-2.0.2/actionpack/test/fixtures/public/images/rails.png
new file mode 100644
index 000000000..b8441f182
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/public/images/rails.png
Binary files differ
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/public/javascripts/application.js b/vendor/rails-2.0.2/actionpack/test/fixtures/public/javascripts/application.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/public/javascripts/application.js
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/public/javascripts/bank.js b/vendor/rails-2.0.2/actionpack/test/fixtures/public/javascripts/bank.js
new file mode 100644
index 000000000..4a1bee718
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/public/javascripts/bank.js
@@ -0,0 +1 @@
+// bank js \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/public/javascripts/robber.js b/vendor/rails-2.0.2/actionpack/test/fixtures/public/javascripts/robber.js
new file mode 100644
index 000000000..eb82fcbdf
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/public/javascripts/robber.js
@@ -0,0 +1 @@
+// robber js \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/public/stylesheets/bank.css b/vendor/rails-2.0.2/actionpack/test/fixtures/public/stylesheets/bank.css
new file mode 100644
index 000000000..ea161b12b
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/public/stylesheets/bank.css
@@ -0,0 +1 @@
+/* bank.css */ \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/public/stylesheets/robber.css b/vendor/rails-2.0.2/actionpack/test/fixtures/public/stylesheets/robber.css
new file mode 100644
index 000000000..0fdd00a6a
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/public/stylesheets/robber.css
@@ -0,0 +1 @@
+/* robber.css */ \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/replies.yml b/vendor/rails-2.0.2/actionpack/test/fixtures/replies.yml
new file mode 100644
index 000000000..a17d2fc42
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/replies.yml
@@ -0,0 +1,15 @@
+witty_retort:
+ id: 1
+ topic_id: 1
+ developer_id: 1
+ content: Birdman is better!
+ created_at: <%= 6.hours.ago.to_s(:db) %>
+ updated_at: nil
+
+another:
+ id: 2
+ topic_id: 2
+ developer_id: 1
+ content: Nuh uh!
+ created_at: <%= 1.hour.ago.to_s(:db) %>
+ updated_at: nil \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/reply.rb b/vendor/rails-2.0.2/actionpack/test/fixtures/reply.rb
new file mode 100644
index 000000000..588713de1
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/reply.rb
@@ -0,0 +1,6 @@
+class Reply < ActiveRecord::Base
+ belongs_to :topic, :include => [:replies]
+ belongs_to :developer
+
+ validates_presence_of :content
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/all_types_with_layout.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/all_types_with_layout.html.erb
new file mode 100644
index 000000000..84a84049f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/all_types_with_layout.html.erb
@@ -0,0 +1 @@
+HTML for all_types_with_layout \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/all_types_with_layout.js.rjs b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/all_types_with_layout.js.rjs
new file mode 100644
index 000000000..b7aec7c50
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/all_types_with_layout.js.rjs
@@ -0,0 +1 @@
+page << "RJS for all_types_with_layout" \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb
new file mode 100644
index 000000000..0cdfa4149
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb
@@ -0,0 +1 @@
+Mobile \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/iphone_with_html_response_type.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/iphone_with_html_response_type.html.erb
new file mode 100644
index 000000000..1f3f1c651
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/iphone_with_html_response_type.html.erb
@@ -0,0 +1 @@
+Hello future from <%= @type -%>! \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb
new file mode 100644
index 000000000..17888ac30
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb
@@ -0,0 +1 @@
+Hello iPhone future from <%= @type -%>! \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/layouts/missing.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/layouts/missing.html.erb
new file mode 100644
index 000000000..d6f92a312
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/layouts/missing.html.erb
@@ -0,0 +1 @@
+<html><div id="html_missing"><%= yield %></div></html> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/layouts/standard.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/layouts/standard.html.erb
new file mode 100644
index 000000000..c6c1a586d
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/layouts/standard.html.erb
@@ -0,0 +1 @@
+<html><div id="html"><%= yield %></div></html> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/layouts/standard.iphone.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/layouts/standard.iphone.erb
new file mode 100644
index 000000000..84444517f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/layouts/standard.iphone.erb
@@ -0,0 +1 @@
+<html><div id="iphone"><%= yield %></div></html> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults.html.erb
new file mode 100644
index 000000000..6769dd60b
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults.html.erb
@@ -0,0 +1 @@
+Hello world! \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults.js.rjs b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults.js.rjs
new file mode 100644
index 000000000..469fcd8e1
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults.js.rjs
@@ -0,0 +1 @@
+page[:body].visual_effect :highlight \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults.xml.builder b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults.xml.builder
new file mode 100644
index 000000000..598d62e2f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults.xml.builder
@@ -0,0 +1 @@
+xml.p "Hello world!" \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.html.erb
new file mode 100644
index 000000000..6769dd60b
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.html.erb
@@ -0,0 +1 @@
+Hello world! \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs
new file mode 100644
index 000000000..469fcd8e1
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs
@@ -0,0 +1 @@
+page[:body].visual_effect :highlight \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder
new file mode 100644
index 000000000..598d62e2f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder
@@ -0,0 +1 @@
+xml.p "Hello world!" \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/scope/test/modgreet.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/scope/test/modgreet.erb
new file mode 100644
index 000000000..8947726e8
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/scope/test/modgreet.erb
@@ -0,0 +1 @@
+<p>Beautiful modules!</p> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/_customer.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_customer.erb
new file mode 100644
index 000000000..872d8c44e
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_customer.erb
@@ -0,0 +1 @@
+Hello: <%= customer.name %> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/_customer_greeting.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_customer_greeting.erb
new file mode 100644
index 000000000..6acbcb20c
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_customer_greeting.erb
@@ -0,0 +1 @@
+<%= greeting %>: <%= customer_greeting.name %> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/_hash_greeting.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_hash_greeting.erb
new file mode 100644
index 000000000..fc54a36f2
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_hash_greeting.erb
@@ -0,0 +1 @@
+<%= greeting %>: <%= hash_greeting[:first_name] %> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/_hash_object.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_hash_object.erb
new file mode 100644
index 000000000..55c03afb2
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_hash_object.erb
@@ -0,0 +1,2 @@
+<%= hash_object[:first_name] %>
+<%= object[:first_name].reverse %>
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/_hello.builder b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_hello.builder
new file mode 100644
index 000000000..ef52f632d
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_hello.builder
@@ -0,0 +1 @@
+xm.hello \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/_layout_for_partial.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_layout_for_partial.html.erb
new file mode 100644
index 000000000..666efadbb
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_layout_for_partial.html.erb
@@ -0,0 +1,3 @@
+Before (<%= name %>)
+<%= yield %>
+After \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial.erb
new file mode 100644
index 000000000..e466dcbd8
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial.erb
@@ -0,0 +1 @@
+invalid \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial.html.erb
new file mode 100644
index 000000000..e39f6c982
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial.html.erb
@@ -0,0 +1 @@
+partial html \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial.js.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial.js.erb
new file mode 100644
index 000000000..b350cdd7e
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial.js.erb
@@ -0,0 +1 @@
+partial js \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial_for_use_in_layout.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial_for_use_in_layout.html.erb
new file mode 100644
index 000000000..3a03a64e3
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial_for_use_in_layout.html.erb
@@ -0,0 +1 @@
+Inside from partial (<%= name %>) \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial_only.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial_only.erb
new file mode 100644
index 000000000..a44b3eed4
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_partial_only.erb
@@ -0,0 +1 @@
+only partial \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/_person.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_person.erb
new file mode 100644
index 000000000..b2e568895
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/_person.erb
@@ -0,0 +1,2 @@
+Second: <%= name %>
+Third: <%= @name %>
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/action_talk_to_layout.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/action_talk_to_layout.erb
new file mode 100644
index 000000000..36e896daa
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/action_talk_to_layout.erb
@@ -0,0 +1,2 @@
+<% @title = "Talking to the layout" -%>
+Action was here! \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/block_content_for.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/block_content_for.erb
new file mode 100644
index 000000000..951033736
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/block_content_for.erb
@@ -0,0 +1,2 @@
+<% block_content_for :title do 'Putting stuff in the title!' end %>
+Great stuff! \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/calling_partial_with_layout.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/calling_partial_with_layout.html.erb
new file mode 100644
index 000000000..ac44bc0d8
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/calling_partial_with_layout.html.erb
@@ -0,0 +1 @@
+<%= render(:layout => "layout_for_partial", :partial => "partial_for_use_in_layout", :locals => { :name => "David" }) %> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/capturing.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/capturing.erb
new file mode 100644
index 000000000..1addaa40d
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/capturing.erb
@@ -0,0 +1,4 @@
+<% days = capture do %>
+ Dreamy days
+<% end %>
+<%= days %> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/content_for.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/content_for.erb
new file mode 100644
index 000000000..0e47ca8c3
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/content_for.erb
@@ -0,0 +1,2 @@
+<% content_for :title do %>Putting stuff in the title!<% end %>
+Great stuff! \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/content_for_concatenated.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/content_for_concatenated.erb
new file mode 100644
index 000000000..fb6b4b05d
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/content_for_concatenated.erb
@@ -0,0 +1,3 @@
+<% content_for :title, "Putting stuff "
+ content_for :title, "in the title!" %>
+Great stuff! \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/content_for_with_parameter.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/content_for_with_parameter.erb
new file mode 100644
index 000000000..57aecbac0
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/content_for_with_parameter.erb
@@ -0,0 +1,2 @@
+<% content_for :title, "Putting stuff in the title!" %>
+Great stuff! \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/delete_with_js.rjs b/vendor/rails-2.0.2/actionpack/test/fixtures/test/delete_with_js.rjs
new file mode 100644
index 000000000..4b75a955a
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/delete_with_js.rjs
@@ -0,0 +1,2 @@
+page.remove 'person'
+page.visual_effect :highlight, "project-#{@project_id}"
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/dot.directory/render_file_with_ivar.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/dot.directory/render_file_with_ivar.erb
new file mode 100644
index 000000000..8b8a44923
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/dot.directory/render_file_with_ivar.erb
@@ -0,0 +1 @@
+The secret is <%= @secret %>
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/enum_rjs_test.rjs b/vendor/rails-2.0.2/actionpack/test/fixtures/test/enum_rjs_test.rjs
new file mode 100644
index 000000000..e3004076a
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/enum_rjs_test.rjs
@@ -0,0 +1,6 @@
+page.select('.product').each do |value|
+ page.visual_effect :highlight
+ page.visual_effect :highlight, value
+ page.sortable(value, :url => { :action => "order" })
+ page.draggable(value)
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/erb_content_for.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/erb_content_for.erb
new file mode 100644
index 000000000..c3bdd1364
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/erb_content_for.erb
@@ -0,0 +1,2 @@
+<% erb_content_for :title do %>Putting stuff in the title!<% end %>
+Great stuff! \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_html_erb.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_html_erb.html.erb
new file mode 100644
index 000000000..1c64efabd
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_html_erb.html.erb
@@ -0,0 +1 @@
+formatted html erb \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_xml_erb.builder b/vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_xml_erb.builder
new file mode 100644
index 000000000..14fd3549f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_xml_erb.builder
@@ -0,0 +1 @@
+xml.test 'failed' \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_xml_erb.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_xml_erb.html.erb
new file mode 100644
index 000000000..0c855a604
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_xml_erb.html.erb
@@ -0,0 +1 @@
+<test>passed formatted html erb</test> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_xml_erb.xml.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_xml_erb.xml.erb
new file mode 100644
index 000000000..6ca09d530
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/formatted_xml_erb.xml.erb
@@ -0,0 +1 @@
+<test>passed formatted xml erb</test> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/greeting.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/greeting.erb
new file mode 100644
index 000000000..62fb0293f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/greeting.erb
@@ -0,0 +1 @@
+<p>This is grand!</p>
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello.builder b/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello.builder
new file mode 100644
index 000000000..82a4a310d
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello.builder
@@ -0,0 +1,4 @@
+xml.html do
+ xml.p "Hello #{@name}"
+ xml << render_file("test/greeting")
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world.erb
new file mode 100644
index 000000000..6769dd60b
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world.erb
@@ -0,0 +1 @@
+Hello world! \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world_container.builder b/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world_container.builder
new file mode 100644
index 000000000..e48d75c40
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world_container.builder
@@ -0,0 +1,3 @@
+xml.test do
+ render :partial => 'hello', :locals => { :xm => xml }
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world_from_rxml.builder b/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world_from_rxml.builder
new file mode 100644
index 000000000..8455b11ed
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world_from_rxml.builder
@@ -0,0 +1,4 @@
+xml.html do
+ xml.p "Hello"
+end
+"String return value"
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world_with_layout_false.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world_with_layout_false.erb
new file mode 100644
index 000000000..6769dd60b
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_world_with_layout_false.erb
@@ -0,0 +1 @@
+Hello world! \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_xml_world.builder b/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_xml_world.builder
new file mode 100644
index 000000000..02b14fe87
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/hello_xml_world.builder
@@ -0,0 +1,11 @@
+xml.html do
+ xml.head do
+ xml.title "Hello World"
+ end
+
+ xml.body do
+ xml.p "abes"
+ xml.p "monks"
+ xml.p "wiseguys"
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/list.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/list.erb
new file mode 100644
index 000000000..0a4bda58e
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/list.erb
@@ -0,0 +1 @@
+<%= @test_unchanged = 'goodbye' %><%= render :partial => 'customer', :collection => @customers %><%= @test_unchanged %>
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/non_erb_block_content_for.builder b/vendor/rails-2.0.2/actionpack/test/fixtures/test/non_erb_block_content_for.builder
new file mode 100644
index 000000000..6ff6db0f9
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/non_erb_block_content_for.builder
@@ -0,0 +1,4 @@
+content_for :title do
+ 'Putting stuff in the title!'
+end
+xml << "\nGreat stuff!" \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/potential_conflicts.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/potential_conflicts.erb
new file mode 100644
index 000000000..a5e964e35
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/potential_conflicts.erb
@@ -0,0 +1,4 @@
+First: <%= @name %>
+<%= render :partial => "person", :locals => { :name => "Stephan" } -%>
+Fourth: <%= @name %>
+Fifth: <%= name %> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/render_file_with_ivar.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/render_file_with_ivar.erb
new file mode 100644
index 000000000..8b8a44923
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/render_file_with_ivar.erb
@@ -0,0 +1 @@
+The secret is <%= @secret %>
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/render_file_with_locals.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/render_file_with_locals.erb
new file mode 100644
index 000000000..ebe09faee
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/render_file_with_locals.erb
@@ -0,0 +1 @@
+The secret is <%= secret %>
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/render_to_string_test.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/render_to_string_test.erb
new file mode 100644
index 000000000..6e267e863
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/render_to_string_test.erb
@@ -0,0 +1 @@
+The value of foo is: ::<%= @foo %>::
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/update_element_with_capture.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/update_element_with_capture.erb
new file mode 100644
index 000000000..fa3ef200f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/update_element_with_capture.erb
@@ -0,0 +1,9 @@
+<% replacement_function = update_element_function("products", :action => :update) do %>
+ <p>Product 1</p>
+ <p>Product 2</p>
+<% end %>
+<%= javascript_tag(replacement_function) %>
+
+<% update_element_function("status", :action => :update, :binding => binding) do %>
+ <b>You bought something!</b>
+<% end %>
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/test/using_layout_around_block.html.erb b/vendor/rails-2.0.2/actionpack/test/fixtures/test/using_layout_around_block.html.erb
new file mode 100644
index 000000000..a93fa02c9
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/test/using_layout_around_block.html.erb
@@ -0,0 +1 @@
+<% render(:layout => "layout_for_partial", :locals => { :name => "David" }) do %>Inside from block<% end %> \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/topic.rb b/vendor/rails-2.0.2/actionpack/test/fixtures/topic.rb
new file mode 100644
index 000000000..9fa974653
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/topic.rb
@@ -0,0 +1,3 @@
+class Topic < ActiveRecord::Base
+ has_many :replies, :dependent => :destroy
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/fixtures/topics.yml b/vendor/rails-2.0.2/actionpack/test/fixtures/topics.yml
new file mode 100644
index 000000000..61ea02d76
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/fixtures/topics.yml
@@ -0,0 +1,22 @@
+futurama:
+ id: 1
+ title: Isnt futurama awesome?
+ subtitle: It really is, isnt it.
+ content: I like futurama
+ created_at: <%= 1.day.ago.to_s(:db) %>
+ updated_at:
+
+harvey_birdman:
+ id: 2
+ title: Harvey Birdman is the king of all men
+ subtitle: yup
+ content: It really is
+ created_at: <%= 2.hours.ago.to_s(:db) %>
+ updated_at:
+
+rails:
+ id: 3
+ title: Rails is nice
+ subtitle: It makes me happy
+ content: except when I have to hack internals to fix pagination. even then really.
+ created_at: <%= 20.minutes.ago.to_s(:db) %>
diff --git a/vendor/rails-2.0.2/actionpack/test/template/active_record_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/active_record_helper_test.rb
new file mode 100644
index 000000000..bec29e134
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/active_record_helper_test.rb
@@ -0,0 +1,251 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+class ActiveRecordHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::FormHelper
+ include ActionView::Helpers::ActiveRecordHelper
+ include ActionView::Helpers::TextHelper
+ include ActionView::Helpers::TagHelper
+ include ActionView::Helpers::UrlHelper
+ include ActionView::Helpers::FormTagHelper
+
+ silence_warnings do
+ Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on)
+ Post.class_eval do
+ alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast)
+ alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast)
+ alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast)
+ end
+
+ User = Struct.new("User", :email)
+ User.class_eval do
+ alias_method :email_before_type_cast, :email unless respond_to?(:email_before_type_cast)
+ end
+
+ Column = Struct.new("Column", :type, :name, :human_name)
+ end
+
+ def setup_post
+ @post = Post.new
+ def @post.errors
+ Class.new {
+ def on(field)
+ case field.to_s
+ when "author_name"
+ "can't be empty"
+ when "body"
+ true
+ else
+ false
+ end
+ end
+ def empty?() false end
+ def count() 1 end
+ def full_messages() [ "Author name can't be empty" ] end
+ }.new
+ end
+
+ def @post.new_record?() true end
+ def @post.to_param() nil end
+
+ def @post.column_for_attribute(attr_name)
+ Post.content_columns.select { |column| column.name == attr_name }.first
+ end
+
+ silence_warnings do
+ def Post.content_columns() [ Column.new(:string, "title", "Title"), Column.new(:text, "body", "Body") ] end
+ end
+
+ @post.title = "Hello World"
+ @post.author_name = ""
+ @post.body = "Back to the hill and over it again!"
+ @post.secret = 1
+ @post.written_on = Date.new(2004, 6, 15)
+ end
+
+ def setup_user
+ @user = User.new
+ def @user.errors
+ Class.new {
+ def on(field) field == "email" end
+ def empty?() false end
+ def count() 1 end
+ def full_messages() [ "User email can't be empty" ] end
+ }.new
+ end
+
+ def @user.new_record?() true end
+ def @user.to_param() nil end
+
+ def @user.column_for_attribute(attr_name)
+ User.content_columns.select { |column| column.name == attr_name }.first
+ end
+
+ silence_warnings do
+ def User.content_columns() [ Column.new(:string, "email", "Email") ] end
+ end
+
+ @user.email = ""
+ end
+
+ def setup
+ setup_post
+ setup_user
+
+ @response = ActionController::TestResponse.new
+
+ @controller = Object.new
+ def @controller.url_for(options)
+ options = options.symbolize_keys
+
+ [options[:action], options[:id].to_param].compact.join('/')
+ end
+ end
+
+ def test_generic_input_tag
+ assert_dom_equal(
+ %(<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />), input("post", "title")
+ )
+ end
+
+ def test_text_area_with_errors
+ assert_dom_equal(
+ %(<div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div>),
+ text_area("post", "body")
+ )
+ end
+
+ def test_text_field_with_errors
+ assert_dom_equal(
+ %(<div class="fieldWithErrors"><input id="post_author_name" name="post[author_name]" size="30" type="text" value="" /></div>),
+ text_field("post", "author_name")
+ )
+ end
+
+ def test_form_with_string
+ assert_dom_equal(
+ %(<form action="create" method="post"><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Create" /></form>),
+ form("post")
+ )
+
+ silence_warnings do
+ class << @post
+ def new_record?() false end
+ def to_param() id end
+ def id() 1 end
+ end
+ end
+
+ assert_dom_equal(
+ %(<form action="update/1" method="post"><input id="post_id" name="post[id]" type="hidden" value="1" /><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Update" /></form>),
+ form("post")
+ )
+ end
+
+ def test_form_with_action_option
+ @response.body = form("post", :action => "sign")
+ assert_select "form[action=sign]" do |form|
+ assert_select "input[type=submit][value=Sign]"
+ end
+ end
+
+ def test_form_with_date
+ silence_warnings do
+ def Post.content_columns() [ Column.new(:date, "written_on", "Written on") ] end
+ end
+
+ assert_dom_equal(
+ %(<form action="create" method="post"><p><label for="post_written_on">Written on</label><br /><select id="post_written_on_1i" name="post[written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n<select id="post_written_on_2i" name="post[written_on(2i)]">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n<select id="post_written_on_3i" name="post[written_on(3i)]">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n</p><input name="commit" type="submit" value="Create" /></form>),
+ form("post")
+ )
+ end
+
+ def test_form_with_datetime
+ silence_warnings do
+ def Post.content_columns() [ Column.new(:datetime, "written_on", "Written on") ] end
+ end
+ @post.written_on = Time.gm(2004, 6, 15, 16, 30)
+
+ assert_dom_equal(
+ %(<form action="create" method="post"><p><label for="post_written_on">Written on</label><br /><select id="post_written_on_1i" name="post[written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n<select id="post_written_on_2i" name="post[written_on(2i)]">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n<select id="post_written_on_3i" name="post[written_on(3i)]">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n &mdash; <select id="post_written_on_4i" name="post[written_on(4i)]">\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n : <select id="post_written_on_5i" name="post[written_on(5i)]">\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30" selected="selected">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n</select>\n</p><input name="commit" type="submit" value="Create" /></form>),
+ form("post")
+ )
+ end
+
+ def test_error_for_block
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post")
+ assert_equal %(<div class="errorDeathByClass" id="errorDeathById"><h1>1 error prohibited this post from being saved</h1><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post", :class => "errorDeathByClass", :id => "errorDeathById", :header_tag => "h1")
+ assert_equal %(<div id="errorDeathById"><h1>1 error prohibited this post from being saved</h1><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post", :class => nil, :id => "errorDeathById", :header_tag => "h1")
+ assert_equal %(<div class="errorDeathByClass"><h1>1 error prohibited this post from being saved</h1><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post", :class => "errorDeathByClass", :id => nil, :header_tag => "h1")
+ end
+
+ def test_error_messages_for_handles_nil
+ assert_equal "", error_messages_for("notthere")
+ end
+
+ def test_error_message_on_handles_nil
+ assert_equal "", error_message_on("notthere", "notthere")
+ end
+
+ def test_error_message_on
+ assert_dom_equal "<div class=\"formError\">can't be empty</div>", error_message_on(:post, :author_name)
+ end
+
+ def test_error_message_on_no_instance_variable
+ other_post = @post
+ assert_dom_equal "<div class=\"formError\">can't be empty</div>", error_message_on(other_post, :author_name)
+ end
+
+ def test_error_message_on_should_use_options
+ assert_dom_equal "<div class=\"differentError\">beforecan't be emptyafter</div>", error_message_on(:post, :author_name, "before", "after", "differentError")
+ end
+
+ def test_error_messages_for_many_objects
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li><li>User email can't be empty</li></ul></div>), error_messages_for("post", "user")
+
+ # reverse the order, error order changes and so does the title
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this user from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for("user", "post")
+
+ # add the default to put post back in the title
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for("user", "post", :object_name => "post")
+
+ # symbols work as well
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for(:user, :post, :object_name => :post)
+
+ # any default works too
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this monkey from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for(:user, :post, :object_name => "monkey")
+
+ # should space object name
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this chunky bacon from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for(:user, :post, :object_name => "chunky_bacon")
+
+ # hide header and explanation messages with nil or empty string
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for(:user, :post, :header_message => nil, :message => "")
+
+ # override header and explanation messages
+ header_message = "Yikes! Some errors"
+ message = "Please fix the following fields and resubmit:"
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>#{header_message}</h2><p>#{message}</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for(:user, :post, :header_message => header_message, :message => message)
+ end
+
+ def test_error_messages_for_non_instance_variable
+ actual_user = @user
+ actual_post = @post
+ @user = nil
+ @post = nil
+
+ #explicitly set object
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post", :object => actual_post)
+
+ #multiple objects
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this user from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for("user", "post", :object => [actual_user, actual_post])
+
+ #nil object
+ assert_equal '', error_messages_for('user', :object => nil)
+ end
+
+ def test_form_with_string_multipart
+ assert_dom_equal(
+ %(<form action="create" enctype="multipart/form-data" method="post"><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Create" /></form>),
+ form("post", :multipart => true)
+ )
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/template/asset_tag_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/asset_tag_helper_test.rb
new file mode 100644
index 000000000..ac21bc2a3
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/asset_tag_helper_test.rb
@@ -0,0 +1,438 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+class AssetTagHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::TagHelper
+ include ActionView::Helpers::UrlHelper
+ include ActionView::Helpers::AssetTagHelper
+
+ def setup
+ silence_warnings do
+ ActionView::Helpers::AssetTagHelper.send(
+ :const_set,
+ :JAVASCRIPTS_DIR,
+ File.dirname(__FILE__) + "/../fixtures/public/javascripts"
+ )
+
+ ActionView::Helpers::AssetTagHelper.send(
+ :const_set,
+ :STYLESHEETS_DIR,
+ File.dirname(__FILE__) + "/../fixtures/public/stylesheets"
+ )
+
+ ActionView::Helpers::AssetTagHelper.send(
+ :const_set,
+ :ASSETS_DIR,
+ File.dirname(__FILE__) + "/../fixtures/public"
+ )
+ end
+
+ @controller = Class.new do
+ attr_accessor :request
+ def url_for(*args) "http://www.example.com" end
+ end.new
+
+ @request = Class.new do
+ def relative_url_root() "" end
+ def protocol() 'http://' end
+ end.new
+
+ @controller.request = @request
+
+ ActionView::Helpers::AssetTagHelper::reset_javascript_include_default
+
+ ActionView::Base.computed_public_paths.clear
+ end
+
+ def teardown
+ ActionController::Base.perform_caching = false
+ ActionController::Base.asset_host = nil
+ ENV["RAILS_ASSET_ID"] = nil
+ end
+
+ AutoDiscoveryToTag = {
+ %(auto_discovery_link_tag) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
+ %(auto_discovery_link_tag(:rss)) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
+ %(auto_discovery_link_tag(:atom)) => %(<link href="http://www.example.com" rel="alternate" title="ATOM" type="application/atom+xml" />),
+ %(auto_discovery_link_tag(:xml)) => %(<link href="http://www.example.com" rel="alternate" title="XML" type="application/xml" />),
+ %(auto_discovery_link_tag(:rss, :action => "feed")) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
+ %(auto_discovery_link_tag(:rss, "http://localhost/feed")) => %(<link href="http://localhost/feed" rel="alternate" title="RSS" type="application/rss+xml" />),
+ %(auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"})) => %(<link href="http://www.example.com" rel="alternate" title="My RSS" type="application/rss+xml" />),
+ %(auto_discovery_link_tag(:rss, {}, {:title => "My RSS"})) => %(<link href="http://www.example.com" rel="alternate" title="My RSS" type="application/rss+xml" />),
+ %(auto_discovery_link_tag(nil, {}, {:type => "text/html"})) => %(<link href="http://www.example.com" rel="alternate" title="" type="text/html" />),
+ %(auto_discovery_link_tag(nil, {}, {:title => "No stream.. really", :type => "text/html"})) => %(<link href="http://www.example.com" rel="alternate" title="No stream.. really" type="text/html" />),
+ %(auto_discovery_link_tag(:rss, {}, {:title => "My RSS", :type => "text/html"})) => %(<link href="http://www.example.com" rel="alternate" title="My RSS" type="text/html" />),
+ %(auto_discovery_link_tag(:atom, {}, {:rel => "Not so alternate"})) => %(<link href="http://www.example.com" rel="Not so alternate" title="ATOM" type="application/atom+xml" />),
+ }
+
+ JavascriptPathToTag = {
+ %(javascript_path("xmlhr")) => %(/javascripts/xmlhr.js),
+ %(javascript_path("super/xmlhr")) => %(/javascripts/super/xmlhr.js),
+ %(javascript_path("/super/xmlhr.js")) => %(/super/xmlhr.js)
+ }
+
+ PathToJavascriptToTag = {
+ %(path_to_javascript("xmlhr")) => %(/javascripts/xmlhr.js),
+ %(path_to_javascript("super/xmlhr")) => %(/javascripts/super/xmlhr.js),
+ %(path_to_javascript("/super/xmlhr.js")) => %(/super/xmlhr.js)
+ }
+
+ JavascriptIncludeToTag = {
+ %(javascript_include_tag("xmlhr")) => %(<script src="/javascripts/xmlhr.js" type="text/javascript"></script>),
+ %(javascript_include_tag("xmlhr.js")) => %(<script src="/javascripts/xmlhr.js" type="text/javascript"></script>),
+ %(javascript_include_tag("xmlhr", :lang => "vbscript")) => %(<script lang="vbscript" src="/javascripts/xmlhr.js" type="text/javascript"></script>),
+ %(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/javascripts/common.javascript" type="text/javascript"></script>\n<script src="/elsewhere/cools.js" type="text/javascript"></script>),
+ %(javascript_include_tag(:defaults)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
+ %(javascript_include_tag(:all)) => %(<script src="/javascripts/application.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>),
+ %(javascript_include_tag(:defaults, "test")) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/test.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
+ %(javascript_include_tag("test", :defaults)) => %(<script src="/javascripts/test.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>)
+ }
+
+ StylePathToTag = {
+ %(stylesheet_path("style")) => %(/stylesheets/style.css),
+ %(stylesheet_path("style.css")) => %(/stylesheets/style.css),
+ %(stylesheet_path('dir/file')) => %(/stylesheets/dir/file.css),
+ %(stylesheet_path('/dir/file.rcss')) => %(/dir/file.rcss)
+ }
+
+ PathToStyleToTag = {
+ %(path_to_stylesheet("style")) => %(/stylesheets/style.css),
+ %(path_to_stylesheet("style.css")) => %(/stylesheets/style.css),
+ %(path_to_stylesheet('dir/file')) => %(/stylesheets/dir/file.css),
+ %(path_to_stylesheet('/dir/file.rcss')) => %(/dir/file.rcss)
+ }
+
+ StyleLinkToTag = {
+ %(stylesheet_link_tag("style")) => %(<link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(stylesheet_link_tag("style.css")) => %(<link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(stylesheet_link_tag("/dir/file")) => %(<link href="/dir/file.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(stylesheet_link_tag("dir/file")) => %(<link href="/stylesheets/dir/file.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(stylesheet_link_tag("style", :media => "all")) => %(<link href="/stylesheets/style.css" media="all" rel="stylesheet" type="text/css" />),
+ %(stylesheet_link_tag(:all)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(stylesheet_link_tag(:all, :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="all" rel="stylesheet" type="text/css" />),
+ %(stylesheet_link_tag("random.styles", "/css/stylish")) => %(<link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />\n<link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(stylesheet_link_tag("http://www.example.com/styles/style")) => %(<link href="http://www.example.com/styles/style.css" media="screen" rel="stylesheet" type="text/css" />)
+ }
+
+ ImagePathToTag = {
+ %(image_path("xml")) => %(/images/xml),
+ %(image_path("xml.png")) => %(/images/xml.png),
+ %(image_path("dir/xml.png")) => %(/images/dir/xml.png),
+ %(image_path("/dir/xml.png")) => %(/dir/xml.png)
+ }
+
+ PathToImageToTag = {
+ %(path_to_image("xml")) => %(/images/xml),
+ %(path_to_image("xml.png")) => %(/images/xml.png),
+ %(path_to_image("dir/xml.png")) => %(/images/dir/xml.png),
+ %(path_to_image("/dir/xml.png")) => %(/dir/xml.png)
+ }
+
+ ImageLinkToTag = {
+ %(image_tag("xml.png")) => %(<img alt="Xml" src="/images/xml.png" />),
+ %(image_tag("rss.gif", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/images/rss.gif" />),
+ %(image_tag("gold.png", :size => "45x70")) => %(<img alt="Gold" height="70" src="/images/gold.png" width="45" />),
+ %(image_tag("gold.png", "size" => "45x70")) => %(<img alt="Gold" height="70" src="/images/gold.png" width="45" />),
+ %(image_tag("error.png", "size" => "45")) => %(<img alt="Error" src="/images/error.png" />),
+ %(image_tag("error.png", "size" => "45 x 70")) => %(<img alt="Error" src="/images/error.png" />),
+ %(image_tag("error.png", "size" => "x")) => %(<img alt="Error" src="/images/error.png" />),
+ %(image_tag("http://www.rubyonrails.com/images/rails.png")) => %(<img alt="Rails" src="http://www.rubyonrails.com/images/rails.png" />),
+ %(image_tag("http://www.rubyonrails.com/images/rails.png")) => %(<img alt="Rails" src="http://www.rubyonrails.com/images/rails.png" />),
+ %(image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />),
+ %(image_tag("mouse.png", :mouseover => image_path("mouse_over.png"))) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />)
+ }
+
+
+ def test_auto_discovery_link_tag
+ AutoDiscoveryToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_javascript_path
+ JavascriptPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_path_to_javascript_alias_for_javascript_path
+ PathToJavascriptToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_javascript_include_tag
+ ENV["RAILS_ASSET_ID"] = ""
+ JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+
+ ActionView::Base.computed_public_paths.clear
+
+ ENV["RAILS_ASSET_ID"] = "1"
+ assert_dom_equal(%(<script src="/javascripts/prototype.js?1" type="text/javascript"></script>\n<script src="/javascripts/effects.js?1" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js?1" type="text/javascript"></script>\n<script src="/javascripts/controls.js?1" type="text/javascript"></script>\n<script src="/javascripts/application.js?1" type="text/javascript"></script>), javascript_include_tag(:defaults))
+ end
+
+ def test_register_javascript_include_default
+ ENV["RAILS_ASSET_ID"] = ""
+ ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'slider'
+ assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/slider.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
+ ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'lib1', '/elsewhere/blub/lib2'
+ assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/slider.js" type="text/javascript"></script>\n<script src="/javascripts/lib1.js" type="text/javascript"></script>\n<script src="/elsewhere/blub/lib2.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
+ end
+
+ def test_stylesheet_path
+ StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_path_to_stylesheet_alias_for_stylesheet_path
+ PathToStyleToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_stylesheet_link_tag
+ ENV["RAILS_ASSET_ID"] = ""
+ StyleLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_image_path
+ ImagePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_path_to_image_alias_for_image_path
+ PathToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_image_tag
+ ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_timebased_asset_id
+ expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s
+ assert_equal %(<img alt="Rails" src="/images/rails.png?#{expected_time}" />), image_tag("rails.png")
+ end
+
+ def test_should_skip_asset_id_on_complete_url
+ assert_equal %(<img alt="Rails" src="http://www.example.com/rails.png" />), image_tag("http://www.example.com/rails.png")
+ end
+
+ def test_should_use_preset_asset_id
+ ENV["RAILS_ASSET_ID"] = "4500"
+ assert_equal %(<img alt="Rails" src="/images/rails.png?4500" />), image_tag("rails.png")
+ end
+
+ def test_preset_empty_asset_id
+ ENV["RAILS_ASSET_ID"] = ""
+ assert_equal %(<img alt="Rails" src="/images/rails.png" />), image_tag("rails.png")
+ end
+
+ def test_should_not_modify_source_string
+ source = '/images/rails.png'
+ copy = source.dup
+ image_tag(source)
+ assert_equal copy, source
+ end
+
+ def test_caching_javascript_include_tag_when_caching_on
+ ENV["RAILS_ASSET_ID"] = ""
+ ActionController::Base.asset_host = 'http://a%d.example.com'
+ ActionController::Base.perform_caching = true
+
+ assert_dom_equal(
+ %(<script src="http://a0.example.com/javascripts/all.js" type="text/javascript"></script>),
+ javascript_include_tag(:all, :cache => true)
+ )
+
+ assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
+
+ assert_dom_equal(
+ %(<script src="http://a2.example.com/javascripts/money.js" type="text/javascript"></script>),
+ javascript_include_tag(:all, :cache => "money")
+ )
+
+ assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js'))
+
+ ensure
+ File.delete(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
+ File.delete(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js'))
+ end
+
+ def test_caching_javascript_include_tag_when_caching_on_with_proc_asset_host
+ ENV["RAILS_ASSET_ID"] = ""
+ ActionController::Base.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" }
+ ActionController::Base.perform_caching = true
+
+ assert_equal '/javascripts/scripts.js'.length, 23
+ assert_dom_equal(
+ %(<script src="http://a23.example.com/javascripts/scripts.js" type="text/javascript"></script>),
+ javascript_include_tag(:all, :cache => 'scripts')
+ )
+
+ assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'scripts.js'))
+
+ ensure
+ File.delete(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'scripts.js'))
+ end
+
+ def test_caching_javascript_include_tag_when_caching_on_and_using_subdirectory
+ ENV["RAILS_ASSET_ID"] = ""
+ ActionController::Base.asset_host = 'http://a%d.example.com'
+ ActionController::Base.perform_caching = true
+
+ assert_dom_equal(
+ %(<script src="http://a3.example.com/javascripts/cache/money.js" type="text/javascript"></script>),
+ javascript_include_tag(:all, :cache => "cache/money")
+ )
+
+ assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'cache', 'money.js'))
+ ensure
+ File.delete(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'cache', 'money.js'))
+ end
+
+ def test_caching_javascript_include_tag_when_caching_off
+ ENV["RAILS_ASSET_ID"] = ""
+ ActionController::Base.perform_caching = false
+
+ assert_dom_equal(
+ %(<script src="/javascripts/application.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>),
+ javascript_include_tag(:all, :cache => true)
+ )
+
+ assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
+
+ assert_dom_equal(
+ %(<script src="/javascripts/application.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>),
+ javascript_include_tag(:all, :cache => "money")
+ )
+
+ assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js'))
+ end
+
+ def test_caching_stylesheet_link_tag_when_caching_on
+ ENV["RAILS_ASSET_ID"] = ""
+ ActionController::Base.asset_host = 'http://a%d.example.com'
+ ActionController::Base.perform_caching = true
+
+ assert_dom_equal(
+ %(<link href="http://a3.example.com/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />),
+ stylesheet_link_tag(:all, :cache => true)
+ )
+
+ assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
+
+ assert_dom_equal(
+ %(<link href="http://a3.example.com/stylesheets/money.css" media="screen" rel="stylesheet" type="text/css" />),
+ stylesheet_link_tag(:all, :cache => "money")
+ )
+
+ assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
+ ensure
+ File.delete(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
+ File.delete(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
+ end
+
+ def test_caching_stylesheet_link_tag_when_caching_on_with_proc_asset_host
+ ENV["RAILS_ASSET_ID"] = ""
+ ActionController::Base.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" }
+ ActionController::Base.perform_caching = true
+
+ assert_equal '/stylesheets/styles.css'.length, 23
+ assert_dom_equal(
+ %(<link href="http://a23.example.com/stylesheets/styles.css" media="screen" rel="stylesheet" type="text/css" />),
+ stylesheet_link_tag(:all, :cache => 'styles')
+ )
+
+ assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'styles.css'))
+
+ ensure
+ File.delete(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'styles.css'))
+ end
+
+ def test_caching_stylesheet_include_tag_when_caching_off
+ ENV["RAILS_ASSET_ID"] = ""
+ ActionController::Base.perform_caching = false
+
+ assert_dom_equal(
+ %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />),
+ stylesheet_link_tag(:all, :cache => true)
+ )
+
+ assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
+
+ assert_dom_equal(
+ %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />),
+ stylesheet_link_tag(:all, :cache => "money")
+ )
+
+ assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
+ end
+end
+
+class AssetTagHelperNonVhostTest < Test::Unit::TestCase
+ include ActionView::Helpers::TagHelper
+ include ActionView::Helpers::UrlHelper
+ include ActionView::Helpers::AssetTagHelper
+
+ def setup
+ @controller = Class.new do
+ attr_accessor :request
+
+ def url_for(options)
+ "http://www.example.com/collaboration/hieraki"
+ end
+ end.new
+
+ @request = Class.new do
+ def relative_url_root
+ "/collaboration/hieraki"
+ end
+
+ def protocol
+ 'gopher://'
+ end
+ end.new
+
+ @controller.request = @request
+
+ ActionView::Helpers::AssetTagHelper::reset_javascript_include_default
+ end
+
+ def test_should_compute_proper_path
+ assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag)
+ assert_dom_equal(%(/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr"))
+ assert_dom_equal(%(/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style"))
+ assert_dom_equal(%(/collaboration/hieraki/images/xml.png), image_path("xml.png"))
+ end
+
+ def test_should_ignore_relative_root_path_on_complete_url
+ assert_dom_equal(%(http://www.example.com/images/xml.png), image_path("http://www.example.com/images/xml.png"))
+ end
+
+ def test_should_compute_proper_path_with_asset_host
+ ActionController::Base.asset_host = "http://assets.example.com"
+ assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag)
+ assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr"))
+ assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style"))
+ assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png"))
+ ensure
+ ActionController::Base.asset_host = ""
+ end
+
+ def test_should_ignore_asset_host_on_complete_url
+ ActionController::Base.asset_host = "http://assets.example.com"
+ assert_dom_equal(%(<link href="http://bar.example.com/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag("http://bar.example.com/stylesheets/style.css"))
+ ensure
+ ActionController::Base.asset_host = ""
+ end
+
+ def test_should_wildcard_asset_host_between_zero_and_four
+ ActionController::Base.asset_host = 'http://a%d.example.com'
+ assert_match %r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path('xml.png')
+ ensure
+ ActionController::Base.asset_host = nil
+ end
+
+ def test_asset_host_without_protocol_should_use_request_protocol
+ ActionController::Base.asset_host = 'a.example.com'
+ assert_equal 'gopher://a.example.com/collaboration/hieraki/images/xml.png', image_path('xml.png')
+ ensure
+ ActionController::Base.asset_host = nil
+ end
+
+ def test_asset_host_without_protocol_should_use_request_protocol_even_if_path_present
+ ActionController::Base.asset_host = 'a.example.com/files/go/here'
+ assert_equal 'gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_path('xml.png')
+ ensure
+ ActionController::Base.asset_host = nil
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/template/atom_feed_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/atom_feed_helper_test.rb
new file mode 100644
index 000000000..66ea48f83
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/atom_feed_helper_test.rb
@@ -0,0 +1,101 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+Scroll = Struct.new(:id, :to_param, :title, :body, :updated_at, :created_at)
+
+class ScrollsController < ActionController::Base
+ FEEDS = {}
+ FEEDS["defaults"] = <<-EOT
+ atom_feed do |feed|
+ feed.title("My great blog!")
+ feed.updated((@scrolls.first.created_at))
+
+ for scroll in @scrolls
+ feed.entry(scroll) do |entry|
+ entry.title(scroll.title)
+ entry.content(scroll.body, :type => 'html')
+
+ entry.author do |author|
+ author.name("DHH")
+ end
+ end
+ end
+ end
+ EOT
+ FEEDS["entry_options"] = <<-EOT
+ atom_feed do |feed|
+ feed.title("My great blog!")
+ feed.updated((@scrolls.first.created_at))
+
+ for scroll in @scrolls
+ feed.entry(scroll, :url => "/otherstuff/" + scroll.to_param, :updated => Time.utc(2007, 1, scroll.id)) do |entry|
+ entry.title(scroll.title)
+ entry.content(scroll.body, :type => 'html')
+
+ entry.author do |author|
+ author.name("DHH")
+ end
+ end
+ end
+ end
+ EOT
+
+ def index
+ @scrolls = [
+ Scroll.new(1, "1", "Hello One", "Something <i>COOL!</i>", Time.utc(2007, 12, 12, 15), Time.utc(2007, 12, 12, 15)),
+ Scroll.new(2, "2", "Hello Two", "Something Boring", Time.utc(2007, 12, 12, 15)),
+ ]
+
+ render :inline => FEEDS[params[:id]], :type => :builder
+ end
+end
+
+class AtomFeedTest < Test::Unit::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = ScrollsController.new
+
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_feed_should_use_default_language_if_none_is_given
+ with_restful_routing(:scrolls) do
+ get :index, :id => "defaults"
+ assert_match %r{xml:lang="en-US"}, @response.body
+ end
+ end
+
+ def test_feed_should_include_two_entries
+ with_restful_routing(:scrolls) do
+ get :index, :id => "defaults"
+ assert_select "entry", 2
+ end
+ end
+
+ def test_entry_should_only_use_published_if_created_at_is_present
+ with_restful_routing(:scrolls) do
+ get :index, :id => "defaults"
+ assert_select "published", 1
+ end
+ end
+
+ def test_entry_with_prefilled_options_should_use_those_instead_of_querying_the_record
+ with_restful_routing(:scrolls) do
+ get :index, :id => "entry_options"
+
+ assert_select "updated", Time.utc(2007, 1, 1).xmlschema
+ assert_select "updated", Time.utc(2007, 1, 2).xmlschema
+ end
+ end
+
+
+ private
+ def with_restful_routing(resources)
+ with_routing do |set|
+ set.draw do |map|
+ map.resources(resources)
+ end
+ yield
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/template/benchmark_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/benchmark_helper_test.rb
new file mode 100644
index 000000000..85f08bc6a
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/benchmark_helper_test.rb
@@ -0,0 +1,72 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+require 'action_view/helpers/benchmark_helper'
+
+class BenchmarkHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::BenchmarkHelper
+
+ class MockLogger
+ attr_reader :logged
+
+ def initialize
+ @logged = []
+ end
+
+ def method_missing(method, *args)
+ @logged << [method, args]
+ end
+ end
+
+ def setup
+ @logger = MockLogger.new
+ end
+
+ def test_without_logger_or_block
+ @logger = nil
+ assert_nothing_raised { benchmark }
+ end
+
+ def test_without_block
+ assert_raise(LocalJumpError) { benchmark }
+ assert @logger.logged.empty?
+ end
+
+ def test_without_logger
+ @logger = nil
+ i_was_run = false
+ benchmark { i_was_run = true }
+ assert !i_was_run
+ end
+
+ def test_defaults
+ i_was_run = false
+ benchmark { i_was_run = true }
+ assert i_was_run
+ assert 1, @logger.logged.size
+ assert_last_logged
+ end
+
+ def test_with_message
+ i_was_run = false
+ benchmark('test_run') { i_was_run = true }
+ assert i_was_run
+ assert 1, @logger.logged.size
+ assert_last_logged 'test_run'
+ end
+
+ def test_with_message_and_level
+ i_was_run = false
+ benchmark('debug_run', :debug) { i_was_run = true }
+ assert i_was_run
+ assert 1, @logger.logged.size
+ assert_last_logged 'debug_run', :debug
+ end
+
+ private
+ def assert_last_logged(message = 'Benchmarking', level = :info)
+ last = @logger.logged.last
+ assert 2, last.size
+ assert_equal level, last.first
+ assert 1, last[1].size
+ assert last[1][0] =~ /^#{message} \(.*\)$/
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/template/compiled_templates_test.rb b/vendor/rails-2.0.2/actionpack/test/template/compiled_templates_test.rb
new file mode 100644
index 000000000..d7ef75eb8
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/compiled_templates_test.rb
@@ -0,0 +1,193 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+require 'action_view/helpers/date_helper'
+require 'action_view/compiled_templates'
+
+class CompiledTemplateTests < Test::Unit::TestCase
+ def setup
+ @ct = ActionView::CompiledTemplates.new
+ @v = Class.new
+ @v.send :include, @ct
+ @a = './test_compile_template_a.rhtml'
+ @b = './test_compile_template_b.rhtml'
+ @s = './test_compile_template_link.rhtml'
+ end
+ def teardown
+ [@a, @b, @s].each do |f|
+ FileUtils.rm(f) if File.exist?(f) || File.symlink?(f)
+ end
+ end
+ attr_reader :ct, :v
+
+ def test_name_allocation
+ hi_world = ct.method_names['hi world']
+ hi_sexy = ct.method_names['hi sexy']
+ wish_upon_a_star = ct.method_names['I love seeing decent error messages']
+
+ assert_equal hi_world, ct.method_names['hi world']
+ assert_equal hi_sexy, ct.method_names['hi sexy']
+ assert_equal wish_upon_a_star, ct.method_names['I love seeing decent error messages']
+ assert_equal 3, [hi_world, hi_sexy, wish_upon_a_star].uniq.length
+ end
+
+ def test_wrap_source
+ assert_equal(
+ "def aliased_assignment(value)\nself.value = value\nend",
+ @ct.wrap_source(:aliased_assignment, [:value], 'self.value = value')
+ )
+
+ assert_equal(
+ "def simple()\nnil\nend",
+ @ct.wrap_source(:simple, [], 'nil')
+ )
+ end
+
+ def test_compile_source_single_method
+ selector = ct.compile_source('doubling method', [:a], 'a + a')
+ assert_equal 2, @v.new.send(selector, 1)
+ assert_equal 4, @v.new.send(selector, 2)
+ assert_equal -4, @v.new.send(selector, -2)
+ assert_equal 0, @v.new.send(selector, 0)
+ selector
+ end
+
+ def test_compile_source_two_method
+ sel1 = test_compile_source_single_method # compile the method in the other test
+ sel2 = ct.compile_source('doubling method', [:a, :b], 'a + b + a + b')
+ assert_not_equal sel1, sel2
+
+ assert_equal 2, @v.new.send(sel1, 1)
+ assert_equal 4, @v.new.send(sel1, 2)
+
+ assert_equal 6, @v.new.send(sel2, 1, 2)
+ assert_equal 32, @v.new.send(sel2, 15, 1)
+ end
+
+ def test_mtime
+ t1 = Time.now
+ test_compile_source_single_method
+ assert (t1..Time.now).include?(ct.mtime('doubling method', [:a]))
+ end
+
+ uses_mocha 'test_compile_time' do
+ def test_compile_time
+ t = Time.now
+
+ File.open(@a, "w"){|f| f.puts @a}
+ File.open(@b, "w"){|f| f.puts @b}
+ # windows doesn't support symlinks (even under cygwin)
+ windows = (RUBY_PLATFORM =~ /win32/)
+ `ln -s #{@a} #{@s}` unless windows
+
+ v = ActionView::Base.new
+ v.base_path = '.'
+ v.cache_template_loading = false
+
+ # All templates were created at t+1
+ File::Stat.any_instance.expects(:mtime).times(windows ? 2 : 3).returns(t + 1.second)
+
+ # private methods template_changed_since? and compile_template?
+ # should report true for all since they have not been compiled
+ assert v.send(:template_changed_since?, @a, t)
+ assert v.send(:template_changed_since?, @b, t)
+ assert v.send(:template_changed_since?, @s, t) unless windows
+
+ assert v.send(:compile_template?, nil, @a, {})
+ assert v.send(:compile_template?, nil, @b, {})
+ assert v.send(:compile_template?, nil, @s, {}) unless windows
+
+ @handler = ActionView::Base.handler_for_extension(:rhtml)
+
+ # All templates are rendered at t+2
+ Time.expects(:now).times(windows ? 2 : 3).returns(t + 2.seconds)
+ v.send(:compile_and_render_template, @handler, '', @a)
+ v.send(:compile_and_render_template, @handler, '', @b)
+ v.send(:compile_and_render_template, @handler, '', @s) unless windows
+ a_n = v.method_names[@a]
+ b_n = v.method_names[@b]
+ s_n = v.method_names[@s] unless windows
+ # all of the files have changed since last compile
+ assert v.compile_time[a_n] > t
+ assert v.compile_time[b_n] > t
+ assert v.compile_time[s_n] > t unless windows
+
+ # private methods template_changed_since? and compile_template?
+ # should report false for all since none have changed since compile
+ File::Stat.any_instance.expects(:mtime).times(windows ? 6 : 12).returns(t + 1.second)
+ assert !v.send(:template_changed_since?, @a, v.compile_time[a_n])
+ assert !v.send(:template_changed_since?, @b, v.compile_time[b_n])
+ assert !v.send(:template_changed_since?, @s, v.compile_time[s_n]) unless windows
+ assert !v.send(:compile_template?, nil, @a, {})
+ assert !v.send(:compile_template?, nil, @b, {})
+ assert !v.send(:compile_template?, nil, @s, {}) unless windows
+ v.send(:compile_and_render_template, @handler, '', @a)
+ v.send(:compile_and_render_template, @handler, '', @b)
+ v.send(:compile_and_render_template, @handler, '', @s) unless windows
+ # none of the files have changed since last compile
+ assert v.compile_time[a_n] < t + 3.seconds
+ assert v.compile_time[b_n] < t + 3.seconds
+ assert v.compile_time[s_n] < t + 3.seconds unless windows
+
+ `rm #{@s}; ln -s #{@b} #{@s}` unless windows
+ # private methods template_changed_since? and compile_template?
+ # should report true for symlink since it has changed since compile
+
+ # t + 3.seconds is for the symlink
+ File::Stat.any_instance.expects(:mtime).times(windows ? 6 : 9).returns(
+ *(windows ? [ t + 1.second, t + 1.second ] :
+ [ t + 1.second, t + 1.second, t + 3.second ]) * 3)
+ assert !v.send(:template_changed_since?, @a, v.compile_time[a_n])
+ assert !v.send(:template_changed_since?, @b, v.compile_time[b_n])
+ assert v.send(:template_changed_since?, @s, v.compile_time[s_n]) unless windows
+ assert !v.send(:compile_template?, nil, @a, {})
+ assert !v.send(:compile_template?, nil, @b, {})
+ assert v.send(:compile_template?, nil, @s, {}) unless windows
+
+ # Only the symlink template gets rendered at t+3
+ Time.stubs(:now).returns(t + 3.seconds) unless windows
+ v.send(:compile_and_render_template, @handler, '', @a)
+ v.send(:compile_and_render_template, @handler, '', @b)
+ v.send(:compile_and_render_template, @handler, '', @s) unless windows
+ # the symlink has changed since last compile
+ assert v.compile_time[a_n] < t + 3.seconds
+ assert v.compile_time[b_n] < t + 3.seconds
+ assert_equal v.compile_time[s_n], t + 3.seconds unless windows
+
+ FileUtils.touch @b
+ # private methods template_changed_since? and compile_template?
+ # should report true for symlink and file at end of symlink
+ # since it has changed since last compile
+ #
+ # t+4 is for @b and also for the file that @s points to, which is @b
+ File::Stat.any_instance.expects(:mtime).times(windows ? 6 : 12).returns(
+ *(windows ? [ t + 1.second, t + 4.seconds ] :
+ [ t + 1.second, t + 4.seconds, t + 3.second, t + 4.seconds ]) * 3)
+ assert !v.send(:template_changed_since?, @a, v.compile_time[a_n])
+ assert v.send(:template_changed_since?, @b, v.compile_time[b_n])
+ assert v.send(:template_changed_since?, @s, v.compile_time[s_n]) unless windows
+ assert !v.send(:compile_template?, nil, @a, {})
+ assert v.send(:compile_template?, nil, @b, {})
+ assert v.send(:compile_template?, nil, @s, {}) unless windows
+
+ Time.expects(:now).times(windows ? 1 : 2).returns(t + 5.seconds)
+ v.send(:compile_and_render_template, @handler, '', @a)
+ v.send(:compile_and_render_template, @handler, '', @b)
+ v.send(:compile_and_render_template, @handler, '', @s) unless windows
+ # the file at the end of the symlink has changed since last compile
+ # both the symlink and the file at the end of it should be recompiled
+ assert v.compile_time[a_n] < t + 5.seconds
+ assert_equal v.compile_time[b_n], t + 5.seconds
+ assert_equal v.compile_time[s_n], t + 5.seconds unless windows
+ end
+ end
+end
+
+module ActionView
+ class Base
+ def compile_time
+ @@compile_time
+ end
+ def method_names
+ @@method_names
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/template/date_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/date_helper_test.rb
new file mode 100755
index 000000000..c5b672a79
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/date_helper_test.rb
@@ -0,0 +1,1438 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+class DateHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::DateHelper
+ include ActionView::Helpers::FormHelper
+
+ silence_warnings do
+ Post = Struct.new("Post", :id, :written_on, :updated_at)
+ Post.class_eval do
+ def id
+ 123
+ end
+ def id_before_type_cast
+ 123
+ end
+ def to_param
+ '123'
+ end
+ end
+ end
+
+ def test_distance_in_words
+ from = Time.mktime(2004, 6, 6, 21, 45, 0)
+
+ # 0..1 with include_seconds
+ assert_equal "less than 5 seconds", distance_of_time_in_words(from, from + 0.seconds, true)
+ assert_equal "less than 5 seconds", distance_of_time_in_words(from, from + 4.seconds, true)
+ assert_equal "less than 10 seconds", distance_of_time_in_words(from, from + 5.seconds, true)
+ assert_equal "less than 10 seconds", distance_of_time_in_words(from, from + 9.seconds, true)
+ assert_equal "less than 20 seconds", distance_of_time_in_words(from, from + 10.seconds, true)
+ assert_equal "less than 20 seconds", distance_of_time_in_words(from, from + 19.seconds, true)
+ assert_equal "half a minute", distance_of_time_in_words(from, from + 20.seconds, true)
+ assert_equal "half a minute", distance_of_time_in_words(from, from + 39.seconds, true)
+ assert_equal "less than a minute", distance_of_time_in_words(from, from + 40.seconds, true)
+ assert_equal "less than a minute", distance_of_time_in_words(from, from + 59.seconds, true)
+ assert_equal "1 minute", distance_of_time_in_words(from, from + 60.seconds, true)
+ assert_equal "1 minute", distance_of_time_in_words(from, from + 89.seconds, true)
+
+ # First case 0..1
+ assert_equal "less than a minute", distance_of_time_in_words(from, from + 0.seconds)
+ assert_equal "less than a minute", distance_of_time_in_words(from, from + 29.seconds)
+ assert_equal "1 minute", distance_of_time_in_words(from, from + 30.seconds)
+ assert_equal "1 minute", distance_of_time_in_words(from, from + 1.minutes + 29.seconds)
+
+ # 2..44
+ assert_equal "2 minutes", distance_of_time_in_words(from, from + 1.minutes + 30.seconds)
+ assert_equal "44 minutes", distance_of_time_in_words(from, from + 44.minutes + 29.seconds)
+
+ # 45..89
+ assert_equal "about 1 hour", distance_of_time_in_words(from, from + 44.minutes + 30.seconds)
+ assert_equal "about 1 hour", distance_of_time_in_words(from, from + 89.minutes + 29.seconds)
+
+ # 90..1439
+ assert_equal "about 2 hours", distance_of_time_in_words(from, from + 89.minutes + 30.seconds)
+ assert_equal "about 24 hours", distance_of_time_in_words(from, from + 23.hours + 59.minutes + 29.seconds)
+
+ # 1440..2879
+ assert_equal "1 day", distance_of_time_in_words(from, from + 23.hours + 59.minutes + 30.seconds)
+ assert_equal "1 day", distance_of_time_in_words(from, from + 47.hours + 59.minutes + 29.seconds)
+
+ # 2880..43199
+ assert_equal "2 days", distance_of_time_in_words(from, from + 47.hours + 59.minutes + 30.seconds)
+ assert_equal "29 days", distance_of_time_in_words(from, from + 29.days + 23.hours + 59.minutes + 29.seconds)
+
+ # 43200..86399
+ assert_equal "about 1 month", distance_of_time_in_words(from, from + 29.days + 23.hours + 59.minutes + 30.seconds)
+ assert_equal "about 1 month", distance_of_time_in_words(from, from + 59.days + 23.hours + 59.minutes + 29.seconds)
+
+ # 86400..525599
+ assert_equal "2 months", distance_of_time_in_words(from, from + 59.days + 23.hours + 59.minutes + 30.seconds)
+ assert_equal "12 months", distance_of_time_in_words(from, from + 1.years - 31.seconds)
+
+ # 525600..1051199
+ assert_equal "about 1 year", distance_of_time_in_words(from, from + 1.years - 30.seconds)
+ assert_equal "about 1 year", distance_of_time_in_words(from, from + 2.years - 31.seconds)
+
+ # > 1051199
+ assert_equal "over 2 years", distance_of_time_in_words(from, from + 2.years + 30.seconds)
+ assert_equal "over 10 years", distance_of_time_in_words(from, from + 10.years)
+
+ # test to < from
+ assert_equal "about 4 hours", distance_of_time_in_words(from + 4.hours, from)
+ assert_equal "less than 20 seconds", distance_of_time_in_words(from + 19.seconds, from, true)
+
+ # test with integers
+ assert_equal "less than a minute", distance_of_time_in_words(59)
+ assert_equal "about 1 hour", distance_of_time_in_words(60*60)
+ assert_equal "less than a minute", distance_of_time_in_words(0, 59)
+ assert_equal "about 1 hour", distance_of_time_in_words(60*60, 0)
+ end
+
+ def test_distance_in_words_with_dates
+ start_date = Date.new 1975, 1, 31
+ end_date = Date.new 1977, 1, 31
+ assert_equal("over 2 years", distance_of_time_in_words(start_date, end_date))
+ end
+
+ def test_time_ago_in_words
+ assert_equal "about 1 year", time_ago_in_words(1.year.ago - 1.day)
+ end
+
+ def test_select_day
+ expected = %(<select id="date_day" name="date[day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_day(Time.mktime(2003, 8, 16))
+ assert_equal expected, select_day(16)
+ end
+
+ def test_select_day_with_blank
+ expected = %(<select id="date_day" name="date[day]">\n)
+ expected << %(<option value=""></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_day(Time.mktime(2003, 8, 16), :include_blank => true)
+ assert_equal expected, select_day(16, :include_blank => true)
+ end
+
+ def test_select_day_nil_with_blank
+ expected = %(<select id="date_day" name="date[day]">\n)
+ expected << %(<option value=""></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_day(nil, :include_blank => true)
+ end
+
+ def test_select_month
+ expected = %(<select id="date_month" name="date[month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_month(Time.mktime(2003, 8, 16))
+ assert_equal expected, select_month(8)
+ end
+
+ def test_select_month_with_disabled
+ expected = %(<select id="date_month" name="date[month]" disabled="disabled">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_month(Time.mktime(2003, 8, 16), :disabled => true)
+ assert_equal expected, select_month(8, :disabled => true)
+ end
+
+ def test_select_month_with_field_name_override
+ expected = %(<select id="date_mois" name="date[mois]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_month(Time.mktime(2003, 8, 16), :field_name => 'mois')
+ assert_equal expected, select_month(8, :field_name => 'mois')
+ end
+
+ def test_select_month_with_blank
+ expected = %(<select id="date_month" name="date[month]">\n)
+ expected << %(<option value=""></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_month(Time.mktime(2003, 8, 16), :include_blank => true)
+ assert_equal expected, select_month(8, :include_blank => true)
+ end
+
+ def test_select_month_nil_with_blank
+ expected = %(<select id="date_month" name="date[month]">\n)
+ expected << %(<option value=""></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_month(nil, :include_blank => true)
+ end
+
+ def test_select_month_with_numbers
+ expected = %(<select id="date_month" name="date[month]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8" selected="selected">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_month(Time.mktime(2003, 8, 16), :use_month_numbers => true)
+ assert_equal expected, select_month(8, :use_month_numbers => true)
+ end
+
+ def test_select_month_with_numbers_and_names
+ expected = %(<select id="date_month" name="date[month]">\n)
+ expected << %(<option value="1">1 - January</option>\n<option value="2">2 - February</option>\n<option value="3">3 - March</option>\n<option value="4">4 - April</option>\n<option value="5">5 - May</option>\n<option value="6">6 - June</option>\n<option value="7">7 - July</option>\n<option value="8" selected="selected">8 - August</option>\n<option value="9">9 - September</option>\n<option value="10">10 - October</option>\n<option value="11">11 - November</option>\n<option value="12">12 - December</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_month(Time.mktime(2003, 8, 16), :add_month_numbers => true)
+ assert_equal expected, select_month(8, :add_month_numbers => true)
+ end
+
+ def test_select_month_with_numbers_and_names_with_abbv
+ expected = %(<select id="date_month" name="date[month]">\n)
+ expected << %(<option value="1">1 - Jan</option>\n<option value="2">2 - Feb</option>\n<option value="3">3 - Mar</option>\n<option value="4">4 - Apr</option>\n<option value="5">5 - May</option>\n<option value="6">6 - Jun</option>\n<option value="7">7 - Jul</option>\n<option value="8" selected="selected">8 - Aug</option>\n<option value="9">9 - Sep</option>\n<option value="10">10 - Oct</option>\n<option value="11">11 - Nov</option>\n<option value="12">12 - Dec</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_month(Time.mktime(2003, 8, 16), :add_month_numbers => true, :use_short_month => true)
+ assert_equal expected, select_month(8, :add_month_numbers => true, :use_short_month => true)
+ end
+
+ def test_select_month_with_abbv
+ expected = %(<select id="date_month" name="date[month]">\n)
+ expected << %(<option value="1">Jan</option>\n<option value="2">Feb</option>\n<option value="3">Mar</option>\n<option value="4">Apr</option>\n<option value="5">May</option>\n<option value="6">Jun</option>\n<option value="7">Jul</option>\n<option value="8" selected="selected">Aug</option>\n<option value="9">Sep</option>\n<option value="10">Oct</option>\n<option value="11">Nov</option>\n<option value="12">Dec</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_month(Time.mktime(2003, 8, 16), :use_short_month => true)
+ assert_equal expected, select_month(8, :use_short_month => true)
+ end
+
+ def test_select_month_with_custom_names
+ month_names = %w(nil Januar Februar Marts April Maj Juni Juli August September Oktober November December)
+
+ expected = %(<select id="date_month" name="date[month]">\n)
+ 1.upto(12) { |month| expected << %(<option value="#{month}"#{' selected="selected"' if month == 8}>#{month_names[month]}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, select_month(Time.mktime(2003, 8, 16), :use_month_names => month_names)
+ assert_equal expected, select_month(8, :use_month_names => month_names)
+ end
+
+ def test_select_month_with_zero_indexed_custom_names
+ month_names = %w(Januar Februar Marts April Maj Juni Juli August September Oktober November December)
+
+ expected = %(<select id="date_month" name="date[month]">\n)
+ 1.upto(12) { |month| expected << %(<option value="#{month}"#{' selected="selected"' if month == 8}>#{month_names[month-1]}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, select_month(Time.mktime(2003, 8, 16), :use_month_names => month_names)
+ assert_equal expected, select_month(8, :use_month_names => month_names)
+ end
+
+ def test_select_month_with_hidden
+ assert_dom_equal "<input type=\"hidden\" id=\"date_month\" name=\"date[month]\" value=\"8\" />\n", select_month(8, :use_hidden => true)
+ end
+
+ def test_select_month_with_hidden_and_field_name
+ assert_dom_equal "<input type=\"hidden\" id=\"date_mois\" name=\"date[mois]\" value=\"8\" />\n", select_month(8, :use_hidden => true, :field_name => 'mois')
+ end
+
+ def test_select_year
+ expected = %(<select id="date_year" name="date[year]">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_year(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005)
+ assert_equal expected, select_year(2003, :start_year => 2003, :end_year => 2005)
+ end
+
+ def test_select_year_with_disabled
+ expected = %(<select id="date_year" name="date[year]" disabled="disabled">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_year(Time.mktime(2003, 8, 16), :disabled => true, :start_year => 2003, :end_year => 2005)
+ assert_equal expected, select_year(2003, :disabled => true, :start_year => 2003, :end_year => 2005)
+ end
+
+ def test_select_year_with_field_name_override
+ expected = %(<select id="date_annee" name="date[annee]">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_year(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :field_name => 'annee')
+ assert_equal expected, select_year(2003, :start_year => 2003, :end_year => 2005, :field_name => 'annee')
+ end
+
+ def test_select_year_with_type_discarding
+ expected = %(<select id="date_year" name="date_year">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_year(
+ Time.mktime(2003, 8, 16), :prefix => "date_year", :discard_type => true, :start_year => 2003, :end_year => 2005)
+ assert_equal expected, select_year(
+ 2003, :prefix => "date_year", :discard_type => true, :start_year => 2003, :end_year => 2005)
+ end
+
+ def test_select_year_descending
+ expected = %(<select id="date_year" name="date[year]">\n)
+ expected << %(<option value="2005" selected="selected">2005</option>\n<option value="2004">2004</option>\n<option value="2003">2003</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_year(Time.mktime(2005, 8, 16), :start_year => 2005, :end_year => 2003)
+ assert_equal expected, select_year(2005, :start_year => 2005, :end_year => 2003)
+ end
+
+ def test_select_year_with_hidden
+ assert_dom_equal "<input type=\"hidden\" id=\"date_year\" name=\"date[year]\" value=\"2007\" />\n", select_year(2007, :use_hidden => true)
+ end
+
+ def test_select_year_with_hidden_and_field_name
+ assert_dom_equal "<input type=\"hidden\" id=\"date_anno\" name=\"date[anno]\" value=\"2007\" />\n", select_year(2007, :use_hidden => true, :field_name => 'anno')
+ end
+
+ def test_select_hour
+ expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18))
+ end
+
+ def test_select_hour_with_disabled
+ expected = %(<select id="date_hour" name="date[hour]" disabled="disabled">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :disabled => true)
+ end
+
+ def test_select_hour_with_field_name_override
+ expected = %(<select id="date_heure" name="date[heure]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :field_name => 'heure')
+ end
+
+ def test_select_hour_with_blank
+ expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :include_blank => true)
+ end
+
+ def test_select_hour_nil_with_blank
+ expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_hour(nil, :include_blank => true)
+ end
+
+ def test_select_minute
+ expected = %(<select id="date_minute" name="date[minute]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18))
+ end
+
+ def test_select_minute_with_disabled
+ expected = %(<select id="date_minute" name="date[minute]" disabled="disabled">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :disabled => true)
+ end
+
+ def test_select_minute_with_field_name_override
+ expected = %(<select id="date_minuto" name="date[minuto]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :field_name => 'minuto')
+ end
+
+ def test_select_minute_with_blank
+ expected = %(<select id="date_minute" name="date[minute]">\n)
+ expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :include_blank => true)
+ end
+
+ def test_select_minute_with_blank_and_step
+ expected = %(<select id="date_minute" name="date[minute]">\n)
+ expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="15">15</option>\n<option value="30">30</option>\n<option value="45">45</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), { :include_blank => true , :minute_step => 15 })
+ end
+
+ def test_select_minute_nil_with_blank
+ expected = %(<select id="date_minute" name="date[minute]">\n)
+ expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_minute(nil, :include_blank => true)
+ end
+
+ def test_select_minute_nil_with_blank_and_step
+ expected = %(<select id="date_minute" name="date[minute]">\n)
+ expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="15">15</option>\n<option value="30">30</option>\n<option value="45">45</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_minute(nil, { :include_blank => true , :minute_step => 15 })
+ end
+
+ def test_select_minute_with_hidden
+ assert_dom_equal "<input type=\"hidden\" id=\"date_minute\" name=\"date[minute]\" value=\"8\" />\n", select_minute(8, :use_hidden => true)
+ end
+
+ def test_select_minute_with_hidden_and_field_name
+ assert_dom_equal "<input type=\"hidden\" id=\"date_minuto\" name=\"date[minuto]\" value=\"8\" />\n", select_minute(8, :use_hidden => true, :field_name => 'minuto')
+ end
+
+ def test_select_second
+ expected = %(<select id="date_second" name="date[second]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18))
+ end
+
+ def test_select_second_with_disabled
+ expected = %(<select id="date_second" name="date[second]" disabled="disabled">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :disabled => true)
+ end
+
+ def test_select_second_with_field_name_override
+ expected = %(<select id="date_segundo" name="date[segundo]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :field_name => 'segundo')
+ end
+
+ def test_select_second_with_blank
+ expected = %(<select id="date_second" name="date[second]">\n)
+ expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :include_blank => true)
+ end
+
+ def test_select_second_nil_with_blank
+ expected = %(<select id="date_second" name="date[second]">\n)
+ expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_second(nil, :include_blank => true)
+ end
+
+ def test_select_date
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]")
+ end
+
+ def test_select_date_with_order
+ expected = %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :order => [:month, :day, :year])
+ end
+
+ def test_select_date_with_incomplete_order
+ expected = %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :order => [:day])
+ end
+
+ def test_select_date_with_disabled
+ expected = %(<select id="date_first_year" name="date[first][year]" disabled="disabled">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]" disabled="disabled">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]" disabled="disabled">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :disabled => true)
+ end
+
+ def test_select_date_with_no_start_year
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year-5).upto(Date.today.year+1) do |y|
+ if y == Date.today.year
+ expected << %(<option value="#{y}" selected="selected">#{y}</option>\n)
+ else
+ expected << %(<option value="#{y}">#{y}</option>\n)
+ end
+ end
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(
+ Time.mktime(Date.today.year, 8, 16), :end_year => Date.today.year+1, :prefix => "date[first]"
+ )
+ end
+
+ def test_select_date_with_no_end_year
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ 2003.upto(2008) do |y|
+ if y == 2003
+ expected << %(<option value="#{y}" selected="selected">#{y}</option>\n)
+ else
+ expected << %(<option value="#{y}">#{y}</option>\n)
+ end
+ end
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(
+ Time.mktime(2003, 8, 16), :start_year => 2003, :prefix => "date[first]"
+ )
+ end
+
+ def test_select_date_with_no_start_or_end_year
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year-5).upto(Date.today.year+5) do |y|
+ if y == Date.today.year
+ expected << %(<option value="#{y}" selected="selected">#{y}</option>\n)
+ else
+ expected << %(<option value="#{y}">#{y}</option>\n)
+ end
+ end
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(
+ Time.mktime(Date.today.year, 8, 16), :prefix => "date[first]"
+ )
+ end
+
+ def test_select_date_with_zero_value
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(0, :start_year => 2003, :end_year => 2005, :prefix => "date[first]")
+ end
+
+ def test_select_date_with_zero_value_and_no_start_year
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year-5).upto(Date.today.year+1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(0, :end_year => Date.today.year+1, :prefix => "date[first]")
+ end
+
+ def test_select_date_with_zero_value_and_no_end_year
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ last_year = Time.now.year + 5
+ 2003.upto(last_year) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(0, :start_year => 2003, :prefix => "date[first]")
+ end
+
+ def test_select_date_with_zero_value_and_no_start_and_end_year
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(0, :prefix => "date[first]")
+ end
+
+ def test_select_date_with_nil_value_and_no_start_and_end_year
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(nil, :prefix => "date[first]")
+ end
+
+ def test_select_datetime
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_hour" name="date[first][hour]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_minute" name="date[first][minute]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :prefix => "date[first]")
+ end
+
+ def test_select_datetime_with_separators
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %(<select id="date_first_hour" name="date[first][hour]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="date_first_minute" name="date[first][minute]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :datetime_separator => ' &mdash; ', :time_separator => ' : ')
+ end
+
+ def test_select_datetime_with_nil_value_and_no_start_and_end_year
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_hour" name="date[first][hour]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_minute" name="date[first][minute]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_datetime(nil, :prefix => "date[first]")
+ end
+
+ def test_select_time
+ expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_minute" name="date[minute]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18))
+ assert_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => false)
+ end
+
+ def test_select_time_with_separator
+ expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="date_minute" name="date[minute]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :time_separator => ' : ')
+ assert_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :time_separator => ' : ', :include_seconds => false)
+ end
+
+ def test_select_time_with_seconds
+ expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_minute" name="date[minute]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_second" name="date[second]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => true)
+ end
+
+ def test_select_time_with_seconds_and_separator
+ expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="date_minute" name="date[minute]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="date_second" name="date[second]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => true, :time_separator => ' : ')
+ end
+
+ def test_date_select
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+
+ expected << "</select>\n"
+
+ assert_equal expected, date_select("post", "written_on")
+ end
+
+ def test_date_select_without_day
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+
+ expected = "<input type=\"hidden\" id=\"post_written_on_3i\" name=\"post[written_on(3i)]\" value=\"1\" />\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ assert_equal expected, date_select("post", "written_on", :order => [ :month, :year ])
+ end
+
+ def test_date_select_within_fields_for
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+
+ _erbout = ''
+
+ fields_for :post, @post do |f|
+ _erbout.concat f.date_select(:written_on)
+ end
+
+ expected = "<select id='post_written_on_1i' name='post[written_on(1i)]'>\n<option value='1999'>1999</option>\n<option value='2000'>2000</option>\n<option value='2001'>2001</option>\n<option value='2002'>2002</option>\n<option value='2003'>2003</option>\n<option selected='selected' value='2004'>2004</option>\n<option value='2005'>2005</option>\n<option value='2006'>2006</option>\n<option value='2007'>2007</option>\n<option value='2008'>2008</option>\n<option value='2009'>2009</option>\n</select>\n"
+ expected << "<select id='post_written_on_2i' name='post[written_on(2i)]'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option selected='selected' value='6'>June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n"
+ expected << "<select id='post_written_on_3i' name='post[written_on(3i)]'>\n<option value='1'>1</option>\n<option value='2'>2</option>\n<option value='3'>3</option>\n<option value='4'>4</option>\n<option value='5'>5</option>\n<option value='6'>6</option>\n<option value='7'>7</option>\n<option value='8'>8</option>\n<option value='9'>9</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option selected='selected' value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n</select>\n"
+
+ assert_dom_equal(expected, _erbout)
+ end
+
+ def test_date_select_with_index
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+ id = 456
+
+ expected = %{<select id="post_456_written_on_1i" name="post[#{id}][written_on(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_456_written_on_2i" name="post[#{id}][written_on(2i)]">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_456_written_on_3i" name="post[#{id}][written_on(3i)]">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+
+ expected << "</select>\n"
+
+ assert_equal expected, date_select("post", "written_on", :index => id)
+ end
+
+ def test_date_select_with_auto_index
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+ id = 123
+
+ expected = %{<select id="post_123_written_on_1i" name="post[#{id}][written_on(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_123_written_on_2i" name="post[#{id}][written_on(2i)]">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_123_written_on_3i" name="post[#{id}][written_on(3i)]">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+ expected << "</select>\n"
+
+ assert_equal expected, date_select("post[]", "written_on")
+ end
+
+ def test_date_select_with_different_order
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+
+ expected = %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
+ 1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ 1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ 1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, date_select("post", "written_on", :order => [:day, :month, :year])
+ end
+
+ def test_date_select_with_nil
+ @post = Post.new
+
+ start_year = Time.now.year-5
+ end_year = Time.now.year+5
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ start_year.upto(end_year) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == Time.now.year}>#{i}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ 1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == Time.now.month}>#{Date::MONTHNAMES[i]}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
+ 1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == Time.now.day}>#{i}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, date_select("post", "written_on")
+ end
+
+ def test_date_select_with_nil_and_blank
+ @post = Post.new
+
+ start_year = Time.now.year-5
+ end_year = Time.now.year+5
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected << "<option value=\"\"></option>\n"
+ start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ expected << "<option value=\"\"></option>\n"
+ 1.upto(12) { |i| expected << %(<option value="#{i}">#{Date::MONTHNAMES[i]}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
+ expected << "<option value=\"\"></option>\n"
+ 1.upto(31) { |i| expected << %(<option value="#{i}">#{i}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, date_select("post", "written_on", :include_blank => true)
+ end
+
+ def test_date_select_cant_override_discard_hour
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+ expected << "</select>\n"
+
+ assert_equal expected, date_select("post", "written_on", :discard_hour => false)
+ end
+
+ def test_time_select
+ @post = Post.new
+ @post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
+ expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
+
+ expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n)
+ 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n)
+ 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, time_select("post", "written_on")
+ end
+
+ def test_time_select_with_seconds
+ @post = Post.new
+ @post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
+ expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
+
+ expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n)
+ 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n)
+ 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %(<select id="post_written_on_6i" name="post[written_on(6i)]">\n)
+ 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 35}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, time_select("post", "written_on", :include_seconds => true)
+ end
+
+ def test_datetime_select
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 16, 35)
+
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n}
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35" selected="selected">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n}
+ expected << "</select>\n"
+
+ assert_equal expected, datetime_select("post", "updated_at")
+ end
+
+ def test_datetime_select_within_fields_for
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 16, 35)
+
+ _erbout = ''
+
+ fields_for :post, @post do |f|
+ _erbout.concat f.datetime_select(:updated_at)
+ end
+
+ expected = "<select id='post_updated_at_1i' name='post[updated_at(1i)]'>\n<option value='1999'>1999</option>\n<option value='2000'>2000</option>\n<option value='2001'>2001</option>\n<option value='2002'>2002</option>\n<option value='2003'>2003</option>\n<option selected='selected' value='2004'>2004</option>\n<option value='2005'>2005</option>\n<option value='2006'>2006</option>\n<option value='2007'>2007</option>\n<option value='2008'>2008</option>\n<option value='2009'>2009</option>\n</select>\n"
+ expected << "<select id='post_updated_at_2i' name='post[updated_at(2i)]'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option selected='selected' value='6'>June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n"
+ expected << "<select id='post_updated_at_3i' name='post[updated_at(3i)]'>\n<option value='1'>1</option>\n<option value='2'>2</option>\n<option value='3'>3</option>\n<option value='4'>4</option>\n<option value='5'>5</option>\n<option value='6'>6</option>\n<option value='7'>7</option>\n<option value='8'>8</option>\n<option value='9'>9</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option selected='selected' value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n</select>\n"
+ expected << " &mdash; <select id='post_updated_at_4i' name='post[updated_at(4i)]'>\n<option value='00'>00</option>\n<option value='01'>01</option>\n<option value='02'>02</option>\n<option value='03'>03</option>\n<option value='04'>04</option>\n<option value='05'>05</option>\n<option value='06'>06</option>\n<option value='07'>07</option>\n<option value='08'>08</option>\n<option value='09'>09</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option value='15'>15</option>\n<option selected='selected' value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n</select>\n"
+ expected << " : <select id='post_updated_at_5i' name='post[updated_at(5i)]'>\n<option value='00'>00</option>\n<option value='01'>01</option>\n<option value='02'>02</option>\n<option value='03'>03</option>\n<option value='04'>04</option>\n<option value='05'>05</option>\n<option value='06'>06</option>\n<option value='07'>07</option>\n<option value='08'>08</option>\n<option value='09'>09</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n<option value='32'>32</option>\n<option value='33'>33</option>\n<option value='34'>34</option>\n<option selected='selected' value='35'>35</option>\n<option value='36'>36</option>\n<option value='37'>37</option>\n<option value='38'>38</option>\n<option value='39'>39</option>\n<option value='40'>40</option>\n<option value='41'>41</option>\n<option value='42'>42</option>\n<option value='43'>43</option>\n<option value='44'>44</option>\n<option value='45'>45</option>\n<option value='46'>46</option>\n<option value='47'>47</option>\n<option value='48'>48</option>\n<option value='49'>49</option>\n<option value='50'>50</option>\n<option value='51'>51</option>\n<option value='52'>52</option>\n<option value='53'>53</option>\n<option value='54'>54</option>\n<option value='55'>55</option>\n<option value='56'>56</option>\n<option value='57'>57</option>\n<option value='58'>58</option>\n<option value='59'>59</option>\n</select>\n"
+
+ assert_dom_equal(expected, _erbout)
+ end
+
+ def test_date_select_with_zero_value_and_no_start_year
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year-5).upto(Date.today.year+1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected <<
+%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(0, :end_year => Date.today.year+1, :prefix => "date[first]")
+ end
+
+ def test_date_select_with_zero_value_and_no_end_year
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ last_year = Time.now.year + 5
+ 2003.upto(last_year) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected <<
+%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(0, :start_year => 2003, :prefix => "date[first]")
+ end
+
+ def test_date_select_with_zero_value_and_no_start_and_end_year
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected <<
+%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(0, :prefix => "date[first]")
+ end
+
+ def test_date_select_with_nil_value_and_no_start_and_end_year
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected <<
+%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(nil, :prefix => "date[first]")
+ end
+
+ def test_datetime_select_with_nil_value_and_no_start_and_end_year
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected <<
+%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_hour" name="date[first][hour]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_minute" name="date[first][minute]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_equal expected, select_datetime(nil, :prefix => "date[first]")
+ end
+
+
+ def test_datetime_select_with_options_index
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 16, 35)
+ id = 456
+
+ expected = %{<select id="post_456_updated_at_1i" name="post[#{id}][updated_at(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_456_updated_at_2i" name="post[#{id}][updated_at(2i)]">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_456_updated_at_3i" name="post[#{id}][updated_at(3i)]">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_456_updated_at_4i" name="post[#{id}][updated_at(4i)]">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n}
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_456_updated_at_5i" name="post[#{id}][updated_at(5i)]">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35" selected="selected">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n}
+ expected << "</select>\n"
+
+ assert_equal expected, datetime_select("post", "updated_at", :index => id)
+ end
+
+ def test_datetime_select_with_auto_index
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 16, 35)
+ id = @post.id
+
+ expected = %{<select id="post_123_updated_at_1i" name="post[#{id}][updated_at(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_123_updated_at_2i" name="post[#{id}][updated_at(2i)]">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_123_updated_at_3i" name="post[#{id}][updated_at(3i)]">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_123_updated_at_4i" name="post[#{id}][updated_at(4i)]">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n}
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_123_updated_at_5i" name="post[#{id}][updated_at(5i)]">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35" selected="selected">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n}
+ expected << "</select>\n"
+
+ assert_equal expected, datetime_select("post[]", "updated_at")
+ end
+
+ def test_datetime_select_with_seconds
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ 1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) }
+ expected << "</select>\n"
+ expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
+ 1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) }
+ expected << "</select>\n"
+ expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}
+ 1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) }
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n}
+ 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n}
+ 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_updated_at_6i" name="post[updated_at(6i)]">\n}
+ 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 35}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, datetime_select("post", "updated_at", :include_seconds => true)
+ end
+
+ def test_datetime_select_discard_year
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<input type="hidden" id="post_updated_at_1i" name="post[updated_at(1i)]" value="2004" />\n}
+ expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
+ 1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) }
+ expected << "</select>\n"
+ expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}
+ 1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) }
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n}
+ 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n}
+ 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, datetime_select("post", "updated_at", :discard_year => true)
+ end
+
+ def test_datetime_select_discard_month
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ 1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) }
+ expected << "</select>\n"
+ expected << %{<input type="hidden" id="post_updated_at_2i" name="post[updated_at(2i)]" value="6" />\n}
+ expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="15" />\n}
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n}
+ 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n}
+ 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, datetime_select("post", "updated_at", :discard_month => true)
+ end
+
+ def test_datetime_select_discard_year_and_month
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<input type="hidden" id="post_updated_at_1i" name="post[updated_at(1i)]" value="2004" />\n}
+ expected << %{<input type="hidden" id="post_updated_at_2i" name="post[updated_at(2i)]" value="6" />\n}
+ expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="15" />\n}
+
+ expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n}
+ 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n}
+ 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, datetime_select("post", "updated_at", :discard_year => true, :discard_month => true)
+ end
+
+ def test_datetime_select_invalid_order
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}
+ 1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) }
+ expected << "</select>\n"
+ expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
+ 1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) }
+ expected << "</select>\n"
+ expected << %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ 1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) }
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n}
+ 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n}
+ 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, datetime_select("post", "updated_at", :order => [:minute, :day, :hour, :month, :year, :second])
+ end
+
+ def test_datetime_select_discard_with_order
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<input type="hidden" id="post_updated_at_1i" name="post[updated_at(1i)]" value="2004" />\n}
+ expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}
+ 1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) }
+ expected << "</select>\n"
+ expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
+ 1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) }
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n}
+ 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n}
+ 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, datetime_select("post", "updated_at", :order => [:day, :month])
+ end
+
+ def test_datetime_select_with_default_value_as_time
+ @post = Post.new
+ @post.updated_at = nil
+
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ 2001.upto(2011) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2006}>#{i}</option>\n) }
+ expected << "</select>\n"
+ expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
+ 1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 9}>#{Date::MONTHNAMES[i]}</option>\n) }
+ expected << "</select>\n"
+ expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}
+ 1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 19}>#{i}</option>\n) }
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n}
+ 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n}
+ 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, datetime_select("post", "updated_at", :default => Time.local(2006, 9, 19, 15, 16, 35))
+ end
+
+ def test_include_blank_overrides_default_option
+ @post = Post.new
+ @post.updated_at = nil
+
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected << %(<option value=""></option>\n)
+ 2002.upto(2012) { |i| expected << %(<option value="#{i}">#{i}</option>\n) }
+ expected << "</select>\n"
+ expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
+ expected << %(<option value=""></option>\n)
+ 1.upto(12) { |i| expected << %(<option value="#{i}">#{Date::MONTHNAMES[i]}</option>\n) }
+ expected << "</select>\n"
+ expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}
+ expected << %(<option value=""></option>\n)
+ 1.upto(31) { |i| expected << %(<option value="#{i}">#{i}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, date_select("post", "updated_at", :default => Time.local(2006, 9, 19, 15, 16, 35), :include_blank => true)
+ end
+
+ def test_datetime_select_with_default_value_as_hash
+ @post = Post.new
+ @post.updated_at = nil
+
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ (Time.now.year - 5).upto(Time.now.year + 5) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == Time.now.year}>#{i}</option>\n) }
+ expected << "</select>\n"
+ expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
+ 1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 10}>#{Date::MONTHNAMES[i]}</option>\n) }
+ expected << "</select>\n"
+ expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}
+ 1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == Time.now.day}>#{i}</option>\n) }
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n}
+ 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 9}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n}
+ 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 42}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_equal expected, datetime_select("post", "updated_at", :default => { :month => 10, :minute => 42, :hour => 9 })
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/template/erb_util_test.rb b/vendor/rails-2.0.2/actionpack/test/template/erb_util_test.rb
new file mode 100644
index 000000000..3aff987b2
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/erb_util_test.rb
@@ -0,0 +1,56 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+class ErbUtilTest < Test::Unit::TestCase
+ include ERB::Util
+
+ def test_amp
+ assert_equal '&amp;', html_escape('&')
+ end
+
+ def test_quot
+ assert_equal '&quot;', html_escape('"')
+ end
+
+ def test_lt
+ assert_equal '&lt;', html_escape('<')
+ end
+
+ def test_gt
+ assert_equal '&gt;', html_escape('>')
+ end
+
+ def test_rest_in_ascii
+ (0..127).to_a.map(&:chr).each do |chr|
+ next if %w(& " < >).include?(chr)
+ assert_equal chr, html_escape(chr)
+ end
+ end
+end
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+class ErbUtilTest < Test::Unit::TestCase
+ include ERB::Util
+
+ def test_amp
+ assert_equal '&amp;', html_escape('&')
+ end
+
+ def test_quot
+ assert_equal '&quot;', html_escape('"')
+ end
+
+ def test_lt
+ assert_equal '&lt;', html_escape('<')
+ end
+
+ def test_gt
+ assert_equal '&gt;', html_escape('>')
+ end
+
+ def test_rest_in_ascii
+ (0..127).to_a.map(&:chr).each do |chr|
+ next if %w(& " < >).include?(chr)
+ assert_equal chr, html_escape(chr)
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/template/form_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/form_helper_test.rb
new file mode 100644
index 000000000..37e3538b1
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/form_helper_test.rb
@@ -0,0 +1,792 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+silence_warnings do
+ Post = Struct.new(:title, :author_name, :body, :secret, :written_on, :cost)
+ Post.class_eval do
+ alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast)
+ alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast)
+ alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast)
+
+ def new_record=(boolean)
+ @new_record = boolean
+ end
+
+ def new_record?
+ @new_record
+ end
+ end
+
+ class Comment
+ attr_reader :id
+ attr_reader :post_id
+ def save; @id = 1; @post_id = 1 end
+ def new_record?; @id.nil? end
+ def name
+ @id.nil? ? 'new comment' : "comment ##{@id}"
+ end
+ end
+end
+
+class Comment::Nested < Comment; end
+
+
+class FormHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::FormHelper
+ include ActionView::Helpers::FormTagHelper
+ include ActionView::Helpers::UrlHelper
+ include ActionView::Helpers::TagHelper
+ include ActionView::Helpers::TextHelper
+ include ActionView::Helpers::ActiveRecordHelper
+ include ActionView::Helpers::RecordIdentificationHelper
+ include ActionController::PolymorphicRoutes
+
+ def setup
+ @post = Post.new
+ @comment = Comment.new
+ def @post.errors()
+ Class.new{
+ def on(field); "can't be empty" if field == "author_name"; end
+ def empty?() false end
+ def count() 1 end
+ def full_messages() [ "Author name can't be empty" ] end
+ }.new
+ end
+ def @post.id; 123; end
+ def @post.id_before_type_cast; 123; end
+ def @post.to_param; '123'; end
+
+ @post.title = "Hello World"
+ @post.author_name = ""
+ @post.body = "Back to the hill and over it again!"
+ @post.secret = 1
+ @post.written_on = Date.new(2004, 6, 15)
+
+ @controller = Class.new do
+ attr_reader :url_for_options
+ def url_for(options)
+ @url_for_options = options
+ "http://www.example.com"
+ end
+ end
+ @controller = @controller.new
+ end
+
+ def test_label
+ assert_dom_equal('<label for="post_title">Title</label>', label("post", "title"))
+ assert_dom_equal('<label for="post_title">The title goes here</label>', label("post", "title", "The title goes here"))
+ assert_dom_equal(
+ '<label class="title_label" for="post_title">Title</label>',
+ label("post", "title", nil, :class => 'title_label')
+ )
+ end
+
+ def test_label_with_symbols
+ assert_dom_equal('<label for="post_title">Title</label>', label(:post, :title))
+ end
+
+ def test_text_field
+ assert_dom_equal(
+ '<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title")
+ )
+ assert_dom_equal(
+ '<input id="post_title" name="post[title]" size="30" type="password" value="Hello World" />', password_field("post", "title")
+ )
+ assert_dom_equal(
+ '<input id="person_name" name="person[name]" size="30" type="password" />', password_field("person", "name")
+ )
+ end
+
+ def test_text_field_with_escapes
+ @post.title = "<b>Hello World</b>"
+ assert_dom_equal(
+ '<input id="post_title" name="post[title]" size="30" type="text" value="&lt;b&gt;Hello World&lt;/b&gt;" />', text_field("post", "title")
+ )
+ end
+
+ def test_text_field_with_options
+ expected = '<input id="post_title" name="post[title]" size="35" type="text" value="Hello World" />'
+ assert_dom_equal expected, text_field("post", "title", "size" => 35)
+ assert_dom_equal expected, text_field("post", "title", :size => 35)
+ end
+
+ def test_text_field_assuming_size
+ expected = '<input id="post_title" maxlength="35" name="post[title]" size="35" type="text" value="Hello World" />'
+ assert_dom_equal expected, text_field("post", "title", "maxlength" => 35)
+ assert_dom_equal expected, text_field("post", "title", :maxlength => 35)
+ end
+
+ def test_text_field_removing_size
+ expected = '<input id="post_title" maxlength="35" name="post[title]" type="text" value="Hello World" />'
+ assert_dom_equal expected, text_field("post", "title", "maxlength" => 35, "size" => nil)
+ assert_dom_equal expected, text_field("post", "title", :maxlength => 35, :size => nil)
+ end
+
+ def test_text_field_doesnt_change_param_values
+ object_name = 'post[]'
+ expected = '<input id="post_123_title" name="post[123][title]" size="30" type="text" value="Hello World" />'
+ assert_equal expected, text_field(object_name, "title")
+ assert_equal object_name, "post[]"
+ end
+
+ def test_hidden_field
+ assert_dom_equal '<input id="post_title" name="post[title]" type="hidden" value="Hello World" />',
+ hidden_field("post", "title")
+ end
+
+ def test_hidden_field_with_escapes
+ @post.title = "<b>Hello World</b>"
+ assert_dom_equal '<input id="post_title" name="post[title]" type="hidden" value="&lt;b&gt;Hello World&lt;/b&gt;" />',
+ hidden_field("post", "title")
+ end
+
+ def test_text_field_with_options
+ assert_dom_equal '<input id="post_title" name="post[title]" type="hidden" value="Something Else" />',
+ hidden_field("post", "title", :value => "Something Else")
+ end
+
+ def test_check_box
+ assert_dom_equal(
+ '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
+ check_box("post", "secret")
+ )
+ @post.secret = 0
+ assert_dom_equal(
+ '<input id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
+ check_box("post", "secret")
+ )
+ assert_dom_equal(
+ '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
+ check_box("post", "secret" ,{"checked"=>"checked"})
+ )
+ @post.secret = true
+ assert_dom_equal(
+ '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
+ check_box("post", "secret")
+ )
+ end
+
+ def test_check_box_with_explicit_checked_and_unchecked_values
+ @post.secret = "on"
+ assert_dom_equal(
+ '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="on" /><input name="post[secret]" type="hidden" value="off" />',
+ check_box("post", "secret", {}, "on", "off")
+ )
+ end
+
+ def test_checkbox_disabled_still_submits_checked_value
+ assert_dom_equal(
+ '<input checked="checked" disabled="disabled" id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="1" />',
+ check_box("post", "secret", { :disabled => :true })
+ )
+ end
+
+ def test_radio_button
+ assert_dom_equal('<input checked="checked" id="post_title_hello_world" name="post[title]" type="radio" value="Hello World" />',
+ radio_button("post", "title", "Hello World")
+ )
+ assert_dom_equal('<input id="post_title_goodbye_world" name="post[title]" type="radio" value="Goodbye World" />',
+ radio_button("post", "title", "Goodbye World")
+ )
+ end
+
+ def test_radio_button_is_checked_with_integers
+ assert_dom_equal('<input checked="checked" id="post_secret_1" name="post[secret]" type="radio" value="1" />',
+ radio_button("post", "secret", "1")
+ )
+ end
+
+ def test_radio_button_respects_passed_in_id
+ assert_dom_equal('<input checked="checked" id="foo" name="post[secret]" type="radio" value="1" />',
+ radio_button("post", "secret", "1", :id=>"foo")
+ )
+ end
+
+ def test_text_area
+ assert_dom_equal(
+ '<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
+ text_area("post", "body")
+ )
+ end
+
+ def test_text_area_with_escapes
+ @post.body = "Back to <i>the</i> hill and over it again!"
+ assert_dom_equal(
+ '<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to &lt;i&gt;the&lt;/i&gt; hill and over it again!</textarea>',
+ text_area("post", "body")
+ )
+ end
+
+ def test_text_area_with_alternate_value
+ assert_dom_equal(
+ '<textarea cols="40" id="post_body" name="post[body]" rows="20">Testing alternate values.</textarea>',
+ text_area("post", "body", :value => 'Testing alternate values.')
+ )
+ end
+
+ def test_text_area_with_size_option
+ assert_dom_equal(
+ '<textarea cols="183" id="post_body" name="post[body]" rows="820">Back to the hill and over it again!</textarea>',
+ text_area("post", "body", :size => "183x820")
+ )
+ end
+
+ def test_explicit_name
+ assert_dom_equal(
+ '<input id="post_title" name="dont guess" size="30" type="text" value="Hello World" />', text_field("post", "title", "name" => "dont guess")
+ )
+ assert_dom_equal(
+ '<textarea cols="40" id="post_body" name="really!" rows="20">Back to the hill and over it again!</textarea>',
+ text_area("post", "body", "name" => "really!")
+ )
+ assert_dom_equal(
+ '<input checked="checked" id="post_secret" name="i mean it" type="checkbox" value="1" /><input name="i mean it" type="hidden" value="0" />',
+ check_box("post", "secret", "name" => "i mean it")
+ )
+ assert_dom_equal text_field("post", "title", "name" => "dont guess"),
+ text_field("post", "title", :name => "dont guess")
+ assert_dom_equal text_area("post", "body", "name" => "really!"),
+ text_area("post", "body", :name => "really!")
+ assert_dom_equal check_box("post", "secret", "name" => "i mean it"),
+ check_box("post", "secret", :name => "i mean it")
+ end
+
+ def test_explicit_id
+ assert_dom_equal(
+ '<input id="dont guess" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title", "id" => "dont guess")
+ )
+ assert_dom_equal(
+ '<textarea cols="40" id="really!" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
+ text_area("post", "body", "id" => "really!")
+ )
+ assert_dom_equal(
+ '<input checked="checked" id="i mean it" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
+ check_box("post", "secret", "id" => "i mean it")
+ )
+ assert_dom_equal text_field("post", "title", "id" => "dont guess"),
+ text_field("post", "title", :id => "dont guess")
+ assert_dom_equal text_area("post", "body", "id" => "really!"),
+ text_area("post", "body", :id => "really!")
+ assert_dom_equal check_box("post", "secret", "id" => "i mean it"),
+ check_box("post", "secret", :id => "i mean it")
+ end
+
+ def test_auto_index
+ pid = @post.id
+ assert_dom_equal(
+ "<label for=\"post_#{pid}_title\">Title</label>",
+ label("post[]", "title")
+ )
+ assert_dom_equal(
+ "<input id=\"post_#{pid}_title\" name=\"post[#{pid}][title]\" size=\"30\" type=\"text\" value=\"Hello World\" />", text_field("post[]","title")
+ )
+ assert_dom_equal(
+ "<textarea cols=\"40\" id=\"post_#{pid}_body\" name=\"post[#{pid}][body]\" rows=\"20\">Back to the hill and over it again!</textarea>",
+ text_area("post[]", "body")
+ )
+ assert_dom_equal(
+ "<input checked=\"checked\" id=\"post_#{pid}_secret\" name=\"post[#{pid}][secret]\" type=\"checkbox\" value=\"1\" /><input name=\"post[#{pid}][secret]\" type=\"hidden\" value=\"0\" />",
+ check_box("post[]", "secret")
+ )
+ assert_dom_equal(
+"<input checked=\"checked\" id=\"post_#{pid}_title_hello_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Hello World\" />",
+ radio_button("post[]", "title", "Hello World")
+ )
+ assert_dom_equal("<input id=\"post_#{pid}_title_goodbye_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Goodbye World\" />",
+ radio_button("post[]", "title", "Goodbye World")
+ )
+ end
+
+ def test_form_for
+ _erbout = ''
+
+ form_for(:post, @post, :html => { :id => 'create-post' }) do |f|
+ _erbout.concat f.label(:title)
+ _erbout.concat f.text_field(:title)
+ _erbout.concat f.text_area(:body)
+ _erbout.concat f.check_box(:secret)
+ _erbout.concat f.submit('Create post')
+ end
+
+ expected =
+ "<form action='http://www.example.com' id='create-post' method='post'>" +
+ "<label for='post_title'>Title</label>" +
+ "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='commit' id='post_submit' type='submit' value='Create post' />" +
+ "</form>"
+
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_form_for_with_method
+ _erbout = ''
+
+ form_for(:post, @post, :html => { :id => 'create-post', :method => :put }) do |f|
+ _erbout.concat f.text_field(:title)
+ _erbout.concat f.text_area(:body)
+ _erbout.concat f.check_box(:secret)
+ end
+
+ expected =
+ "<form action='http://www.example.com' id='create-post' method='post'>" +
+ "<div style='margin:0;padding:0'><input name='_method' type='hidden' value='put' /></div>" +
+ "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "</form>"
+
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_form_for_without_object
+ _erbout = ''
+
+ form_for(:post, :html => { :id => 'create-post' }) do |f|
+ _erbout.concat f.text_field(:title)
+ _erbout.concat f.text_area(:body)
+ _erbout.concat f.check_box(:secret)
+ end
+
+ expected =
+ "<form action='http://www.example.com' id='create-post' method='post'>" +
+ "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "</form>"
+
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_form_for_with_index
+ _erbout = ''
+
+ form_for("post[]", @post) do |f|
+ _erbout.concat f.label(:title)
+ _erbout.concat f.text_field(:title)
+ _erbout.concat f.text_area(:body)
+ _erbout.concat f.check_box(:secret)
+ end
+
+ expected =
+ "<form action='http://www.example.com' method='post'>" +
+ "<label for=\"post_123_title\">Title</label>" +
+ "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" +
+ "<textarea name='post[123][body]' id='post_123_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />" +
+ "<input name='post[123][secret]' type='hidden' value='0' />" +
+ "</form>"
+
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_nested_fields_for
+ _erbout = ''
+ form_for(:post, @post) do |f|
+ f.fields_for(:comment, @post) do |c|
+ _erbout.concat c.text_field(:title)
+ end
+ end
+
+ expected = "<form action='http://www.example.com' method='post'>" +
+ "<input name='post[comment][title]' size='30' type='text' id='post_comment_title' value='Hello World' />" +
+ "</form>"
+
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_fields_for
+ _erbout = ''
+
+ fields_for(:post, @post) do |f|
+ _erbout.concat f.text_field(:title)
+ _erbout.concat f.text_area(:body)
+ _erbout.concat f.check_box(:secret)
+ end
+
+ expected =
+ "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
+ "<input name='post[secret]' type='hidden' value='0' />"
+
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_fields_for_without_object
+ _erbout = ''
+ fields_for(:post) do |f|
+ _erbout.concat f.text_field(:title)
+ _erbout.concat f.text_area(:body)
+ _erbout.concat f.check_box(:secret)
+ end
+
+ expected =
+ "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
+ "<input name='post[secret]' type='hidden' value='0' />"
+
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_fields_for_with_only_object
+ _erbout = ''
+ fields_for(@post) do |f|
+ _erbout.concat f.text_field(:title)
+ _erbout.concat f.text_area(:body)
+ _erbout.concat f.check_box(:secret)
+ end
+
+ expected =
+ "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
+ "<input name='post[secret]' type='hidden' value='0' />"
+
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_fields_for_object_with_bracketed_name
+ _erbout = ''
+ fields_for("author[post]", @post) do |f|
+ _erbout.concat f.label(:title)
+ _erbout.concat f.text_field(:title)
+ end
+
+ assert_dom_equal "<label for=\"author_post_title\">Title</label>" +
+ "<input name='author[post][title]' size='30' type='text' id='author_post_title' value='Hello World' />",
+ _erbout
+ end
+
+ def test_form_builder_does_not_have_form_for_method
+ assert ! ActionView::Helpers::FormBuilder.instance_methods.include?('form_for')
+ end
+
+ def test_form_for_and_fields_for
+ _erbout = ''
+
+ form_for(:post, @post, :html => { :id => 'create-post' }) do |post_form|
+ _erbout.concat post_form.text_field(:title)
+ _erbout.concat post_form.text_area(:body)
+
+ fields_for(:parent_post, @post) do |parent_fields|
+ _erbout.concat parent_fields.check_box(:secret)
+ end
+ end
+
+ expected =
+ "<form action='http://www.example.com' id='create-post' method='post'>" +
+ "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='parent_post[secret]' checked='checked' type='checkbox' id='parent_post_secret' value='1' />" +
+ "<input name='parent_post[secret]' type='hidden' value='0' />" +
+ "</form>"
+
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_form_for_and_fields_for_with_object
+ _erbout = ''
+
+ form_for(:post, @post, :html => { :id => 'create-post' }) do |post_form|
+ _erbout.concat post_form.text_field(:title)
+ _erbout.concat post_form.text_area(:body)
+
+ post_form.fields_for(@comment) do |comment_fields|
+ _erbout.concat comment_fields.text_field(:name)
+ end
+ end
+
+ expected =
+ "<form action='http://www.example.com' id='create-post' method='post'>" +
+ "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' size='30' />" +
+ "</form>"
+
+ assert_dom_equal expected, _erbout
+ end
+
+ class LabelledFormBuilder < ActionView::Helpers::FormBuilder
+ (field_helpers - %w(hidden_field)).each do |selector|
+ src = <<-END_SRC
+ def #{selector}(field, *args, &proc)
+ "<label for='\#{field}'>\#{field.to_s.humanize}:</label> " + super + "<br/>"
+ end
+ END_SRC
+ class_eval src, __FILE__, __LINE__
+ end
+ end
+
+ def test_form_for_with_labelled_builder
+ _erbout = ''
+
+ form_for(:post, @post, :builder => LabelledFormBuilder) do |f|
+ _erbout.concat f.text_field(:title)
+ _erbout.concat f.text_area(:body)
+ _erbout.concat f.check_box(:secret)
+ end
+
+ expected =
+ "<form action='http://www.example.com' method='post'>" +
+ "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
+ "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
+ "<label for='secret'>Secret:</label> <input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
+ "<input name='post[secret]' type='hidden' value='0' /><br/>" +
+ "</form>"
+
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_default_form_builder
+ old_default_form_builder, ActionView::Base.default_form_builder =
+ ActionView::Base.default_form_builder, LabelledFormBuilder
+
+ _erbout = ''
+ form_for(:post, @post) do |f|
+ _erbout.concat f.text_field(:title)
+ _erbout.concat f.text_area(:body)
+ _erbout.concat f.check_box(:secret)
+ end
+
+ expected =
+ "<form action='http://www.example.com' method='post'>" +
+ "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
+ "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
+ "<label for='secret'>Secret:</label> <input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
+ "<input name='post[secret]' type='hidden' value='0' /><br/>" +
+ "</form>"
+
+ assert_dom_equal expected, _erbout
+ ensure
+ ActionView::Base.default_form_builder = old_default_form_builder
+ end
+
+ def test_default_form_builder_with_active_record_helpers
+
+ _erbout = ''
+ form_for(:post, @post) do |f|
+ _erbout.concat f.error_message_on('author_name')
+ _erbout.concat f.error_messages
+ end
+
+ expected = %(<form action='http://www.example.com' method='post'>) +
+ %(<div class='formError'>can't be empty</div>) +
+ %(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>) +
+ %(</form>)
+
+ assert_dom_equal expected, _erbout
+
+ end
+
+ def test_default_form_builder_no_instance_variable
+ post = @post
+ @post = nil
+
+ _erbout = ''
+ form_for(:post, post) do |f|
+ _erbout.concat f.error_message_on('author_name')
+ _erbout.concat f.error_messages
+ end
+
+ expected = %(<form action='http://www.example.com' method='post'>) +
+ %(<div class='formError'>can't be empty</div>) +
+ %(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>) +
+ %(</form>)
+
+ assert_dom_equal expected, _erbout
+
+ end
+
+ # Perhaps this test should be moved to prototype helper tests.
+ def test_remote_form_for_with_labelled_builder
+ self.extend ActionView::Helpers::PrototypeHelper
+ _erbout = ''
+
+ remote_form_for(:post, @post, :builder => LabelledFormBuilder) do |f|
+ _erbout.concat f.text_field(:title)
+ _erbout.concat f.text_area(:body)
+ _erbout.concat f.check_box(:secret)
+ end
+
+ expected =
+ %(<form action="http://www.example.com" onsubmit="new Ajax.Request('http://www.example.com', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" method="post">) +
+ "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
+ "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
+ "<label for='secret'>Secret:</label> <input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
+ "<input name='post[secret]' type='hidden' value='0' /><br/>" +
+ "</form>"
+
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_fields_for_with_labelled_builder
+ _erbout = ''
+
+ fields_for(:post, @post, :builder => LabelledFormBuilder) do |f|
+ _erbout.concat f.text_field(:title)
+ _erbout.concat f.text_area(:body)
+ _erbout.concat f.check_box(:secret)
+ end
+
+ expected =
+ "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
+ "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
+ "<label for='secret'>Secret:</label> <input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
+ "<input name='post[secret]' type='hidden' value='0' /><br/>"
+
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_form_for_with_html_options_adds_options_to_form_tag
+ _erbout = ''
+
+ form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end
+ expected = "<form action=\"http://www.example.com\" class=\"some_class\" id=\"some_form\" method=\"post\"></form>"
+
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_form_for_with_string_url_option
+ _erbout = ''
+
+ form_for(:post, @post, :url => 'http://www.otherdomain.com') do |f| end
+
+ assert_equal '<form action="http://www.otherdomain.com" method="post"></form>', _erbout
+ end
+
+ def test_form_for_with_hash_url_option
+ _erbout = ''
+
+ form_for(:post, @post, :url => {:controller => 'controller', :action => 'action'}) do |f| end
+
+ assert_equal 'controller', @controller.url_for_options[:controller]
+ assert_equal 'action', @controller.url_for_options[:action]
+ end
+
+ def test_form_for_with_record_url_option
+ _erbout = ''
+
+ form_for(:post, @post, :url => @post) do |f| end
+
+ expected = "<form action=\"/posts/123\" method=\"post\"></form>"
+ assert_equal expected, _erbout
+ end
+
+ def test_form_for_with_existing_object
+ _erbout = ''
+
+ form_for(@post) do |f| end
+
+ expected = "<form action=\"/posts/123\" class=\"edit_post\" id=\"edit_post_123\" method=\"post\"><div style=\"margin:0;padding:0\"><input name=\"_method\" type=\"hidden\" value=\"put\" /></div></form>"
+ assert_equal expected, _erbout
+ end
+
+ def test_form_for_with_new_object
+ _erbout = ''
+
+ post = Post.new
+ post.new_record = true
+ def post.id() nil end
+
+ form_for(post) do |f| end
+
+ expected = "<form action=\"/posts\" class=\"new_post\" id=\"new_post\" method=\"post\"></form>"
+ assert_equal expected, _erbout
+ end
+
+ def test_form_for_with_existing_object_in_list
+ @post.new_record = false
+ @comment.save
+ _erbout = ''
+ form_for([@post, @comment]) {}
+
+ expected = %(<form action="#{comment_path(@post, @comment)}" class="edit_comment" id="edit_comment_1" method="post"><div style="margin:0;padding:0"><input name="_method" type="hidden" value="put" /></div></form>)
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_form_for_with_new_object_in_list
+ @post.new_record = false
+ _erbout = ''
+ form_for([@post, @comment]) {}
+
+ expected = %(<form action="#{comments_path(@post)}" class="new_comment" id="new_comment" method="post"></form>)
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_form_for_with_existing_object_and_namespace_in_list
+ @post.new_record = false
+ @comment.save
+ _erbout = ''
+ form_for([:admin, @post, @comment]) {}
+
+ expected = %(<form action="#{admin_comment_path(@post, @comment)}" class="edit_comment" id="edit_comment_1" method="post"><div style="margin:0;padding:0"><input name="_method" type="hidden" value="put" /></div></form>)
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_form_for_with_new_object_and_namespace_in_list
+ @post.new_record = false
+ _erbout = ''
+ form_for([:admin, @post, @comment]) {}
+
+ expected = %(<form action="#{admin_comments_path(@post)}" class="new_comment" id="new_comment" method="post"></form>)
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_form_for_with_existing_object_and_custom_url
+ _erbout = ''
+
+ form_for(@post, :url => "/super_posts") do |f| end
+
+ expected = "<form action=\"/super_posts\" class=\"edit_post\" id=\"edit_post_123\" method=\"post\"><div style=\"margin:0;padding:0\"><input name=\"_method\" type=\"hidden\" value=\"put\" /></div></form>"
+ assert_equal expected, _erbout
+ end
+
+ def test_remote_form_for_with_html_options_adds_options_to_form_tag
+ self.extend ActionView::Helpers::PrototypeHelper
+ _erbout = ''
+
+ remote_form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end
+ expected = "<form action=\"http://www.example.com\" class=\"some_class\" id=\"some_form\" method=\"post\" onsubmit=\"new Ajax.Request('http://www.example.com', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\"></form>"
+
+ assert_dom_equal expected, _erbout
+ end
+
+
+ protected
+ def comments_path(post)
+ "/posts/#{post.id}/comments"
+ end
+ alias_method :post_comments_path, :comments_path
+
+ def comment_path(post, comment)
+ "/posts/#{post.id}/comments/#{comment.id}"
+ end
+ alias_method :post_comment_path, :comment_path
+
+ def admin_comments_path(post)
+ "/admin/posts/#{post.id}/comments"
+ end
+ alias_method :admin_post_comments_path, :admin_comments_path
+
+ def admin_comment_path(post, comment)
+ "/admin/posts/#{post.id}/comments/#{comment.id}"
+ end
+ alias_method :admin_post_comment_path, :admin_comment_path
+
+ def posts_path
+ "/posts"
+ end
+
+ def post_path(post)
+ "/posts/#{post.id}"
+ end
+
+ def protect_against_forgery?
+ false
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/template/form_options_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/form_options_helper_test.rb
new file mode 100644
index 000000000..8f7db02f2
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/form_options_helper_test.rb
@@ -0,0 +1,1296 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+class MockTimeZone
+ attr_reader :name
+
+ def initialize( name )
+ @name = name
+ end
+
+ def self.all
+ [ "A", "B", "C", "D", "E" ].map { |s| new s }
+ end
+
+ def ==( z )
+ z && @name == z.name
+ end
+
+ def to_s
+ @name
+ end
+end
+
+ActionView::Helpers::FormOptionsHelper::TimeZone = MockTimeZone
+
+class FormOptionsHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::FormHelper
+ include ActionView::Helpers::FormOptionsHelper
+
+ silence_warnings do
+ Post = Struct.new('Post', :title, :author_name, :body, :secret, :written_on, :category, :origin)
+ Continent = Struct.new('Continent', :continent_name, :countries)
+ Country = Struct.new('Country', :country_id, :country_name)
+ Firm = Struct.new('Firm', :time_zone)
+ end
+
+ def test_collection_options
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ assert_dom_equal(
+ "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
+ options_from_collection_for_select(@posts, "author_name", "title")
+ )
+ end
+
+
+ def test_collection_options_with_preselected_value
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ assert_dom_equal(
+ "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
+ options_from_collection_for_select(@posts, "author_name", "title", "Babe")
+ )
+ end
+
+ def test_collection_options_with_preselected_value_array
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ assert_dom_equal(
+ "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\" selected=\"selected\">Cabe went home</option>",
+ options_from_collection_for_select(@posts, "author_name", "title", [ "Babe", "Cabe" ])
+ )
+ end
+
+ def test_array_options_for_select
+ assert_dom_equal(
+ "<option value=\"&lt;Denmark&gt;\">&lt;Denmark&gt;</option>\n<option value=\"USA\">USA</option>\n<option value=\"Sweden\">Sweden</option>",
+ options_for_select([ "<Denmark>", "USA", "Sweden" ])
+ )
+ end
+
+ def test_array_options_for_select_with_selection
+ assert_dom_equal(
+ "<option value=\"Denmark\">Denmark</option>\n<option value=\"&lt;USA&gt;\" selected=\"selected\">&lt;USA&gt;</option>\n<option value=\"Sweden\">Sweden</option>",
+ options_for_select([ "Denmark", "<USA>", "Sweden" ], "<USA>")
+ )
+ end
+
+ def test_array_options_for_select_with_selection_array
+ assert_dom_equal(
+ "<option value=\"Denmark\">Denmark</option>\n<option value=\"&lt;USA&gt;\" selected=\"selected\">&lt;USA&gt;</option>\n<option value=\"Sweden\" selected=\"selected\">Sweden</option>",
+ options_for_select([ "Denmark", "<USA>", "Sweden" ], [ "<USA>", "Sweden" ])
+ )
+ end
+
+ def test_array_options_for_string_include_in_other_string_bug_fix
+ assert_dom_equal(
+ "<option value=\"ruby\">ruby</option>\n<option value=\"rubyonrails\" selected=\"selected\">rubyonrails</option>",
+ options_for_select([ "ruby", "rubyonrails" ], "rubyonrails")
+ )
+ assert_dom_equal(
+ "<option value=\"ruby\" selected=\"selected\">ruby</option>\n<option value=\"rubyonrails\">rubyonrails</option>",
+ options_for_select([ "ruby", "rubyonrails" ], "ruby")
+ )
+ assert_dom_equal(
+ %(<option value="ruby" selected="selected">ruby</option>\n<option value="rubyonrails">rubyonrails</option>\n<option value=""></option>),
+ options_for_select([ "ruby", "rubyonrails", nil ], "ruby")
+ )
+ end
+
+ def test_hash_options_for_select
+ assert_dom_equal(
+ "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\">$</option>",
+ options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" })
+ )
+ assert_dom_equal(
+ "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
+ options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, "Dollar")
+ )
+ assert_dom_equal(
+ "<option value=\"&lt;Kroner&gt;\" selected=\"selected\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
+ options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, [ "Dollar", "<Kroner>" ])
+ )
+ end
+
+ def test_ducktyped_options_for_select
+ quack = Struct.new(:first, :last)
+ assert_dom_equal(
+ "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\">$</option>",
+ options_for_select([quack.new("<DKR>", "<Kroner>"), quack.new("$", "Dollar")])
+ )
+ assert_dom_equal(
+ "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
+ options_for_select([quack.new("<DKR>", "<Kroner>"), quack.new("$", "Dollar")], "Dollar")
+ )
+ assert_dom_equal(
+ "<option value=\"&lt;Kroner&gt;\" selected=\"selected\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
+ options_for_select([quack.new("<DKR>", "<Kroner>"), quack.new("$", "Dollar")], ["Dollar", "<Kroner>"])
+ )
+ end
+
+ def test_option_groups_from_collection_for_select
+ @continents = [
+ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
+ Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
+ ]
+
+ assert_dom_equal(
+ "<optgroup label=\"&lt;Africa&gt;\"><option value=\"&lt;sa&gt;\">&lt;South Africa&gt;</option>\n<option value=\"so\">Somalia</option></optgroup><optgroup label=\"Europe\"><option value=\"dk\" selected=\"selected\">Denmark</option>\n<option value=\"ie\">Ireland</option></optgroup>",
+ option_groups_from_collection_for_select(@continents, "countries", "continent_name", "country_id", "country_name", "dk")
+ )
+ end
+
+ def test_time_zone_options_no_parms
+ opts = time_zone_options_for_select
+ assert_dom_equal "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\">D</option>\n" +
+ "<option value=\"E\">E</option>",
+ opts
+ end
+
+ def test_time_zone_options_with_selected
+ opts = time_zone_options_for_select( "D" )
+ assert_dom_equal "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>",
+ opts
+ end
+
+ def test_time_zone_options_with_unknown_selected
+ opts = time_zone_options_for_select( "K" )
+ assert_dom_equal "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\">D</option>\n" +
+ "<option value=\"E\">E</option>",
+ opts
+ end
+
+ def test_time_zone_options_with_priority_zones
+ zones = [ TimeZone.new( "B" ), TimeZone.new( "E" ) ]
+ opts = time_zone_options_for_select( nil, zones )
+ assert_dom_equal "<option value=\"B\">B</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\">D</option>",
+ opts
+ end
+
+ def test_time_zone_options_with_selected_priority_zones
+ zones = [ TimeZone.new( "B" ), TimeZone.new( "E" ) ]
+ opts = time_zone_options_for_select( "E", zones )
+ assert_dom_equal "<option value=\"B\">B</option>\n" +
+ "<option value=\"E\" selected=\"selected\">E</option>" +
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\">D</option>",
+ opts
+ end
+
+ def test_time_zone_options_with_unselected_priority_zones
+ zones = [ TimeZone.new( "B" ), TimeZone.new( "E" ) ]
+ opts = time_zone_options_for_select( "C", zones )
+ assert_dom_equal "<option value=\"B\">B</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"C\" selected=\"selected\">C</option>\n" +
+ "<option value=\"D\">D</option>",
+ opts
+ end
+
+ def test_select
+ @post = Post.new
+ @post.category = "<mus>"
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
+ select("post", "category", %w( abe <mus> hest))
+ )
+ end
+
+ def test_select_under_fields_for
+ @post = Post.new
+ @post.category = "<mus>"
+
+ _erbout = ''
+
+ fields_for :post, @post do |f|
+ _erbout.concat f.select(:category, %w( abe <mus> hest))
+ end
+
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
+ _erbout
+ )
+ end
+
+ def test_select_with_blank
+ @post = Post.new
+ @post.category = "<mus>"
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\"></option>\n<option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
+ select("post", "category", %w( abe <mus> hest), :include_blank => true)
+ )
+ end
+
+ def test_select_with_blank_as_string
+ @post = Post.new
+ @post.category = "<mus>"
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">None</option>\n<option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
+ select("post", "category", %w( abe <mus> hest), :include_blank => 'None')
+ )
+ end
+
+ def test_select_with_default_prompt
+ @post = Post.new
+ @post.category = ""
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
+ select("post", "category", %w( abe <mus> hest), :prompt => true)
+ )
+ end
+
+ def test_select_no_prompt_when_select_has_value
+ @post = Post.new
+ @post.category = "<mus>"
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
+ select("post", "category", %w( abe <mus> hest), :prompt => true)
+ )
+ end
+
+ def test_select_with_given_prompt
+ @post = Post.new
+ @post.category = ""
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">The prompt</option>\n<option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
+ select("post", "category", %w( abe <mus> hest), :prompt => 'The prompt')
+ )
+ end
+
+ def test_select_with_prompt_and_blank
+ @post = Post.new
+ @post.category = ""
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"\"></option>\n<option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
+ select("post", "category", %w( abe <mus> hest), :prompt => true, :include_blank => true)
+ )
+ end
+
+ def test_select_with_selected_value
+ @post = Post.new
+ @post.category = "<mus>"
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\" selected=\"selected\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
+ select("post", "category", %w( abe <mus> hest ), :selected => 'abe')
+ )
+ end
+
+ def test_select_with_selected_nil
+ @post = Post.new
+ @post.category = "<mus>"
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
+ select("post", "category", %w( abe <mus> hest ), :selected => nil)
+ )
+ end
+
+ def test_collection_select
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ @post = Post.new
+ @post.author_name = "Babe"
+
+ assert_dom_equal(
+ "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
+ collection_select("post", "author_name", @posts, "author_name", "author_name")
+ )
+ end
+
+ def test_collection_select_under_fields_for
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ @post = Post.new
+ @post.author_name = "Babe"
+
+ _erbout = ''
+
+ fields_for :post, @post do |f|
+ _erbout.concat f.collection_select(:author_name, @posts, :author_name, :author_name)
+ end
+
+ assert_dom_equal(
+ "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
+ _erbout
+ )
+ end
+
+ def test_collection_select_with_blank_and_style
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ @post = Post.new
+ @post.author_name = "Babe"
+
+ assert_dom_equal(
+ "<select id=\"post_author_name\" name=\"post[author_name]\" style=\"width: 200px\"><option value=\"\"></option>\n<option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
+ collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true }, "style" => "width: 200px")
+ )
+ end
+
+ def test_collection_select_with_blank_as_string_and_style
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ @post = Post.new
+ @post.author_name = "Babe"
+
+ assert_dom_equal(
+ "<select id=\"post_author_name\" name=\"post[author_name]\" style=\"width: 200px\"><option value=\"\">No Selection</option>\n<option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
+ collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => 'No Selection' }, "style" => "width: 200px")
+ )
+ end
+
+ def test_collection_select_with_multiple_option_appends_array_brackets
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ @post = Post.new
+ @post.author_name = "Babe"
+
+ expected = "<select id=\"post_author_name\" name=\"post[author_name][]\" multiple=\"multiple\"><option value=\"\"></option>\n<option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>"
+
+ # Should suffix default name with [].
+ assert_dom_equal expected, collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true }, :multiple => true)
+
+ # Shouldn't suffix custom name with [].
+ assert_dom_equal expected, collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true, :name => 'post[author_name][]' }, :multiple => true)
+ end
+
+ def test_country_select
+ @post = Post.new
+ @post.origin = "Denmark"
+ expected_select = <<-COUNTRIES
+<select id="post_origin" name="post[origin]"><option value="Afghanistan">Afghanistan</option>
+<option value="Aland Islands">Aland Islands</option>
+<option value="Albania">Albania</option>
+<option value="Algeria">Algeria</option>
+<option value="American Samoa">American Samoa</option>
+<option value="Andorra">Andorra</option>
+<option value="Angola">Angola</option>
+<option value="Anguilla">Anguilla</option>
+<option value="Antarctica">Antarctica</option>
+<option value="Antigua And Barbuda">Antigua And Barbuda</option>
+<option value="Argentina">Argentina</option>
+<option value="Armenia">Armenia</option>
+<option value="Aruba">Aruba</option>
+<option value="Australia">Australia</option>
+<option value="Austria">Austria</option>
+<option value="Azerbaijan">Azerbaijan</option>
+<option value="Bahamas">Bahamas</option>
+<option value="Bahrain">Bahrain</option>
+<option value="Bangladesh">Bangladesh</option>
+<option value="Barbados">Barbados</option>
+<option value="Belarus">Belarus</option>
+<option value="Belgium">Belgium</option>
+<option value="Belize">Belize</option>
+<option value="Benin">Benin</option>
+<option value="Bermuda">Bermuda</option>
+<option value="Bhutan">Bhutan</option>
+<option value="Bolivia">Bolivia</option>
+<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option>
+<option value="Botswana">Botswana</option>
+<option value="Bouvet Island">Bouvet Island</option>
+<option value="Brazil">Brazil</option>
+<option value="British Indian Ocean Territory">British Indian Ocean Territory</option>
+<option value="Brunei Darussalam">Brunei Darussalam</option>
+<option value="Bulgaria">Bulgaria</option>
+<option value="Burkina Faso">Burkina Faso</option>
+<option value="Burundi">Burundi</option>
+<option value="Cambodia">Cambodia</option>
+<option value="Cameroon">Cameroon</option>
+<option value="Canada">Canada</option>
+<option value="Cape Verde">Cape Verde</option>
+<option value="Cayman Islands">Cayman Islands</option>
+<option value="Central African Republic">Central African Republic</option>
+<option value="Chad">Chad</option>
+<option value="Chile">Chile</option>
+<option value="China">China</option>
+<option value="Christmas Island">Christmas Island</option>
+<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option>
+<option value="Colombia">Colombia</option>
+<option value="Comoros">Comoros</option>
+<option value="Congo">Congo</option>
+<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option>
+<option value="Cook Islands">Cook Islands</option>
+<option value="Costa Rica">Costa Rica</option>
+<option value="Cote d'Ivoire">Cote d'Ivoire</option>
+<option value="Croatia">Croatia</option>
+<option value="Cuba">Cuba</option>
+<option value="Cyprus">Cyprus</option>
+<option value="Czech Republic">Czech Republic</option>
+<option selected="selected" value="Denmark">Denmark</option>
+<option value="Djibouti">Djibouti</option>
+<option value="Dominica">Dominica</option>
+<option value="Dominican Republic">Dominican Republic</option>
+<option value="Ecuador">Ecuador</option>
+<option value="Egypt">Egypt</option>
+<option value="El Salvador">El Salvador</option>
+<option value="Equatorial Guinea">Equatorial Guinea</option>
+<option value="Eritrea">Eritrea</option>
+<option value="Estonia">Estonia</option>
+<option value="Ethiopia">Ethiopia</option>
+<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option>
+<option value="Faroe Islands">Faroe Islands</option>
+<option value="Fiji">Fiji</option>
+<option value="Finland">Finland</option>
+<option value="France">France</option>
+<option value="French Guiana">French Guiana</option>
+<option value="French Polynesia">French Polynesia</option>
+<option value="French Southern Territories">French Southern Territories</option>
+<option value="Gabon">Gabon</option>
+<option value="Gambia">Gambia</option>
+<option value="Georgia">Georgia</option>
+<option value="Germany">Germany</option>
+<option value="Ghana">Ghana</option>
+<option value="Gibraltar">Gibraltar</option>
+<option value="Greece">Greece</option>
+<option value="Greenland">Greenland</option>
+<option value="Grenada">Grenada</option>
+<option value="Guadeloupe">Guadeloupe</option>
+<option value="Guam">Guam</option>
+<option value="Guatemala">Guatemala</option>
+<option value="Guernsey">Guernsey</option>
+<option value="Guinea">Guinea</option>
+<option value="Guinea-Bissau">Guinea-Bissau</option>
+<option value="Guyana">Guyana</option>
+<option value="Haiti">Haiti</option>
+<option value="Heard and McDonald Islands">Heard and McDonald Islands</option>
+<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option>
+<option value="Honduras">Honduras</option>
+<option value="Hong Kong">Hong Kong</option>
+<option value="Hungary">Hungary</option>
+<option value="Iceland">Iceland</option>
+<option value="India">India</option>
+<option value="Indonesia">Indonesia</option>
+<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option>
+<option value="Iraq">Iraq</option>
+<option value="Ireland">Ireland</option>
+<option value="Isle of Man">Isle of Man</option>
+<option value="Israel">Israel</option>
+<option value="Italy">Italy</option>
+<option value="Jamaica">Jamaica</option>
+<option value="Japan">Japan</option>
+<option value="Jersey">Jersey</option>
+<option value="Jordan">Jordan</option>
+<option value="Kazakhstan">Kazakhstan</option>
+<option value="Kenya">Kenya</option>
+<option value="Kiribati">Kiribati</option>
+<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option>
+<option value="Korea, Republic of">Korea, Republic of</option>
+<option value="Kuwait">Kuwait</option>
+<option value="Kyrgyzstan">Kyrgyzstan</option>
+<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option>
+<option value="Latvia">Latvia</option>
+<option value="Lebanon">Lebanon</option>
+<option value="Lesotho">Lesotho</option>
+<option value="Liberia">Liberia</option>
+<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option>
+<option value="Liechtenstein">Liechtenstein</option>
+<option value="Lithuania">Lithuania</option>
+<option value="Luxembourg">Luxembourg</option>
+<option value="Macao">Macao</option>
+<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option>
+<option value="Madagascar">Madagascar</option>
+<option value="Malawi">Malawi</option>
+<option value="Malaysia">Malaysia</option>
+<option value="Maldives">Maldives</option>
+<option value="Mali">Mali</option>
+<option value="Malta">Malta</option>
+<option value="Marshall Islands">Marshall Islands</option>
+<option value="Martinique">Martinique</option>
+<option value="Mauritania">Mauritania</option>
+<option value="Mauritius">Mauritius</option>
+<option value="Mayotte">Mayotte</option>
+<option value="Mexico">Mexico</option>
+<option value="Micronesia, Federated States of">Micronesia, Federated States of</option>
+<option value="Moldova, Republic of">Moldova, Republic of</option>
+<option value="Monaco">Monaco</option>
+<option value="Mongolia">Mongolia</option>
+<option value="Montenegro">Montenegro</option>
+<option value="Montserrat">Montserrat</option>
+<option value="Morocco">Morocco</option>
+<option value="Mozambique">Mozambique</option>
+<option value="Myanmar">Myanmar</option>
+<option value="Namibia">Namibia</option>
+<option value="Nauru">Nauru</option>
+<option value="Nepal">Nepal</option>
+<option value="Netherlands">Netherlands</option>
+<option value="Netherlands Antilles">Netherlands Antilles</option>
+<option value="New Caledonia">New Caledonia</option>
+<option value="New Zealand">New Zealand</option>
+<option value="Nicaragua">Nicaragua</option>
+<option value="Niger">Niger</option>
+<option value="Nigeria">Nigeria</option>
+<option value="Niue">Niue</option>
+<option value="Norfolk Island">Norfolk Island</option>
+<option value="Northern Mariana Islands">Northern Mariana Islands</option>
+<option value="Norway">Norway</option>
+<option value="Oman">Oman</option>
+<option value="Pakistan">Pakistan</option>
+<option value="Palau">Palau</option>
+<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option>
+<option value="Panama">Panama</option>
+<option value="Papua New Guinea">Papua New Guinea</option>
+<option value="Paraguay">Paraguay</option>
+<option value="Peru">Peru</option>
+<option value="Philippines">Philippines</option>
+<option value="Pitcairn">Pitcairn</option>
+<option value="Poland">Poland</option>
+<option value="Portugal">Portugal</option>
+<option value="Puerto Rico">Puerto Rico</option>
+<option value="Qatar">Qatar</option>
+<option value="Reunion">Reunion</option>
+<option value="Romania">Romania</option>
+<option value="Russian Federation">Russian Federation</option>
+<option value="Rwanda">Rwanda</option>
+<option value="Saint Barthelemy">Saint Barthelemy</option>
+<option value="Saint Helena">Saint Helena</option>
+<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option>
+<option value="Saint Lucia">Saint Lucia</option>
+<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option>
+<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option>
+<option value="Samoa">Samoa</option>
+<option value="San Marino">San Marino</option>
+<option value="Sao Tome and Principe">Sao Tome and Principe</option>
+<option value="Saudi Arabia">Saudi Arabia</option>
+<option value="Senegal">Senegal</option>
+<option value="Serbia">Serbia</option>
+<option value="Seychelles">Seychelles</option>
+<option value="Sierra Leone">Sierra Leone</option>
+<option value="Singapore">Singapore</option>
+<option value="Slovakia">Slovakia</option>
+<option value="Slovenia">Slovenia</option>
+<option value="Solomon Islands">Solomon Islands</option>
+<option value="Somalia">Somalia</option>
+<option value="South Africa">South Africa</option>
+<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option>
+<option value="Spain">Spain</option>
+<option value="Sri Lanka">Sri Lanka</option>
+<option value="Sudan">Sudan</option>
+<option value="Suriname">Suriname</option>
+<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option>
+<option value="Swaziland">Swaziland</option>
+<option value="Sweden">Sweden</option>
+<option value="Switzerland">Switzerland</option>
+<option value="Syrian Arab Republic">Syrian Arab Republic</option>
+<option value="Taiwan, Province of China">Taiwan, Province of China</option>
+<option value="Tajikistan">Tajikistan</option>
+<option value="Tanzania, United Republic of">Tanzania, United Republic of</option>
+<option value="Thailand">Thailand</option>
+<option value="Timor-Leste">Timor-Leste</option>
+<option value="Togo">Togo</option>
+<option value="Tokelau">Tokelau</option>
+<option value="Tonga">Tonga</option>
+<option value="Trinidad and Tobago">Trinidad and Tobago</option>
+<option value="Tunisia">Tunisia</option>
+<option value="Turkey">Turkey</option>
+<option value="Turkmenistan">Turkmenistan</option>
+<option value="Turks and Caicos Islands">Turks and Caicos Islands</option>
+<option value="Tuvalu">Tuvalu</option>
+<option value="Uganda">Uganda</option>
+<option value="Ukraine">Ukraine</option>
+<option value="United Arab Emirates">United Arab Emirates</option>
+<option value="United Kingdom">United Kingdom</option>
+<option value="United States">United States</option>
+<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option>
+<option value="Uruguay">Uruguay</option>
+<option value="Uzbekistan">Uzbekistan</option>
+<option value="Vanuatu">Vanuatu</option>
+<option value="Venezuela">Venezuela</option>
+<option value="Viet Nam">Viet Nam</option>
+<option value="Virgin Islands, British">Virgin Islands, British</option>
+<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option>
+<option value="Wallis and Futuna">Wallis and Futuna</option>
+<option value="Western Sahara">Western Sahara</option>
+<option value="Yemen">Yemen</option>
+<option value="Zambia">Zambia</option>
+<option value="Zimbabwe">Zimbabwe</option></select>
+COUNTRIES
+ assert_dom_equal(expected_select[0..-2], country_select("post", "origin"))
+ end
+
+ def test_country_select_with_priority_countries
+ @post = Post.new
+ @post.origin = "Denmark"
+ expected_select = <<-COUNTRIES
+<select id="post_origin" name="post[origin]"><option value="New Zealand">New Zealand</option>
+<option value="Nicaragua">Nicaragua</option><option value="" disabled="disabled">-------------</option>
+<option value="Afghanistan">Afghanistan</option>
+<option value="Aland Islands">Aland Islands</option>
+<option value="Albania">Albania</option>
+<option value="Algeria">Algeria</option>
+<option value="American Samoa">American Samoa</option>
+<option value="Andorra">Andorra</option>
+<option value="Angola">Angola</option>
+<option value="Anguilla">Anguilla</option>
+<option value="Antarctica">Antarctica</option>
+<option value="Antigua And Barbuda">Antigua And Barbuda</option>
+<option value="Argentina">Argentina</option>
+<option value="Armenia">Armenia</option>
+<option value="Aruba">Aruba</option>
+<option value="Australia">Australia</option>
+<option value="Austria">Austria</option>
+<option value="Azerbaijan">Azerbaijan</option>
+<option value="Bahamas">Bahamas</option>
+<option value="Bahrain">Bahrain</option>
+<option value="Bangladesh">Bangladesh</option>
+<option value="Barbados">Barbados</option>
+<option value="Belarus">Belarus</option>
+<option value="Belgium">Belgium</option>
+<option value="Belize">Belize</option>
+<option value="Benin">Benin</option>
+<option value="Bermuda">Bermuda</option>
+<option value="Bhutan">Bhutan</option>
+<option value="Bolivia">Bolivia</option>
+<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option>
+<option value="Botswana">Botswana</option>
+<option value="Bouvet Island">Bouvet Island</option>
+<option value="Brazil">Brazil</option>
+<option value="British Indian Ocean Territory">British Indian Ocean Territory</option>
+<option value="Brunei Darussalam">Brunei Darussalam</option>
+<option value="Bulgaria">Bulgaria</option>
+<option value="Burkina Faso">Burkina Faso</option>
+<option value="Burundi">Burundi</option>
+<option value="Cambodia">Cambodia</option>
+<option value="Cameroon">Cameroon</option>
+<option value="Canada">Canada</option>
+<option value="Cape Verde">Cape Verde</option>
+<option value="Cayman Islands">Cayman Islands</option>
+<option value="Central African Republic">Central African Republic</option>
+<option value="Chad">Chad</option>
+<option value="Chile">Chile</option>
+<option value="China">China</option>
+<option value="Christmas Island">Christmas Island</option>
+<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option>
+<option value="Colombia">Colombia</option>
+<option value="Comoros">Comoros</option>
+<option value="Congo">Congo</option>
+<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option>
+<option value="Cook Islands">Cook Islands</option>
+<option value="Costa Rica">Costa Rica</option>
+<option value="Cote d'Ivoire">Cote d'Ivoire</option>
+<option value="Croatia">Croatia</option>
+<option value="Cuba">Cuba</option>
+<option value="Cyprus">Cyprus</option>
+<option value="Czech Republic">Czech Republic</option>
+<option selected="selected" value="Denmark">Denmark</option>
+<option value="Djibouti">Djibouti</option>
+<option value="Dominica">Dominica</option>
+<option value="Dominican Republic">Dominican Republic</option>
+<option value="Ecuador">Ecuador</option>
+<option value="Egypt">Egypt</option>
+<option value="El Salvador">El Salvador</option>
+<option value="Equatorial Guinea">Equatorial Guinea</option>
+<option value="Eritrea">Eritrea</option>
+<option value="Estonia">Estonia</option>
+<option value="Ethiopia">Ethiopia</option>
+<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option>
+<option value="Faroe Islands">Faroe Islands</option>
+<option value="Fiji">Fiji</option>
+<option value="Finland">Finland</option>
+<option value="France">France</option>
+<option value="French Guiana">French Guiana</option>
+<option value="French Polynesia">French Polynesia</option>
+<option value="French Southern Territories">French Southern Territories</option>
+<option value="Gabon">Gabon</option>
+<option value="Gambia">Gambia</option>
+<option value="Georgia">Georgia</option>
+<option value="Germany">Germany</option>
+<option value="Ghana">Ghana</option>
+<option value="Gibraltar">Gibraltar</option>
+<option value="Greece">Greece</option>
+<option value="Greenland">Greenland</option>
+<option value="Grenada">Grenada</option>
+<option value="Guadeloupe">Guadeloupe</option>
+<option value="Guam">Guam</option>
+<option value="Guatemala">Guatemala</option>
+<option value="Guernsey">Guernsey</option>
+<option value="Guinea">Guinea</option>
+<option value="Guinea-Bissau">Guinea-Bissau</option>
+<option value="Guyana">Guyana</option>
+<option value="Haiti">Haiti</option>
+<option value="Heard and McDonald Islands">Heard and McDonald Islands</option>
+<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option>
+<option value="Honduras">Honduras</option>
+<option value="Hong Kong">Hong Kong</option>
+<option value="Hungary">Hungary</option>
+<option value="Iceland">Iceland</option>
+<option value="India">India</option>
+<option value="Indonesia">Indonesia</option>
+<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option>
+<option value="Iraq">Iraq</option>
+<option value="Ireland">Ireland</option>
+<option value="Isle of Man">Isle of Man</option>
+<option value="Israel">Israel</option>
+<option value="Italy">Italy</option>
+<option value="Jamaica">Jamaica</option>
+<option value="Japan">Japan</option>
+<option value="Jersey">Jersey</option>
+<option value="Jordan">Jordan</option>
+<option value="Kazakhstan">Kazakhstan</option>
+<option value="Kenya">Kenya</option>
+<option value="Kiribati">Kiribati</option>
+<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option>
+<option value="Korea, Republic of">Korea, Republic of</option>
+<option value="Kuwait">Kuwait</option>
+<option value="Kyrgyzstan">Kyrgyzstan</option>
+<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option>
+<option value="Latvia">Latvia</option>
+<option value="Lebanon">Lebanon</option>
+<option value="Lesotho">Lesotho</option>
+<option value="Liberia">Liberia</option>
+<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option>
+<option value="Liechtenstein">Liechtenstein</option>
+<option value="Lithuania">Lithuania</option>
+<option value="Luxembourg">Luxembourg</option>
+<option value="Macao">Macao</option>
+<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option>
+<option value="Madagascar">Madagascar</option>
+<option value="Malawi">Malawi</option>
+<option value="Malaysia">Malaysia</option>
+<option value="Maldives">Maldives</option>
+<option value="Mali">Mali</option>
+<option value="Malta">Malta</option>
+<option value="Marshall Islands">Marshall Islands</option>
+<option value="Martinique">Martinique</option>
+<option value="Mauritania">Mauritania</option>
+<option value="Mauritius">Mauritius</option>
+<option value="Mayotte">Mayotte</option>
+<option value="Mexico">Mexico</option>
+<option value="Micronesia, Federated States of">Micronesia, Federated States of</option>
+<option value="Moldova, Republic of">Moldova, Republic of</option>
+<option value="Monaco">Monaco</option>
+<option value="Mongolia">Mongolia</option>
+<option value="Montenegro">Montenegro</option>
+<option value="Montserrat">Montserrat</option>
+<option value="Morocco">Morocco</option>
+<option value="Mozambique">Mozambique</option>
+<option value="Myanmar">Myanmar</option>
+<option value="Namibia">Namibia</option>
+<option value="Nauru">Nauru</option>
+<option value="Nepal">Nepal</option>
+<option value="Netherlands">Netherlands</option>
+<option value="Netherlands Antilles">Netherlands Antilles</option>
+<option value="New Caledonia">New Caledonia</option>
+<option value="New Zealand">New Zealand</option>
+<option value="Nicaragua">Nicaragua</option>
+<option value="Niger">Niger</option>
+<option value="Nigeria">Nigeria</option>
+<option value="Niue">Niue</option>
+<option value="Norfolk Island">Norfolk Island</option>
+<option value="Northern Mariana Islands">Northern Mariana Islands</option>
+<option value="Norway">Norway</option>
+<option value="Oman">Oman</option>
+<option value="Pakistan">Pakistan</option>
+<option value="Palau">Palau</option>
+<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option>
+<option value="Panama">Panama</option>
+<option value="Papua New Guinea">Papua New Guinea</option>
+<option value="Paraguay">Paraguay</option>
+<option value="Peru">Peru</option>
+<option value="Philippines">Philippines</option>
+<option value="Pitcairn">Pitcairn</option>
+<option value="Poland">Poland</option>
+<option value="Portugal">Portugal</option>
+<option value="Puerto Rico">Puerto Rico</option>
+<option value="Qatar">Qatar</option>
+<option value="Reunion">Reunion</option>
+<option value="Romania">Romania</option>
+<option value="Russian Federation">Russian Federation</option>
+<option value="Rwanda">Rwanda</option>
+<option value="Saint Barthelemy">Saint Barthelemy</option>
+<option value="Saint Helena">Saint Helena</option>
+<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option>
+<option value="Saint Lucia">Saint Lucia</option>
+<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option>
+<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option>
+<option value="Samoa">Samoa</option>
+<option value="San Marino">San Marino</option>
+<option value="Sao Tome and Principe">Sao Tome and Principe</option>
+<option value="Saudi Arabia">Saudi Arabia</option>
+<option value="Senegal">Senegal</option>
+<option value="Serbia">Serbia</option>
+<option value="Seychelles">Seychelles</option>
+<option value="Sierra Leone">Sierra Leone</option>
+<option value="Singapore">Singapore</option>
+<option value="Slovakia">Slovakia</option>
+<option value="Slovenia">Slovenia</option>
+<option value="Solomon Islands">Solomon Islands</option>
+<option value="Somalia">Somalia</option>
+<option value="South Africa">South Africa</option>
+<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option>
+<option value="Spain">Spain</option>
+<option value="Sri Lanka">Sri Lanka</option>
+<option value="Sudan">Sudan</option>
+<option value="Suriname">Suriname</option>
+<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option>
+<option value="Swaziland">Swaziland</option>
+<option value="Sweden">Sweden</option>
+<option value="Switzerland">Switzerland</option>
+<option value="Syrian Arab Republic">Syrian Arab Republic</option>
+<option value="Taiwan, Province of China">Taiwan, Province of China</option>
+<option value="Tajikistan">Tajikistan</option>
+<option value="Tanzania, United Republic of">Tanzania, United Republic of</option>
+<option value="Thailand">Thailand</option>
+<option value="Timor-Leste">Timor-Leste</option>
+<option value="Togo">Togo</option>
+<option value="Tokelau">Tokelau</option>
+<option value="Tonga">Tonga</option>
+<option value="Trinidad and Tobago">Trinidad and Tobago</option>
+<option value="Tunisia">Tunisia</option>
+<option value="Turkey">Turkey</option>
+<option value="Turkmenistan">Turkmenistan</option>
+<option value="Turks and Caicos Islands">Turks and Caicos Islands</option>
+<option value="Tuvalu">Tuvalu</option>
+<option value="Uganda">Uganda</option>
+<option value="Ukraine">Ukraine</option>
+<option value="United Arab Emirates">United Arab Emirates</option>
+<option value="United Kingdom">United Kingdom</option>
+<option value="United States">United States</option>
+<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option>
+<option value="Uruguay">Uruguay</option>
+<option value="Uzbekistan">Uzbekistan</option>
+<option value="Vanuatu">Vanuatu</option>
+<option value="Venezuela">Venezuela</option>
+<option value="Viet Nam">Viet Nam</option>
+<option value="Virgin Islands, British">Virgin Islands, British</option>
+<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option>
+<option value="Wallis and Futuna">Wallis and Futuna</option>
+<option value="Western Sahara">Western Sahara</option>
+<option value="Yemen">Yemen</option>
+<option value="Zambia">Zambia</option>
+<option value="Zimbabwe">Zimbabwe</option></select>
+COUNTRIES
+ assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"]))
+ end
+
+ def test_country_select_with_selected_priority_country
+ @post = Post.new
+ @post.origin = "New Zealand"
+ expected_select = <<-COUNTRIES
+<select id="post_origin" name="post[origin]"><option selected="selected" value="New Zealand">New Zealand</option>
+<option value="Nicaragua">Nicaragua</option><option value="" disabled="disabled">-------------</option>
+<option value="Afghanistan">Afghanistan</option>
+<option value="Aland Islands">Aland Islands</option>
+<option value="Albania">Albania</option>
+<option value="Algeria">Algeria</option>
+<option value="American Samoa">American Samoa</option>
+<option value="Andorra">Andorra</option>
+<option value="Angola">Angola</option>
+<option value="Anguilla">Anguilla</option>
+<option value="Antarctica">Antarctica</option>
+<option value="Antigua And Barbuda">Antigua And Barbuda</option>
+<option value="Argentina">Argentina</option>
+<option value="Armenia">Armenia</option>
+<option value="Aruba">Aruba</option>
+<option value="Australia">Australia</option>
+<option value="Austria">Austria</option>
+<option value="Azerbaijan">Azerbaijan</option>
+<option value="Bahamas">Bahamas</option>
+<option value="Bahrain">Bahrain</option>
+<option value="Bangladesh">Bangladesh</option>
+<option value="Barbados">Barbados</option>
+<option value="Belarus">Belarus</option>
+<option value="Belgium">Belgium</option>
+<option value="Belize">Belize</option>
+<option value="Benin">Benin</option>
+<option value="Bermuda">Bermuda</option>
+<option value="Bhutan">Bhutan</option>
+<option value="Bolivia">Bolivia</option>
+<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option>
+<option value="Botswana">Botswana</option>
+<option value="Bouvet Island">Bouvet Island</option>
+<option value="Brazil">Brazil</option>
+<option value="British Indian Ocean Territory">British Indian Ocean Territory</option>
+<option value="Brunei Darussalam">Brunei Darussalam</option>
+<option value="Bulgaria">Bulgaria</option>
+<option value="Burkina Faso">Burkina Faso</option>
+<option value="Burundi">Burundi</option>
+<option value="Cambodia">Cambodia</option>
+<option value="Cameroon">Cameroon</option>
+<option value="Canada">Canada</option>
+<option value="Cape Verde">Cape Verde</option>
+<option value="Cayman Islands">Cayman Islands</option>
+<option value="Central African Republic">Central African Republic</option>
+<option value="Chad">Chad</option>
+<option value="Chile">Chile</option>
+<option value="China">China</option>
+<option value="Christmas Island">Christmas Island</option>
+<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option>
+<option value="Colombia">Colombia</option>
+<option value="Comoros">Comoros</option>
+<option value="Congo">Congo</option>
+<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option>
+<option value="Cook Islands">Cook Islands</option>
+<option value="Costa Rica">Costa Rica</option>
+<option value="Cote d'Ivoire">Cote d'Ivoire</option>
+<option value="Croatia">Croatia</option>
+<option value="Cuba">Cuba</option>
+<option value="Cyprus">Cyprus</option>
+<option value="Czech Republic">Czech Republic</option>
+<option value="Denmark">Denmark</option>
+<option value="Djibouti">Djibouti</option>
+<option value="Dominica">Dominica</option>
+<option value="Dominican Republic">Dominican Republic</option>
+<option value="Ecuador">Ecuador</option>
+<option value="Egypt">Egypt</option>
+<option value="El Salvador">El Salvador</option>
+<option value="Equatorial Guinea">Equatorial Guinea</option>
+<option value="Eritrea">Eritrea</option>
+<option value="Estonia">Estonia</option>
+<option value="Ethiopia">Ethiopia</option>
+<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option>
+<option value="Faroe Islands">Faroe Islands</option>
+<option value="Fiji">Fiji</option>
+<option value="Finland">Finland</option>
+<option value="France">France</option>
+<option value="French Guiana">French Guiana</option>
+<option value="French Polynesia">French Polynesia</option>
+<option value="French Southern Territories">French Southern Territories</option>
+<option value="Gabon">Gabon</option>
+<option value="Gambia">Gambia</option>
+<option value="Georgia">Georgia</option>
+<option value="Germany">Germany</option>
+<option value="Ghana">Ghana</option>
+<option value="Gibraltar">Gibraltar</option>
+<option value="Greece">Greece</option>
+<option value="Greenland">Greenland</option>
+<option value="Grenada">Grenada</option>
+<option value="Guadeloupe">Guadeloupe</option>
+<option value="Guam">Guam</option>
+<option value="Guatemala">Guatemala</option>
+<option value="Guernsey">Guernsey</option>
+<option value="Guinea">Guinea</option>
+<option value="Guinea-Bissau">Guinea-Bissau</option>
+<option value="Guyana">Guyana</option>
+<option value="Haiti">Haiti</option>
+<option value="Heard and McDonald Islands">Heard and McDonald Islands</option>
+<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option>
+<option value="Honduras">Honduras</option>
+<option value="Hong Kong">Hong Kong</option>
+<option value="Hungary">Hungary</option>
+<option value="Iceland">Iceland</option>
+<option value="India">India</option>
+<option value="Indonesia">Indonesia</option>
+<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option>
+<option value="Iraq">Iraq</option>
+<option value="Ireland">Ireland</option>
+<option value="Isle of Man">Isle of Man</option>
+<option value="Israel">Israel</option>
+<option value="Italy">Italy</option>
+<option value="Jamaica">Jamaica</option>
+<option value="Japan">Japan</option>
+<option value="Jersey">Jersey</option>
+<option value="Jordan">Jordan</option>
+<option value="Kazakhstan">Kazakhstan</option>
+<option value="Kenya">Kenya</option>
+<option value="Kiribati">Kiribati</option>
+<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option>
+<option value="Korea, Republic of">Korea, Republic of</option>
+<option value="Kuwait">Kuwait</option>
+<option value="Kyrgyzstan">Kyrgyzstan</option>
+<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option>
+<option value="Latvia">Latvia</option>
+<option value="Lebanon">Lebanon</option>
+<option value="Lesotho">Lesotho</option>
+<option value="Liberia">Liberia</option>
+<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option>
+<option value="Liechtenstein">Liechtenstein</option>
+<option value="Lithuania">Lithuania</option>
+<option value="Luxembourg">Luxembourg</option>
+<option value="Macao">Macao</option>
+<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option>
+<option value="Madagascar">Madagascar</option>
+<option value="Malawi">Malawi</option>
+<option value="Malaysia">Malaysia</option>
+<option value="Maldives">Maldives</option>
+<option value="Mali">Mali</option>
+<option value="Malta">Malta</option>
+<option value="Marshall Islands">Marshall Islands</option>
+<option value="Martinique">Martinique</option>
+<option value="Mauritania">Mauritania</option>
+<option value="Mauritius">Mauritius</option>
+<option value="Mayotte">Mayotte</option>
+<option value="Mexico">Mexico</option>
+<option value="Micronesia, Federated States of">Micronesia, Federated States of</option>
+<option value="Moldova, Republic of">Moldova, Republic of</option>
+<option value="Monaco">Monaco</option>
+<option value="Mongolia">Mongolia</option>
+<option value="Montenegro">Montenegro</option>
+<option value="Montserrat">Montserrat</option>
+<option value="Morocco">Morocco</option>
+<option value="Mozambique">Mozambique</option>
+<option value="Myanmar">Myanmar</option>
+<option value="Namibia">Namibia</option>
+<option value="Nauru">Nauru</option>
+<option value="Nepal">Nepal</option>
+<option value="Netherlands">Netherlands</option>
+<option value="Netherlands Antilles">Netherlands Antilles</option>
+<option value="New Caledonia">New Caledonia</option>
+<option selected="selected" value="New Zealand">New Zealand</option>
+<option value="Nicaragua">Nicaragua</option>
+<option value="Niger">Niger</option>
+<option value="Nigeria">Nigeria</option>
+<option value="Niue">Niue</option>
+<option value="Norfolk Island">Norfolk Island</option>
+<option value="Northern Mariana Islands">Northern Mariana Islands</option>
+<option value="Norway">Norway</option>
+<option value="Oman">Oman</option>
+<option value="Pakistan">Pakistan</option>
+<option value="Palau">Palau</option>
+<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option>
+<option value="Panama">Panama</option>
+<option value="Papua New Guinea">Papua New Guinea</option>
+<option value="Paraguay">Paraguay</option>
+<option value="Peru">Peru</option>
+<option value="Philippines">Philippines</option>
+<option value="Pitcairn">Pitcairn</option>
+<option value="Poland">Poland</option>
+<option value="Portugal">Portugal</option>
+<option value="Puerto Rico">Puerto Rico</option>
+<option value="Qatar">Qatar</option>
+<option value="Reunion">Reunion</option>
+<option value="Romania">Romania</option>
+<option value="Russian Federation">Russian Federation</option>
+<option value="Rwanda">Rwanda</option>
+<option value="Saint Barthelemy">Saint Barthelemy</option>
+<option value="Saint Helena">Saint Helena</option>
+<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option>
+<option value="Saint Lucia">Saint Lucia</option>
+<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option>
+<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option>
+<option value="Samoa">Samoa</option>
+<option value="San Marino">San Marino</option>
+<option value="Sao Tome and Principe">Sao Tome and Principe</option>
+<option value="Saudi Arabia">Saudi Arabia</option>
+<option value="Senegal">Senegal</option>
+<option value="Serbia">Serbia</option>
+<option value="Seychelles">Seychelles</option>
+<option value="Sierra Leone">Sierra Leone</option>
+<option value="Singapore">Singapore</option>
+<option value="Slovakia">Slovakia</option>
+<option value="Slovenia">Slovenia</option>
+<option value="Solomon Islands">Solomon Islands</option>
+<option value="Somalia">Somalia</option>
+<option value="South Africa">South Africa</option>
+<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option>
+<option value="Spain">Spain</option>
+<option value="Sri Lanka">Sri Lanka</option>
+<option value="Sudan">Sudan</option>
+<option value="Suriname">Suriname</option>
+<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option>
+<option value="Swaziland">Swaziland</option>
+<option value="Sweden">Sweden</option>
+<option value="Switzerland">Switzerland</option>
+<option value="Syrian Arab Republic">Syrian Arab Republic</option>
+<option value="Taiwan, Province of China">Taiwan, Province of China</option>
+<option value="Tajikistan">Tajikistan</option>
+<option value="Tanzania, United Republic of">Tanzania, United Republic of</option>
+<option value="Thailand">Thailand</option>
+<option value="Timor-Leste">Timor-Leste</option>
+<option value="Togo">Togo</option>
+<option value="Tokelau">Tokelau</option>
+<option value="Tonga">Tonga</option>
+<option value="Trinidad and Tobago">Trinidad and Tobago</option>
+<option value="Tunisia">Tunisia</option>
+<option value="Turkey">Turkey</option>
+<option value="Turkmenistan">Turkmenistan</option>
+<option value="Turks and Caicos Islands">Turks and Caicos Islands</option>
+<option value="Tuvalu">Tuvalu</option>
+<option value="Uganda">Uganda</option>
+<option value="Ukraine">Ukraine</option>
+<option value="United Arab Emirates">United Arab Emirates</option>
+<option value="United Kingdom">United Kingdom</option>
+<option value="United States">United States</option>
+<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option>
+<option value="Uruguay">Uruguay</option>
+<option value="Uzbekistan">Uzbekistan</option>
+<option value="Vanuatu">Vanuatu</option>
+<option value="Venezuela">Venezuela</option>
+<option value="Viet Nam">Viet Nam</option>
+<option value="Virgin Islands, British">Virgin Islands, British</option>
+<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option>
+<option value="Wallis and Futuna">Wallis and Futuna</option>
+<option value="Western Sahara">Western Sahara</option>
+<option value="Yemen">Yemen</option>
+<option value="Zambia">Zambia</option>
+<option value="Zimbabwe">Zimbabwe</option></select>
+COUNTRIES
+ assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"]))
+ end
+
+ def test_time_zone_select
+ @firm = Firm.new("D")
+ html = time_zone_select( "firm", "time_zone" )
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ end
+
+ def test_time_zone_select_under_fields_for
+ @firm = Firm.new("D")
+
+ _erbout = ''
+
+ fields_for :firm, @firm do |f|
+ _erbout.concat f.time_zone_select(:time_zone)
+ end
+
+ assert_dom_equal(
+ "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ _erbout
+ )
+ end
+
+ def test_time_zone_select_with_blank
+ @firm = Firm.new("D")
+ html = time_zone_select("firm", "time_zone", nil, :include_blank => true)
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"\"></option>\n" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ end
+
+ def test_time_zone_select_with_blank_as_string
+ @firm = Firm.new("D")
+ html = time_zone_select("firm", "time_zone", nil, :include_blank => 'No Zone')
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"\">No Zone</option>\n" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ end
+
+ def test_time_zone_select_with_style
+ @firm = Firm.new("D")
+ html = time_zone_select("firm", "time_zone", nil, {},
+ "style" => "color: red")
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ assert_dom_equal html, time_zone_select("firm", "time_zone", nil, {},
+ :style => "color: red")
+ end
+
+ def test_time_zone_select_with_blank_and_style
+ @firm = Firm.new("D")
+ html = time_zone_select("firm", "time_zone", nil,
+ { :include_blank => true }, "style" => "color: red")
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
+ "<option value=\"\"></option>\n" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ assert_dom_equal html, time_zone_select("firm", "time_zone", nil,
+ { :include_blank => true }, :style => "color: red")
+ end
+
+ def test_time_zone_select_with_blank_as_string_and_style
+ @firm = Firm.new("D")
+ html = time_zone_select("firm", "time_zone", nil,
+ { :include_blank => 'No Zone' }, "style" => "color: red")
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
+ "<option value=\"\">No Zone</option>\n" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ assert_dom_equal html, time_zone_select("firm", "time_zone", nil,
+ { :include_blank => 'No Zone' }, :style => "color: red")
+ end
+
+ def test_time_zone_select_with_priority_zones
+ @firm = Firm.new("D")
+ zones = [ TimeZone.new("A"), TimeZone.new("D") ]
+ html = time_zone_select("firm", "time_zone", zones )
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>" +
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/template/form_tag_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/form_tag_helper_test.rb
new file mode 100644
index 000000000..d0f9e9903
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/form_tag_helper_test.rb
@@ -0,0 +1,238 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+class FormTagHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::UrlHelper
+ include ActionView::Helpers::TagHelper
+ include ActionView::Helpers::FormTagHelper
+ include ActionView::Helpers::TextHelper
+ include ActionView::Helpers::CaptureHelper
+
+ def setup
+ @controller = Class.new do
+ def url_for(options)
+ "http://www.example.com"
+ end
+ end
+ @controller = @controller.new
+ end
+
+ def test_check_box_tag
+ actual = check_box_tag "admin"
+ expected = %(<input id="admin" name="admin" type="checkbox" value="1" />)
+ assert_dom_equal expected, actual
+ end
+
+ def test_form_tag
+ actual = form_tag
+ expected = %(<form action="http://www.example.com" method="post">)
+ assert_dom_equal expected, actual
+ end
+
+ def test_form_tag_multipart
+ actual = form_tag({}, { 'multipart' => true })
+ expected = %(<form action="http://www.example.com" enctype="multipart/form-data" method="post">)
+ assert_dom_equal expected, actual
+ end
+
+ def test_form_tag_with_method_put
+ actual = form_tag({}, { :method => :put })
+ expected = %(<form action="http://www.example.com" method="post"><div style='margin:0;padding:0'><input type="hidden" name="_method" value="put" /></div>)
+ assert_dom_equal expected, actual
+ end
+
+ def test_form_tag_with_method_delete
+ actual = form_tag({}, { :method => :delete })
+ expected = %(<form action="http://www.example.com" method="post"><div style='margin:0;padding:0'><input type="hidden" name="_method" value="delete" /></div>)
+ assert_dom_equal expected, actual
+ end
+
+ def test_form_tag_with_block
+ _erbout = ''
+ form_tag("http://example.com") { _erbout.concat "Hello world!" }
+
+ expected = %(<form action="http://example.com" method="post">Hello world!</form>)
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_form_tag_with_block_and_method
+ _erbout = ''
+ form_tag("http://example.com", :method => :put) { _erbout.concat "Hello world!" }
+
+ expected = %(<form action="http://example.com" method="post"><div style='margin:0;padding:0'><input type="hidden" name="_method" value="put" /></div>Hello world!</form>)
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_hidden_field_tag
+ actual = hidden_field_tag "id", 3
+ expected = %(<input id="id" name="id" type="hidden" value="3" />)
+ assert_dom_equal expected, actual
+ end
+
+ def test_file_field_tag
+ assert_dom_equal "<input name=\"picsplz\" type=\"file\" id=\"picsplz\" />", file_field_tag("picsplz")
+ end
+
+ def test_file_field_tag_with_options
+ assert_dom_equal "<input name=\"picsplz\" type=\"file\" id=\"picsplz\" class=\"pix\"/>", file_field_tag("picsplz", :class => "pix")
+ end
+
+ def test_password_field_tag
+ actual = password_field_tag
+ expected = %(<input id="password" name="password" type="password" />)
+ assert_dom_equal expected, actual
+ end
+
+ def test_radio_button_tag
+ actual = radio_button_tag "people", "david"
+ expected = %(<input id="people_david" name="people" type="radio" value="david" />)
+ assert_dom_equal expected, actual
+
+ actual = radio_button_tag("num_people", 5)
+ expected = %(<input id="num_people_5" name="num_people" type="radio" value="5" />)
+ assert_dom_equal expected, actual
+
+ actual = radio_button_tag("gender", "m") + radio_button_tag("gender", "f")
+ expected = %(<input id="gender_m" name="gender" type="radio" value="m" /><input id="gender_f" name="gender" type="radio" value="f" />)
+ assert_dom_equal expected, actual
+
+ actual = radio_button_tag("opinion", "-1") + radio_button_tag("opinion", "1")
+ expected = %(<input id="opinion_-1" name="opinion" type="radio" value="-1" /><input id="opinion_1" name="opinion" type="radio" value="1" />)
+ assert_dom_equal expected, actual
+
+ actual = radio_button_tag("person[gender]", "m")
+ expected = %(<input id="person_gender_m" name="person[gender]" type="radio" value="m" />)
+ assert_dom_equal expected, actual
+ end
+
+ def test_select_tag
+ actual = select_tag "people", "<option>david</option>"
+ expected = %(<select id="people" name="people"><option>david</option></select>)
+ assert_dom_equal expected, actual
+ end
+
+ def test_select_tag_with_multiple
+ actual = select_tag "colors", "<option>Red</option><option>Blue</option><option>Green</option>", :multiple => :true
+ expected = %(<select id="colors" multiple="multiple" name="colors"><option>Red</option><option>Blue</option><option>Green</option></select>)
+ assert_dom_equal expected, actual
+ end
+
+ def test_select_tag_disabled
+ actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>", :disabled => :true
+ expected = %(<select id="places" disabled="disabled" name="places"><option>Home</option><option>Work</option><option>Pub</option></select>)
+ assert_dom_equal expected, actual
+ end
+
+ def test_text_area_tag_size_string
+ actual = text_area_tag "body", "hello world", "size" => "20x40"
+ expected = %(<textarea cols="20" id="body" name="body" rows="40">hello world</textarea>)
+ assert_dom_equal expected, actual
+ end
+
+ def test_text_area_tag_size_symbol
+ actual = text_area_tag "body", "hello world", :size => "20x40"
+ expected = %(<textarea cols="20" id="body" name="body" rows="40">hello world</textarea>)
+ assert_dom_equal expected, actual
+ end
+
+ def test_text_area_tag_should_disregard_size_if_its_given_as_an_integer
+ actual = text_area_tag "body", "hello world", :size => 20
+ expected = %(<textarea id="body" name="body">hello world</textarea>)
+ assert_dom_equal expected, actual
+ end
+
+ def test_text_field_tag
+ actual = text_field_tag "title", "Hello!"
+ expected = %(<input id="title" name="title" type="text" value="Hello!" />)
+ assert_dom_equal expected, actual
+ end
+
+ def test_text_field_tag_class_string
+ actual = text_field_tag "title", "Hello!", "class" => "admin"
+ expected = %(<input class="admin" id="title" name="title" type="text" value="Hello!" />)
+ assert_dom_equal expected, actual
+ end
+
+ def test_text_field_tag_size_symbol
+ actual = text_field_tag "title", "Hello!", :size => 75
+ expected = %(<input id="title" name="title" size="75" type="text" value="Hello!" />)
+ assert_dom_equal expected, actual
+ end
+
+ def test_text_field_tag_size_string
+ actual = text_field_tag "title", "Hello!", "size" => "75"
+ expected = %(<input id="title" name="title" size="75" type="text" value="Hello!" />)
+ assert_dom_equal expected, actual
+ end
+
+ def test_text_field_tag_maxlength_symbol
+ actual = text_field_tag "title", "Hello!", :maxlength => 75
+ expected = %(<input id="title" name="title" maxlength="75" type="text" value="Hello!" />)
+ assert_dom_equal expected, actual
+ end
+
+ def test_text_field_tag_maxlength_string
+ actual = text_field_tag "title", "Hello!", "maxlength" => "75"
+ expected = %(<input id="title" name="title" maxlength="75" type="text" value="Hello!" />)
+ assert_dom_equal expected, actual
+ end
+
+ def test_text_field_disabled
+ actual = text_field_tag "title", "Hello!", :disabled => :true
+ expected = %(<input id="title" name="title" disabled="disabled" type="text" value="Hello!" />)
+ assert_dom_equal expected, actual
+ end
+
+ def test_text_field_tag_with_multiple_options
+ actual = text_field_tag "title", "Hello!", :size => 70, :maxlength => 80
+ expected = %(<input id="title" name="title" size="70" maxlength="80" type="text" value="Hello!" />)
+ assert_dom_equal expected, actual
+ end
+
+ def test_boolean_optios
+ assert_dom_equal %(<input checked="checked" disabled="disabled" id="admin" name="admin" readonly="readonly" type="checkbox" value="1" />), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes")
+ assert_dom_equal %(<input checked="checked" id="admin" name="admin" type="checkbox" value="1" />), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil)
+ assert_dom_equal %(<select id="people" multiple="multiple" name="people"><option>david</option></select>), select_tag("people", "<option>david</option>", :multiple => true)
+ assert_dom_equal %(<select id="people" name="people"><option>david</option></select>), select_tag("people", "<option>david</option>", :multiple => nil)
+ end
+
+ def test_stringify_symbol_keys
+ actual = text_field_tag "title", "Hello!", :id => "admin"
+ expected = %(<input id="admin" name="title" type="text" value="Hello!" />)
+ assert_dom_equal expected, actual
+ end
+
+ def test_submit_tag
+ assert_dom_equal(
+ %(<input name='commit' type='submit' value='Save' onclick="this.setAttribute('originalValue', this.value);this.disabled=true;this.value='Saving...';alert('hello!');result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());if (result == false) { this.value = this.getAttribute('originalValue'); this.disabled = false };return result" />),
+ submit_tag("Save", :disable_with => "Saving...", :onclick => "alert('hello!')")
+ )
+ end
+
+ def test_pass
+ assert_equal 1, 1
+ end
+
+ def test_field_set_tag
+ _erbout = ''
+ field_set_tag("Your details") { _erbout.concat "Hello world!" }
+
+ expected = %(<fieldset><legend>Your details</legend>Hello world!</fieldset>)
+ assert_dom_equal expected, _erbout
+
+ _erbout = ''
+ field_set_tag { _erbout.concat "Hello world!" }
+
+ expected = %(<fieldset>Hello world!</fieldset>)
+ assert_dom_equal expected, _erbout
+
+ _erbout = ''
+ field_set_tag('') { _erbout.concat "Hello world!" }
+
+ expected = %(<fieldset>Hello world!</fieldset>)
+ assert_dom_equal expected, _erbout
+ end
+
+ def protect_against_forgery?
+ false
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/template/javascript_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/javascript_helper_test.rb
new file mode 100644
index 000000000..0d44e96f8
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/javascript_helper_test.rb
@@ -0,0 +1,115 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+class JavaScriptHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::JavaScriptHelper
+
+ include ActionView::Helpers::UrlHelper
+ include ActionView::Helpers::TagHelper
+ include ActionView::Helpers::TextHelper
+ include ActionView::Helpers::FormHelper
+ include ActionView::Helpers::CaptureHelper
+
+ def test_define_javascript_functions
+ # check if prototype.js is included first
+ assert_not_nil define_javascript_functions.split("\n")[1].match(/Prototype JavaScript framework/)
+
+ # check that scriptaculous.js is not in here, only needed if loaded remotely
+ assert_nil define_javascript_functions.split("\n")[1].match(/var Scriptaculous = \{/)
+ end
+
+ def test_escape_javascript
+ assert_equal '', escape_javascript(nil)
+ assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos'))
+ assert_equal %(backslash\\\\test), escape_javascript( %(backslash\\test) )
+ assert_equal %(dont <\\/close> tags), escape_javascript(%(dont </close> tags))
+ end
+
+ def test_link_to_function
+ assert_dom_equal %(<a href="#" onclick="alert('Hello world!'); return false;">Greeting</a>),
+ link_to_function("Greeting", "alert('Hello world!')")
+ end
+
+ def test_link_to_function_with_existing_onclick
+ assert_dom_equal %(<a href="#" onclick="confirm('Sanity!'); alert('Hello world!'); return false;">Greeting</a>),
+ link_to_function("Greeting", "alert('Hello world!')", :onclick => "confirm('Sanity!')")
+ end
+
+ def test_link_to_function_with_rjs_block
+ html = link_to_function( "Greet me!" ) do |page|
+ page.replace_html 'header', "<h1>Greetings</h1>"
+ end
+ assert_dom_equal %(<a href="#" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E&quot;);; return false;">Greet me!</a>), html
+ end
+
+ def test_link_to_function_with_rjs_block_and_options
+ html = link_to_function( "Greet me!", :class => "updater" ) do |page|
+ page.replace_html 'header', "<h1>Greetings</h1>"
+ end
+ assert_dom_equal %(<a href="#" class="updater" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E&quot;);; return false;">Greet me!</a>), html
+ end
+
+ def test_link_to_function_with_href
+ assert_dom_equal %(<a href="http://example.com/" onclick="alert('Hello world!'); return false;">Greeting</a>),
+ link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/')
+ end
+
+ def test_link_to_function_with_href
+ assert_dom_equal %(<a href="http://example.com/" onclick="alert('Hello world!'); return false;">Greeting</a>),
+ link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/')
+ end
+
+ def test_button_to_function
+ assert_dom_equal %(<input type="button" onclick="alert('Hello world!');" value="Greeting" />),
+ button_to_function("Greeting", "alert('Hello world!')")
+ end
+
+ def test_button_to_function_with_rjs_block
+ html = button_to_function( "Greet me!" ) do |page|
+ page.replace_html 'header', "<h1>Greetings</h1>"
+ end
+ assert_dom_equal %(<input type="button" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E&quot;);;" value="Greet me!" />), html
+ end
+
+ def test_button_to_function_with_rjs_block_and_options
+ html = button_to_function( "Greet me!", :class => "greeter" ) do |page|
+ page.replace_html 'header', "<h1>Greetings</h1>"
+ end
+ assert_dom_equal %(<input type="button" class="greeter" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C\/h1\\u003E&quot;);;" value="Greet me!" />), html
+ end
+
+ def test_button_to_function_with_onclick
+ assert_dom_equal "<input onclick=\"alert('Goodbye World :('); alert('Hello world!');\" type=\"button\" value=\"Greeting\" />",
+ button_to_function("Greeting", "alert('Hello world!')", :onclick => "alert('Goodbye World :(')")
+ end
+
+ def test_button_to_function_without_function
+ assert_dom_equal "<input onclick=\";\" type=\"button\" value=\"Greeting\" />",
+ button_to_function("Greeting")
+ end
+
+ def test_javascript_tag
+ assert_dom_equal "<script type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>",
+ javascript_tag("alert('hello')")
+ end
+
+ def test_javascript_tag_with_options
+ assert_dom_equal "<script id=\"the_js_tag\" type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>",
+ javascript_tag("alert('hello')", :id => "the_js_tag")
+ end
+
+ def test_javascript_tag_with_block
+ _erbout = ''
+ javascript_tag { _erbout.concat "alert('hello')" }
+ assert_dom_equal "<script type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>", _erbout
+ end
+
+ def test_javascript_tag_with_block_and_options
+ _erbout = ''
+ javascript_tag(:id => "the_js_tag") { _erbout.concat "alert('hello')" }
+ assert_dom_equal "<script id=\"the_js_tag\" type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>", _erbout
+ end
+
+ def test_javascript_cdata_section
+ assert_dom_equal "\n//<![CDATA[\nalert('hello')\n//]]>\n", javascript_cdata_section("alert('hello')")
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/template/number_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/number_helper_test.rb
new file mode 100644
index 000000000..19ca47124
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/number_helper_test.rb
@@ -0,0 +1,93 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+class NumberHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::NumberHelper
+
+ def test_number_to_phone
+ assert_equal("800-555-1212", number_to_phone(8005551212))
+ assert_equal("(800) 555-1212", number_to_phone(8005551212, {:area_code => true}))
+ assert_equal("800 555 1212", number_to_phone(8005551212, {:delimiter => " "}))
+ assert_equal("(800) 555-1212 x 123", number_to_phone(8005551212, {:area_code => true, :extension => 123}))
+ assert_equal("800-555-1212", number_to_phone(8005551212, :extension => " "))
+ assert_equal("800-555-1212", number_to_phone("8005551212"))
+ assert_equal("+1-800-555-1212", number_to_phone(8005551212, :country_code => 1))
+ assert_equal("+18005551212", number_to_phone(8005551212, :country_code => 1, :delimiter => ''))
+ assert_equal("22-555-1212", number_to_phone(225551212))
+ assert_equal("+45-22-555-1212", number_to_phone(225551212, :country_code => 45))
+ assert_equal("x", number_to_phone("x"))
+ assert_nil number_to_phone(nil)
+ end
+
+ def test_number_to_currency
+ assert_equal("$1,234,567,890.50", number_to_currency(1234567890.50))
+ assert_equal("$1,234,567,890.51", number_to_currency(1234567890.506))
+ assert_equal("$1,234,567,892", number_to_currency(1234567891.50, {:precision => 0}))
+ assert_equal("$1,234,567,890.5", number_to_currency(1234567890.50, {:precision => 1}))
+ assert_equal("&pound;1234567890,50", number_to_currency(1234567890.50, {:unit => "&pound;", :separator => ",", :delimiter => ""}))
+ assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50"))
+ assert_equal("$x.", number_to_currency("x"))
+ assert_nil number_to_currency(nil)
+ end
+
+ def test_number_to_percentage
+ assert_equal("100.000%", number_to_percentage(100))
+ assert_equal("100%", number_to_percentage(100, {:precision => 0}))
+ assert_equal("302.06%", number_to_percentage(302.0574, {:precision => 2}))
+ assert_equal("100.000%", number_to_percentage("100"))
+ assert_equal("x%", number_to_percentage("x"))
+ assert_nil number_to_percentage(nil)
+ end
+
+ def test_number_with_delimiter
+ assert_equal("12,345,678", number_with_delimiter(12345678))
+ assert_equal("0", number_with_delimiter(0))
+ assert_equal("123", number_with_delimiter(123))
+ assert_equal("123,456", number_with_delimiter(123456))
+ assert_equal("123,456.78", number_with_delimiter(123456.78))
+ assert_equal("123,456.789", number_with_delimiter(123456.789))
+ assert_equal("123,456.78901", number_with_delimiter(123456.78901))
+ assert_equal("123,456,789.78901", number_with_delimiter(123456789.78901))
+ assert_equal("0.78901", number_with_delimiter(0.78901))
+ assert_equal("123,456.78", number_with_delimiter("123456.78"))
+ assert_equal("x", number_with_delimiter("x"))
+ assert_nil number_with_delimiter(nil)
+ end
+
+ def test_number_with_precision
+ assert_equal("111.235", number_with_precision(111.2346))
+ assert_equal("111.23", number_with_precision(111.2346, 2))
+ assert_equal("111.00", number_with_precision(111, 2))
+ assert_equal("111.235", number_with_precision("111.2346"))
+ assert_equal("112", number_with_precision(111.50, 0))
+ assert_equal("1234567892", number_with_precision(1234567891.50, 0))
+
+ # Return non-numeric params unchanged.
+ assert_equal("x", number_with_precision("x"))
+ assert_nil number_with_precision(nil)
+ end
+
+ def test_number_to_human_size
+ assert_equal '0 Bytes', number_to_human_size(0)
+ assert_equal '1 Byte', number_to_human_size(1)
+ assert_equal '3 Bytes', number_to_human_size(3.14159265)
+ assert_equal '123 Bytes', number_to_human_size(123.0)
+ assert_equal '123 Bytes', number_to_human_size(123)
+ assert_equal '1.2 KB', number_to_human_size(1234)
+ assert_equal '12.1 KB', number_to_human_size(12345)
+ assert_equal '1.2 MB', number_to_human_size(1234567)
+ assert_equal '1.1 GB', number_to_human_size(1234567890)
+ assert_equal '1.1 TB', number_to_human_size(1234567890123)
+ assert_equal '444 KB', number_to_human_size(444.kilobytes)
+ assert_equal '1023 MB', number_to_human_size(1023.megabytes)
+ assert_equal '3 TB', number_to_human_size(3.terabytes)
+ assert_equal '1.18 MB', number_to_human_size(1234567, 2)
+ assert_equal '3 Bytes', number_to_human_size(3.14159265, 4)
+ assert_equal("123 Bytes", number_to_human_size("123"))
+ assert_equal '1.01 KB', number_to_human_size(1.0123.kilobytes, 2)
+ assert_equal '1.01 KB', number_to_human_size(1.0100.kilobytes, 4)
+ assert_equal '10 KB', number_to_human_size(10.000.kilobytes, 4)
+ assert_equal '1 Byte', number_to_human_size(1.1)
+ assert_nil number_to_human_size('x')
+ assert_nil number_to_human_size(nil)
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/template/prototype_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/prototype_helper_test.rb
new file mode 100644
index 000000000..397872241
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/prototype_helper_test.rb
@@ -0,0 +1,627 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+Bunny = Struct.new(:Bunny, :id)
+
+class Author
+ attr_reader :id
+ def save; @id = 1 end
+ def new_record?; @id.nil? end
+ def name
+ @id.nil? ? 'new author' : "author ##{@id}"
+ end
+end
+
+class Article
+ attr_reader :id
+ attr_reader :author_id
+ def save; @id = 1; @author_id = 1 end
+ def new_record?; @id.nil? end
+ def name
+ @id.nil? ? 'new article' : "article ##{@id}"
+ end
+end
+
+class Author::Nested < Author; end
+
+
+module BaseTest
+ def self.included(base)
+ base.send :attr_accessor, :template_format
+ end
+
+ include ActionView::Helpers::JavaScriptHelper
+ include ActionView::Helpers::PrototypeHelper
+ include ActionView::Helpers::ScriptaculousHelper
+
+ include ActionView::Helpers::UrlHelper
+ include ActionView::Helpers::TagHelper
+ include ActionView::Helpers::TextHelper
+ include ActionView::Helpers::FormTagHelper
+ include ActionView::Helpers::FormHelper
+ include ActionView::Helpers::CaptureHelper
+ include ActionView::Helpers::RecordIdentificationHelper
+ include ActionController::PolymorphicRoutes
+
+ def setup
+ @template = nil
+ @controller = Class.new do
+ def url_for(options)
+ if options.is_a?(String)
+ options
+ else
+ url = "http://www.example.com/"
+ url << options[:action].to_s if options and options[:action]
+ url << "?a=#{options[:a]}" if options && options[:a]
+ url << "&b=#{options[:b]}" if options && options[:a] && options[:b]
+ url
+ end
+ end
+ end.new
+ end
+
+protected
+
+ def request_forgery_protection_token
+ nil
+ end
+
+ def protect_against_forgery?
+ false
+ end
+
+ def create_generator
+ block = Proc.new { |*args| yield *args if block_given? }
+ JavaScriptGenerator.new self, &block
+ end
+end
+
+class PrototypeHelperTest < Test::Unit::TestCase
+ include BaseTest
+
+ def setup
+ @record = @author = Author.new
+ @article = Article.new
+ super
+ end
+
+ def test_link_to_remote
+ assert_dom_equal %(<a class=\"fine\" href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true}); return false;\">Remote outauthor</a>),
+ link_to_remote("Remote outauthor", { :url => { :action => "whatnot" }}, { :class => "fine" })
+ assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onComplete:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>),
+ link_to_remote("Remote outauthor", :complete => "alert(request.responseText)", :url => { :action => "whatnot" })
+ assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onSuccess:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>),
+ link_to_remote("Remote outauthor", :success => "alert(request.responseText)", :url => { :action => "whatnot" })
+ assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>),
+ link_to_remote("Remote outauthor", :failure => "alert(request.responseText)", :url => { :action => "whatnot" })
+ assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot?a=10&amp;b=20', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>),
+ link_to_remote("Remote outauthor", :failure => "alert(request.responseText)", :url => { :action => "whatnot", :a => '10', :b => '20' })
+ end
+
+ def test_link_to_remote_html_options
+ assert_dom_equal %(<a class=\"fine\" href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true}); return false;\">Remote outauthor</a>),
+ link_to_remote("Remote outauthor", { :url => { :action => "whatnot" }, :html => { :class => "fine" } })
+ end
+
+ def test_periodically_call_remote
+ assert_dom_equal %(<script type="text/javascript">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Updater('schremser_bier', 'http://www.example.com/mehr_bier', {asynchronous:true, evalScripts:true})}, 10)\n//]]>\n</script>),
+ periodically_call_remote(:update => "schremser_bier", :url => { :action => "mehr_bier" })
+ end
+
+ def test_periodically_call_remote_with_frequency
+ assert_dom_equal(
+ "<script type=\"text/javascript\">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true})}, 2)\n//]]>\n</script>",
+ periodically_call_remote(:frequency => 2)
+ )
+ end
+
+ def test_form_remote_tag
+ assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
+ form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast })
+ assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
+ form_remote_tag(:update => { :success => "glass_of_beer" }, :url => { :action => :fast })
+ assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
+ form_remote_tag(:update => { :failure => "glass_of_water" }, :url => { :action => :fast })
+ assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer',failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
+ form_remote_tag(:update => { :success => 'glass_of_beer', :failure => "glass_of_water" }, :url => { :action => :fast })
+ end
+
+ def test_form_remote_tag_with_method
+ assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\"><div style='margin:0;padding:0'><input name='_method' type='hidden' value='put' /></div>),
+ form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, :html => { :method => :put })
+ end
+
+ def test_form_remote_tag_with_block
+ _erbout = ''
+ form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }) { _erbout.concat "Hello world!" }
+ assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">Hello world!</form>), _erbout
+ end
+
+ def test_remote_form_for_with_record_identification_with_new_record
+ _erbout = ''
+ remote_form_for(@record, {:html => { :id => 'create-author' }}) {}
+
+ expected = %(<form action='#{authors_path}' onsubmit="new Ajax.Request('#{authors_path}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='new_author' id='create-author' method='post'></form>)
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_remote_form_for_with_record_identification_without_html_options
+ _erbout = ''
+ remote_form_for(@record) {}
+
+ expected = %(<form action='#{authors_path}' onsubmit="new Ajax.Request('#{authors_path}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='new_author' method='post' id='new_author'></form>)
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_remote_form_for_with_record_identification_with_existing_record
+ @record.save
+ _erbout = ''
+ remote_form_for(@record) {}
+
+ expected = %(<form action='#{author_path(@record)}' id='edit_author_1' method='post' onsubmit="new Ajax.Request('#{author_path(@record)}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='edit_author'><div style='margin:0;padding:0'><input name='_method' type='hidden' value='put' /></div></form>)
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_remote_form_for_with_new_object_in_list
+ _erbout = ''
+ remote_form_for([@author, @article]) {}
+
+ expected = %(<form action='#{author_articles_path(@author)}' onsubmit="new Ajax.Request('#{author_articles_path(@author)}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='new_article' method='post' id='new_article'></form>)
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_remote_form_for_with_existing_object_in_list
+ @author.save
+ @article.save
+ _erbout = ''
+ remote_form_for([@author, @article]) {}
+
+ expected = %(<form action='#{author_article_path(@author, @article)}' id='edit_article_1' method='post' onsubmit="new Ajax.Request('#{author_article_path(@author, @article)}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='edit_article'><div style='margin:0;padding:0'><input name='_method' type='hidden' value='put' /></div></form>)
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_on_callbacks
+ callbacks = [:uninitialized, :loading, :loaded, :interactive, :complete, :success, :failure]
+ callbacks.each do |callback|
+ assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
+ form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
+ assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
+ form_remote_tag(:update => { :success => "glass_of_beer" }, :url => { :action => :fast }, callback=>"monkeys();")
+ assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({failure:'glass_of_beer'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
+ form_remote_tag(:update => { :failure => "glass_of_beer" }, :url => { :action => :fast }, callback=>"monkeys();")
+ assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer',failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
+ form_remote_tag(:update => { :success => "glass_of_beer", :failure => "glass_of_water" }, :url => { :action => :fast }, callback=>"monkeys();")
+ end
+
+ #HTTP status codes 200 up to 599 have callbacks
+ #these should work
+ 100.upto(599) do |callback|
+ assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
+ form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
+ end
+
+ #test 200 and 404
+ assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on200:function(request){monkeys();}, on404:function(request){bananas();}, parameters:Form.serialize(this)}); return false;">),
+ form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, 200=>"monkeys();", 404=>"bananas();")
+
+ #these shouldn't
+ 1.upto(99) do |callback|
+ assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">),
+ form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
+ end
+ 600.upto(999) do |callback|
+ assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">),
+ form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
+ end
+
+ #test ultimate combo
+ assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on200:function(request){monkeys();}, on404:function(request){bananas();}, onComplete:function(request){c();}, onFailure:function(request){f();}, onLoading:function(request){c1()}, onSuccess:function(request){s()}, parameters:Form.serialize(this)}); return false;\">),
+ form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, :loading => "c1()", :success => "s()", :failure => "f();", :complete => "c();", 200=>"monkeys();", 404=>"bananas();")
+
+ end
+
+ def test_submit_to_remote
+ assert_dom_equal %(<input name=\"More beer!\" onclick=\"new Ajax.Updater('empty_bottle', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); return false;\" type=\"button\" value=\"1000000\" />),
+ submit_to_remote("More beer!", 1_000_000, :update => "empty_bottle")
+ end
+
+ def test_observe_field
+ assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/reorder_if_empty', {asynchronous:true, evalScripts:true, parameters:value})})\n//]]>\n</script>),
+ observe_field("glass", :frequency => 5.minutes, :url => { :action => "reorder_if_empty" })
+ end
+
+ def test_observe_field_using_with_option
+ expected = %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/check_value', {asynchronous:true, evalScripts:true, parameters:'id=' + value})})\n//]]>\n</script>)
+ assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => 'id')
+ assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => "'id=' + value")
+ end
+
+ def test_observe_field_using_json_in_with_option
+ expected = %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/check_value', {asynchronous:true, evalScripts:true, parameters:{'id':value}})})\n//]]>\n</script>)
+ assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => "{'id':value}")
+ end
+
+ def test_observe_field_using_function_for_callback
+ assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {alert('Element changed')})\n//]]>\n</script>),
+ observe_field("glass", :frequency => 5.minutes, :function => "alert('Element changed')")
+ end
+
+ def test_observe_form
+ assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {new Ajax.Request('http://www.example.com/cart_changed', {asynchronous:true, evalScripts:true, parameters:value})})\n//]]>\n</script>),
+ observe_form("cart", :frequency => 2, :url => { :action => "cart_changed" })
+ end
+
+ def test_observe_form_using_function_for_callback
+ assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {alert('Form changed')})\n//]]>\n</script>),
+ observe_form("cart", :frequency => 2, :function => "alert('Form changed')")
+ end
+
+ def test_observe_field_without_frequency
+ assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.EventObserver('glass', function(element, value) {new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:value})})\n//]]>\n</script>),
+ observe_field("glass")
+ end
+
+ def test_update_page
+ block = Proc.new { |page| page.replace_html('foo', 'bar') }
+ assert_equal create_generator(&block).to_s, update_page(&block)
+ end
+
+ def test_update_page_tag
+ block = Proc.new { |page| page.replace_html('foo', 'bar') }
+ assert_equal javascript_tag(create_generator(&block).to_s), update_page_tag(&block)
+ end
+
+ def test_update_page_tag_with_html_options
+ block = Proc.new { |page| page.replace_html('foo', 'bar') }
+ assert_equal javascript_tag(create_generator(&block).to_s, {:defer => 'true'}), update_page_tag({:defer => 'true'}, &block)
+ end
+
+
+ protected
+ def author_path(record)
+ "/authors/#{record.id}"
+ end
+
+ def authors_path
+ "/authors"
+ end
+
+ def author_articles_path(author)
+ "/authors/#{author.id}/articles"
+ end
+
+ def author_article_path(author, article)
+ "/authors/#{author.id}/articles/#{article.id}"
+ end
+end
+
+class JavaScriptGeneratorTest < Test::Unit::TestCase
+ include BaseTest
+
+ def setup
+ super
+ @generator = create_generator
+ end
+
+ def test_insert_html_with_string
+ assert_equal 'new Insertion.Top("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");',
+ @generator.insert_html(:top, 'element', '<p>This is a test</p>')
+ assert_equal 'new Insertion.Bottom("element", "\\u003Cp\u003EThis is a test\\u003C/p\u003E");',
+ @generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
+ assert_equal 'new Insertion.Before("element", "\\u003Cp\u003EThis is a test\\u003C/p\u003E");',
+ @generator.insert_html(:before, 'element', '<p>This is a test</p>')
+ assert_equal 'new Insertion.After("element", "\\u003Cp\u003EThis is a test\\u003C/p\u003E");',
+ @generator.insert_html(:after, 'element', '<p>This is a test</p>')
+ end
+
+ def test_replace_html_with_string
+ assert_equal 'Element.update("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");',
+ @generator.replace_html('element', '<p>This is a test</p>')
+ end
+
+ def test_replace_element_with_string
+ assert_equal 'Element.replace("element", "\\u003Cdiv id=\"element\"\\u003E\\u003Cp\\u003EThis is a test\\u003C/p\\u003E\\u003C/div\\u003E");',
+ @generator.replace('element', '<div id="element"><p>This is a test</p></div>')
+ end
+
+ def test_remove
+ assert_equal 'Element.remove("foo");',
+ @generator.remove('foo')
+ assert_equal '["foo", "bar", "baz"].each(Element.remove);',
+ @generator.remove('foo', 'bar', 'baz')
+ end
+
+ def test_show
+ assert_equal 'Element.show("foo");',
+ @generator.show('foo')
+ assert_equal '["foo", "bar", "baz"].each(Element.show);',
+ @generator.show('foo', 'bar', 'baz')
+ end
+
+ def test_hide
+ assert_equal 'Element.hide("foo");',
+ @generator.hide('foo')
+ assert_equal '["foo", "bar", "baz"].each(Element.hide);',
+ @generator.hide('foo', 'bar', 'baz')
+ end
+
+ def test_toggle
+ assert_equal 'Element.toggle("foo");',
+ @generator.toggle('foo')
+ assert_equal '["foo", "bar", "baz"].each(Element.toggle);',
+ @generator.toggle('foo', 'bar', 'baz')
+ end
+
+ def test_alert
+ assert_equal 'alert("hello");', @generator.alert('hello')
+ end
+
+ def test_redirect_to
+ assert_equal 'window.location.href = "http://www.example.com/welcome";',
+ @generator.redirect_to(:action => 'welcome')
+ end
+
+ def test_delay
+ @generator.delay(20) do
+ @generator.hide('foo')
+ end
+
+ assert_equal "setTimeout(function() {\n;\nElement.hide(\"foo\");\n}, 20000);", @generator.to_s
+ end
+
+ def test_to_s
+ @generator.insert_html(:top, 'element', '<p>This is a test</p>')
+ @generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
+ @generator.remove('foo', 'bar')
+ @generator.replace_html('baz', '<p>This is a test</p>')
+
+ assert_equal <<-EOS.chomp, @generator.to_s
+new Insertion.Top("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
+new Insertion.Bottom("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
+["foo", "bar"].each(Element.remove);
+Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
+ EOS
+ end
+
+ def test_element_access
+ assert_equal %($("hello");), @generator['hello']
+ end
+
+ def test_element_access_on_records
+ assert_equal %($("bunny_5");), @generator[Bunny.new(:id => 5)]
+ assert_equal %($("new_bunny");), @generator[Bunny.new]
+ end
+
+ def test_element_proxy_one_deep
+ @generator['hello'].hide
+ assert_equal %($("hello").hide();), @generator.to_s
+ end
+
+ def test_element_proxy_variable_access
+ @generator['hello']['style']
+ assert_equal %($("hello").style;), @generator.to_s
+ end
+
+ def test_element_proxy_variable_access_with_assignment
+ @generator['hello']['style']['color'] = 'red'
+ assert_equal %($("hello").style.color = "red";), @generator.to_s
+ end
+
+ def test_element_proxy_assignment
+ @generator['hello'].width = 400
+ assert_equal %($("hello").width = 400;), @generator.to_s
+ end
+
+ def test_element_proxy_two_deep
+ @generator['hello'].hide("first").clean_whitespace
+ assert_equal %($("hello").hide("first").cleanWhitespace();), @generator.to_s
+ end
+
+ def test_select_access
+ assert_equal %($$("div.hello");), @generator.select('div.hello')
+ end
+
+ def test_select_proxy_one_deep
+ @generator.select('p.welcome b').first.hide
+ assert_equal %($$("p.welcome b").first().hide();), @generator.to_s
+ end
+
+ def test_visual_effect
+ assert_equal %(new Effect.Puff("blah",{});),
+ @generator.visual_effect(:puff,'blah')
+ end
+
+ def test_visual_effect_toggle
+ assert_equal %(Effect.toggle("blah",'appear',{});),
+ @generator.visual_effect(:toggle_appear,'blah')
+ end
+
+ def test_sortable
+ assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("blah")})}});),
+ @generator.sortable('blah', :url => { :action => "order" })
+ end
+
+ def test_draggable
+ assert_equal %(new Draggable("blah", {});),
+ @generator.draggable('blah')
+ end
+
+ def test_drop_receiving
+ assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});),
+ @generator.drop_receiving('blah', :url => { :action => "order" })
+ end
+
+ def test_collection_first_and_last
+ @generator.select('p.welcome b').first.hide()
+ @generator.select('p.welcome b').last.show()
+ assert_equal <<-EOS.strip, @generator.to_s
+$$("p.welcome b").first().hide();
+$$("p.welcome b").last().show();
+ EOS
+ end
+
+ def test_collection_proxy_with_each
+ @generator.select('p.welcome b').each do |value|
+ value.remove_class_name 'selected'
+ end
+ @generator.select('p.welcome b').each do |value, index|
+ @generator.visual_effect :highlight, value
+ end
+ assert_equal <<-EOS.strip, @generator.to_s
+$$("p.welcome b").each(function(value, index) {
+value.removeClassName("selected");
+});
+$$("p.welcome b").each(function(value, index) {
+new Effect.Highlight(value,{});
+});
+ EOS
+ end
+
+ def test_collection_proxy_on_collect
+ @generator.select('p').collect('a') { |para| para.show }
+ @generator.select('p').collect { |para| para.hide }
+ assert_equal <<-EOS.strip, @generator.to_s
+var a = $$("p").collect(function(value, index) {
+return value.show();
+});
+$$("p").collect(function(value, index) {
+return value.hide();
+});
+ EOS
+ @generator = create_generator
+ end
+
+ def test_collection_proxy_with_grep
+ @generator.select('p').grep 'a', /^a/ do |value|
+ @generator << '(value.className == "welcome")'
+ end
+ @generator.select('p').grep 'b', /b$/ do |value, index|
+ @generator.call 'alert', value
+ @generator << '(value.className == "welcome")'
+ end
+
+ assert_equal <<-EOS.strip, @generator.to_s
+var a = $$("p").grep(/^a/, function(value, index) {
+return (value.className == "welcome");
+});
+var b = $$("p").grep(/b$/, function(value, index) {
+alert(value);
+return (value.className == "welcome");
+});
+ EOS
+ end
+
+ def test_collection_proxy_with_inject
+ @generator.select('p').inject 'a', [] do |memo, value|
+ @generator << '(value.className == "welcome")'
+ end
+ @generator.select('p').inject 'b', nil do |memo, value, index|
+ @generator.call 'alert', memo
+ @generator << '(value.className == "welcome")'
+ end
+
+ assert_equal <<-EOS.strip, @generator.to_s
+var a = $$("p").inject([], function(memo, value, index) {
+return (value.className == "welcome");
+});
+var b = $$("p").inject(null, function(memo, value, index) {
+alert(memo);
+return (value.className == "welcome");
+});
+ EOS
+ end
+
+ def test_collection_proxy_with_pluck
+ @generator.select('p').pluck('a', 'className')
+ assert_equal %(var a = $$("p").pluck("className");), @generator.to_s
+ end
+
+ def test_collection_proxy_with_zip
+ ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('a', [4, 5, 6], [7, 8, 9])
+ ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('b', [4, 5, 6], [7, 8, 9]) do |array|
+ @generator.call 'array.reverse'
+ end
+
+ assert_equal <<-EOS.strip, @generator.to_s
+var a = [1, 2, 3].zip([4, 5, 6], [7, 8, 9]);
+var b = [1, 2, 3].zip([4, 5, 6], [7, 8, 9], function(array) {
+return array.reverse();
+});
+ EOS
+ end
+
+ def test_collection_proxy_with_find_all
+ @generator.select('p').find_all 'a' do |value, index|
+ @generator << '(value.className == "welcome")'
+ end
+
+ assert_equal <<-EOS.strip, @generator.to_s
+var a = $$("p").findAll(function(value, index) {
+return (value.className == "welcome");
+});
+ EOS
+ end
+
+ def test_collection_proxy_with_in_groups_of
+ @generator.select('p').in_groups_of('a', 3)
+ @generator.select('p').in_groups_of('a', 3, 'x')
+ assert_equal <<-EOS.strip, @generator.to_s
+var a = $$("p").inGroupsOf(3);
+var a = $$("p").inGroupsOf(3, "x");
+ EOS
+ end
+
+ def test_collection_proxy_with_each_slice
+ @generator.select('p').each_slice('a', 3)
+ @generator.select('p').each_slice('a', 3) do |group, index|
+ group.reverse
+ end
+
+ assert_equal <<-EOS.strip, @generator.to_s
+var a = $$("p").eachSlice(3);
+var a = $$("p").eachSlice(3, function(value, index) {
+return value.reverse();
+});
+ EOS
+ end
+
+ def test_debug_rjs
+ ActionView::Base.debug_rjs = true
+ @generator['welcome'].replace_html 'Welcome'
+ assert_equal "try {\n$(\"welcome\").update(\"Welcome\");\n} catch (e) { alert('RJS error:\\n\\n' + e.toString()); alert('$(\\\"welcome\\\").update(\\\"Welcome\\\");'); throw e }", @generator.to_s
+ ensure
+ ActionView::Base.debug_rjs = false
+ end
+
+ def test_literal
+ literal = @generator.literal("function() {}")
+ assert_equal "function() {}", literal.to_json
+ assert_equal "", @generator.to_s
+ end
+
+ def test_class_proxy
+ @generator.form.focus('my_field')
+ assert_equal "Form.focus(\"my_field\");", @generator.to_s
+ end
+
+ def test_call_with_block
+ @generator.call(:before)
+ @generator.call(:my_method) do |p|
+ p[:one].show
+ p[:two].hide
+ end
+ @generator.call(:in_between)
+ @generator.call(:my_method_with_arguments, true, "hello") do |p|
+ p[:three].visual_effect(:highlight)
+ end
+ assert_equal "before();\nmy_method(function() { $(\"one\").show();\n$(\"two\").hide(); });\nin_between();\nmy_method_with_arguments(true, \"hello\", function() { $(\"three\").visualEffect(\"highlight\"); });", @generator.to_s
+ end
+
+ def test_class_proxy_call_with_block
+ @generator.my_object.my_method do |p|
+ p[:one].show
+ p[:two].hide
+ end
+ assert_equal "MyObject.myMethod(function() { $(\"one\").show();\n$(\"two\").hide(); });", @generator.to_s
+ end
+end
+
diff --git a/vendor/rails-2.0.2/actionpack/test/template/sanitize_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/sanitize_helper_test.rb
new file mode 100644
index 000000000..7f2d2d9cc
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/sanitize_helper_test.rb
@@ -0,0 +1,49 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+require "#{File.dirname(__FILE__)}/../testing_sandbox"
+
+# The exhaustive tests are in test/controller/html/sanitizer_test.rb.
+# This tests the that the helpers hook up correctly to the sanitizer classes.
+class SanitizeHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::SanitizeHelper
+ include ActionView::Helpers::TagHelper
+ include TestingSandbox
+
+ def test_strip_links
+ assert_equal "Dont touch me", strip_links("Dont touch me")
+ assert_equal "<a<a", strip_links("<a<a")
+ assert_equal "on my mind\nall day long", strip_links("<a href='almost'>on my mind</a>\n<A href='almost'>all day long</A>")
+ assert_equal "0wn3d", strip_links("<a href='http://www.rubyonrails.com/'><a href='http://www.rubyonrails.com/' onlclick='steal()'>0wn3d</a></a>")
+ assert_equal "Magic", strip_links("<a href='http://www.rubyonrails.com/'>Mag<a href='http://www.ruby-lang.org/'>ic")
+ assert_equal "FrrFox", strip_links("<href onlclick='steal()'>FrrFox</a></href>")
+ assert_equal "My mind\nall <b>day</b> long", strip_links("<a href='almost'>My mind</a>\n<A href='almost'>all <b>day</b> long</A>")
+ assert_equal "all <b>day</b> long", strip_links("<<a>a href='hello'>all <b>day</b> long<</A>/a>")
+ end
+
+ def test_sanitize_form
+ assert_sanitized "<form action=\"/foo/bar\" method=\"post\"><input></form>", ''
+ end
+
+ def test_should_sanitize_illegal_style_properties
+ raw = %(display:block; position:absolute; left:0; top:0; width:100%; height:100%; z-index:1; background-color:black; background-image:url(http://www.ragingplatypus.com/i/cam-full.jpg); background-x:center; background-y:center; background-repeat:repeat;)
+ expected = %(display: block; width: 100%; height: 100%; background-color: black; background-image: ; background-x: center; background-y: center;)
+ assert_equal expected, sanitize_css(raw)
+ end
+
+ def test_strip_tags
+ assert_equal("<<<bad html", strip_tags("<<<bad html"))
+ assert_equal("<<", strip_tags("<<<bad html>"))
+ assert_equal("Dont touch me", strip_tags("Dont touch me"))
+ assert_equal("This is a test.", strip_tags("<p>This <u>is<u> a <a href='test.html'><strong>test</strong></a>.</p>"))
+ assert_equal("Weirdos", strip_tags("Wei<<a>a onclick='alert(document.cookie);'</a>/>rdos"))
+ assert_equal("This is a test.", strip_tags("This is a test."))
+ assert_equal(
+ %{This is a test.\n\n\nIt no longer contains any HTML.\n}, strip_tags(
+ %{<title>This is <b>a <a href="" target="_blank">test</a></b>.</title>\n\n<!-- it has a comment -->\n\n<p>It no <b>longer <strong>contains <em>any <strike>HTML</strike></em>.</strong></b></p>\n}))
+ assert_equal "This has a here.", strip_tags("This has a <!-- comment --> here.")
+ [nil, '', ' '].each { |blank| assert_equal blank, strip_tags(blank) }
+ end
+
+ def assert_sanitized(text, expected = nil)
+ assert_equal((expected || text), sanitize(text))
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.0.2/actionpack/test/template/scriptaculous_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/scriptaculous_helper_test.rb
new file mode 100644
index 000000000..04fbe33d5
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/scriptaculous_helper_test.rb
@@ -0,0 +1,96 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+class ScriptaculousHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::JavaScriptHelper
+ include ActionView::Helpers::PrototypeHelper
+ include ActionView::Helpers::ScriptaculousHelper
+
+ include ActionView::Helpers::UrlHelper
+ include ActionView::Helpers::TagHelper
+ include ActionView::Helpers::TextHelper
+ include ActionView::Helpers::FormHelper
+ include ActionView::Helpers::CaptureHelper
+
+ def setup
+ @controller = Class.new do
+ def url_for(options)
+ url = "http://www.example.com/"
+ url << options[:action].to_s if options and options[:action]
+ url
+ end
+ end.new
+ end
+
+ def test_effect
+ assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect(:highlight, "posts")
+ assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect("highlight", :posts)
+ assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect(:highlight, :posts)
+ assert_equal "new Effect.Fade(\"fademe\",{duration:4.0});", visual_effect(:fade, "fademe", :duration => 4.0)
+ assert_equal "new Effect.Shake(element,{});", visual_effect(:shake)
+ assert_equal "new Effect.DropOut(\"dropme\",{queue:'end'});", visual_effect(:drop_out, 'dropme', :queue => :end)
+ assert_equal "new Effect.Highlight(\"status\",{endcolor:'#EEEEEE'});", visual_effect(:highlight, 'status', :endcolor => '#EEEEEE')
+ assert_equal "new Effect.Highlight(\"status\",{restorecolor:'#500000', startcolor:'#FEFEFE'});", visual_effect(:highlight, 'status', :restorecolor => '#500000', :startcolor => '#FEFEFE')
+
+ # chop the queue params into a comma separated list
+ beginning, ending = 'new Effect.DropOut("dropme",{queue:{', '}});'
+ ve = [
+ visual_effect(:drop_out, 'dropme', :queue => {:position => "end", :scope => "test", :limit => 2}),
+ visual_effect(:drop_out, 'dropme', :queue => {:scope => :list, :limit => 2}),
+ visual_effect(:drop_out, 'dropme', :queue => {:position => :end, :scope => :test, :limit => 2})
+ ].collect { |v| v[beginning.length..-ending.length-1].split(',') }
+
+ assert ve[0].include?("limit:2")
+ assert ve[0].include?("scope:'test'")
+ assert ve[0].include?("position:'end'")
+
+ assert ve[1].include?("limit:2")
+ assert ve[1].include?("scope:'list'")
+
+ assert ve[2].include?("limit:2")
+ assert ve[2].include?("scope:'test'")
+ assert ve[2].include?("position:'end'")
+ end
+
+ def test_toggle_effects
+ assert_equal "Effect.toggle(\"posts\",'appear',{});", visual_effect(:toggle_appear, "posts")
+ assert_equal "Effect.toggle(\"posts\",'slide',{});", visual_effect(:toggle_slide, "posts")
+ assert_equal "Effect.toggle(\"posts\",'blind',{});", visual_effect(:toggle_blind, "posts")
+ assert_equal "Effect.toggle(\"posts\",'appear',{});", visual_effect("toggle_appear", "posts")
+ assert_equal "Effect.toggle(\"posts\",'slide',{});", visual_effect("toggle_slide", "posts")
+ assert_equal "Effect.toggle(\"posts\",'blind',{});", visual_effect("toggle_blind", "posts")
+ end
+
+
+ def test_sortable_element
+ assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}})\n//]]>\n</script>),
+ sortable_element("mylist", :url => { :action => "order" })
+ assert_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {constraint:'horizontal', onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}, tag:'div'})\n//]]>\n</script>),
+ sortable_element("mylist", :tag => "div", :constraint => "horizontal", :url => { :action => "order" })
+ assert_dom_equal %|<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {constraint:'horizontal', containment:['list1','list2'], onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}})\n//]]>\n</script>|,
+ sortable_element("mylist", :containment => ['list1','list2'], :constraint => "horizontal", :url => { :action => "order" })
+ assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {constraint:'horizontal', containment:'list1', onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}})\n//]]>\n</script>),
+ sortable_element("mylist", :containment => 'list1', :constraint => "horizontal", :url => { :action => "order" })
+ end
+
+ def test_draggable_element
+ assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable(\"product_13\", {})\n//]]>\n</script>),
+ draggable_element("product_13")
+ assert_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable(\"product_13\", {revert:true})\n//]]>\n</script>),
+ draggable_element("product_13", :revert => true)
+ end
+
+ def test_drop_receiving_element
+ assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {onDrop:function(element){new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
+ drop_receiving_element("droptarget1")
+ assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {accept:'products', onDrop:function(element){new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
+ drop_receiving_element("droptarget1", :accept => 'products')
+ assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {accept:'products', onDrop:function(element){new Ajax.Updater('infobox', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
+ drop_receiving_element("droptarget1", :accept => 'products', :update => 'infobox')
+ assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {accept:['tshirts','mugs'], onDrop:function(element){new Ajax.Updater('infobox', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
+ drop_receiving_element("droptarget1", :accept => ['tshirts','mugs'], :update => 'infobox')
+ end
+
+ def protect_against_forgery?
+ false
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/template/tag_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/tag_helper_test.rb
new file mode 100644
index 000000000..c7edc678f
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/tag_helper_test.rb
@@ -0,0 +1,80 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+class TagHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::TagHelper
+ include ActionView::Helpers::UrlHelper
+ include ActionView::Helpers::TextHelper
+ include ActionView::Helpers::CaptureHelper
+
+ def test_tag
+ assert_equal "<br />", tag("br")
+ assert_equal "<br clear=\"left\" />", tag(:br, :clear => "left")
+ assert_equal "<br>", tag("br", nil, true)
+ end
+
+ def test_tag_options
+ str = tag("p", "class" => "show", :class => "elsewhere")
+ assert_match /class="show"/, str
+ assert_match /class="elsewhere"/, str
+ end
+
+ def test_tag_options_rejects_nil_option
+ assert_equal "<p />", tag("p", :ignored => nil)
+ end
+
+ def test_tag_options_accepts_blank_option
+ assert_equal "<p included=\"\" />", tag("p", :included => '')
+ end
+
+ def test_tag_options_converts_boolean_option
+ assert_equal '<p disabled="disabled" multiple="multiple" readonly="readonly" />',
+ tag("p", :disabled => true, :multiple => true, :readonly => true)
+ end
+
+ def test_content_tag
+ assert_equal "<a href=\"create\">Create</a>", content_tag("a", "Create", "href" => "create")
+ assert_equal content_tag("a", "Create", "href" => "create"),
+ content_tag("a", "Create", :href => "create")
+ end
+
+ def test_content_tag_with_block
+ _erbout = ''
+ content_tag(:div) { _erbout.concat "Hello world!" }
+ assert_dom_equal "<div>Hello world!</div>", _erbout
+ end
+
+ def test_content_tag_with_block_and_options
+ _erbout = ''
+ content_tag(:div, :class => "green") { _erbout.concat "Hello world!" }
+ assert_dom_equal %(<div class="green">Hello world!</div>), _erbout
+ end
+
+ def test_content_tag_with_block_and_options_outside_of_action_view
+ assert_equal content_tag("a", "Create", :href => "create"),
+ content_tag("a", "href" => "create") { "Create" }
+ end
+
+ def test_cdata_section
+ assert_equal "<![CDATA[<hello world>]]>", cdata_section("<hello world>")
+ end
+
+ def test_escape_once
+ assert_equal '1 &lt; 2 &amp; 3', escape_once('1 < 2 &amp; 3')
+ end
+
+ def test_double_escaping_attributes
+ ['1&amp;2', '1 &lt; 2', '&#8220;test&#8220;'].each do |escaped|
+ assert_equal %(<a href="#{escaped}" />), tag('a', :href => escaped)
+ end
+ end
+
+ def test_skip_invalid_escaped_attributes
+ ['&1;', '&#1dfa3;', '& #123;'].each do |escaped|
+ assert_equal %(<a href="#{escaped.gsub /&/, '&amp;'}" />), tag('a', :href => escaped)
+ end
+ end
+
+ def test_disable_escaping
+ assert_equal '<a href="&amp;" />', tag('a', { :href => '&amp;' }, false, false)
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/template/text_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/text_helper_test.rb
new file mode 100644
index 000000000..92d6bc3ae
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/text_helper_test.rb
@@ -0,0 +1,335 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+require "#{File.dirname(__FILE__)}/../testing_sandbox"
+
+class TextHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::TextHelper
+ include ActionView::Helpers::TagHelper
+ include TestingSandbox
+
+ def setup
+ # This simulates the fact that instance variables are reset every time
+ # a view is rendered. The cycle helper depends on this behavior.
+ @_cycles = nil if (defined? @_cycles)
+ end
+
+ def test_simple_format
+ assert_equal "<p></p>", simple_format(nil)
+
+ assert_equal "<p>crazy\n<br /> cross\n<br /> platform linebreaks</p>", simple_format("crazy\r\n cross\r platform linebreaks")
+ assert_equal "<p>A paragraph</p>\n\n<p>and another one!</p>", simple_format("A paragraph\n\nand another one!")
+ assert_equal "<p>A paragraph\n<br /> With a newline</p>", simple_format("A paragraph\n With a newline")
+
+ text = "A\nB\nC\nD".freeze
+ assert_equal "<p>A\n<br />B\n<br />C\n<br />D</p>", simple_format(text)
+
+ text = "A\r\n \nB\n\n\r\n\t\nC\nD".freeze
+ assert_equal "<p>A\n<br /> \n<br />B</p>\n\n<p>\t\n<br />C\n<br />D</p>", simple_format(text)
+ end
+
+ def test_truncate
+ assert_equal "Hello World!", truncate("Hello World!", 12)
+ assert_equal "Hello Wor...", truncate("Hello World!!", 12)
+ end
+
+ def test_truncate_should_use_default_length_of_30
+ str = "This is a string that will go longer then the default truncate length of 30"
+ assert_equal str[0...27] + "...", truncate(str)
+ end
+
+ def test_truncate_multibyte
+ with_kcode 'none' do
+ assert_equal "\354\225\210\353\205\225\355...", truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", 10)
+ end
+ with_kcode 'u' do
+ assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...",
+ truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244", 10)
+ end
+ end
+
+ def test_highlighter
+ assert_equal(
+ "This is a <strong class=\"highlight\">beautiful</strong> morning",
+ highlight("This is a beautiful morning", "beautiful")
+ )
+
+ assert_equal(
+ "This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day",
+ highlight("This is a beautiful morning, but also a beautiful day", "beautiful")
+ )
+
+ assert_equal(
+ "This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day",
+ highlight("This is a beautiful morning, but also a beautiful day", "beautiful", '<b>\1</b>')
+ )
+
+ assert_equal(
+ "This text is not changed because we supplied an empty phrase",
+ highlight("This text is not changed because we supplied an empty phrase", nil)
+ )
+
+ assert_equal ' ', highlight(' ', 'blank text is returned verbatim')
+ end
+
+ def test_highlighter_with_regexp
+ assert_equal(
+ "This is a <strong class=\"highlight\">beautiful!</strong> morning",
+ highlight("This is a beautiful! morning", "beautiful!")
+ )
+
+ assert_equal(
+ "This is a <strong class=\"highlight\">beautiful! morning</strong>",
+ highlight("This is a beautiful! morning", "beautiful! morning")
+ )
+
+ assert_equal(
+ "This is a <strong class=\"highlight\">beautiful? morning</strong>",
+ highlight("This is a beautiful? morning", "beautiful? morning")
+ )
+ end
+
+ def test_highlighting_multiple_phrases_in_one_pass
+ assert_equal %(<em>wow</em> <em>em</em>), highlight('wow em', %w(wow em), '<em>\1</em>')
+ end
+
+ def test_excerpt
+ assert_equal("...is a beautiful morni...", excerpt("This is a beautiful morning", "beautiful", 5))
+ assert_equal("This is a...", excerpt("This is a beautiful morning", "this", 5))
+ assert_equal("...iful morning", excerpt("This is a beautiful morning", "morning", 5))
+ assert_nil excerpt("This is a beautiful morning", "day")
+ end
+
+ def test_excerpt_with_regex
+ assert_equal('...is a beautiful! morn...', excerpt('This is a beautiful! morning', 'beautiful', 5))
+ assert_equal('...is a beautiful? morn...', excerpt('This is a beautiful? morning', 'beautiful', 5))
+ end
+
+ def test_excerpt_with_utf8
+ with_kcode('u') do
+ assert_equal("...fficiency could not be h...", excerpt("That's why efficiency could not be helped", 'could', 8))
+ end
+ with_kcode('none') do
+ assert_equal("...\203ciency could not be h...", excerpt("That's why efficiency could not be helped", 'could', 8))
+ end
+ end
+
+ def test_word_wrap
+ assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", 15))
+ end
+
+ def test_word_wrap_with_extra_newlines
+ assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", 15))
+ end
+
+ def test_pluralization
+ assert_equal("1 count", pluralize(1, "count"))
+ assert_equal("2 counts", pluralize(2, "count"))
+ assert_equal("1 count", pluralize('1', "count"))
+ assert_equal("2 counts", pluralize('2', "count"))
+ assert_equal("1,066 counts", pluralize('1,066', "count"))
+ assert_equal("1.25 counts", pluralize('1.25', "count"))
+ assert_equal("2 counters", pluralize(2, "count", "counters"))
+ assert_equal("0 counters", pluralize(nil, "count", "counters"))
+ assert_equal("2 people", pluralize(2, "person"))
+ assert_equal("10 buffaloes", pluralize(10, "buffalo"))
+ end
+
+ uses_mocha("should_just_add_s_for_pluralize_without_inflector_loaded") do
+ def test_should_just_add_s_for_pluralize_without_inflector_loaded
+ Object.expects(:const_defined?).with("Inflector").times(4).returns(false)
+ assert_equal("1 count", pluralize(1, "count"))
+ assert_equal("2 persons", pluralize(2, "person"))
+ assert_equal("2 personss", pluralize("2", "persons"))
+ assert_equal("2 counts", pluralize(2, "count"))
+ assert_equal("10 buffalos", pluralize(10, "buffalo"))
+ end
+ end
+
+ def test_auto_link_parsing
+ urls = %w(http://www.rubyonrails.com
+ http://www.rubyonrails.com:80
+ http://www.rubyonrails.com/~minam
+ https://www.rubyonrails.com/~minam
+ http://www.rubyonrails.com/~minam/url%20with%20spaces
+ http://www.rubyonrails.com/foo.cgi?something=here
+ http://www.rubyonrails.com/foo.cgi?something=here&and=here
+ http://www.rubyonrails.com/contact;new
+ http://www.rubyonrails.com/contact;new%20with%20spaces
+ http://www.rubyonrails.com/contact;new?with=query&string=params
+ http://www.rubyonrails.com/~minam/contact;new?with=query&string=params
+ http://en.wikipedia.org/wiki/Wikipedia:Today%27s_featured_picture_%28animation%29/January_20%2C_2007
+ http://www.mail-archive.com/rails@lists.rubyonrails.org/
+ )
+
+ urls.each do |url|
+ assert_equal %(<a href="#{url}">#{url}</a>), auto_link(url)
+ end
+ end
+
+ def test_auto_linking
+ email_raw = 'david@loudthinking.com'
+ email_result = %{<a href="mailto:#{email_raw}">#{email_raw}</a>}
+ email2_raw = '+david@loudthinking.com'
+ email2_result = %{<a href="mailto:#{email2_raw}">#{email2_raw}</a>}
+ link_raw = 'http://www.rubyonrails.com'
+ link_result = %{<a href="#{link_raw}">#{link_raw}</a>}
+ link_result_with_options = %{<a href="#{link_raw}" target="_blank">#{link_raw}</a>}
+ link2_raw = 'www.rubyonrails.com'
+ link2_result = %{<a href="http://#{link2_raw}">#{link2_raw}</a>}
+ link3_raw = 'http://manuals.ruby-on-rails.com/read/chapter.need_a-period/103#page281'
+ link3_result = %{<a href="#{link3_raw}">#{link3_raw}</a>}
+ link4_raw = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor123'
+ link4_result = %{<a href="#{link4_raw}">#{link4_raw}</a>}
+ link5_raw = 'http://foo.example.com:3000/controller/action'
+ link5_result = %{<a href="#{link5_raw}">#{link5_raw}</a>}
+ link6_raw = 'http://foo.example.com:3000/controller/action+pack'
+ link6_result = %{<a href="#{link6_raw}">#{link6_raw}</a>}
+ link7_raw = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor-123'
+ link7_result = %{<a href="#{link7_raw}">#{link7_raw}</a>}
+ link8_raw = 'http://foo.example.com:3000/controller/action.html'
+ link8_result = %{<a href="#{link8_raw}">#{link8_raw}</a>}
+ link9_raw = 'http://business.timesonline.co.uk/article/0,,9065-2473189,00.html'
+ link9_result = %{<a href="#{link9_raw}">#{link9_raw}</a>}
+ link10_raw = 'http://www.mail-archive.com/ruby-talk@ruby-lang.org/'
+ link10_result = %{<a href="#{link10_raw}">#{link10_raw}</a>}
+
+ assert_equal %(hello #{email_result}), auto_link("hello #{email_raw}", :email_addresses)
+ assert_equal %(Go to #{link_result}), auto_link("Go to #{link_raw}", :urls)
+ assert_equal %(Go to #{link_raw}), auto_link("Go to #{link_raw}", :email_addresses)
+ assert_equal %(Go to #{link_result} and say hello to #{email_result}), auto_link("Go to #{link_raw} and say hello to #{email_raw}")
+ assert_equal %(<p>Link #{link_result}</p>), auto_link("<p>Link #{link_raw}</p>")
+ assert_equal %(<p>#{link_result} Link</p>), auto_link("<p>#{link_raw} Link</p>")
+ assert_equal %(<p>Link #{link_result_with_options}</p>), auto_link("<p>Link #{link_raw}</p>", :all, {:target => "_blank"})
+ assert_equal %(Go to #{link_result}.), auto_link(%(Go to #{link_raw}.))
+ assert_equal %(<p>Go to #{link_result}, then say hello to #{email_result}.</p>), auto_link(%(<p>Go to #{link_raw}, then say hello to #{email_raw}.</p>))
+ assert_equal %(Go to #{link2_result}), auto_link("Go to #{link2_raw}", :urls)
+ assert_equal %(Go to #{link2_raw}), auto_link("Go to #{link2_raw}", :email_addresses)
+ assert_equal %(<p>Link #{link2_result}</p>), auto_link("<p>Link #{link2_raw}</p>")
+ assert_equal %(<p>#{link2_result} Link</p>), auto_link("<p>#{link2_raw} Link</p>")
+ assert_equal %(Go to #{link2_result}.), auto_link(%(Go to #{link2_raw}.))
+ assert_equal %(<p>Say hello to #{email_result}, then go to #{link2_result}.</p>), auto_link(%(<p>Say hello to #{email_raw}, then go to #{link2_raw}.</p>))
+ assert_equal %(Go to #{link3_result}), auto_link("Go to #{link3_raw}", :urls)
+ assert_equal %(Go to #{link3_raw}), auto_link("Go to #{link3_raw}", :email_addresses)
+ assert_equal %(<p>Link #{link3_result}</p>), auto_link("<p>Link #{link3_raw}</p>")
+ assert_equal %(<p>#{link3_result} Link</p>), auto_link("<p>#{link3_raw} Link</p>")
+ assert_equal %(Go to #{link3_result}.), auto_link(%(Go to #{link3_raw}.))
+ assert_equal %(<p>Go to #{link3_result}. seriously, #{link3_result}? i think I'll say hello to #{email_result}. instead.</p>), auto_link(%(<p>Go to #{link3_raw}. seriously, #{link3_raw}? i think I'll say hello to #{email_raw}. instead.</p>))
+ assert_equal %(<p>Link #{link4_result}</p>), auto_link("<p>Link #{link4_raw}</p>")
+ assert_equal %(<p>#{link4_result} Link</p>), auto_link("<p>#{link4_raw} Link</p>")
+ assert_equal %(<p>#{link5_result} Link</p>), auto_link("<p>#{link5_raw} Link</p>")
+ assert_equal %(<p>#{link6_result} Link</p>), auto_link("<p>#{link6_raw} Link</p>")
+ assert_equal %(<p>#{link7_result} Link</p>), auto_link("<p>#{link7_raw} Link</p>")
+ assert_equal %(Go to #{link8_result}), auto_link("Go to #{link8_raw}", :urls)
+ assert_equal %(Go to #{link8_raw}), auto_link("Go to #{link8_raw}", :email_addresses)
+ assert_equal %(<p>Link #{link8_result}</p>), auto_link("<p>Link #{link8_raw}</p>")
+ assert_equal %(<p>#{link8_result} Link</p>), auto_link("<p>#{link8_raw} Link</p>")
+ assert_equal %(Go to #{link8_result}.), auto_link(%(Go to #{link8_raw}.))
+ assert_equal %(<p>Go to #{link8_result}. seriously, #{link8_result}? i think I'll say hello to #{email_result}. instead.</p>), auto_link(%(<p>Go to #{link8_raw}. seriously, #{link8_raw}? i think I'll say hello to #{email_raw}. instead.</p>))
+ assert_equal %(Go to #{link9_result}), auto_link("Go to #{link9_raw}", :urls)
+ assert_equal %(Go to #{link9_raw}), auto_link("Go to #{link9_raw}", :email_addresses)
+ assert_equal %(<p>Link #{link9_result}</p>), auto_link("<p>Link #{link9_raw}</p>")
+ assert_equal %(<p>#{link9_result} Link</p>), auto_link("<p>#{link9_raw} Link</p>")
+ assert_equal %(Go to #{link9_result}.), auto_link(%(Go to #{link9_raw}.))
+ assert_equal %(<p>Go to #{link9_result}. seriously, #{link9_result}? i think I'll say hello to #{email_result}. instead.</p>), auto_link(%(<p>Go to #{link9_raw}. seriously, #{link9_raw}? i think I'll say hello to #{email_raw}. instead.</p>))
+ assert_equal %(<p>#{link10_result} Link</p>), auto_link("<p>#{link10_raw} Link</p>")
+ assert_equal email2_result, auto_link(email2_raw)
+ assert_equal '', auto_link(nil)
+ assert_equal '', auto_link('')
+ end
+
+ def test_auto_link_at_eol
+ url1 = "http://api.rubyonrails.com/Foo.html"
+ url2 = "http://www.ruby-doc.org/core/Bar.html"
+
+ assert_equal %(<p><a href="#{url1}">#{url1}</a><br /><a href="#{url2}">#{url2}</a><br /></p>), auto_link("<p>#{url1}<br />#{url2}<br /></p>")
+ end
+
+ def test_auto_link_with_block
+ url = "http://api.rubyonrails.com/Foo.html"
+ email = "fantabulous@shiznadel.ic"
+
+ assert_equal %(<p><a href="#{url}">#{url[0...7]}...</a><br /><a href="mailto:#{email}">#{email[0...7]}...</a><br /></p>), auto_link("<p>#{url}<br />#{email}<br /></p>") { |url| truncate(url, 10) }
+ end
+
+ def test_cycle_class
+ value = Cycle.new("one", 2, "3")
+ assert_equal("one", value.to_s)
+ assert_equal("2", value.to_s)
+ assert_equal("3", value.to_s)
+ assert_equal("one", value.to_s)
+ value.reset
+ assert_equal("one", value.to_s)
+ assert_equal("2", value.to_s)
+ assert_equal("3", value.to_s)
+ end
+
+ def test_cycle_class_with_no_arguments
+ assert_raise(ArgumentError) { value = Cycle.new() }
+ end
+
+ def test_cycle
+ assert_equal("one", cycle("one", 2, "3"))
+ assert_equal("2", cycle("one", 2, "3"))
+ assert_equal("3", cycle("one", 2, "3"))
+ assert_equal("one", cycle("one", 2, "3"))
+ assert_equal("2", cycle("one", 2, "3"))
+ assert_equal("3", cycle("one", 2, "3"))
+ end
+
+ def test_cycle_with_no_arguments
+ assert_raise(ArgumentError) { value = cycle() }
+ end
+
+ def test_cycle_resets_with_new_values
+ assert_equal("even", cycle("even", "odd"))
+ assert_equal("odd", cycle("even", "odd"))
+ assert_equal("even", cycle("even", "odd"))
+ assert_equal("1", cycle(1, 2, 3))
+ assert_equal("2", cycle(1, 2, 3))
+ assert_equal("3", cycle(1, 2, 3))
+ assert_equal("1", cycle(1, 2, 3))
+ end
+
+ def test_named_cycles
+ assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("red", cycle("red", "blue", :name => "colors"))
+ assert_equal("2", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("blue", cycle("red", "blue", :name => "colors"))
+ assert_equal("3", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("red", cycle("red", "blue", :name => "colors"))
+ end
+
+ def test_default_named_cycle
+ assert_equal("1", cycle(1, 2, 3))
+ assert_equal("2", cycle(1, 2, 3, :name => "default"))
+ assert_equal("3", cycle(1, 2, 3))
+ end
+
+ def test_reset_cycle
+ assert_equal("1", cycle(1, 2, 3))
+ assert_equal("2", cycle(1, 2, 3))
+ reset_cycle
+ assert_equal("1", cycle(1, 2, 3))
+ end
+
+ def test_reset_unknown_cycle
+ reset_cycle("colors")
+ end
+
+ def test_recet_named_cycle
+ assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("red", cycle("red", "blue", :name => "colors"))
+ reset_cycle("numbers")
+ assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("blue", cycle("red", "blue", :name => "colors"))
+ assert_equal("2", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("red", cycle("red", "blue", :name => "colors"))
+ end
+
+ def test_cycle_no_instance_variable_clashes
+ @cycles = %w{Specialized Fuji Giant}
+ assert_equal("red", cycle("red", "blue"))
+ assert_equal("blue", cycle("red", "blue"))
+ assert_equal("red", cycle("red", "blue"))
+ assert_equal(%w{Specialized Fuji Giant}, @cycles)
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/template/url_helper_test.rb b/vendor/rails-2.0.2/actionpack/test/template/url_helper_test.rb
new file mode 100644
index 000000000..ee75965be
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/template/url_helper_test.rb
@@ -0,0 +1,536 @@
+require "#{File.dirname(__FILE__)}/../abstract_unit"
+
+RequestMock = Struct.new("Request", :request_uri, :protocol, :host_with_port, :env)
+
+class UrlHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::AssetTagHelper
+ include ActionView::Helpers::UrlHelper
+ include ActionView::Helpers::TagHelper
+
+ def setup
+ @controller = Class.new do
+ attr_accessor :url, :request
+ def url_for(options)
+ url
+ end
+ end
+ @controller = @controller.new
+ @controller.url = "http://www.example.com"
+ end
+
+ def test_url_for_escapes_urls
+ @controller.url = "http://www.example.com?a=b&c=d"
+ assert_equal "http://www.example.com?a=b&amp;c=d", url_for(:a => 'b', :c => 'd')
+ assert_equal "http://www.example.com?a=b&amp;c=d", url_for(:a => 'b', :c => 'd', :escape => true)
+ assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd', :escape => false)
+ end
+
+ def test_url_for_escapes_url_once
+ @controller.url = "http://www.example.com?a=b&amp;c=d"
+ assert_equal "http://www.example.com?a=b&amp;c=d", url_for("http://www.example.com?a=b&amp;c=d")
+ end
+
+ # todo: missing test cases
+ def test_button_to_with_straight_url
+ assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com")
+ end
+
+ def test_button_to_with_query
+ assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&amp;q2=v2\" class=\"button-to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&q2=v2")
+ end
+
+ def test_button_to_with_escaped_query
+ assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&amp;q2=v2\" class=\"button-to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&amp;q2=v2")
+ end
+
+ def test_button_to_with_query_and_no_name
+ assert_dom_equal "<form method=\"post\" action=\"http://www.example.com?q1=v1&amp;q2=v2\" class=\"button-to\"><div><input type=\"submit\" value=\"http://www.example.com?q1=v1&amp;q2=v2\" /></div></form>", button_to(nil, "http://www.example.com?q1=v1&q2=v2")
+ end
+
+ def test_button_to_with_javascript_confirm
+ assert_dom_equal(
+ "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input onclick=\"return confirm('Are you sure?');\" type=\"submit\" value=\"Hello\" /></div></form>",
+ button_to("Hello", "http://www.example.com", :confirm => "Are you sure?")
+ )
+ end
+
+ def test_button_to_enabled_disabled
+ assert_dom_equal(
+ "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>",
+ button_to("Hello", "http://www.example.com", :disabled => false)
+ )
+ assert_dom_equal(
+ "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input disabled=\"disabled\" type=\"submit\" value=\"Hello\" /></div></form>",
+ button_to("Hello", "http://www.example.com", :disabled => true)
+ )
+ end
+
+ def test_button_to_with_method_delete
+ assert_dom_equal(
+ "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input type=\"hidden\" name=\"_method\" value=\"delete\" /><input type=\"submit\" value=\"Hello\" /></div></form>",
+ button_to("Hello", "http://www.example.com", :method => :delete)
+ )
+ end
+
+ def test_button_to_with_method_get
+ assert_dom_equal(
+ "<form method=\"get\" action=\"http://www.example.com\" class=\"button-to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>",
+ button_to("Hello", "http://www.example.com", :method => :get)
+ )
+ end
+
+ def test_link_tag_with_straight_url
+ assert_dom_equal "<a href=\"http://www.example.com\">Hello</a>", link_to("Hello", "http://www.example.com")
+ end
+
+ def test_link_tag_without_host_option
+ ActionController::Base.class_eval { attr_accessor :url }
+ url = {:controller => 'weblog', :action => 'show'}
+ @controller = ActionController::Base.new
+ @controller.request = ActionController::TestRequest.new
+ @controller.url = ActionController::UrlRewriter.new(@controller.request, url)
+ assert_dom_equal(%q{<a href="/weblog/show">Test Link</a>}, link_to('Test Link', url))
+ end
+
+ def test_link_tag_with_host_option
+ ActionController::Base.class_eval { attr_accessor :url }
+ url = {:controller => 'weblog', :action => 'show', :host => 'www.example.com'}
+ @controller = ActionController::Base.new
+ @controller.request = ActionController::TestRequest.new
+ @controller.url = ActionController::UrlRewriter.new(@controller.request, url)
+ assert_dom_equal(%q{<a href="http://www.example.com/weblog/show">Test Link</a>}, link_to('Test Link', url))
+ end
+
+ def test_link_tag_with_query
+ assert_dom_equal "<a href=\"http://www.example.com?q1=v1&amp;q2=v2\">Hello</a>", link_to("Hello", "http://www.example.com?q1=v1&amp;q2=v2")
+ end
+
+ def test_link_tag_with_query_and_no_name
+ assert_dom_equal "<a href=\"http://www.example.com?q1=v1&amp;q2=v2\">http://www.example.com?q1=v1&amp;q2=v2</a>", link_to(nil, "http://www.example.com?q1=v1&amp;q2=v2")
+ end
+
+ def test_link_tag_with_back
+ @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {'HTTP_REFERER' => 'http://www.example.com/referer'})
+ assert_dom_equal "<a href=\"http://www.example.com/referer\">go back</a>", link_to('go back', :back)
+ end
+
+ def test_link_tag_with_back_and_no_referer
+ @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {})
+ assert_dom_equal "<a href=\"javascript:history.back()\">go back</a>", link_to('go back', :back)
+ end
+
+ def test_link_tag_with_back
+ @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {'HTTP_REFERER' => 'http://www.example.com/referer'})
+ assert_dom_equal "<a href=\"http://www.example.com/referer\">go back</a>", link_to('go back', :back)
+ end
+
+ def test_link_tag_with_back_and_no_referer
+ @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {})
+ assert_dom_equal "<a href=\"javascript:history.back()\">go back</a>", link_to('go back', :back)
+ end
+
+ def test_link_tag_with_img
+ assert_dom_equal "<a href=\"http://www.example.com\"><img src='/favicon.jpg' /></a>", link_to("<img src='/favicon.jpg' />", "http://www.example.com")
+ end
+
+ def test_link_with_nil_html_options
+ assert_dom_equal "<a href=\"http://www.example.com\">Hello</a>", link_to("Hello", {:action => 'myaction'}, nil)
+ end
+
+ def test_link_tag_with_custom_onclick
+ assert_dom_equal "<a href=\"http://www.example.com\" onclick=\"alert('yay!')\">Hello</a>", link_to("Hello", "http://www.example.com", :onclick => "alert('yay!')")
+ end
+
+ def test_link_tag_with_javascript_confirm
+ assert_dom_equal(
+ "<a href=\"http://www.example.com\" onclick=\"return confirm('Are you sure?');\">Hello</a>",
+ link_to("Hello", "http://www.example.com", :confirm => "Are you sure?")
+ )
+ assert_dom_equal(
+ "<a href=\"http://www.example.com\" onclick=\"return confirm('You can\\'t possibly be sure, can you?');\">Hello</a>",
+ link_to("Hello", "http://www.example.com", :confirm => "You can't possibly be sure, can you?")
+ )
+ assert_dom_equal(
+ "<a href=\"http://www.example.com\" onclick=\"return confirm('You can\\'t possibly be sure,\\n can you?');\">Hello</a>",
+ link_to("Hello", "http://www.example.com", :confirm => "You can't possibly be sure,\n can you?")
+ )
+ end
+
+ def test_link_tag_with_popup
+ assert_dom_equal(
+ "<a href=\"http://www.example.com\" onclick=\"window.open(this.href);return false;\">Hello</a>",
+ link_to("Hello", "http://www.example.com", :popup => true)
+ )
+ assert_dom_equal(
+ "<a href=\"http://www.example.com\" onclick=\"window.open(this.href);return false;\">Hello</a>",
+ link_to("Hello", "http://www.example.com", :popup => 'true')
+ )
+ assert_dom_equal(
+ "<a href=\"http://www.example.com\" onclick=\"window.open(this.href,'window_name','width=300,height=300');return false;\">Hello</a>",
+ link_to("Hello", "http://www.example.com", :popup => ['window_name', 'width=300,height=300'])
+ )
+ end
+
+ def test_link_tag_with_popup_and_javascript_confirm
+ assert_dom_equal(
+ "<a href=\"http://www.example.com\" onclick=\"if (confirm('Fo\\' sho\\'?')) { window.open(this.href); };return false;\">Hello</a>",
+ link_to("Hello", "http://www.example.com", { :popup => true, :confirm => "Fo' sho'?" })
+ )
+ assert_dom_equal(
+ "<a href=\"http://www.example.com\" onclick=\"if (confirm('Are you serious?')) { window.open(this.href,'window_name','width=300,height=300'); };return false;\">Hello</a>",
+ link_to("Hello", "http://www.example.com", { :popup => ['window_name', 'width=300,height=300'], :confirm => "Are you serious?" })
+ )
+ end
+
+ def test_link_tag_using_post_javascript
+ assert_dom_equal(
+ "<a href='http://www.example.com' onclick=\"var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;f.submit();return false;\">Hello</a>",
+ link_to("Hello", "http://www.example.com", :method => :post)
+ )
+ end
+
+ def test_link_tag_using_delete_javascript
+ assert_dom_equal(
+ "<a href='http://www.example.com' onclick=\"var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); m.setAttribute('value', 'delete'); f.appendChild(m);f.submit();return false;\">Destroy</a>",
+ link_to("Destroy", "http://www.example.com", :method => :delete)
+ )
+ end
+
+ def test_link_tag_using_delete_javascript_and_href
+ assert_dom_equal(
+ "<a href='\#' onclick=\"var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = 'http://www.example.com';var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); m.setAttribute('value', 'delete'); f.appendChild(m);f.submit();return false;\">Destroy</a>",
+ link_to("Destroy", "http://www.example.com", :method => :delete, :href => '#')
+ )
+ end
+
+ def test_link_tag_using_post_javascript_and_confirm
+ assert_dom_equal(
+ "<a href=\"http://www.example.com\" onclick=\"if (confirm('Are you serious?')) { var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;f.submit(); };return false;\">Hello</a>",
+ link_to("Hello", "http://www.example.com", :method => :post, :confirm => "Are you serious?")
+ )
+ end
+
+ def test_link_tag_using_post_javascript_and_popup
+ assert_raises(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :method => :post, :confirm => "Are you serious?") }
+ end
+
+ def test_link_to_unless
+ assert_equal "Showing", link_to_unless(true, "Showing", :action => "show", :controller => "weblog")
+ assert_dom_equal "<a href=\"http://www.example.com\">Listing</a>", link_to_unless(false, "Listing", :action => "list", :controller => "weblog")
+ assert_equal "Showing", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1)
+ assert_equal "<strong>Showing</strong>", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1) { |name, options, html_options|
+ "<strong>#{name}</strong>"
+ }
+ assert_equal "<strong>Showing</strong>", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1) { |name|
+ "<strong>#{name}</strong>"
+ }
+ assert_equal "test", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1) {
+ "test"
+ }
+ end
+
+ def test_link_to_if
+ assert_equal "Showing", link_to_if(false, "Showing", :action => "show", :controller => "weblog")
+ assert_dom_equal "<a href=\"http://www.example.com\">Listing</a>", link_to_if(true, "Listing", :action => "list", :controller => "weblog")
+ assert_equal "Showing", link_to_if(false, "Showing", :action => "show", :controller => "weblog", :id => 1)
+ end
+
+ def test_link_unless_current
+ @controller.request = RequestMock.new("http://www.example.com/weblog/show")
+ @controller.url = "http://www.example.com/weblog/show"
+ assert_equal "Showing", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" })
+ assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show")
+
+ @controller.request = RequestMock.new("http://www.example.com/weblog/show")
+ @controller.url = "http://www.example.com/weblog/list"
+ assert_equal "<a href=\"http://www.example.com/weblog/list\">Listing</a>",
+ link_to_unless_current("Listing", :action => "list", :controller => "weblog")
+ assert_equal "<a href=\"http://www.example.com/weblog/list\">Listing</a>",
+ link_to_unless_current("Listing", "http://www.example.com/weblog/list")
+ end
+
+ def test_mail_to
+ assert_dom_equal "<a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>", mail_to("david@loudthinking.com")
+ assert_dom_equal "<a href=\"mailto:david@loudthinking.com\">David Heinemeier Hansson</a>", mail_to("david@loudthinking.com", "David Heinemeier Hansson")
+ assert_dom_equal(
+ "<a class=\"admin\" href=\"mailto:david@loudthinking.com\">David Heinemeier Hansson</a>",
+ mail_to("david@loudthinking.com", "David Heinemeier Hansson", "class" => "admin")
+ )
+ assert_equal mail_to("david@loudthinking.com", "David Heinemeier Hansson", "class" => "admin"),
+ mail_to("david@loudthinking.com", "David Heinemeier Hansson", :class => "admin")
+ end
+
+ def test_mail_to_with_javascript
+ assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript")
+ end
+
+ def test_mail_with_options
+ assert_dom_equal(
+ %(<a href="mailto:me@example.com?cc=ccaddress%40example.com&amp;bcc=bccaddress%40example.com&amp;body=This%20is%20the%20body%20of%20the%20message.&amp;subject=This%20is%20an%20example%20email">My email</a>),
+ mail_to("me@example.com", "My email", :cc => "ccaddress@example.com", :bcc => "bccaddress@example.com", :subject => "This is an example email", :body => "This is the body of the message.")
+ )
+ end
+
+ def test_mail_to_with_img
+ assert_dom_equal %(<a href="mailto:feedback@example.com"><img src="/feedback.png" /></a>), mail_to('feedback@example.com', '<img src="/feedback.png" />')
+ end
+
+ def test_mail_to_with_hex
+ assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex")
+ assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#64;&#100;&#111;&#109;&#97;&#105;&#110;&#46;&#99;&#111;&#109;</a>", mail_to("me@domain.com", nil, :encode => "hex")
+ end
+
+ def test_mail_to_with_replace_options
+ assert_dom_equal "<a href=\"mailto:wolfgang@stufenlos.net\">wolfgang(at)stufenlos(dot)net</a>", mail_to("wolfgang@stufenlos.net", nil, :replace_at => "(at)", :replace_dot => "(dot)")
+ assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#46;&#99;&#111;&#109;</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)")
+ assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex", :replace_at => "(at)")
+ assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#40;&#100;&#111;&#116;&#41;&#99;&#111;&#109;</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)")
+ assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
+ end
+
+ def protect_against_forgery?
+ false
+ end
+end
+
+class UrlHelperWithControllerTest < Test::Unit::TestCase
+ class UrlHelperController < ActionController::Base
+ self.view_paths = [ "#{File.dirname(__FILE__)}/../fixtures/" ]
+
+ def self.controller_path; 'url_helper_with_controller' end
+
+ def show_url_for
+ render :inline => "<%= url_for :controller => 'url_helper_with_controller', :action => 'show_url_for' %>"
+ end
+
+ def show_named_route
+ render :inline => "<%= show_named_route_#{params[:kind]} %>"
+ end
+
+ def rescue_action(e) raise e end
+ end
+
+ include ActionView::Helpers::UrlHelper
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = UrlHelperController.new
+ end
+
+ def test_url_for_shows_only_path
+ get :show_url_for
+ assert_equal '/url_helper_with_controller/show_url_for', @response.body
+ end
+
+ def test_named_route_shows_host_and_path
+ with_url_helper_routing do
+ get :show_named_route, :kind => 'url'
+ assert_equal 'http://test.host/url_helper_with_controller/show_named_route', @response.body
+ end
+ end
+
+ def test_named_route_path_shows_only_path
+ with_url_helper_routing do
+ get :show_named_route, :kind => 'path'
+ assert_equal '/url_helper_with_controller/show_named_route', @response.body
+ end
+ end
+
+ protected
+ def with_url_helper_routing
+ with_routing do |set|
+ set.draw do |map|
+ map.show_named_route 'url_helper_with_controller/show_named_route', :controller => 'url_helper_with_controller', :action => 'show_named_route'
+ end
+ yield
+ end
+ end
+end
+
+class LinkToUnlessCurrentWithControllerTest < Test::Unit::TestCase
+ class TasksController < ActionController::Base
+ self.view_paths = ["#{File.dirname(__FILE__)}/../fixtures/"]
+
+ def self.controller_path; 'tasks' end
+
+ def index
+ render_default
+ end
+
+ def show
+ render_default
+ end
+
+ def rescue_action(e) raise e end
+
+ protected
+ def render_default
+ render :inline =>
+ "<%= link_to_unless_current(\"tasks\", tasks_path) %>\n" +
+ "<%= link_to_unless_current(\"tasks\", tasks_url) %>"
+ end
+ end
+
+ include ActionView::Helpers::UrlHelper
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = TasksController.new
+ end
+
+ def test_link_to_unless_current_to_current
+ with_restful_routing do
+ get :index
+ assert_equal "tasks\ntasks", @response.body
+ end
+ end
+
+ def test_link_to_unless_current_shows_link
+ with_restful_routing do
+ get :show, :id => 1
+ assert_equal "<a href=\"/tasks\">tasks</a>\n" +
+ "<a href=\"#{@request.protocol}#{@request.host_with_port}/tasks\">tasks</a>",
+ @response.body
+ end
+ end
+
+ protected
+ def with_restful_routing
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :tasks
+ end
+ yield
+ end
+ end
+end
+
+
+class Workshop
+ attr_accessor :id, :new_record
+
+ def initialize(id, new_record)
+ @id, @new_record = id, new_record
+ end
+
+ def new_record?
+ @new_record
+ end
+
+ def to_s
+ id.to_s
+ end
+end
+
+class Session
+ attr_accessor :id, :workshop_id, :new_record
+
+ def initialize(id, new_record)
+ @id, @new_record = id, new_record
+ end
+
+ def new_record?
+ @new_record
+ end
+
+ def to_s
+ id.to_s
+ end
+end
+
+class PolymorphicControllerTest < Test::Unit::TestCase
+ class WorkshopsController < ActionController::Base
+ self.view_paths = ["#{File.dirname(__FILE__)}/../fixtures/"]
+
+ def self.controller_path; 'workshops' end
+
+ def index
+ @workshop = Workshop.new(1, true)
+ render :inline => "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>"
+ end
+
+ def show
+ @workshop = Workshop.new(params[:id], false)
+ render :inline => "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>"
+ end
+
+ def rescue_action(e) raise e end
+ end
+
+ class SessionsController < ActionController::Base
+ self.view_paths = ["#{File.dirname(__FILE__)}/../fixtures/"]
+
+ def self.controller_path; 'sessions' end
+
+ def index
+ @workshop = Workshop.new(params[:workshop_id], false)
+ @session = Session.new(1, true)
+ render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>"
+ end
+
+ def show
+ @workshop = Workshop.new(params[:workshop_id], false)
+ @session = Session.new(params[:id], false)
+ render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>"
+ end
+
+ def rescue_action(e) raise e end
+ end
+
+ include ActionView::Helpers::UrlHelper
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_new_resource
+ @controller = WorkshopsController.new
+
+ with_restful_routing do
+ get :index
+ assert_equal "/workshops\n<a href=\"/workshops\">Workshop</a>", @response.body
+ end
+ end
+
+ def test_existing_resource
+ @controller = WorkshopsController.new
+
+ with_restful_routing do
+ get :show, :id => 1
+ assert_equal "/workshops/1\n<a href=\"/workshops/1\">Workshop</a>", @response.body
+ end
+ end
+
+ def test_new_nested_resource
+ @controller = SessionsController.new
+
+ with_restful_routing do
+ get :index, :workshop_id => 1
+ assert_equal "/workshops/1/sessions\n<a href=\"/workshops/1/sessions\">Session</a>", @response.body
+ end
+ end
+
+ def test_existing_nested_resource
+ @controller = SessionsController.new
+
+ with_restful_routing do
+ get :show, :workshop_id => 1, :id => 1
+ assert_equal "/workshops/1/sessions/1\n<a href=\"/workshops/1/sessions/1\">Session</a>", @response.body
+ end
+ end
+
+ protected
+ def with_restful_routing
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :workshops do |w|
+ w.resources :sessions
+ end
+ end
+ yield
+ end
+ end
+end
diff --git a/vendor/rails-2.0.2/actionpack/test/testing_sandbox.rb b/vendor/rails-2.0.2/actionpack/test/testing_sandbox.rb
new file mode 100644
index 000000000..b3b8b0f4d
--- /dev/null
+++ b/vendor/rails-2.0.2/actionpack/test/testing_sandbox.rb
@@ -0,0 +1,11 @@
+module TestingSandbox
+ # Temporarily replaces KCODE for the block
+ def with_kcode(kcode)
+ old_kcode, $KCODE = $KCODE, kcode
+ begin
+ yield
+ ensure
+ $KCODE = old_kcode
+ end
+ end
+end