aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPratik Naik <pratiknaik@gmail.com>2009-08-31 22:11:50 +0100
committerPratik Naik <pratiknaik@gmail.com>2009-08-31 22:11:50 +0100
commitbae00bb1cc392e1cf408369809b9cf85468bef42 (patch)
tree17103af6eeb5de96c72beda1debce28950cc7fea
parent93c76b2fb08668bc4b8364cc8051476e6d1d15ba (diff)
parentffd2cf167040b60c26d97c01598560c87bd4b2d3 (diff)
downloadrails-bae00bb1cc392e1cf408369809b9cf85468bef42.tar.gz
rails-bae00bb1cc392e1cf408369809b9cf85468bef42.tar.bz2
rails-bae00bb1cc392e1cf408369809b9cf85468bef42.zip
Merge commit 'mainstream/master'
-rw-r--r--.gitmodules0
-rw-r--r--Rakefile11
-rw-r--r--actionpack/CHANGELOG2
-rw-r--r--actionpack/Rakefile2
-rw-r--r--actionpack/examples/minimal.rb118
-rw-r--r--actionpack/examples/very_simple.rb50
-rw-r--r--actionpack/examples/views/_collection.erb1
-rw-r--r--actionpack/examples/views/_hello.erb1
-rw-r--r--actionpack/examples/views/_many_partials.erb10
-rw-r--r--actionpack/examples/views/_partial.erb10
-rw-r--r--actionpack/examples/views/layouts/alt.html.erb1
-rw-r--r--actionpack/examples/views/layouts/kaigi.html.erb1
-rw-r--r--actionpack/examples/views/template.html.erb1
-rw-r--r--actionpack/lib/abstract_controller/base.rb1
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb77
-rw-r--r--actionpack/lib/abstract_controller/logger.rb4
-rw-r--r--actionpack/lib/abstract_controller/rendering_controller.rb17
-rw-r--r--actionpack/lib/action_controller.rb6
-rw-r--r--actionpack/lib/action_controller/metal.rb18
-rw-r--r--actionpack/lib/action_controller/metal/compatibility.rb18
-rw-r--r--actionpack/lib/action_controller/metal/hide_actions.rb14
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb20
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb52
-rw-r--r--actionpack/lib/action_controller/metal/rack_convenience.rb8
-rw-r--r--actionpack/lib/action_controller/metal/redirector.rb3
-rw-r--r--actionpack/lib/action_controller/metal/rendering_controller.rb48
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb43
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb3
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb10
-rw-r--r--actionpack/lib/action_controller/middleware.rb38
-rw-r--r--actionpack/lib/action_controller/routing/generation/url_rewriter.rb2
-rw-r--r--actionpack/lib/action_controller/routing/resources.rb30
-rw-r--r--actionpack/lib/action_controller/routing/route_set.rb25
-rw-r--r--actionpack/lib/action_controller/testing/integration.rb3
-rw-r--r--actionpack/lib/action_controller/testing/process.rb10
-rw-r--r--actionpack/lib/action_controller/testing/test_case.rb1
-rw-r--r--actionpack/lib/action_dispatch.rb10
-rwxr-xr-xactionpack/lib/action_dispatch/http/request.rb56
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb145
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb18
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb6
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb299
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack.rb90
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/adapter/camping.rb22
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/abstract/handler.rb37
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/abstract/request.rb37
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/basic.rb58
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/md5.rb124
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/nonce.rb51
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/params.rb55
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/request.rb40
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/openid.rb480
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/builder.rb63
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/cascade.rb36
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/chunked.rb49
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/commonlogger.rb61
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/conditionalget.rb47
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/content_length.rb29
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/content_type.rb23
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/deflater.rb96
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/directory.rb153
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/file.rb88
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler.rb69
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/cgi.rb61
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/evented_mongrel.rb8
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/fastcgi.rb88
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/lsws.rb55
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/mongrel.rb84
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/scgi.rb59
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/swiftiplied_mongrel.rb8
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/thin.rb18
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/webrick.rb67
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/head.rb19
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/lint.rb537
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/lobster.rb65
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/lock.rb16
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/methodoverride.rb27
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/mime.rb204
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/mock.rb184
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/recursive.rb57
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/reloader.rb106
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/request.rb254
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/response.rb183
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/rewindable_input.rb98
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/abstract/id.rb142
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/cookie.rb91
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/memcache.rb109
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/pool.rb100
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/showexceptions.rb349
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/showstatus.rb106
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/static.rb38
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/urlmap.rb55
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/utils.rb516
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-test/rack/mock_session.rb50
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-test/rack/test.rb239
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-test/rack/test/cookie_jar.rb169
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-test/rack/test/methods.rb45
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-test/rack/test/mock_digest_request.rb27
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-test/rack/test/uploaded_file.rb36
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-test/rack/test/utils.rb75
-rw-r--r--actionpack/lib/action_view/base.rb29
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb1
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb70
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb5
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb2
-rw-r--r--actionpack/lib/action_view/locale/en.yml4
-rw-r--r--actionpack/lib/action_view/paths.rb3
-rw-r--r--actionpack/lib/action_view/render/partials.rb159
-rw-r--r--actionpack/lib/action_view/render/rendering.rb79
-rw-r--r--actionpack/lib/action_view/template/handler.rb6
-rw-r--r--actionpack/lib/action_view/template/resolver.rb38
-rw-r--r--actionpack/lib/action_view/template/template.rb28
-rw-r--r--actionpack/lib/action_view/test_case.rb21
-rw-r--r--actionpack/test/abstract_controller/abstract_controller_test.rb53
-rw-r--r--actionpack/test/abstract_controller/callbacks_test.rb94
-rw-r--r--actionpack/test/abstract_controller/helper_test.rb5
-rw-r--r--actionpack/test/abstract_controller/layouts_test.rb55
-rw-r--r--actionpack/test/abstract_unit.rb17
-rw-r--r--actionpack/test/activerecord/active_record_store_test.rb25
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb12
-rw-r--r--actionpack/test/controller/base_test.rb34
-rw-r--r--actionpack/test/controller/caching_test.rb6
-rw-r--r--actionpack/test/controller/content_type_test.rb2
-rw-r--r--actionpack/test/controller/mime_responds_test.rb86
-rw-r--r--actionpack/test/controller/redirect_test.rb24
-rw-r--r--actionpack/test/controller/render_js_test.rb6
-rw-r--r--actionpack/test/controller/render_test.rb27
-rw-r--r--actionpack/test/controller/render_xml_test.rb14
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb6
-rw-r--r--actionpack/test/controller/resources_test.rb44
-rw-r--r--actionpack/test/controller/routing_test.rb1082
-rw-r--r--actionpack/test/controller/test_test.rb7
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb167
-rw-r--r--actionpack/test/controller/verification_test.rb5
-rw-r--r--actionpack/test/controller/webservice_test.rb130
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb2
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb29
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb1
-rw-r--r--actionpack/test/dispatch/request/query_string_parsing_test.rb1
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb1
-rw-r--r--actionpack/test/dispatch/request/xml_params_parsing_test.rb18
-rw-r--r--actionpack/test/dispatch/request_test.rb65
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb19
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb16
-rw-r--r--actionpack/test/fixtures/content_type/render_default_content_types_for_respond_to.xml.erb (renamed from actionpack/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml)0
-rw-r--r--actionpack/test/fixtures/test/_customer_with_var.erb2
-rw-r--r--actionpack/test/fixtures/test/_hash_object.erb2
-rw-r--r--actionpack/test/fixtures/test/using_layout_around_block_with_args.html.erb1
-rw-r--r--actionpack/test/lib/controller/fake_controllers.rb9
-rw-r--r--actionpack/test/lib/controller/fake_models.rb12
-rw-r--r--actionpack/test/lib/fixture_template.rb20
-rw-r--r--actionpack/test/new_base/content_type_test.rb4
-rw-r--r--actionpack/test/new_base/metal_test.rb44
-rw-r--r--actionpack/test/new_base/render_layout_test.rb2
-rw-r--r--actionpack/test/new_base/render_rjs_test.rb7
-rw-r--r--actionpack/test/new_base/render_template_test.rb2
-rw-r--r--actionpack/test/new_base/test_helper.rb24
-rw-r--r--actionpack/test/template/form_helper_test.rb16
-rw-r--r--actionpack/test/template/form_options_helper_i18n_test.rb27
-rw-r--r--actionpack/test/template/form_options_helper_test.rb34
-rw-r--r--actionpack/test/template/javascript_helper_test.rb4
-rw-r--r--actionpack/test/template/prototype_helper_test.rb4
-rw-r--r--actionpack/test/template/render_test.rb38
-rw-r--r--actionpack/test/template/text_helper_test.rb30
-rw-r--r--actionpack/test/template/url_helper_test.rb8
-rw-r--r--actionpack/test/tmp/.gitignore0
-rw-r--r--activemodel/CHANGELOG11
-rw-r--r--activemodel/CHANGES2
-rw-r--r--activemodel/lib/active_model.rb4
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb43
-rw-r--r--activemodel/lib/active_model/dirty.rb126
-rw-r--r--activemodel/lib/active_model/lint.rb96
-rw-r--r--activemodel/lib/active_model/serialization.rb30
-rw-r--r--activemodel/lib/active_model/serializer.rb60
-rw-r--r--activemodel/lib/active_model/serializers/json.rb18
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb76
-rw-r--r--activemodel/lib/active_model/validations/format.rb41
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb21
-rw-r--r--activemodel/lib/active_model/validations/with.rb64
-rw-r--r--activemodel/test/cases/lint_test.rb50
-rw-r--r--activemodel/test/cases/tests_database.rb13
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb29
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb18
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb116
-rw-r--r--activerecord/CHANGELOG8
-rw-r--r--activerecord/Rakefile40
-rw-r--r--activerecord/examples/.gitignore1
-rwxr-xr-xactiverecord/examples/performance.rb162
-rw-r--r--activerecord/lib/active_record.rb1
-rwxr-xr-xactiverecord/lib/active_record/associations.rb31
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb16
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb33
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/through_association_scope.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb147
-rwxr-xr-xactiverecord/lib/active_record/base.rb28
-rw-r--r--activerecord/lib/active_record/calculations.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb4
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb45
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb44
-rw-r--r--activerecord/lib/active_record/fixtures.rb16
-rw-r--r--activerecord/lib/active_record/locale/en.yml1
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/serialization.rb80
-rw-r--r--activerecord/lib/active_record/serializers/json_serializer.rb14
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb19
-rw-r--r--activerecord/lib/active_record/validations.rb3
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
-rw-r--r--activerecord/lib/active_record/validator.rb68
-rw-r--r--activerecord/test/cases/adapter_test.rb12
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb18
-rw-r--r--activerecord/test/cases/associations/habtm_join_table_test.rb56
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb58
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb55
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb10
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb8
-rwxr-xr-xactiverecord/test/cases/base_test.rb65
-rw-r--r--activerecord/test/cases/column_definition_test.rb34
-rw-r--r--activerecord/test/cases/dirty_test.rb78
-rw-r--r--activerecord/test/cases/i18n_test.rb5
-rw-r--r--activerecord/test/cases/migration_test.rb50
-rw-r--r--activerecord/test/cases/named_scope_test.rb6
-rw-r--r--activerecord/test/cases/pk_test.rb18
-rw-r--r--activerecord/test/cases/reflection_test.rb4
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb12
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb188
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb19
-rw-r--r--activerecord/test/connections/native_sqlite/connection.rb25
-rw-r--r--activerecord/test/fixtures/posts.yml3
-rw-r--r--activerecord/test/models/author.rb1
-rw-r--r--activerecord/test/models/comment.rb6
-rw-r--r--activerecord/test/models/company.rb2
-rw-r--r--activerecord/test/models/contract.rb4
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb15
-rw-r--r--activerecord/test/schema/schema.rb4
-rw-r--r--activeresource/CHANGELOG6
-rw-r--r--activeresource/lib/active_resource/base.rb136
-rw-r--r--activeresource/lib/active_resource/connection.rb42
-rw-r--r--activeresource/lib/active_resource/exceptions.rb11
-rw-r--r--activeresource/lib/active_resource/validations.rb66
-rw-r--r--activeresource/test/base_errors_test.rb48
-rw-r--r--activeresource/test/cases/authorization_test.rb (renamed from activeresource/test/authorization_test.rb)0
-rw-r--r--activeresource/test/cases/base/custom_methods_test.rb (renamed from activeresource/test/base/custom_methods_test.rb)0
-rw-r--r--activeresource/test/cases/base/equality_test.rb (renamed from activeresource/test/base/equality_test.rb)0
-rw-r--r--activeresource/test/cases/base/load_test.rb (renamed from activeresource/test/base/load_test.rb)17
-rw-r--r--activeresource/test/cases/base_errors_test.rb83
-rw-r--r--activeresource/test/cases/base_test.rb (renamed from activeresource/test/base_test.rb)165
-rw-r--r--activeresource/test/cases/finder_test.rb233
-rw-r--r--activeresource/test/cases/format_test.rb (renamed from activeresource/test/format_test.rb)0
-rw-r--r--activeresource/test/cases/observing_test.rb (renamed from activeresource/test/observing_test.rb)0
-rw-r--r--activeresource/test/cases/validations_test.rb55
-rw-r--r--activeresource/test/connection_test.rb21
-rw-r--r--activeresource/test/fixtures/project.rb25
-rw-r--r--activesupport/lib/active_support/cache.rb22
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute_accessors.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/class/delegating_attributes.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/deep_merge.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/regexp.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb3
-rw-r--r--activesupport/lib/active_support/inflector.rb3
-rw-r--r--activesupport/lib/active_support/json/backends/yaml.rb5
-rw-r--r--activesupport/lib/active_support/memoizable.rb2
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb17
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb7
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb6
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb2
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb19
-rw-r--r--activesupport/test/core_ext/module_test.rb13
-rw-r--r--activesupport/test/core_ext/regexp_ext_test.rb18
-rw-r--r--activesupport/test/dependencies_test.rb77
-rw-r--r--activesupport/test/flush_cache_on_private_memoization_test.rb44
-rw-r--r--activesupport/test/inflector_test.rb10
-rw-r--r--activesupport/test/isolation_test.rb8
-rw-r--r--activesupport/test/json/decoding_test.rb8
-rw-r--r--activesupport/test/multibyte_chars_test.rb15
-rwxr-xr-xci/ci_build.rb12
-rw-r--r--ci/ci_setup_notes.txt4
-rw-r--r--ci/cruise_config.rb11
-rw-r--r--ci/geminstaller.yml4
-rw-r--r--railties/CHANGELOG2
-rw-r--r--railties/lib/commands/dbconsole.rb12
-rw-r--r--railties/lib/generators.rb93
-rw-r--r--railties/lib/generators/actions.rb12
-rw-r--r--railties/lib/generators/active_record.rb1
-rw-r--r--railties/lib/generators/base.rb52
-rw-r--r--railties/lib/generators/erb/scaffold/scaffold_generator.rb5
-rw-r--r--railties/lib/generators/erb/scaffold/templates/_form.html.erb17
-rw-r--r--railties/lib/generators/erb/scaffold/templates/edit.html.erb16
-rw-r--r--railties/lib/generators/erb/scaffold/templates/layout.html.erb2
-rw-r--r--railties/lib/generators/erb/scaffold/templates/new.html.erb16
-rw-r--r--railties/lib/generators/named_base.rb62
-rw-r--r--railties/lib/generators/rails/app/app_generator.rb2
-rw-r--r--railties/lib/generators/rails/app/templates/config/databases/sqlite2.yml19
-rw-r--r--railties/lib/generators/rails/generator/generator_generator.rb2
-rw-r--r--railties/lib/generators/rails/resource/resource_generator.rb16
-rw-r--r--railties/lib/generators/rails/scaffold/scaffold_generator.rb3
-rw-r--r--railties/lib/generators/rails/scaffold_controller/scaffold_controller_generator.rb5
-rw-r--r--railties/lib/generators/rails/stylesheets/templates/scaffold.css8
-rw-r--r--railties/lib/generators/resource_helpers.rb74
-rw-r--r--railties/lib/generators/test_unit/scaffold/scaffold_generator.rb3
-rw-r--r--railties/lib/rails/plugin.rb12
-rw-r--r--railties/lib/rails/plugin/loader.rb9
-rw-r--r--railties/lib/tasks/databases.rake8
-rw-r--r--railties/lib/vendor/thor-0.11.6/CHANGELOG.rdoc (renamed from railties/lib/vendor/thor-0.11.5/CHANGELOG.rdoc)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/LICENSE (renamed from railties/lib/vendor/thor-0.11.5/LICENSE)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/README.rdoc (renamed from railties/lib/vendor/thor-0.11.5/README.rdoc)0
-rwxr-xr-xrailties/lib/vendor/thor-0.11.6/bin/rake2thor (renamed from railties/lib/vendor/thor-0.11.5/bin/rake2thor)0
-rwxr-xr-xrailties/lib/vendor/thor-0.11.6/bin/thor (renamed from railties/lib/vendor/thor-0.11.5/bin/thor)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor.rb)2
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/actions.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/actions.rb)31
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/actions/create_file.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/actions/create_file.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/actions/directory.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/actions/directory.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/actions/empty_directory.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/actions/empty_directory.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/actions/file_manipulation.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/actions/file_manipulation.rb)58
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/actions/inject_into_file.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/actions/inject_into_file.rb)63
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/base.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/base.rb)7
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/core_ext/hash_with_indifferent_access.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/hash_with_indifferent_access.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/core_ext/ordered_hash.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/ordered_hash.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/error.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/error.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/group.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/group.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/invocation.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/invocation.rb)2
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/parser.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/parser.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/parser/argument.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/parser/argument.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/parser/arguments.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/parser/arguments.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/parser/option.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/parser/option.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/parser/options.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/parser/options.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/rake_compat.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/rake_compat.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/runner.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/runner.rb)4
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/shell.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/shell.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/shell/basic.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/shell/basic.rb)5
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/shell/color.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/shell/color.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/task.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/task.rb)20
-rw-r--r--railties/lib/vendor/thor-0.11.6/lib/thor/util.rb (renamed from railties/lib/vendor/thor-0.11.5/lib/thor/util.rb)17
-rw-r--r--railties/test/fixtures/plugins/engines/engine/config/locales/en.yml2
-rw-r--r--railties/test/fixtures/vendor/another_gem_path/xspec/lib/generators/xspec_generator.rb2
-rw-r--r--railties/test/fixtures/vendor/plugins/mspec/lib/generators/mspec_generator.rb (renamed from railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb)0
-rw-r--r--railties/test/generators/generators_test_helper.rb7
-rw-r--r--railties/test/generators/scaffold_generator_test.rb1
-rw-r--r--railties/test/generators/session_migration_generator_test.rb13
-rw-r--r--railties/test/generators_test.rb19
-rw-r--r--railties/test/initializer_test.rb1
-rw-r--r--railties/test/plugin_loader_test.rb8
354 files changed, 5361 insertions, 9052 deletions
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/.gitmodules
diff --git a/Rakefile b/Rakefile
index 5dd9f40310..3a6d9b72cb 100644
--- a/Rakefile
+++ b/Rakefile
@@ -3,7 +3,7 @@ require 'rake/rdoctask'
env = %(PKG_BUILD="#{ENV['PKG_BUILD']}") if ENV['PKG_BUILD']
-PROJECTS = %w(activesupport actionpack actionmailer activemodel activeresource activerecord railties)
+PROJECTS = %w(activesupport actionpack actionmailer activeresource activerecord railties)
Dir["#{File.dirname(__FILE__)}/*/lib/*/version.rb"].each do |version_path|
require version_path
@@ -12,7 +12,7 @@ end
desc 'Run all tests by default'
task :default => :test
-%w(test isolated_test rdoc pgem package release).each do |task_name|
+%w(test isolated_test rdoc pgem package release gem).each do |task_name|
desc "Run #{task_name} task for all projects"
task task_name do
errors = []
@@ -23,6 +23,13 @@ task :default => :test
end
end
+task :install => :gem do
+ (PROJECTS - ["railties"]).each do |project|
+ system("gem install #{project}/pkg/#{project}-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc")
+ end
+ system("gem install railties/pkg/rails-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc")
+end
+
desc "Generate documentation for the Rails framework"
Rake::RDocTask.new do |rdoc|
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 75de1fe2a6..30f3f31563 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*
+* Introduce grouped_collection_select helper. #1249 [Dan Codeape, Erik Ostrom]
+
* Make sure javascript_include_tag/stylesheet_link_tag does not append ".js" or ".css" onto external urls. #1664 [Matthew Rudy Jacobs]
* Ruby 1.9: fix Content-Length for multibyte send_data streaming. #2661 [Sava Chankov]
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 1fc5018561..1181995436 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -116,6 +116,8 @@ spec = Gem::Specification.new do |s|
s.requirements << 'none'
s.add_dependency('activesupport', '= 3.0.pre' + PKG_BUILD)
+ s.add_dependency('rack', '~> 1.0.0')
+ s.add_dependency('rack-test', '~> 0.4.2')
s.require_path = 'lib'
s.autorequire = 'action_controller'
diff --git a/actionpack/examples/minimal.rb b/actionpack/examples/minimal.rb
deleted file mode 100644
index a9015da053..0000000000
--- a/actionpack/examples/minimal.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-# Pass NEW=1 to run with the new Base
-ENV['RAILS_ENV'] ||= 'production'
-ENV['NO_RELOAD'] ||= '1'
-
-$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
-$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib"
-require 'action_controller'
-require 'action_controller/new_base' if ENV['NEW']
-require 'action_view'
-require 'benchmark'
-
-class Runner
- def initialize(app, output)
- @app, @output = app, output
- end
-
- def puts(*)
- super if @output
- end
-
- def call(env)
- env['n'].to_i.times { @app.call(env) }
- @app.call(env).tap { |response| report(env, response) }
- end
-
- def report(env, response)
- return unless ENV["DEBUG"]
- out = env['rack.errors']
- out.puts response[0], response[1].to_yaml, '---'
- response[2].each { |part| out.puts part }
- out.puts '---'
- end
-
- def self.puts(*)
- super if @output
- end
-
- def self.run(app, n, label, output = true)
- @output = output
- puts label, '=' * label.size if label
- env = Rack::MockRequest.env_for("/").merge('n' => n, 'rack.input' => StringIO.new(''), 'rack.errors' => $stdout)
- t = Benchmark.realtime { new(app, output).call(env) }
- puts "%d ms / %d req = %.1f usec/req" % [10**3 * t, n, 10**6 * t / n]
- puts
- end
-end
-
-
-N = (ENV['N'] || 1000).to_i
-
-module ActionController::Rails2Compatibility
- instance_methods.each do |name|
- remove_method name
- end
-end
-
-class BasePostController < ActionController::Base
- append_view_path "#{File.dirname(__FILE__)}/views"
-
- def overhead
- self.response_body = ''
- end
-
- def index
- render :text => ''
- end
-
- def partial
- render :partial => "/partial"
- end
-
- def many_partials
- render :partial => "/many_partials"
- end
-
- def partial_collection
- render :partial => "/collection", :collection => [1,2,3,4,5,6,7,8,9,10]
- end
-
- def show_template
- render :template => "template"
- end
-end
-
-OK = [200, {}, []]
-MetalPostController = lambda { OK }
-
-class HttpPostController < ActionController::Metal
- def index
- self.response_body = ''
- end
-end
-
-unless ENV["PROFILE"]
- Runner.run(BasePostController.action(:overhead), N, 'overhead', false)
- Runner.run(BasePostController.action(:index), N, 'index', false)
- Runner.run(BasePostController.action(:partial), N, 'partial', false)
- Runner.run(BasePostController.action(:many_partials), N, 'many_partials', false)
- Runner.run(BasePostController.action(:partial_collection), N, 'collection', false)
- Runner.run(BasePostController.action(:show_template), N, 'template', false)
-
- (ENV["M"] || 1).to_i.times do
- Runner.run(BasePostController.action(:overhead), N, 'overhead')
- Runner.run(BasePostController.action(:index), N, 'index')
- Runner.run(BasePostController.action(:partial), N, 'partial')
- Runner.run(BasePostController.action(:many_partials), N, 'many_partials')
- Runner.run(BasePostController.action(:partial_collection), N, 'collection')
- Runner.run(BasePostController.action(:show_template), N, 'template')
- end
-else
- Runner.run(BasePostController.action(:many_partials), N, 'many_partials')
- require "ruby-prof"
- RubyProf.start
- Runner.run(BasePostController.action(:many_partials), N, 'many_partials')
- result = RubyProf.stop
- printer = RubyProf::CallStackPrinter.new(result)
- printer.print(File.open("output.html", "w"))
-end \ No newline at end of file
diff --git a/actionpack/examples/very_simple.rb b/actionpack/examples/very_simple.rb
deleted file mode 100644
index 6714185172..0000000000
--- a/actionpack/examples/very_simple.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-$:.push "rails/activesupport/lib"
-$:.push "rails/actionpack/lib"
-
-require "action_controller"
-
-class Kaigi < ActionController::Metal
- include AbstractController::Callbacks
- include ActionController::RackConvenience
- include ActionController::RenderingController
- include ActionController::Layouts
- include ActionView::Context
-
- before_filter :set_name
- append_view_path "views"
-
- def view_context
- self
- end
-
- def controller
- self
- end
-
- DEFAULT_LAYOUT = Object.new.tap {|l| def l.render(*) yield end }
-
- def render_template(template, layout = DEFAULT_LAYOUT, options = {}, partial = false)
- ret = template.render(self, {})
- layout.render(self, {}) { ret }
- end
-
- def index
- render :template => "template"
- end
-
- def alt
- render :template => "template", :layout => "alt"
- end
-
- private
- def set_name
- @name = params[:name]
- end
-end
-
-app = Rack::Builder.new do
- map("/kaigi") { run Kaigi.action(:index) }
- map("/kaigi/alt") { run Kaigi.action(:alt) }
-end.to_app
-
-Rack::Handler::Mongrel.run app, :Port => 3000
diff --git a/actionpack/examples/views/_collection.erb b/actionpack/examples/views/_collection.erb
deleted file mode 100644
index bcfe958e2c..0000000000
--- a/actionpack/examples/views/_collection.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= collection %> \ No newline at end of file
diff --git a/actionpack/examples/views/_hello.erb b/actionpack/examples/views/_hello.erb
deleted file mode 100644
index 5ab2f8a432..0000000000
--- a/actionpack/examples/views/_hello.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hello \ No newline at end of file
diff --git a/actionpack/examples/views/_many_partials.erb b/actionpack/examples/views/_many_partials.erb
deleted file mode 100644
index 7e379d46f5..0000000000
--- a/actionpack/examples/views/_many_partials.erb
+++ /dev/null
@@ -1,10 +0,0 @@
-<%= render :partial => '/hello' %>
-<%= render :partial => '/hello' %>
-<%= render :partial => '/hello' %>
-<%= render :partial => '/hello' %>
-<%= render :partial => '/hello' %>
-<%= render :partial => '/hello' %>
-<%= render :partial => '/hello' %>
-<%= render :partial => '/hello' %>
-<%= render :partial => '/hello' %>
-<%= render :partial => '/hello' %> \ No newline at end of file
diff --git a/actionpack/examples/views/_partial.erb b/actionpack/examples/views/_partial.erb
deleted file mode 100644
index 3ca8e80b52..0000000000
--- a/actionpack/examples/views/_partial.erb
+++ /dev/null
@@ -1,10 +0,0 @@
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
-<%= "Hello" %>
diff --git a/actionpack/examples/views/layouts/alt.html.erb b/actionpack/examples/views/layouts/alt.html.erb
deleted file mode 100644
index c4816337a6..0000000000
--- a/actionpack/examples/views/layouts/alt.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-+ <%= yield %> + \ No newline at end of file
diff --git a/actionpack/examples/views/layouts/kaigi.html.erb b/actionpack/examples/views/layouts/kaigi.html.erb
deleted file mode 100644
index 274607a96a..0000000000
--- a/actionpack/examples/views/layouts/kaigi.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hello <%= yield %> Goodbye \ No newline at end of file
diff --git a/actionpack/examples/views/template.html.erb b/actionpack/examples/views/template.html.erb
deleted file mode 100644
index 5ab2f8a432..0000000000
--- a/actionpack/examples/views/template.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hello \ No newline at end of file
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index b93e6ce634..f5b1c9e4d1 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -89,7 +89,6 @@ module AbstractController
end
process_action(action_name)
- self
end
private
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 0063d54149..ef66b24dd6 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -6,13 +6,56 @@ module AbstractController
included do
extlib_inheritable_accessor(:_layout_conditions) { Hash.new }
+ extlib_inheritable_accessor(:_action_has_layout) { Hash.new }
_write_layout_method
end
module ClassMethods
def inherited(klass)
super
- klass._write_layout_method
+ klass.class_eval do
+ _write_layout_method
+ @found_layouts = {}
+ end
+ end
+
+ def clear_template_caches!
+ @found_layouts.clear if @found_layouts
+ super
+ end
+
+ def cache_layout(details)
+ layout = @found_layouts
+ key = Thread.current[:format_locale_key]
+
+ # Cache nil
+ if layout.key?(key)
+ return layout[key]
+ else
+ layout[key] = yield
+ end
+ end
+
+ # This module is mixed in if layout conditions are provided. This means
+ # that if no layout conditions are used, this method is not used
+ module LayoutConditions
+ # Determines whether the current action has a layout by checking the
+ # action name against the :only and :except conditions set on the
+ # layout.
+ #
+ # ==== Returns
+ # Boolean:: True if the action has a layout, false otherwise.
+ def _action_has_layout?
+ conditions = _layout_conditions
+
+ if only = conditions[:only]
+ only.include?(action_name)
+ elsif except = conditions[:except]
+ !except.include?(action_name)
+ else
+ true
+ end
+ end
end
# Specify the layout to use for this class.
@@ -31,6 +74,8 @@ module AbstractController
# :only<#to_s, Array[#to_s]>:: A list of actions to apply this layout to.
# :except<#to_s, Array[#to_s]>:: Apply this layout to all actions but this one
def layout(layout, conditions = {})
+ include LayoutConditions unless conditions.empty?
+
conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
self._layout_conditions = conditions
@@ -76,10 +121,12 @@ module AbstractController
when nil
self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
def _layout(details)
- if view_paths.exists?("#{_implied_layout_name}", details, "layouts")
- "#{_implied_layout_name}"
- else
- super
+ self.class.cache_layout(details) do
+ if view_paths.exists?("#{_implied_layout_name}", details, "layouts")
+ "#{_implied_layout_name}"
+ else
+ super
+ end
end
end
ruby_eval
@@ -98,6 +145,9 @@ module AbstractController
# This is a little bit messy. We need to explicitly handle partial
# layouts here since the core lookup logic is in the view, but
# we need to determine the layout based on the controller
+ #
+ # TODO: An easier way to handle this would probably be to override
+ # render_template
if layout
layout = _layout_for_option(layout, options[:_template].details)
response = layout.render(view_context, options[:locals] || {}) { response }
@@ -136,7 +186,7 @@ module AbstractController
view_paths.find(name, details, prefix)
end
- # Returns the default layout for this controller and a given set of details.
+ # Returns the default layout for this controller and a given set of details.
# Optionally raises an exception if the layout could not be found.
#
# ==== Parameters
@@ -162,21 +212,8 @@ module AbstractController
end
end
- # Determines whether the current action has a layout by checking the
- # action name against the :only and :except conditions set on the
- # layout.
- #
- # ==== Returns
- # Boolean:: True if the action has a layout, false otherwise.
def _action_has_layout?
- conditions = _layout_conditions
- if only = conditions[:only]
- only.include?(action_name)
- elsif except = conditions[:except]
- !except.include?(action_name)
- else
- true
- end
+ true
end
end
end
diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb
index fd33bd2ddd..1b879b963b 100644
--- a/actionpack/lib/abstract_controller/logger.rb
+++ b/actionpack/lib/abstract_controller/logger.rb
@@ -29,7 +29,7 @@ module AbstractController
# Override process_action in the AbstractController::Base
# to log details about the method.
def process_action(action)
- super
+ retval = super
if logger
log = DelayedLog.new do
@@ -40,6 +40,8 @@ module AbstractController
logger.info(log)
end
+
+ retval
end
private
diff --git a/actionpack/lib/abstract_controller/rendering_controller.rb b/actionpack/lib/abstract_controller/rendering_controller.rb
index bb7891fbfd..feca1bc4b7 100644
--- a/actionpack/lib/abstract_controller/rendering_controller.rb
+++ b/actionpack/lib/abstract_controller/rendering_controller.rb
@@ -111,12 +111,21 @@ module AbstractController
def _determine_template(options)
name = (options[:_template_name] || action_name).to_s
- options[:_template] ||= view_paths.find(
- name, { :formats => formats }, options[:_prefix], options[:_partial]
- )
+ options[:_template] ||= with_template_cache(name) do
+ view_paths.find(
+ name, { :formats => formats }, options[:_prefix], options[:_partial]
+ )
+ end
+ end
+
+ def with_template_cache(name)
+ yield
end
module ClassMethods
+ def clear_template_caches!
+ end
+
# Append a path to the list of view paths for this controller.
#
# ==== Parameters
@@ -134,6 +143,7 @@ module AbstractController
# the default view path. You may also provide a custom view path
# (see ActionView::ViewPathSet for more information)
def prepend_view_path(path)
+ clear_template_caches!
self.view_paths.unshift(path)
end
@@ -148,6 +158,7 @@ module AbstractController
# paths<ViewPathSet, Object>:: If a ViewPathSet is provided, use that;
# otherwise, process the parameter into a ViewPathSet.
def view_paths=(paths)
+ clear_template_caches!
self._view_paths = paths.is_a?(ActionView::PathSet) ?
paths : ActionView::Base.process_view_paths(paths)
end
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 37ff618edd..634206e065 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -3,6 +3,7 @@ module ActionController
autoload :ConditionalGet, "action_controller/metal/conditional_get"
autoload :HideActions, "action_controller/metal/hide_actions"
autoload :Metal, "action_controller/metal"
+ autoload :Middleware, "action_controller/middleware"
autoload :Layouts, "action_controller/metal/layouts"
autoload :RackConvenience, "action_controller/metal/rack_convenience"
autoload :Rails2Compatibility, "action_controller/metal/compatibility"
@@ -58,9 +59,8 @@ end
autoload :HTML, 'action_controller/vendor/html-scanner'
autoload :AbstractController, 'abstract_controller'
-autoload :Rack, 'action_dispatch'
-autoload :ActionDispatch, 'action_dispatch'
-autoload :ActionView, 'action_view'
+require 'action_dispatch'
+require 'action_view'
# Common ActiveSupport usage in ActionController
require "active_support/concern"
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 5333ca497c..51fbba3661 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -47,7 +47,7 @@ module ActionController
# and response object available. You might wish to control the
# environment and response manually for performance reasons.
- attr_internal :status, :headers, :content_type
+ attr_internal :status, :headers, :content_type, :app, :response
def initialize(*)
@_headers = {}
@@ -75,7 +75,17 @@ module ActionController
# :api: private
def to_a
- [status, headers, response_body]
+ response ? response.to_a : [status, headers, response_body]
+ end
+
+ class ActionEndpoint
+ def initialize(controller, action)
+ @controller, @action = controller, action
+ end
+
+ def call(env)
+ controller = @controller.new.call(@action, env)
+ end
end
# Return a rack endpoint for the given action. Memoize the endpoint, so
@@ -89,9 +99,7 @@ module ActionController
# Proc:: A rack application
def self.action(name)
@actions ||= {}
- @actions[name.to_s] ||= proc do |env|
- new.call(name, env)
- end
+ @actions[name.to_s] ||= ActionEndpoint.new(self, name)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb
index f94d1c669c..22f9ab219c 100644
--- a/actionpack/lib/action_controller/metal/compatibility.rb
+++ b/actionpack/lib/action_controller/metal/compatibility.rb
@@ -16,17 +16,12 @@ module ActionController
cattr_accessor :allow_concurrency
self.allow_concurrency = false
- cattr_accessor :param_parsers
- self.param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
- Mime::URL_ENCODED_FORM => :url_encoded_form,
- Mime::XML => :xml_simple,
- Mime::JSON => :json }
-
cattr_accessor :relative_url_root
self.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT']
- cattr_accessor :default_charset
- self.default_charset = "utf-8"
+ class << self
+ delegate :default_charset=, :to => "ActionDispatch::Response"
+ end
# cattr_reader :protected_instance_variables
cattr_accessor :protected_instance_variables
@@ -101,11 +96,10 @@ module ActionController
options[:template].sub!(/^\//, '')
end
- options[:text] = nil if options[:nothing] == true
+ options[:text] = nil if options.delete(:nothing) == true
+ options[:text] = " " if options.key?(:text) && options[:text].nil?
- body = super
- body = [' '] if body.blank?
- body
+ super || " "
end
def _handle_method_missing
diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb
index af68c772b1..cdacdc40a6 100644
--- a/actionpack/lib/action_controller/metal/hide_actions.rb
+++ b/actionpack/lib/action_controller/metal/hide_actions.rb
@@ -13,7 +13,9 @@ module ActionController
# Overrides AbstractController::Base#action_method? to return false if the
# action name is in the list of hidden actions.
def action_method?(action_name)
- !hidden_actions.include?(action_name) && super
+ self.class.visible_action?(action_name) do
+ !hidden_actions.include?(action_name) && super
+ end
end
module ClassMethods
@@ -25,6 +27,16 @@ module ActionController
hidden_actions.merge(args.map! {|a| a.to_s })
end
+ def inherited(klass)
+ klass.instance_variable_set("@visible_actions", {})
+ super
+ end
+
+ def visible_action?(action_name)
+ return @visible_actions[action_name] if @visible_actions.key?(action_name)
+ @visible_actions[action_name] = yield
+ end
+
# Overrides AbstractController::Base#action_methods to remove any methods
# that are listed as hidden methods.
def action_methods
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 2b62a1be85..0f35a7c040 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -115,7 +115,7 @@ module ActionController
end
def authenticate_with_http_basic(&login_procedure)
- HttpAuthentication::Basic.authenticate(self, &login_procedure)
+ HttpAuthentication::Basic.authenticate(request, &login_procedure)
end
def request_http_basic_authentication(realm = "Application")
@@ -123,9 +123,9 @@ module ActionController
end
end
- def authenticate(controller, &login_procedure)
- unless authorization(controller.request).blank?
- login_procedure.call(*user_name_and_password(controller.request))
+ def authenticate(request, &login_procedure)
+ unless authorization(request).blank?
+ login_procedure.call(*user_name_and_password(request))
end
end
@@ -150,7 +150,8 @@ module ActionController
def authentication_request(controller, realm)
controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
- controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
+ controller.response_body = "HTTP Basic: Access denied.\n"
+ controller.status = 401
end
end
@@ -164,7 +165,7 @@ module ActionController
# Authenticate with HTTP Digest, returns true or false
def authenticate_with_http_digest(realm = "Application", &password_procedure)
- HttpAuthentication::Digest.authenticate(self, realm, &password_procedure)
+ HttpAuthentication::Digest.authenticate(request, realm, &password_procedure)
end
# Render output including the HTTP Digest authentication header
@@ -174,8 +175,8 @@ module ActionController
end
# Returns false on a valid response, true otherwise
- def authenticate(controller, realm, &password_procedure)
- authorization(controller.request) && validate_digest_response(controller.request, realm, &password_procedure)
+ def authenticate(request, realm, &password_procedure)
+ authorization(request) && validate_digest_response(request, realm, &password_procedure)
end
def authorization(request)
@@ -243,7 +244,8 @@ module ActionController
def authentication_request(controller, realm, message = nil)
message ||= "HTTP Digest: Access denied.\n"
authentication_header(controller, realm)
- controller.__send__ :render, :text => message, :status => :unauthorized
+ controller.response_body = message
+ controller.status = 401
end
# Uses an MD5 digest based on time to generate a value to be used only once.
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index c8d042acb5..3026067868 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -179,21 +179,8 @@ module ActionController #:nodoc:
def respond_to(*mimes, &block)
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
- collector = Collector.new
- mimes = collect_mimes_from_class_level if mimes.empty?
- mimes.each { |mime| collector.send(mime) }
- block.call(collector) if block_given?
-
- if format = request.negotiate_mime(collector.order)
- self.formats = [format.to_sym]
-
- if response = collector.response_for(format)
- response.call
- else
- default_render
- end
- else
- head :not_acceptable
+ if response = retrieve_response_from_mimes(mimes, &block)
+ response.call
end
end
@@ -226,10 +213,12 @@ module ActionController #:nodoc:
# is quite simple (it just needs to respond to call), you can even give
# a proc to it.
#
- def respond_with(resource, options={}, &block)
- respond_to(&block)
- rescue ActionView::MissingTemplate
- (options.delete(:responder) || responder).call(self, resource, options)
+ def respond_with(*resources, &block)
+ if response = retrieve_response_from_mimes([], &block)
+ options = resources.extract_options!
+ options.merge!(:default_response => response)
+ (options.delete(:responder) || responder).call(self, resources, options)
+ end
end
def responder
@@ -257,11 +246,29 @@ module ActionController #:nodoc:
end
end
+ # Collects mimes and return the response for the negotiated format. Returns
+ # nil if :not_acceptable was sent to the client.
+ #
+ def retrieve_response_from_mimes(mimes, &block)
+ collector = Collector.new { default_render }
+ mimes = collect_mimes_from_class_level if mimes.empty?
+ mimes.each { |mime| collector.send(mime) }
+ block.call(collector) if block_given?
+
+ if format = request.negotiate_mime(collector.order)
+ self.formats = [format.to_sym]
+ collector.response_for(format)
+ else
+ head :not_acceptable
+ nil
+ end
+ end
+
class Collector #:nodoc:
attr_accessor :order
- def initialize
- @order, @responses = [], {}
+ def initialize(&block)
+ @order, @responses, @default_response = [], {}, block
end
def any(*args, &block)
@@ -275,13 +282,12 @@ module ActionController #:nodoc:
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] ||= block
end
def response_for(mime)
- @responses[mime] || @responses[Mime::ALL]
+ @responses[mime] || @responses[Mime::ALL] || @default_response
end
def self.generate_method_for_mime(mime)
diff --git a/actionpack/lib/action_controller/metal/rack_convenience.rb b/actionpack/lib/action_controller/metal/rack_convenience.rb
index 5fac445dab..a80569c530 100644
--- a/actionpack/lib/action_controller/metal/rack_convenience.rb
+++ b/actionpack/lib/action_controller/metal/rack_convenience.rb
@@ -5,7 +5,7 @@ module ActionController
included do
delegate :headers, :status=, :location=, :content_type=,
:status, :location, :content_type, :to => "@_response"
- attr_internal :request, :response
+ attr_internal :request
end
def call(name, env)
@@ -19,12 +19,6 @@ module ActionController
@_params ||= @_request.parameters
end
- # :api: private
- def to_a
- @_response.prepare!
- @_response.to_a
- end
-
def response_body=(body)
response.body = body if response
super
diff --git a/actionpack/lib/action_controller/metal/redirector.rb b/actionpack/lib/action_controller/metal/redirector.rb
index 20060b001f..f79fd54acd 100644
--- a/actionpack/lib/action_controller/metal/redirector.rb
+++ b/actionpack/lib/action_controller/metal/redirector.rb
@@ -8,6 +8,9 @@ module ActionController
end
module Redirector
+ extend ActiveSupport::Concern
+ include AbstractController::Logger
+
def redirect_to(url, status) #:doc:
raise AbstractController::DoubleRenderError if response_body
logger.info("Redirected to #{url}") if logger && logger.info?
diff --git a/actionpack/lib/action_controller/metal/rendering_controller.rb b/actionpack/lib/action_controller/metal/rendering_controller.rb
index 5b1be763ad..4da32ca1b3 100644
--- a/actionpack/lib/action_controller/metal/rendering_controller.rb
+++ b/actionpack/lib/action_controller/metal/rendering_controller.rb
@@ -1,20 +1,56 @@
module ActionController
+ class HashKey
+ @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {} } }
+
+ def self.get(klass, formats, locale)
+ @hash_keys[klass][formats][locale] ||= new(klass, formats, locale)
+ end
+
+ attr_accessor :hash
+ def initialize(klass, formats, locale)
+ @formats, @locale = formats, locale
+ @hash = [formats, locale].hash
+ end
+
+ alias_method :eql?, :equal?
+
+ def inspect
+ "#<HashKey -- formats: #{@formats} locale: #{@locale}>"
+ end
+ end
+
module RenderingController
extend ActiveSupport::Concern
include AbstractController::RenderingController
+ module ClassMethods
+ def clear_template_caches!
+ ActionView::Partials::PartialRenderer::TEMPLATES.clear
+ template_cache.clear
+ super
+ end
+
+ def template_cache
+ @template_cache ||= Hash.new {|h,k| h[k] = {} }
+ end
+ end
+
def process_action(*)
self.formats = request.formats.map {|x| x.to_sym}
+
+ super
+ end
+
+ def _determine_template(*)
super
end
def render(options)
+ Thread.current[:format_locale_key] = HashKey.get(self.class, formats, I18n.locale)
+
super
- self.content_type ||= begin
- mime = options[:_template].mime_type
- formats.include?(mime && mime.to_sym) || formats.include?(:all) ? mime : Mime::Type.lookup_by_extension(formats.first)
- end.to_s
+ self.content_type ||= options[:_template].mime_type.to_s
response_body
end
@@ -34,6 +70,10 @@ module ActionController
controller_path
end
+ def with_template_cache(name)
+ self.class.template_cache[Thread.current[:format_locale_key]][name] ||= super
+ end
+
def _determine_template(options)
if options.key?(:text)
options[:_template] = ActionView::TextTemplate.new(options[:text], formats.first)
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index 9ed99ca623..a16ed97131 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -64,7 +64,7 @@ module ActionController #:nodoc:
# @project = Project.find(params[:project_id])
# @task = @project.comments.build(params[:task])
# flash[:notice] = 'Task was successfully created.' if @task.save
- # respond_with([@project, @task])
+ # respond_with(@project, @task)
# end
#
# Giving an array of resources, you ensure that the responder will redirect to
@@ -74,20 +74,21 @@ module ActionController #:nodoc:
# polymorphic urls. If a project has one manager which has many tasks, it
# should be invoked as:
#
- # respond_with([@project, :manager, @task])
+ # respond_with(@project, :manager, @task)
#
# Check polymorphic_url documentation for more examples.
#
class Responder
- attr_reader :controller, :request, :format, :resource, :resource_location, :options
+ attr_reader :controller, :request, :format, :resource, :resources, :options
- def initialize(controller, resource, options={})
+ def initialize(controller, resources, options={})
@controller = controller
@request = controller.request
@format = controller.formats.first
- @resource = resource.is_a?(Array) ? resource.last : resource
- @resource_location = options[:location] || resource
+ @resource = resources.is_a?(Array) ? resources.last : resources
+ @resources = resources
@options = options
+ @default_response = options.delete(:default_response)
end
delegate :head, :render, :redirect_to, :to => :controller
@@ -109,8 +110,10 @@ module ActionController #:nodoc:
# template.
#
def to_html
+ default_render
+ rescue ActionView::MissingTemplate
if get?
- render
+ raise
elsif has_errors?
render :action => default_action
else
@@ -118,12 +121,14 @@ module ActionController #:nodoc:
end
end
- # All others formats try to render the resource given instead. For this
- # purpose a helper called display as a shortcut to render a resource with
- # the current format.
+ # All others formats follow the procedure below. First we try to render a
+ # template, if the template is not available, we verify if the resource
+ # responds to :to_format and display it.
#
def to_format
- return render unless resourceful?
+ default_render
+ rescue ActionView::MissingTemplate
+ raise unless resourceful?
if get?
display resource
@@ -144,6 +149,20 @@ module ActionController #:nodoc:
resource.respond_to?(:"to_#{format}")
end
+ # Returns the resource location by retrieving it from the options or
+ # returning the resources array.
+ #
+ def resource_location
+ options[:location] || resources
+ end
+
+ # If a given response block was given, use it, otherwise call render on
+ # controller.
+ #
+ def default_render
+ @default_response.call
+ end
+
# display is just a shortcut to render a resource with the current format.
#
# display @user, :status => :ok
@@ -162,7 +181,7 @@ module ActionController #:nodoc:
# render :xml => @user, :status => :created
#
def display(resource, given_options={})
- render given_options.merge!(options).merge!(format => resource)
+ controller.render given_options.merge!(options).merge!(format => resource)
end
# Check if the resource has errors or not.
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 57318e8747..4761763a26 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -145,7 +145,6 @@ module ActionController #:nodoc:
def send_data(data, options = {}) #:doc:
logger.info "Sending data #{options[:filename]}" if logger
send_file_headers! options.merge(:length => data.bytesize)
- @performed_render = false
render :status => options[:status], :text => data
end
@@ -175,6 +174,8 @@ module ActionController #:nodoc:
'Content-Transfer-Encoding' => 'binary'
)
+ response.sending_file = true
+
# 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
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 7119c14cd3..14c6523045 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -4,15 +4,6 @@ module ActionController
include RackConvenience
- def process_action(*)
- initialize_current_url
- super
- end
-
- def initialize_current_url
- @url = UrlRewriter.new(request, params.clone)
- 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:
#
@@ -40,6 +31,7 @@ module ActionController
when String
options
when Hash
+ @url ||= UrlRewriter.new(request, params)
@url.rewrite(rewrite_options(options))
else
polymorphic_url(options)
diff --git a/actionpack/lib/action_controller/middleware.rb b/actionpack/lib/action_controller/middleware.rb
new file mode 100644
index 0000000000..fac0ed2645
--- /dev/null
+++ b/actionpack/lib/action_controller/middleware.rb
@@ -0,0 +1,38 @@
+module ActionController
+ class Middleware < Metal
+ class ActionMiddleware
+ def initialize(controller)
+ @controller = controller
+ end
+
+ def call(env)
+ controller = @controller.allocate
+ controller.send(:initialize)
+ controller.app = @app
+ controller._call(env)
+ end
+
+ def app=(app)
+ @app = app
+ end
+ end
+
+ def self.new(app)
+ middleware = ActionMiddleware.new(self)
+ middleware.app = app
+ middleware
+ end
+
+ def _call(env)
+ @_env = env
+ @_request = ActionDispatch::Request.new(env)
+ @_response = ActionDispatch::Response.new
+ @_response.request = @_request
+ process(:index)
+ end
+
+ def index
+ call(env)
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/routing/generation/url_rewriter.rb b/actionpack/lib/action_controller/routing/generation/url_rewriter.rb
index 9717582b5e..52b66c9303 100644
--- a/actionpack/lib/action_controller/routing/generation/url_rewriter.rb
+++ b/actionpack/lib/action_controller/routing/generation/url_rewriter.rb
@@ -172,7 +172,7 @@ module ActionController
path = rewrite_path(options)
rewritten_url << ActionController::Base.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 << "##{CGI.escape(options[:anchor].to_param.to_s)}" if options[:anchor]
rewritten_url
end
diff --git a/actionpack/lib/action_controller/routing/resources.rb b/actionpack/lib/action_controller/routing/resources.rb
index 2dee0a3d87..06506435a2 100644
--- a/actionpack/lib/action_controller/routing/resources.rb
+++ b/actionpack/lib/action_controller/routing/resources.rb
@@ -320,9 +320,10 @@ module ActionController
# notes.resources :attachments
# end
#
- # * <tt>:path_names</tt> - Specify different names for the 'new' and 'edit' actions. For example:
+ # * <tt>:path_names</tt> - Specify different path names for the actions. For example:
# # new_products_path == '/productos/nuevo'
- # map.resources :products, :as => 'productos', :path_names => { :new => 'nuevo', :edit => 'editar' }
+ # # bids_product_path(1) == '/productos/1/licitacoes'
+ # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
#
# You can also set default action names from an environment, like this:
# config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
@@ -528,16 +529,16 @@ module ActionController
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(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
end
+
+ map_collection_actions(map, resource)
+ map_default_collection_actions(map, resource)
+ map_new_actions(map, resource)
+ map_member_actions(map, resource)
end
end
@@ -545,16 +546,16 @@ module ActionController
resource = SingletonResource.new(entities, options)
with_options :controller => resource.controller do |map|
- map_collection_actions(map, resource)
- map_new_actions(map, resource)
- map_member_actions(map, resource)
- map_default_singleton_actions(map, resource)
-
map_associations(resource, options)
if block_given?
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
end
+
+ map_collection_actions(map, resource)
+ map_new_actions(map, resource)
+ map_member_actions(map, resource)
+ map_default_singleton_actions(map, resource)
end
end
@@ -589,7 +590,10 @@ module ActionController
resource.collection_methods.each do |method, actions|
actions.each do |action|
[method].flatten.each do |m|
- map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
+ action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
+ action_path ||= action
+
+ map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
end
end
end
diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb
index 0c499f3b46..25fdbf480e 100644
--- a/actionpack/lib/action_controller/routing/route_set.rb
+++ b/actionpack/lib/action_controller/routing/route_set.rb
@@ -213,7 +213,7 @@ module ActionController
self.routes = []
self.named_routes = NamedRouteCollection.new
- clear_recognize_optimized!
+ clear!
end
# Subclasses and plugins may override this method to specify a different
@@ -223,6 +223,7 @@ module ActionController
end
def draw
+ clear!
yield Mapper.new(self)
install_helpers
end
@@ -230,8 +231,10 @@ module ActionController
def clear!
routes.clear
named_routes.clear
+
@combined_regexp = nil
@routes_by_controller = nil
+
# This will force routing/recognition_optimization.rb
# to refresh optimisations.
clear_recognize_optimized!
@@ -262,7 +265,6 @@ module ActionController
def load!
Routing.use_controllers!(nil) # Clear the controller cache so we may discover new ones
- clear!
load_routes!
end
@@ -286,10 +288,12 @@ module ActionController
configuration_files.each { |config| load(config) }
@routes_last_modified = routes_changed_at
else
- add_route ":controller/:action/:id"
+ draw do |map|
+ map.connect ":controller/:action/:id"
+ end
end
end
-
+
def routes_changed_at
routes_changed_at = nil
@@ -407,9 +411,11 @@ module ActionController
# don't use the recalled keys when determining which routes to check
routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }]
- routes.each do |route|
+ routes.each_with_index do |route, index|
results = route.__send__(method, options, merged, expire_on)
- return results if results && (!results.is_a?(Array) || results.first)
+ if results && (!results.is_a?(Array) || results.first)
+ return results
+ end
end
end
@@ -460,16 +466,13 @@ module ActionController
merged = options if expire_on[:controller]
action = merged[:action] || 'index'
- routes_by_controller[controller][action][merged.keys]
+ routes_by_controller[controller][action][merged.keys][1]
end
def routes_for_controller_and_action_and_keys(controller, action, keys)
- selected = routes.select do |route|
+ 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
diff --git a/actionpack/lib/action_controller/testing/integration.rb b/actionpack/lib/action_controller/testing/integration.rb
index 5cb0f48f82..fb82f866fe 100644
--- a/actionpack/lib/action_controller/testing/integration.rb
+++ b/actionpack/lib/action_controller/testing/integration.rb
@@ -267,7 +267,8 @@ module ActionController
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
"HTTP_ACCEPT" => accept
}
- env = Rack::MockRequest.env_for(path, opts)
+
+ env = ActionDispatch::TestRequest.env_for(path, opts)
(rack_environment || {}).each do |key, value|
env[key] = value
diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb
index d32d5562e8..a58b2de379 100644
--- a/actionpack/lib/action_controller/testing/process.rb
+++ b/actionpack/lib/action_controller/testing/process.rb
@@ -52,7 +52,7 @@ module ActionController #:nodoc:
class TestResponse < ActionDispatch::TestResponse
def recycle!
@status = 200
- @header = Rack::Utils::HeaderHash.new
+ @header = {}
@writer = lambda { |x| @body << x }
@block = nil
@length = 0
@@ -84,7 +84,7 @@ module ActionController #:nodoc:
#
# 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(ActionController::TestCase.fixture_path + '/files/spongebob.png', 'image/png', :binary)
- TestUploadedFile = Rack::Utils::Multipart::UploadedFile
+ TestUploadedFile = ActionDispatch::TestRequest::Multipart::UploadedFile
module TestProcess
def self.included(base)
@@ -148,7 +148,7 @@ module ActionController #:nodoc:
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- @request.env['HTTP_ACCEPT'] = [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
returning __send__(request_method, action, parameters, session, flash) do
@request.env.delete 'HTTP_X_REQUESTED_WITH'
@request.env.delete 'HTTP_ACCEPT'
@@ -249,6 +249,7 @@ module ActionController #:nodoc:
temporary_routes = ActionController::Routing::RouteSet.new
ActionController::Routing.module_eval { const_set :Routes, temporary_routes }
+ ActionController::Dispatcher.router = temporary_routes
yield temporary_routes
ensure
@@ -256,6 +257,7 @@ module ActionController #:nodoc:
ActionController::Routing.module_eval { remove_const :Routes }
end
ActionController::Routing.const_set(:Routes, real_routes) if real_routes
+ ActionController::Dispatcher.router = ActionController::Routing::Routes
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/testing/test_case.rb b/actionpack/lib/action_controller/testing/test_case.rb
index a11755b517..b66a4c15ff 100644
--- a/actionpack/lib/action_controller/testing/test_case.rb
+++ b/actionpack/lib/action_controller/testing/test_case.rb
@@ -179,7 +179,6 @@ module ActionController
if @controller
@controller.request = @request
@controller.params = {}
- @controller.send(:initialize_current_url)
end
end
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 884828a01a..849f268a8c 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -25,15 +25,11 @@ activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
$:.unshift activesupport_path if File.directory?(activesupport_path)
require 'active_support'
-begin
- gem 'rack', '~> 1.1.pre'
-rescue Gem::LoadError, ArgumentError
- $:.unshift "#{File.dirname(__FILE__)}/action_dispatch/vendor/rack-1.1.pre"
-end
-
require 'rack'
-$:.unshift "#{File.dirname(__FILE__)}/action_dispatch/vendor/rack-test"
+module Rack
+ autoload :Test, 'rack/test'
+end
module ActionDispatch
autoload :Request, 'action_dispatch/http/request'
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 4190fa21cd..bff030f0e4 100755
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -106,16 +106,10 @@ module ActionDispatch
@env["action_dispatch.request.accepts"] ||= begin
header = @env['HTTP_ACCEPT'].to_s.strip
- fallback = xhr? ? Mime::JS : Mime::HTML
-
if header.empty?
- [content_type, fallback, Mime::ALL].compact
+ [content_type]
else
- ret = Mime::Type.parse(header)
- if ret.last == Mime::ALL
- ret.insert(-2, fallback)
- end
- ret
+ Mime::Type.parse(header)
end
end
end
@@ -163,30 +157,20 @@ module ActionDispatch
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
#
def format(view_path = [])
- @env["action_dispatch.request.format"] ||=
- if parameters[:format]
- Mime[parameters[:format]]
- elsif ActionController::Base.use_accept_header && !(accepts == ONLY_ALL)
- accepts.first
- elsif xhr? then Mime::JS
- else Mime::HTML
- end
+ formats.first
end
- # Expand raw_formats by converting Mime::ALL to the Mime::SET.
- #
def formats
- if ActionController::Base.use_accept_header
- raw_formats.tap do |ret|
- if ret == ONLY_ALL
- ret.replace Mime::SET
- elsif all = ret.index(Mime::ALL)
- ret.delete_at(all) && ret.insert(all, *Mime::SET)
- end
+ accept = @env['HTTP_ACCEPT']
+
+ @env["action_dispatch.request.formats"] ||=
+ if parameters[:format]
+ [Mime[parameters[:format]]]
+ elsif xhr? || (accept && !accept.include?(?,))
+ accepts
+ else
+ [Mime::HTML]
end
- else
- raw_formats + Mime::SET
- end
end
# Sets the \format by string extension, which can be used to force custom formats
@@ -202,7 +186,7 @@ module ActionDispatch
# end
def format=(extension)
parameters[:format] = extension.to_s
- @env["action_dispatch.request.format"] = Mime::Type.lookup_by_extension(parameters[:format])
+ @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])]
end
# Returns a symbolized version of the <tt>:format</tt> parameter of the request.
@@ -487,7 +471,7 @@ EOM
# matches the order array.
#
def negotiate_mime(order)
- raw_formats.each do |priority|
+ formats.each do |priority|
if priority == Mime::ALL
return order.first
elsif order.include?(priority)
@@ -500,18 +484,6 @@ EOM
private
- def raw_formats
- if ActionController::Base.use_accept_header
- if param = parameters[:format]
- Array.wrap(Mime[param])
- else
- accepts.dup
- end
- else
- [format]
- end
- end
-
def named_host?(host)
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 03d1780b77..e457450059 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -32,18 +32,35 @@ module ActionDispatch # :nodoc:
# end
# end
class Response < Rack::Response
- attr_accessor :request
+ attr_accessor :request, :blank
attr_reader :cache_control
- attr_writer :header
+ attr_writer :header, :sending_file
alias_method :headers=, :header=
- delegate :default_charset, :to => 'ActionController::Base'
-
def initialize
- super
+ @status = 200
+ @header = {}
@cache_control = {}
- @header = Rack::Utils::HeaderHash.new
+
+ @writer = lambda { |x| @body << x }
+ @block = nil
+ @length = 0
+
+ @body, @cookie = [], []
+ @sending_file = false
+
+ yield self if block_given?
+ end
+
+ def cache_control
+ @cache_control ||= {}
+ end
+
+ def write(str)
+ s = str.to_s
+ @writer.call s
+ str
end
def status=(status)
@@ -71,7 +88,10 @@ module ActionDispatch # :nodoc:
str
end
+ EMPTY = " "
+
def body=(body)
+ @blank = true if body == EMPTY
@body = body.respond_to?(:to_str) ? [body] : body
end
@@ -113,43 +133,44 @@ module ActionDispatch # :nodoc:
end
def etag
- headers['ETag']
+ @etag
end
def etag?
- headers.include?('ETag')
+ @etag
end
def etag=(etag)
- if etag.blank?
- headers.delete('ETag')
- else
- headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
- end
+ key = ActiveSupport::Cache.expand_cache_key(etag)
+ @etag = %("#{Digest::MD5.hexdigest(key)}")
end
- def sending_file?
- headers["Content-Transfer-Encoding"] == "binary"
- end
+ CONTENT_TYPE = "Content-Type"
+
+ cattr_accessor(:default_charset) { "utf-8" }
def assign_default_content_type_and_charset!
- return if !headers["Content-Type"].blank?
+ return if headers[CONTENT_TYPE].present?
@content_type ||= Mime::HTML
- @charset ||= default_charset
+ @charset ||= self.class.default_charset
type = @content_type.to_s.dup
- type << "; charset=#{@charset}" unless sending_file?
+ type << "; charset=#{@charset}" unless @sending_file
- headers["Content-Type"] = type
+ headers[CONTENT_TYPE] = type
end
- def prepare!
+ def to_a
assign_default_content_type_and_charset!
handle_conditional_get!
- self["Set-Cookie"] ||= ""
+ self["Set-Cookie"] = @cookie.join("\n")
+ self["ETag"] = @etag if @etag
+ super
end
+ alias prepare! to_a
+
def each(&callback)
if @body.respond_to?(:call)
@writer = lambda { |x| callback.call(x) }
@@ -168,23 +189,12 @@ module ActionDispatch # :nodoc:
str
end
- def set_cookie(key, value)
- if value.has_key?(:http_only)
- ActiveSupport::Deprecation.warn(
- "The :http_only option in ActionController::Response#set_cookie " +
- "has been renamed. Please use :httponly instead.", caller)
- value[:httponly] ||= value.delete(:http_only)
- end
-
- super(key, value)
- end
-
# Returns the response cookies, converted to a Hash of (name => value) pairs
#
# assert_equal 'AuthorOfNewPage', r.cookies['author']
def cookies
cookies = {}
- if header = headers['Set-Cookie']
+ if header = @cookie
header = header.split("\n") if header.respond_to?(:to_str)
header.each do |cookie|
if pair = cookie.split(';').first
@@ -196,12 +206,43 @@ module ActionDispatch # :nodoc:
cookies
end
+ def set_cookie(key, value)
+ case value
+ when Hash
+ domain = "; domain=" + value[:domain] if value[:domain]
+ path = "; path=" + value[:path] if value[:path]
+ # According to RFC 2109, we need dashes here.
+ # N.B.: cgi.rb uses spaces...
+ expires = "; expires=" + value[:expires].clone.gmtime.
+ strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
+ secure = "; secure" if value[:secure]
+ httponly = "; HttpOnly" if value[:httponly]
+ value = value[:value]
+ end
+ value = [value] unless Array === value
+ cookie = Rack::Utils.escape(key) + "=" +
+ value.map { |v| Rack::Utils.escape v }.join("&") +
+ "#{domain}#{path}#{expires}#{secure}#{httponly}"
+
+ @cookie << cookie
+ end
+
+ def delete_cookie(key, value={})
+ @cookie.reject! { |cookie|
+ cookie =~ /\A#{Rack::Utils.escape(key)}=/
+ }
+
+ set_cookie(key,
+ {:value => '', :path => nil, :domain => nil,
+ :expires => Time.at(0) }.merge(value))
+ end
+
private
def handle_conditional_get!
- if etag? || last_modified? || !cache_control.empty?
+ if etag? || last_modified? || !@cache_control.empty?
set_conditional_cache_control!
elsif nonempty_ok_response?
- self.etag = body
+ self.etag = @body
if request && request.etag_matches?(etag)
self.status = 304
@@ -215,29 +256,33 @@ module ActionDispatch # :nodoc:
end
def nonempty_ok_response?
- ok = !@status || @status == 200
- ok && string_body?
+ @status == 200 && string_body?
end
def string_body?
- !body_parts.respond_to?(:call) && body_parts.any? && body_parts.all? { |part| part.is_a?(String) }
+ !@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) }
end
+ DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
+
def set_conditional_cache_control!
- if cache_control.empty?
- cache_control.merge!(:public => false, :max_age => 0, :must_revalidate => true)
- end
+ control = @cache_control
+
+ if control.empty?
+ headers["Cache-Control"] = DEFAULT_CACHE_CONTROL
+ else
+ extras = control[:extras]
+ max_age = control[:max_age]
- public_cache, max_age, must_revalidate, extras =
- cache_control.values_at(:public, :max_age, :must_revalidate, :extras)
+ options = []
+ options << "max-age=#{max_age}" if max_age
+ options << (control[:public] ? "public" : "private")
+ options << "must-revalidate" if control[:must_revalidate]
+ options.concat(extras) if extras
- options = []
- options << "max-age=#{max_age}" if max_age
- options << (public_cache ? "public" : "private")
- options << "must-revalidate" if must_revalidate
- options.concat(extras) if extras
+ headers["Cache-Control"] = options.join(", ")
+ end
- headers["Cache-Control"] = options.join(", ")
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index e83cf9236b..32ccb5c931 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -2,11 +2,13 @@ require 'active_support/json'
module ActionDispatch
class ParamsParser
- ActionController::Base.param_parsers[Mime::XML] = :xml_simple
- ActionController::Base.param_parsers[Mime::JSON] = :json
+ DEFAULT_PARSERS = {
+ Mime::XML => :xml_simple,
+ Mime::JSON => :json
+ }
- def initialize(app)
- @app = app
+ def initialize(app, parsers = {})
+ @app, @parsers = app, DEFAULT_PARSERS.merge(parsers)
end
def call(env)
@@ -24,7 +26,7 @@ module ActionDispatch
return false if request.content_length.zero?
mime_type = content_type_from_legacy_post_data_format_header(env) || request.content_type
- strategy = ActionController::Base.param_parsers[mime_type]
+ strategy = @parsers[mime_type]
return false unless strategy
@@ -47,6 +49,8 @@ module ActionDispatch
false
end
rescue Exception => e # YAML, XML or Ruby code block errors
+ logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
+
raise
{ "body" => request.raw_post,
"content_type" => request.content_type,
@@ -67,5 +71,9 @@ module ActionDispatch
nil
end
+
+ def logger
+ defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 03761b10bd..a8768633cc 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -45,6 +45,7 @@ module ActionDispatch
ActiveSupport::Deprecation.warn('use replace instead', caller)
replace({})
else
+ load! unless @loaded
super(hash.stringify_keys)
end
end
@@ -54,6 +55,7 @@ module ActionDispatch
ActiveSupport::Deprecation.warn('use clear instead', caller)
clear
else
+ load! unless @loaded
super(key.to_s)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 547a2d2062..9cfd6956d0 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -1,3 +1,5 @@
+require "active_support/core_ext/hash/keys"
+
module ActionDispatch
module Session
# This cookie-based session store is the Rails default. Sessions typically
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index ade2d6f05e..4f71ea6165 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -27,10 +27,10 @@ module ActionDispatch
end
def klass
- if @klass.respond_to?(:call)
- @klass.call
- elsif @klass.is_a?(Class)
+ if @klass.respond_to?(:new)
@klass
+ elsif @klass.respond_to?(:call)
+ @klass.call
else
@klass.to_s.constantize
end
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index 20288aa7a5..8242ea4ef6 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -1,13 +1,308 @@
module ActionDispatch
class TestRequest < Request
- DEFAULT_ENV = Rack::MockRequest.env_for('/')
+ # Improve version of Multipart thats in rack/master
+ module Multipart #:nodoc:
+ class UploadedFile
+ # The filename, *not* including the path, of the "uploaded" file
+ attr_reader :original_filename
+
+ # The content type of the "uploaded" file
+ attr_accessor :content_type
+
+ def initialize(path, content_type = "text/plain", binary = false)
+ raise "#{path} file does not exist" unless ::File.exist?(path)
+ @content_type = content_type
+ @original_filename = ::File.basename(path)
+ @tempfile = Tempfile.new(@original_filename)
+ @tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
+ @tempfile.binmode if binary
+ FileUtils.copy_file(path, @tempfile.path)
+ end
+
+ def path
+ @tempfile.path
+ end
+ alias_method :local_path, :path
+
+ def method_missing(method_name, *args, &block) #:nodoc:
+ @tempfile.__send__(method_name, *args, &block)
+ end
+ end
+
+ EOL = "\r\n"
+ MULTIPART_BOUNDARY = "AaB03x"
+
+ def self.parse_multipart(env)
+ unless env['CONTENT_TYPE'] =~
+ %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
+ nil
+ else
+ boundary = "--#{$1}"
+
+ params = {}
+ buf = ""
+ content_length = env['CONTENT_LENGTH'].to_i
+ input = env['rack.input']
+ input.rewind
+
+ boundary_size = Utils.bytesize(boundary) + EOL.size
+ bufsize = 16384
+
+ content_length -= boundary_size
+
+ read_buffer = ''
+
+ status = input.read(boundary_size, read_buffer)
+ raise EOFError, "bad content body" unless status == boundary + EOL
+
+ rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n
+
+ loop {
+ head = nil
+ body = ''
+ filename = content_type = name = nil
+
+ until head && buf =~ rx
+ if !head && i = buf.index(EOL+EOL)
+ head = buf.slice!(0, i+2) # First \r\n
+ buf.slice!(0, 2) # Second \r\n
+
+ filename = head[/Content-Disposition:.* filename="?([^\";]*)"?/ni, 1]
+ content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
+ name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
+
+ if content_type || filename
+ body = Tempfile.new("RackMultipart")
+ body.binmode if body.respond_to?(:binmode)
+ end
+
+ next
+ end
+
+ # Save the read body part.
+ if head && (boundary_size+4 < buf.size)
+ body << buf.slice!(0, buf.size - (boundary_size+4))
+ end
+
+ c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer)
+ raise EOFError, "bad content body" if c.nil? || c.empty?
+ buf << c
+ content_length -= c.size
+ end
+
+ # Save the rest.
+ if i = buf.index(rx)
+ body << buf.slice!(0, i)
+ buf.slice!(0, boundary_size+2)
+
+ content_length = -1 if $1 == "--"
+ end
+
+ if filename == ""
+ # filename is blank which means no file has been selected
+ data = nil
+ elsif filename
+ body.rewind
+
+ # 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.
+ filename =~ /^(?:.*[:\\\/])?(.*)/m
+ filename = $1
+
+ data = {:filename => filename, :type => content_type,
+ :name => name, :tempfile => body, :head => head}
+ elsif !filename && content_type
+ body.rewind
+
+ # Generic multipart cases, not coming from a form
+ data = {:type => content_type,
+ :name => name, :tempfile => body, :head => head}
+ else
+ data = body
+ end
+
+ Utils.normalize_params(params, name, data) unless data.nil?
+
+ break if buf.empty? || content_length == -1
+ }
+
+ input.rewind
+
+ params
+ end
+ end
+
+ def self.build_multipart(params, first = true)
+ if first
+ unless params.is_a?(Hash)
+ raise ArgumentError, "value must be a Hash"
+ end
+
+ multipart = false
+ query = lambda { |value|
+ case value
+ when Array
+ value.each(&query)
+ when Hash
+ value.values.each(&query)
+ when UploadedFile
+ multipart = true
+ end
+ }
+ params.values.each(&query)
+ return nil unless multipart
+ end
+
+ flattened_params = Hash.new
+
+ params.each do |key, value|
+ k = first ? key.to_s : "[#{key}]"
+
+ case value
+ when Array
+ value.map { |v|
+ build_multipart(v, false).each { |subkey, subvalue|
+ flattened_params["#{k}[]#{subkey}"] = subvalue
+ }
+ }
+ when Hash
+ build_multipart(value, false).each { |subkey, subvalue|
+ flattened_params[k + subkey] = subvalue
+ }
+ else
+ flattened_params[k] = value
+ end
+ end
+
+ if first
+ flattened_params.map { |name, file|
+ if file.respond_to?(:original_filename)
+ ::File.open(file.path, "rb") do |f|
+ f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
+<<-EOF
+--#{MULTIPART_BOUNDARY}\r
+Content-Disposition: form-data; name="#{name}"; filename="#{Rack::Utils.escape(file.original_filename)}"\r
+Content-Type: #{file.content_type}\r
+Content-Length: #{::File.stat(file.path).size}\r
+\r
+#{f.read}\r
+EOF
+ end
+ else
+<<-EOF
+--#{MULTIPART_BOUNDARY}\r
+Content-Disposition: form-data; name="#{name}"\r
+\r
+#{file}\r
+EOF
+ end
+ }.join + "--#{MULTIPART_BOUNDARY}--\r"
+ else
+ flattened_params
+ end
+ end
+ end
+
+ DEFAULT_ENV = {
+ "rack.version" => [1,0],
+ "rack.input" => StringIO.new,
+ "rack.errors" => StringIO.new,
+ "rack.multithread" => true,
+ "rack.multiprocess" => true,
+ "rack.run_once" => false,
+ }
+
+ # Improve version of env_for thats in rack/master
+ def self.env_for(uri="", opts={}) #:nodoc:
+ uri = URI(uri)
+ uri.path = "/#{uri.path}" unless uri.path[0] == ?/
+
+ env = DEFAULT_ENV.dup
+
+ env["REQUEST_METHOD"] = opts[:method] ? opts[:method].to_s.upcase : "GET"
+ env["SERVER_NAME"] = uri.host || "example.org"
+ env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
+ env["QUERY_STRING"] = uri.query.to_s
+ env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path
+ env["rack.url_scheme"] = uri.scheme || "http"
+ env["HTTPS"] = env["rack.url_scheme"] == "https" ? "on" : "off"
+
+ env["SCRIPT_NAME"] = opts[:script_name] || ""
+
+ if opts[:fatal]
+ env["rack.errors"] = FatalWarner.new
+ else
+ env["rack.errors"] = StringIO.new
+ end
+
+ if params = opts[:params]
+ if env["REQUEST_METHOD"] == "GET"
+ params = Rack::Utils.parse_nested_query(params) if params.is_a?(String)
+ params.update(Rack::Utils.parse_nested_query(env["QUERY_STRING"]))
+ env["QUERY_STRING"] = build_nested_query(params)
+ elsif !opts.has_key?(:input)
+ opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
+ if params.is_a?(Hash)
+ if data = Multipart.build_multipart(params)
+ opts[:input] = data
+ opts["CONTENT_LENGTH"] ||= data.length.to_s
+ opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Multipart::MULTIPART_BOUNDARY}"
+ else
+ opts[:input] = build_nested_query(params)
+ end
+ else
+ opts[:input] = params
+ end
+ end
+ end
+
+ empty_str = ""
+ empty_str.force_encoding("ASCII-8BIT") if empty_str.respond_to? :force_encoding
+ opts[:input] ||= empty_str
+ if String === opts[:input]
+ rack_input = StringIO.new(opts[:input])
+ else
+ rack_input = opts[:input]
+ end
+
+ rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
+ env['rack.input'] = rack_input
+
+ env["CONTENT_LENGTH"] ||= env["rack.input"].length.to_s
+
+ opts.each { |field, value|
+ env[field] = value if String === field
+ }
+
+ env
+ end
+
+ def self.build_nested_query(value, prefix = nil)
+ case value
+ when Array
+ value.map { |v|
+ build_nested_query(v, "#{prefix}[]")
+ }.join("&")
+ when Hash
+ value.map { |k, v|
+ build_nested_query(v, prefix ? "#{prefix}[#{Rack::Utils.escape(k)}]" : Rack::Utils.escape(k))
+ }.join("&")
+ when String
+ raise ArgumentError, "value must be a Hash" if prefix.nil?
+ "#{prefix}=#{Rack::Utils.escape(value)}"
+ else
+ prefix
+ end
+ end
def self.new(env = {})
super
end
def initialize(env = {})
- super(DEFAULT_ENV.merge(env))
+ super(self.class.env_for('/').merge(env))
self.host = 'test.host'
self.remote_addr = '0.0.0.0'
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack.rb
deleted file mode 100644
index 371d015690..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-# Copyright (C) 2007, 2008, 2009 Christian Neukirchen <purl.org/net/chneukirchen>
-#
-# Rack is freely distributable under the terms of an MIT-style license.
-# See COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-path = File.expand_path(File.dirname(__FILE__))
-$:.unshift(path) unless $:.include?(path)
-
-
-# The Rack main module, serving as a namespace for all core Rack
-# modules and classes.
-#
-# All modules meant for use in your application are <tt>autoload</tt>ed here,
-# so it should be enough just to <tt>require rack.rb</tt> in your code.
-
-module Rack
- # The Rack protocol version number implemented.
- VERSION = [1,0]
-
- # Return the Rack protocol version as a dotted string.
- def self.version
- VERSION.join(".")
- end
-
- # Return the Rack release as a dotted string.
- def self.release
- "1.0"
- end
-
- autoload :Builder, "rack/builder"
- autoload :Cascade, "rack/cascade"
- autoload :Chunked, "rack/chunked"
- autoload :CommonLogger, "rack/commonlogger"
- autoload :ConditionalGet, "rack/conditionalget"
- autoload :ContentLength, "rack/content_length"
- autoload :ContentType, "rack/content_type"
- autoload :File, "rack/file"
- autoload :Deflater, "rack/deflater"
- autoload :Directory, "rack/directory"
- autoload :ForwardRequest, "rack/recursive"
- autoload :Handler, "rack/handler"
- autoload :Head, "rack/head"
- autoload :Lint, "rack/lint"
- autoload :Lock, "rack/lock"
- autoload :MethodOverride, "rack/methodoverride"
- autoload :Mime, "rack/mime"
- autoload :Recursive, "rack/recursive"
- autoload :Reloader, "rack/reloader"
- autoload :ShowExceptions, "rack/showexceptions"
- autoload :ShowStatus, "rack/showstatus"
- autoload :Static, "rack/static"
- autoload :URLMap, "rack/urlmap"
- autoload :Utils, "rack/utils"
-
- autoload :MockRequest, "rack/mock"
- autoload :MockResponse, "rack/mock"
-
- autoload :Request, "rack/request"
- autoload :Response, "rack/response"
-
- module Auth
- autoload :Basic, "rack/auth/basic"
- autoload :AbstractRequest, "rack/auth/abstract/request"
- autoload :AbstractHandler, "rack/auth/abstract/handler"
- autoload :OpenID, "rack/auth/openid"
- module Digest
- autoload :MD5, "rack/auth/digest/md5"
- autoload :Nonce, "rack/auth/digest/nonce"
- autoload :Params, "rack/auth/digest/params"
- autoload :Request, "rack/auth/digest/request"
- end
- end
-
- module Session
- autoload :Cookie, "rack/session/cookie"
- autoload :Pool, "rack/session/pool"
- autoload :Memcache, "rack/session/memcache"
- end
-
- # *Adapters* connect Rack with third party web frameworks.
- #
- # Rack includes an adapter for Camping, see README for other
- # frameworks supporting Rack in their code bases.
- #
- # Refer to the submodules for framework-specific calling details.
-
- module Adapter
- autoload :Camping, "rack/adapter/camping"
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/adapter/camping.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/adapter/camping.rb
deleted file mode 100644
index 63bc787f54..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/adapter/camping.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-module Rack
- module Adapter
- class Camping
- def initialize(app)
- @app = app
- end
-
- def call(env)
- env["PATH_INFO"] ||= ""
- env["SCRIPT_NAME"] ||= ""
- controller = @app.run(env['rack.input'], env)
- h = controller.headers
- h.each_pair do |k,v|
- if v.kind_of? URI
- h[k] = v.to_s
- end
- end
- [controller.status, controller.headers, [controller.body.to_s]]
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/abstract/handler.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/abstract/handler.rb
deleted file mode 100644
index 214df6299e..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/abstract/handler.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-module Rack
- module Auth
- # Rack::Auth::AbstractHandler implements common authentication functionality.
- #
- # +realm+ should be set for all handlers.
-
- class AbstractHandler
-
- attr_accessor :realm
-
- def initialize(app, realm=nil, &authenticator)
- @app, @realm, @authenticator = app, realm, authenticator
- end
-
-
- private
-
- def unauthorized(www_authenticate = challenge)
- return [ 401,
- { 'Content-Type' => 'text/plain',
- 'Content-Length' => '0',
- 'WWW-Authenticate' => www_authenticate.to_s },
- []
- ]
- end
-
- def bad_request
- return [ 400,
- { 'Content-Type' => 'text/plain',
- 'Content-Length' => '0' },
- []
- ]
- end
-
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/abstract/request.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/abstract/request.rb
deleted file mode 100644
index 1d9ccec685..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/abstract/request.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-module Rack
- module Auth
- class AbstractRequest
-
- def initialize(env)
- @env = env
- end
-
- def provided?
- !authorization_key.nil?
- end
-
- def parts
- @parts ||= @env[authorization_key].split(' ', 2)
- end
-
- def scheme
- @scheme ||= parts.first.downcase.to_sym
- end
-
- def params
- @params ||= parts.last
- end
-
-
- private
-
- AUTHORIZATION_KEYS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION']
-
- def authorization_key
- @authorization_key ||= AUTHORIZATION_KEYS.detect { |key| @env.has_key?(key) }
- end
-
- end
-
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/basic.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/basic.rb
deleted file mode 100644
index 9557224648..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/basic.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-require 'rack/auth/abstract/handler'
-require 'rack/auth/abstract/request'
-
-module Rack
- module Auth
- # Rack::Auth::Basic implements HTTP Basic Authentication, as per RFC 2617.
- #
- # Initialize with the Rack application that you want protecting,
- # and a block that checks if a username and password pair are valid.
- #
- # See also: <tt>example/protectedlobster.rb</tt>
-
- class Basic < AbstractHandler
-
- def call(env)
- auth = Basic::Request.new(env)
-
- return unauthorized unless auth.provided?
-
- return bad_request unless auth.basic?
-
- if valid?(auth)
- env['REMOTE_USER'] = auth.username
-
- return @app.call(env)
- end
-
- unauthorized
- end
-
-
- private
-
- def challenge
- 'Basic realm="%s"' % realm
- end
-
- def valid?(auth)
- @authenticator.call(*auth.credentials)
- end
-
- class Request < Auth::AbstractRequest
- def basic?
- :basic == scheme
- end
-
- def credentials
- @credentials ||= params.unpack("m*").first.split(/:/, 2)
- end
-
- def username
- credentials.first
- end
- end
-
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/md5.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/md5.rb
deleted file mode 100644
index e579dc9632..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/md5.rb
+++ /dev/null
@@ -1,124 +0,0 @@
-require 'rack/auth/abstract/handler'
-require 'rack/auth/digest/request'
-require 'rack/auth/digest/params'
-require 'rack/auth/digest/nonce'
-require 'digest/md5'
-
-module Rack
- module Auth
- module Digest
- # Rack::Auth::Digest::MD5 implements the MD5 algorithm version of
- # HTTP Digest Authentication, as per RFC 2617.
- #
- # Initialize with the [Rack] application that you want protecting,
- # and a block that looks up a plaintext password for a given username.
- #
- # +opaque+ needs to be set to a constant base64/hexadecimal string.
- #
- class MD5 < AbstractHandler
-
- attr_accessor :opaque
-
- attr_writer :passwords_hashed
-
- def initialize(*args)
- super
- @passwords_hashed = nil
- end
-
- def passwords_hashed?
- !!@passwords_hashed
- end
-
- def call(env)
- auth = Request.new(env)
-
- unless auth.provided?
- return unauthorized
- end
-
- if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth)
- return bad_request
- end
-
- if valid?(auth)
- if auth.nonce.stale?
- return unauthorized(challenge(:stale => true))
- else
- env['REMOTE_USER'] = auth.username
-
- return @app.call(env)
- end
- end
-
- unauthorized
- end
-
-
- private
-
- QOP = 'auth'.freeze
-
- def params(hash = {})
- Params.new do |params|
- params['realm'] = realm
- params['nonce'] = Nonce.new.to_s
- params['opaque'] = H(opaque)
- params['qop'] = QOP
-
- hash.each { |k, v| params[k] = v }
- end
- end
-
- def challenge(hash = {})
- "Digest #{params(hash)}"
- end
-
- def valid?(auth)
- valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth)
- end
-
- def valid_qop?(auth)
- QOP == auth.qop
- end
-
- def valid_opaque?(auth)
- H(opaque) == auth.opaque
- end
-
- def valid_nonce?(auth)
- auth.nonce.valid?
- end
-
- def valid_digest?(auth)
- digest(auth, @authenticator.call(auth.username)) == auth.response
- end
-
- def md5(data)
- ::Digest::MD5.hexdigest(data)
- end
-
- alias :H :md5
-
- def KD(secret, data)
- H([secret, data] * ':')
- end
-
- def A1(auth, password)
- [ auth.username, auth.realm, password ] * ':'
- end
-
- def A2(auth)
- [ auth.method, auth.uri ] * ':'
- end
-
- def digest(auth, password)
- password_hash = passwords_hashed? ? password : H(A1(auth, password))
-
- KD(password_hash, [ auth.nonce, auth.nc, auth.cnonce, QOP, H(A2(auth)) ] * ':')
- end
-
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/nonce.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/nonce.rb
deleted file mode 100644
index dbe109f29a..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/nonce.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'digest/md5'
-
-module Rack
- module Auth
- module Digest
- # Rack::Auth::Digest::Nonce is the default nonce generator for the
- # Rack::Auth::Digest::MD5 authentication handler.
- #
- # +private_key+ needs to set to a constant string.
- #
- # +time_limit+ can be optionally set to an integer (number of seconds),
- # to limit the validity of the generated nonces.
-
- class Nonce
-
- class << self
- attr_accessor :private_key, :time_limit
- end
-
- def self.parse(string)
- new(*string.unpack("m*").first.split(' ', 2))
- end
-
- def initialize(timestamp = Time.now, given_digest = nil)
- @timestamp, @given_digest = timestamp.to_i, given_digest
- end
-
- def to_s
- [([ @timestamp, digest ] * ' ')].pack("m*").strip
- end
-
- def digest
- ::Digest::MD5.hexdigest([ @timestamp, self.class.private_key ] * ':')
- end
-
- def valid?
- digest == @given_digest
- end
-
- def stale?
- !self.class.time_limit.nil? && (@timestamp - Time.now.to_i) < self.class.time_limit
- end
-
- def fresh?
- !stale?
- end
-
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/params.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/params.rb
deleted file mode 100644
index 730e2efdc8..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/params.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-module Rack
- module Auth
- module Digest
- class Params < Hash
-
- def self.parse(str)
- split_header_value(str).inject(new) do |header, param|
- k, v = param.split('=', 2)
- header[k] = dequote(v)
- header
- end
- end
-
- def self.dequote(str) # From WEBrick::HTTPUtils
- ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
- ret.gsub!(/\\(.)/, "\\1")
- ret
- end
-
- def self.split_header_value(str)
- str.scan( /(\w+\=(?:"[^\"]+"|[^,]+))/n ).collect{ |v| v[0] }
- end
-
- def initialize
- super
-
- yield self if block_given?
- end
-
- def [](k)
- super k.to_s
- end
-
- def []=(k, v)
- super k.to_s, v.to_s
- end
-
- UNQUOTED = ['qop', 'nc', 'stale']
-
- def to_s
- inject([]) do |parts, (k, v)|
- parts << "#{k}=" + (UNQUOTED.include?(k) ? v.to_s : quote(v))
- parts
- end.join(', ')
- end
-
- def quote(str) # From WEBrick::HTTPUtils
- '"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
- end
-
- end
- end
- end
-end
-
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/request.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/request.rb
deleted file mode 100644
index a8aa3bf996..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/digest/request.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-require 'rack/auth/abstract/request'
-require 'rack/auth/digest/params'
-require 'rack/auth/digest/nonce'
-
-module Rack
- module Auth
- module Digest
- class Request < Auth::AbstractRequest
-
- def method
- @env['rack.methodoverride.original_method'] || @env['REQUEST_METHOD']
- end
-
- def digest?
- :digest == scheme
- end
-
- def correct_uri?
- (@env['SCRIPT_NAME'].to_s + @env['PATH_INFO'].to_s) == uri
- end
-
- def nonce
- @nonce ||= Nonce.parse(params['nonce'])
- end
-
- def params
- @params ||= Params.parse(parts.last)
- end
-
- def method_missing(sym)
- if params.has_key? key = sym.to_s
- return params[key]
- end
- super
- end
-
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/openid.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/openid.rb
deleted file mode 100644
index c5f6a5143e..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/auth/openid.rb
+++ /dev/null
@@ -1,480 +0,0 @@
-# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
-
-gem 'ruby-openid', '~> 2' if defined? Gem
-require 'rack/request'
-require 'rack/utils'
-require 'rack/auth/abstract/handler'
-require 'uri'
-require 'openid' #gem
-require 'openid/extension' #gem
-require 'openid/store/memory' #gem
-
-module Rack
- class Request
- def openid_request
- @env['rack.auth.openid.request']
- end
-
- def openid_response
- @env['rack.auth.openid.response']
- end
- end
-
- module Auth
-
- # Rack::Auth::OpenID provides a simple method for setting up an OpenID
- # Consumer. It requires the ruby-openid library from janrain to operate,
- # as well as a rack method of session management.
- #
- # The ruby-openid home page is at http://openidenabled.com/ruby-openid/.
- #
- # The OpenID specifications can be found at
- # http://openid.net/specs/openid-authentication-1_1.html
- # and
- # http://openid.net/specs/openid-authentication-2_0.html. Documentation
- # for published OpenID extensions and related topics can be found at
- # http://openid.net/developers/specs/.
- #
- # It is recommended to read through the OpenID spec, as well as
- # ruby-openid's documentation, to understand what exactly goes on. However
- # a setup as simple as the presented examples is enough to provide
- # Consumer functionality.
- #
- # This library strongly intends to utilize the OpenID 2.0 features of the
- # ruby-openid library, which provides OpenID 1.0 compatiblity.
- #
- # NOTE: Due to the amount of data that this library stores in the
- # session, Rack::Session::Cookie may fault.
-
- class OpenID
-
- class NoSession < RuntimeError; end
- class BadExtension < RuntimeError; end
- # Required for ruby-openid
- ValidStatus = [:success, :setup_needed, :cancel, :failure]
-
- # = Arguments
- #
- # The first argument is the realm, identifying the site they are trusting
- # with their identity. This is required, also treated as the trust_root
- # in OpenID 1.x exchanges.
- #
- # The optional second argument is a hash of options.
- #
- # == Options
- #
- # <tt>:return_to</tt> defines the url to return to after the client
- # authenticates with the openid service provider. This url should point
- # to where Rack::Auth::OpenID is mounted. If <tt>:return_to</tt> is not
- # provided, return_to will be the current url which allows flexibility
- # with caveats.
- #
- # <tt>:session_key</tt> defines the key to the session hash in the env.
- # It defaults to 'rack.session'.
- #
- # <tt>:openid_param</tt> defines at what key in the request parameters to
- # find the identifier to resolve. As per the 2.0 spec, the default is
- # 'openid_identifier'.
- #
- # <tt>:store</tt> defined what OpenID Store to use for persistant
- # information. By default a Store::Memory will be used.
- #
- # <tt>:immediate</tt> as true will make initial requests to be of an
- # immediate type. This is false by default. See OpenID specification
- # documentation.
- #
- # <tt>:extensions</tt> should be a hash of openid extension
- # implementations. The key should be the extension main module, the value
- # should be an array of arguments for extension::Request.new.
- # The hash is iterated over and passed to #add_extension for processing.
- # Please see #add_extension for further documentation.
- #
- # == Examples
- #
- # simple_oid = OpenID.new('http://mysite.com/')
- #
- # return_oid = OpenID.new('http://mysite.com/', {
- # :return_to => 'http://mysite.com/openid'
- # })
- #
- # complex_oid = OpenID.new('http://mysite.com/',
- # :immediate => true,
- # :extensions => {
- # ::OpenID::SReg => [['email'],['nickname']]
- # }
- # )
- #
- # = Advanced
- #
- # Most of the functionality of this library is encapsulated such that
- # expansion and overriding functions isn't difficult nor tricky.
- # Alternately, to avoid opening up singleton objects or subclassing, a
- # wrapper rack middleware can be composed to act upon Auth::OpenID's
- # responses. See #check and #finish for locations of pertinent data.
- #
- # == Responses
- #
- # To change the responses that Auth::OpenID returns, override the methods
- # #redirect, #bad_request, #unauthorized, #access_denied, and
- # #foreign_server_failure.
- #
- # Additionally #confirm_post_params is used when the URI would exceed
- # length limits on a GET request when doing the initial verification
- # request.
- #
- # == Processing
- #
- # To change methods of processing completed transactions, override the
- # methods #success, #setup_needed, #cancel, and #failure. Please ensure
- # the returned object is a rack compatible response.
- #
- # The first argument is an OpenID::Response, the second is a
- # Rack::Request of the current request, the last is the hash used in
- # ruby-openid handling, which can be found manually at
- # env['rack.session'][:openid].
- #
- # This is useful if you wanted to expand the processing done, such as
- # setting up user accounts.
- #
- # oid_app = Rack::Auth::OpenID.new realm, :return_to => return_to
- # def oid_app.success oid, request, session
- # user = Models::User[oid.identity_url]
- # user ||= Models::User.create_from_openid oid
- # request['rack.session'][:user] = user.id
- # redirect MyApp.site_home
- # end
- #
- # site_map['/openid'] = oid_app
- # map = Rack::URLMap.new site_map
- # ...
-
- def initialize(realm, options={})
- realm = URI(realm)
- raise ArgumentError, "Invalid realm: #{realm}" \
- unless realm.absolute? \
- and realm.fragment.nil? \
- and realm.scheme =~ /^https?$/ \
- and realm.host =~ /^(\*\.)?#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+/
- realm.path = '/' if realm.path.empty?
- @realm = realm.to_s
-
- if ruri = options[:return_to]
- ruri = URI(ruri)
- raise ArgumentError, "Invalid return_to: #{ruri}" \
- unless ruri.absolute? \
- and ruri.scheme =~ /^https?$/ \
- and ruri.fragment.nil?
- raise ArgumentError, "return_to #{ruri} not within realm #{realm}" \
- unless self.within_realm?(ruri)
- @return_to = ruri.to_s
- end
-
- @session_key = options[:session_key] || 'rack.session'
- @openid_param = options[:openid_param] || 'openid_identifier'
- @store = options[:store] || ::OpenID::Store::Memory.new
- @immediate = !!options[:immediate]
-
- @extensions = {}
- if extensions = options.delete(:extensions)
- extensions.each do |ext, args|
- add_extension ext, *args
- end
- end
-
- # Undocumented, semi-experimental
- @anonymous = !!options[:anonymous]
- end
-
- attr_reader :realm, :return_to, :session_key, :openid_param, :store,
- :immediate, :extensions
-
- # Sets up and uses session data at <tt>:openid</tt> within the session.
- # Errors in this setup will raise a NoSession exception.
- #
- # If the parameter 'openid.mode' is set, which implies a followup from
- # the openid server, processing is passed to #finish and the result is
- # returned. However, if there is no appropriate openid information in the
- # session, a 400 error is returned.
- #
- # If the parameter specified by <tt>options[:openid_param]</tt> is
- # present, processing is passed to #check and the result is returned.
- #
- # If neither of these conditions are met, #unauthorized is called.
-
- def call(env)
- env['rack.auth.openid'] = self
- env_session = env[@session_key]
- unless env_session and env_session.is_a?(Hash)
- raise NoSession, 'No compatible session'
- end
- # let us work in our own namespace...
- session = (env_session[:openid] ||= {})
- unless session and session.is_a?(Hash)
- raise NoSession, 'Incompatible openid session'
- end
-
- request = Rack::Request.new(env)
- consumer = ::OpenID::Consumer.new(session, @store)
-
- if mode = request.GET['openid.mode']
- if session.key?(:openid_param)
- finish(consumer, session, request)
- else
- bad_request
- end
- elsif request.GET[@openid_param]
- check(consumer, session, request)
- else
- unauthorized
- end
- end
-
- # As the first part of OpenID consumer action, #check retrieves the data
- # required for completion.
- #
- # If all parameters fit within the max length of a URI, a 303 redirect
- # will be returned. Otherwise #confirm_post_params will be called.
- #
- # Any messages from OpenID's request are logged to env['rack.errors']
- #
- # <tt>env['rack.auth.openid.request']</tt> is the openid checkid request
- # instance.
- #
- # <tt>session[:openid_param]</tt> is set to the openid identifier
- # provided by the user.
- #
- # <tt>session[:return_to]</tt> is set to the return_to uri given to the
- # identity provider.
-
- def check(consumer, session, req)
- oid = consumer.begin(req.GET[@openid_param], @anonymous)
- req.env['rack.auth.openid.request'] = oid
- req.env['rack.errors'].puts(oid.message)
- p oid if $DEBUG
-
- ## Extension support
- extensions.each do |ext,args|
- oid.add_extension(ext::Request.new(*args))
- end
-
- session[:openid_param] = req.GET[openid_param]
- return_to_uri = return_to ? return_to : req.url
- session[:return_to] = return_to_uri
- immediate = session.key?(:setup_needed) ? false : immediate
-
- if oid.send_redirect?(realm, return_to_uri, immediate)
- uri = oid.redirect_url(realm, return_to_uri, immediate)
- redirect(uri)
- else
- confirm_post_params(oid, realm, return_to_uri, immediate)
- end
- rescue ::OpenID::DiscoveryFailure => e
- # thrown from inside OpenID::Consumer#begin by yadis stuff
- req.env['rack.errors'].puts([e.message, *e.backtrace]*"\n")
- return foreign_server_failure
- end
-
- # This is the final portion of authentication.
- # If successful, a redirect to the realm is be returned.
- # Data gathered from extensions are stored in session[:openid] with the
- # extension's namespace uri as the key.
- #
- # Any messages from OpenID's response are logged to env['rack.errors']
- #
- # <tt>env['rack.auth.openid.response']</tt> will contain the openid
- # response.
-
- def finish(consumer, session, req)
- oid = consumer.complete(req.GET, req.url)
- req.env['rack.auth.openid.response'] = oid
- req.env['rack.errors'].puts(oid.message)
- p oid if $DEBUG
-
- raise unless ValidStatus.include?(oid.status)
- __send__(oid.status, oid, req, session)
- end
-
- # The first argument should be the main extension module.
- # The extension module should contain the constants:
- # * class Request, should have OpenID::Extension as an ancestor
- # * class Response, should have OpenID::Extension as an ancestor
- # * string NS_URI, which defining the namespace of the extension
- #
- # All trailing arguments will be passed to extension::Request.new in
- # #check.
- # The openid response will be passed to
- # extension::Response#from_success_response, #get_extension_args will be
- # called on the result to attain the gathered data.
- #
- # This method returns the key at which the response data will be found in
- # the session, which is the namespace uri by default.
-
- def add_extension(ext, *args)
- raise BadExtension unless valid_extension?(ext)
- extensions[ext] = args
- return ext::NS_URI
- end
-
- # Checks the validitity, in the context of usage, of a submitted
- # extension.
-
- def valid_extension?(ext)
- if not %w[NS_URI Request Response].all?{|c| ext.const_defined?(c) }
- raise ArgumentError, 'Extension is missing constants.'
- elsif not ext::Response.respond_to?(:from_success_response)
- raise ArgumentError, 'Response is missing required method.'
- end
- return true
- rescue
- return false
- end
-
- # Checks the provided uri to ensure it'd be considered within the realm.
- # is currently not compatible with wildcard realms.
-
- def within_realm? uri
- uri = URI.parse(uri.to_s)
- realm = URI.parse(self.realm)
- return false unless uri.absolute?
- return false unless uri.path[0, realm.path.size] == realm.path
- return false unless uri.host == realm.host or realm.host[/^\*\./]
- # for wildcard support, is awkward with URI limitations
- realm_match = Regexp.escape(realm.host).
- sub(/^\*\./,"^#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+.")+'$'
- return false unless uri.host.match(realm_match)
- return true
- end
- alias_method :include?, :within_realm?
-
- protected
-
- ### These methods define some of the boilerplate responses.
-
- # Returns an html form page for posting to an Identity Provider if the
- # GET request would exceed the upper URI length limit.
-
- def confirm_post_params(oid, realm, return_to, immediate)
- Rack::Response.new.finish do |r|
- r.write '<html><head><title>Confirm...</title></head><body>'
- r.write oid.form_markup(realm, return_to, immediate)
- r.write '</body></html>'
- end
- end
-
- # Returns a 303 redirect with the destination of that provided by the
- # argument.
-
- def redirect(uri)
- [ 303, {'Content-Length'=>'0', 'Content-Type'=>'text/plain',
- 'Location' => uri},
- [] ]
- end
-
- # Returns an empty 400 response.
-
- def bad_request
- [ 400, {'Content-Type'=>'text/plain', 'Content-Length'=>'0'},
- [''] ]
- end
-
- # Returns a basic unauthorized 401 response.
-
- def unauthorized
- [ 401, {'Content-Type' => 'text/plain', 'Content-Length' => '13'},
- ['Unauthorized.'] ]
- end
-
- # Returns a basic access denied 403 response.
-
- def access_denied
- [ 403, {'Content-Type' => 'text/plain', 'Content-Length' => '14'},
- ['Access denied.'] ]
- end
-
- # Returns a 503 response to be used if communication with the remote
- # OpenID server fails.
-
- def foreign_server_failure
- [ 503, {'Content-Type'=>'text/plain', 'Content-Length' => '23'},
- ['Foreign server failure.'] ]
- end
-
- private
-
- ### These methods are called after a transaction is completed, depending
- # on its outcome. These should all return a rack compatible response.
- # You'd want to override these to provide additional functionality.
-
- # Called to complete processing on a successful transaction.
- # Within the openid session, :openid_identity and :openid_identifier are
- # set to the user friendly and the standard representation of the
- # validated identity. All other data in the openid session is cleared.
-
- def success(oid, request, session)
- session.clear
- session[:openid_identity] = oid.display_identifier
- session[:openid_identifier] = oid.identity_url
- extensions.keys.each do |ext|
- label = ext.name[/[^:]+$/].downcase
- response = ext::Response.from_success_response(oid)
- session[label] = response.data
- end
- redirect(realm)
- end
-
- # Called if the Identity Provider indicates further setup by the user is
- # required.
- # The identifier is retrived from the openid session at :openid_param.
- # And :setup_needed is set to true to prevent looping.
-
- def setup_needed(oid, request, session)
- identifier = session[:openid_param]
- session[:setup_needed] = true
- redirect req.script_name + '?' + openid_param + '=' + identifier
- end
-
- # Called if the user indicates they wish to cancel identification.
- # Data within openid session is cleared.
-
- def cancel(oid, request, session)
- session.clear
- access_denied
- end
-
- # Called if the Identity Provider indicates the user is unable to confirm
- # their identity. Data within the openid session is left alone, in case
- # of swarm auth attacks.
-
- def failure(oid, request, session)
- unauthorized
- end
- end
-
- # A class developed out of the request to use OpenID as an authentication
- # middleware. The request will be sent to the OpenID instance unless the
- # block evaluates to true. For example in rackup, you can use it as such:
- #
- # use Rack::Session::Pool
- # use Rack::Auth::OpenIDAuth, realm, openid_options do |env|
- # env['rack.session'][:authkey] == a_string
- # end
- # run RackApp
- #
- # Or simply:
- #
- # app = Rack::Auth::OpenIDAuth.new app, realm, openid_options, &auth
-
- class OpenIDAuth < Rack::Auth::AbstractHandler
- attr_reader :oid
- def initialize(app, realm, options={}, &auth)
- @oid = OpenID.new(realm, options)
- super(app, &auth)
- end
-
- def call(env)
- to = auth.call(env) ? @app : @oid
- to.call env
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/builder.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/builder.rb
deleted file mode 100644
index 295235e56a..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/builder.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-module Rack
- # Rack::Builder implements a small DSL to iteratively construct Rack
- # applications.
- #
- # Example:
- #
- # app = Rack::Builder.new {
- # use Rack::CommonLogger
- # use Rack::ShowExceptions
- # map "/lobster" do
- # use Rack::Lint
- # run Rack::Lobster.new
- # end
- # }
- #
- # Or
- #
- # app = Rack::Builder.app do
- # use Rack::CommonLogger
- # lambda { |env| [200, {'Content-Type' => 'text/plain'}, 'OK'] }
- # end
- #
- # +use+ adds a middleware to the stack, +run+ dispatches to an application.
- # You can use +map+ to construct a Rack::URLMap in a convenient way.
-
- class Builder
- def initialize(&block)
- @ins = []
- instance_eval(&block) if block_given?
- end
-
- def self.app(&block)
- self.new(&block).to_app
- end
-
- def use(middleware, *args, &block)
- @ins << lambda { |app| middleware.new(app, *args, &block) }
- end
-
- def run(app)
- @ins << app #lambda { |nothing| app }
- end
-
- def map(path, &block)
- if @ins.last.kind_of? Hash
- @ins.last[path] = self.class.new(&block).to_app
- else
- @ins << {}
- map(path, &block)
- end
- end
-
- def to_app
- @ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last
- inner_app = @ins.last
- @ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) }
- end
-
- def call(env)
- to_app.call(env)
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/cascade.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/cascade.rb
deleted file mode 100644
index a038aa1105..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/cascade.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module Rack
- # Rack::Cascade tries an request on several apps, and returns the
- # first response that is not 404 (or in a list of configurable
- # status codes).
-
- class Cascade
- attr_reader :apps
-
- def initialize(apps, catch=404)
- @apps = apps
- @catch = [*catch]
- end
-
- def call(env)
- status = headers = body = nil
- raise ArgumentError, "empty cascade" if @apps.empty?
- @apps.each { |app|
- begin
- status, headers, body = app.call(env)
- break unless @catch.include?(status.to_i)
- end
- }
- [status, headers, body]
- end
-
- def add app
- @apps << app
- end
-
- def include? app
- @apps.include? app
- end
-
- alias_method :<<, :add
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/chunked.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/chunked.rb
deleted file mode 100644
index 280d89dd65..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/chunked.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-require 'rack/utils'
-
-module Rack
-
- # Middleware that applies chunked transfer encoding to response bodies
- # when the response does not include a Content-Length header.
- class Chunked
- include Rack::Utils
-
- def initialize(app)
- @app = app
- end
-
- def call(env)
- status, headers, body = @app.call(env)
- headers = HeaderHash.new(headers)
-
- if env['HTTP_VERSION'] == 'HTTP/1.0' ||
- STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
- headers['Content-Length'] ||
- headers['Transfer-Encoding']
- [status, headers.to_hash, body]
- else
- dup.chunk(status, headers, body)
- end
- end
-
- def chunk(status, headers, body)
- @body = body
- headers.delete('Content-Length')
- headers['Transfer-Encoding'] = 'chunked'
- [status, headers.to_hash, self]
- end
-
- def each
- term = "\r\n"
- @body.each do |chunk|
- size = bytesize(chunk)
- next if size == 0
- yield [size.to_s(16), term, chunk, term].join
- end
- yield ["0", term, "", term].join
- end
-
- def close
- @body.close if @body.respond_to?(:close)
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/commonlogger.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/commonlogger.rb
deleted file mode 100644
index 5e68ac626d..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/commonlogger.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-module Rack
- # Rack::CommonLogger forwards every request to an +app+ given, and
- # logs a line in the Apache common log format to the +logger+, or
- # rack.errors by default.
-
- class CommonLogger
- def initialize(app, logger=nil)
- @app = app
- @logger = logger
- end
-
- def call(env)
- dup._call(env)
- end
-
- def _call(env)
- @env = env
- @logger ||= self
- @time = Time.now
- @status, @header, @body = @app.call(env)
- [@status, @header, self]
- end
-
- def close
- @body.close if @body.respond_to? :close
- end
-
- # By default, log to rack.errors.
- def <<(str)
- @env["rack.errors"].write(str)
- @env["rack.errors"].flush
- end
-
- def each
- length = 0
- @body.each { |part|
- length += part.size
- yield part
- }
-
- @now = Time.now
-
- # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
- # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 -
- # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
- @logger << %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n} %
- [
- @env['HTTP_X_FORWARDED_FOR'] || @env["REMOTE_ADDR"] || "-",
- @env["REMOTE_USER"] || "-",
- @now.strftime("%d/%b/%Y %H:%M:%S"),
- @env["REQUEST_METHOD"],
- @env["PATH_INFO"],
- @env["QUERY_STRING"].empty? ? "" : "?"+@env["QUERY_STRING"],
- @env["HTTP_VERSION"],
- @status.to_s[0..3],
- (length.zero? ? "-" : length.to_s),
- @now - @time
- ]
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/conditionalget.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/conditionalget.rb
deleted file mode 100644
index 046ebdb00a..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/conditionalget.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-require 'rack/utils'
-
-module Rack
-
- # Middleware that enables conditional GET using If-None-Match and
- # If-Modified-Since. The application should set either or both of the
- # Last-Modified or Etag response headers according to RFC 2616. When
- # either of the conditions is met, the response body is set to be zero
- # length and the response status is set to 304 Not Modified.
- #
- # Applications that defer response body generation until the body's each
- # message is received will avoid response body generation completely when
- # a conditional GET matches.
- #
- # Adapted from Michael Klishin's Merb implementation:
- # http://github.com/wycats/merb-core/tree/master/lib/merb-core/rack/middleware/conditional_get.rb
- class ConditionalGet
- def initialize(app)
- @app = app
- end
-
- def call(env)
- return @app.call(env) unless %w[GET HEAD].include?(env['REQUEST_METHOD'])
-
- status, headers, body = @app.call(env)
- headers = Utils::HeaderHash.new(headers)
- if etag_matches?(env, headers) || modified_since?(env, headers)
- status = 304
- headers.delete('Content-Type')
- headers.delete('Content-Length')
- body = []
- end
- [status, headers, body]
- end
-
- private
- def etag_matches?(env, headers)
- etag = headers['Etag'] and etag == env['HTTP_IF_NONE_MATCH']
- end
-
- def modified_since?(env, headers)
- last_modified = headers['Last-Modified'] and
- last_modified == env['HTTP_IF_MODIFIED_SINCE']
- end
- end
-
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/content_length.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/content_length.rb
deleted file mode 100644
index 1e56d43853..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/content_length.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require 'rack/utils'
-
-module Rack
- # Sets the Content-Length header on responses with fixed-length bodies.
- class ContentLength
- include Rack::Utils
-
- def initialize(app)
- @app = app
- end
-
- def call(env)
- status, headers, body = @app.call(env)
- headers = HeaderHash.new(headers)
-
- if !STATUS_WITH_NO_ENTITY_BODY.include?(status) &&
- !headers['Content-Length'] &&
- !headers['Transfer-Encoding'] &&
- (body.respond_to?(:to_ary) || body.respond_to?(:to_str))
-
- body = [body] if body.respond_to?(:to_str) # rack 0.4 compat
- length = body.to_ary.inject(0) { |len, part| len + bytesize(part) }
- headers['Content-Length'] = length.to_s
- end
-
- [status, headers, body]
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/content_type.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/content_type.rb
deleted file mode 100644
index 0c1e1ca3e1..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/content_type.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'rack/utils'
-
-module Rack
-
- # Sets the Content-Type header on responses which don't have one.
- #
- # Builder Usage:
- # use Rack::ContentType, "text/plain"
- #
- # When no content type argument is provided, "text/html" is assumed.
- class ContentType
- def initialize(app, content_type = "text/html")
- @app, @content_type = app, content_type
- end
-
- def call(env)
- status, headers, body = @app.call(env)
- headers = Utils::HeaderHash.new(headers)
- headers['Content-Type'] ||= @content_type
- [status, headers.to_hash, body]
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/deflater.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/deflater.rb
deleted file mode 100644
index 14137a944d..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/deflater.rb
+++ /dev/null
@@ -1,96 +0,0 @@
-require "zlib"
-require "stringio"
-require "time" # for Time.httpdate
-require 'rack/utils'
-
-module Rack
- class Deflater
- def initialize(app)
- @app = app
- end
-
- def call(env)
- status, headers, body = @app.call(env)
- headers = Utils::HeaderHash.new(headers)
-
- # Skip compressing empty entity body responses and responses with
- # no-transform set.
- if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
- headers['Cache-Control'].to_s =~ /\bno-transform\b/
- return [status, headers, body]
- end
-
- request = Request.new(env)
-
- encoding = Utils.select_best_encoding(%w(gzip deflate identity),
- request.accept_encoding)
-
- # Set the Vary HTTP header.
- vary = headers["Vary"].to_s.split(",").map { |v| v.strip }
- unless vary.include?("*") || vary.include?("Accept-Encoding")
- headers["Vary"] = vary.push("Accept-Encoding").join(",")
- end
-
- case encoding
- when "gzip"
- headers['Content-Encoding'] = "gzip"
- headers.delete('Content-Length')
- mtime = headers.key?("Last-Modified") ?
- Time.httpdate(headers["Last-Modified"]) : Time.now
- [status, headers, GzipStream.new(body, mtime)]
- when "deflate"
- headers['Content-Encoding'] = "deflate"
- headers.delete('Content-Length')
- [status, headers, DeflateStream.new(body)]
- when "identity"
- [status, headers, body]
- when nil
- message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
- [406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, [message]]
- end
- end
-
- class GzipStream
- def initialize(body, mtime)
- @body = body
- @mtime = mtime
- end
-
- def each(&block)
- @writer = block
- gzip =::Zlib::GzipWriter.new(self)
- gzip.mtime = @mtime
- @body.each { |part| gzip << part }
- @body.close if @body.respond_to?(:close)
- gzip.close
- @writer = nil
- end
-
- def write(data)
- @writer.call(data)
- end
- end
-
- class DeflateStream
- DEFLATE_ARGS = [
- Zlib::DEFAULT_COMPRESSION,
- # drop the zlib header which causes both Safari and IE to choke
- -Zlib::MAX_WBITS,
- Zlib::DEF_MEM_LEVEL,
- Zlib::DEFAULT_STRATEGY
- ]
-
- def initialize(body)
- @body = body
- end
-
- def each
- deflater = ::Zlib::Deflate.new(*DEFLATE_ARGS)
- @body.each { |part| yield deflater.deflate(part) }
- @body.close if @body.respond_to?(:close)
- yield deflater.finish
- nil
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/directory.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/directory.rb
deleted file mode 100644
index acdd3029d3..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/directory.rb
+++ /dev/null
@@ -1,153 +0,0 @@
-require 'time'
-require 'rack/utils'
-require 'rack/mime'
-
-module Rack
- # Rack::Directory serves entries below the +root+ given, according to the
- # path info of the Rack request. If a directory is found, the file's contents
- # will be presented in an html based index. If a file is found, the env will
- # be passed to the specified +app+.
- #
- # If +app+ is not specified, a Rack::File of the same +root+ will be used.
-
- class Directory
- DIR_FILE = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>"
- DIR_PAGE = <<-PAGE
-<html><head>
- <title>%s</title>
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <style type='text/css'>
-table { width:100%%; }
-.name { text-align:left; }
-.size, .mtime { text-align:right; }
-.type { width:11em; }
-.mtime { width:15em; }
- </style>
-</head><body>
-<h1>%s</h1>
-<hr />
-<table>
- <tr>
- <th class='name'>Name</th>
- <th class='size'>Size</th>
- <th class='type'>Type</th>
- <th class='mtime'>Last Modified</th>
- </tr>
-%s
-</table>
-<hr />
-</body></html>
- PAGE
-
- attr_reader :files
- attr_accessor :root, :path
-
- def initialize(root, app=nil)
- @root = F.expand_path(root)
- @app = app || Rack::File.new(@root)
- end
-
- def call(env)
- dup._call(env)
- end
-
- F = ::File
-
- def _call(env)
- @env = env
- @script_name = env['SCRIPT_NAME']
- @path_info = Utils.unescape(env['PATH_INFO'])
-
- if forbidden = check_forbidden
- forbidden
- else
- @path = F.join(@root, @path_info)
- list_path
- end
- end
-
- def check_forbidden
- return unless @path_info.include? ".."
-
- body = "Forbidden\n"
- size = Rack::Utils.bytesize(body)
- return [403, {"Content-Type" => "text/plain","Content-Length" => size.to_s}, [body]]
- end
-
- def list_directory
- @files = [['../','Parent Directory','','','']]
- glob = F.join(@path, '*')
-
- Dir[glob].sort.each do |node|
- stat = stat(node)
- next unless stat
- basename = F.basename(node)
- ext = F.extname(node)
-
- url = F.join(@script_name, @path_info, basename)
- size = stat.size
- type = stat.directory? ? 'directory' : Mime.mime_type(ext)
- size = stat.directory? ? '-' : filesize_format(size)
- mtime = stat.mtime.httpdate
- url << '/' if stat.directory?
- basename << '/' if stat.directory?
-
- @files << [ url, basename, size, type, mtime ]
- end
-
- return [ 200, {'Content-Type'=>'text/html; charset=utf-8'}, self ]
- end
-
- def stat(node, max = 10)
- F.stat(node)
- rescue Errno::ENOENT, Errno::ELOOP
- return nil
- end
-
- # TODO: add correct response if not readable, not sure if 404 is the best
- # option
- def list_path
- @stat = F.stat(@path)
-
- if @stat.readable?
- return @app.call(@env) if @stat.file?
- return list_directory if @stat.directory?
- else
- raise Errno::ENOENT, 'No such file or directory'
- end
-
- rescue Errno::ENOENT, Errno::ELOOP
- return entity_not_found
- end
-
- def entity_not_found
- body = "Entity not found: #{@path_info}\n"
- size = Rack::Utils.bytesize(body)
- return [404, {"Content-Type" => "text/plain", "Content-Length" => size.to_s}, [body]]
- end
-
- def each
- show_path = @path.sub(/^#{@root}/,'')
- files = @files.map{|f| DIR_FILE % f }*"\n"
- page = DIR_PAGE % [ show_path, show_path , files ]
- page.each_line{|l| yield l }
- end
-
- # Stolen from Ramaze
-
- FILESIZE_FORMAT = [
- ['%.1fT', 1 << 40],
- ['%.1fG', 1 << 30],
- ['%.1fM', 1 << 20],
- ['%.1fK', 1 << 10],
- ]
-
- def filesize_format(int)
- FILESIZE_FORMAT.each do |format, size|
- return format % (int.to_f / size) if int >= size
- end
-
- int.to_s + 'B'
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/file.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/file.rb
deleted file mode 100644
index fe62bd6b86..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/file.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-require 'time'
-require 'rack/utils'
-require 'rack/mime'
-
-module Rack
- # Rack::File serves files below the +root+ given, according to the
- # path info of the Rack request.
- #
- # Handlers can detect if bodies are a Rack::File, and use mechanisms
- # like sendfile on the +path+.
-
- class File
- attr_accessor :root
- attr_accessor :path
-
- alias :to_path :path
-
- def initialize(root)
- @root = root
- end
-
- def call(env)
- dup._call(env)
- end
-
- F = ::File
-
- def _call(env)
- @path_info = Utils.unescape(env["PATH_INFO"])
- return forbidden if @path_info.include? ".."
-
- @path = F.join(@root, @path_info)
-
- begin
- if F.file?(@path) && F.readable?(@path)
- serving
- else
- raise Errno::EPERM
- end
- rescue SystemCallError
- not_found
- end
- end
-
- def forbidden
- body = "Forbidden\n"
- [403, {"Content-Type" => "text/plain",
- "Content-Length" => body.size.to_s},
- [body]]
- end
-
- # NOTE:
- # We check via File::size? whether this file provides size info
- # via stat (e.g. /proc files often don't), otherwise we have to
- # figure it out by reading the whole file into memory. And while
- # we're at it we also use this as body then.
-
- def serving
- if size = F.size?(@path)
- body = self
- else
- body = [F.read(@path)]
- size = Utils.bytesize(body.first)
- end
-
- [200, {
- "Last-Modified" => F.mtime(@path).httpdate,
- "Content-Type" => Mime.mime_type(F.extname(@path), 'text/plain'),
- "Content-Length" => size.to_s
- }, body]
- end
-
- def not_found
- body = "File not found: #{@path_info}\n"
- [404, {"Content-Type" => "text/plain",
- "Content-Length" => body.size.to_s},
- [body]]
- end
-
- def each
- F.open(@path, "rb") { |file|
- while part = file.read(8192)
- yield part
- end
- }
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler.rb
deleted file mode 100644
index 5624a1e79d..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-module Rack
- # *Handlers* connect web servers with Rack.
- #
- # Rack includes Handlers for Mongrel, WEBrick, FastCGI, CGI, SCGI
- # and LiteSpeed.
- #
- # Handlers usually are activated by calling <tt>MyHandler.run(myapp)</tt>.
- # A second optional hash can be passed to include server-specific
- # configuration.
- module Handler
- def self.get(server)
- return unless server
- server = server.to_s
-
- if klass = @handlers[server]
- obj = Object
- klass.split("::").each { |x| obj = obj.const_get(x) }
- obj
- else
- try_require('rack/handler', server)
- const_get(server)
- end
- end
-
- # Transforms server-name constants to their canonical form as filenames,
- # then tries to require them but silences the LoadError if not found
- #
- # Naming convention:
- #
- # Foo # => 'foo'
- # FooBar # => 'foo_bar.rb'
- # FooBAR # => 'foobar.rb'
- # FOObar # => 'foobar.rb'
- # FOOBAR # => 'foobar.rb'
- # FooBarBaz # => 'foo_bar_baz.rb'
- def self.try_require(prefix, const_name)
- file = const_name.gsub(/^[A-Z]+/) { |pre| pre.downcase }.
- gsub(/[A-Z]+[^A-Z]/, '_\&').downcase
-
- require(::File.join(prefix, file))
- rescue LoadError
- end
-
- def self.register(server, klass)
- @handlers ||= {}
- @handlers[server] = klass
- end
-
- autoload :CGI, "rack/handler/cgi"
- autoload :FastCGI, "rack/handler/fastcgi"
- autoload :Mongrel, "rack/handler/mongrel"
- autoload :EventedMongrel, "rack/handler/evented_mongrel"
- autoload :SwiftipliedMongrel, "rack/handler/swiftiplied_mongrel"
- autoload :WEBrick, "rack/handler/webrick"
- autoload :LSWS, "rack/handler/lsws"
- autoload :SCGI, "rack/handler/scgi"
- autoload :Thin, "rack/handler/thin"
-
- register 'cgi', 'Rack::Handler::CGI'
- register 'fastcgi', 'Rack::Handler::FastCGI'
- register 'mongrel', 'Rack::Handler::Mongrel'
- register 'emongrel', 'Rack::Handler::EventedMongrel'
- register 'smongrel', 'Rack::Handler::SwiftipliedMongrel'
- register 'webrick', 'Rack::Handler::WEBrick'
- register 'lsws', 'Rack::Handler::LSWS'
- register 'scgi', 'Rack::Handler::SCGI'
- register 'thin', 'Rack::Handler::Thin'
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/cgi.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/cgi.rb
deleted file mode 100644
index f45f3d735a..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/cgi.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-require 'rack/content_length'
-
-module Rack
- module Handler
- class CGI
- def self.run(app, options=nil)
- serve app
- end
-
- def self.serve(app)
- app = ContentLength.new(app)
-
- env = ENV.to_hash
- env.delete "HTTP_CONTENT_LENGTH"
-
- env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
-
- env.update({"rack.version" => [1,0],
- "rack.input" => $stdin,
- "rack.errors" => $stderr,
-
- "rack.multithread" => false,
- "rack.multiprocess" => true,
- "rack.run_once" => true,
-
- "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
- })
-
- env["QUERY_STRING"] ||= ""
- env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
- env["REQUEST_PATH"] ||= "/"
-
- status, headers, body = app.call(env)
- begin
- send_headers status, headers
- send_body body
- ensure
- body.close if body.respond_to? :close
- end
- end
-
- def self.send_headers(status, headers)
- STDOUT.print "Status: #{status}\r\n"
- headers.each { |k, vs|
- vs.split("\n").each { |v|
- STDOUT.print "#{k}: #{v}\r\n"
- }
- }
- STDOUT.print "\r\n"
- STDOUT.flush
- end
-
- def self.send_body(body)
- body.each { |part|
- STDOUT.print part
- STDOUT.flush
- }
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/evented_mongrel.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/evented_mongrel.rb
deleted file mode 100644
index 0f5cbf7293..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/evented_mongrel.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-require 'swiftcore/evented_mongrel'
-
-module Rack
- module Handler
- class EventedMongrel < Handler::Mongrel
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/fastcgi.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/fastcgi.rb
deleted file mode 100644
index 11e1fcaa74..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/fastcgi.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-require 'fcgi'
-require 'socket'
-require 'rack/content_length'
-require 'rack/rewindable_input'
-
-class FCGI::Stream
- alias _rack_read_without_buffer read
-
- def read(n, buffer=nil)
- buf = _rack_read_without_buffer n
- buffer.replace(buf.to_s) if buffer
- buf
- end
-end
-
-module Rack
- module Handler
- class FastCGI
- def self.run(app, options={})
- file = options[:File] and STDIN.reopen(UNIXServer.new(file))
- port = options[:Port] and STDIN.reopen(TCPServer.new(port))
- FCGI.each { |request|
- serve request, app
- }
- end
-
- def self.serve(request, app)
- app = Rack::ContentLength.new(app)
-
- env = request.env
- env.delete "HTTP_CONTENT_LENGTH"
-
- env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
-
- rack_input = RewindableInput.new(request.in)
-
- env.update({"rack.version" => [1,0],
- "rack.input" => rack_input,
- "rack.errors" => request.err,
-
- "rack.multithread" => false,
- "rack.multiprocess" => true,
- "rack.run_once" => false,
-
- "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
- })
-
- env["QUERY_STRING"] ||= ""
- env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
- env["REQUEST_PATH"] ||= "/"
- env.delete "PATH_INFO" if env["PATH_INFO"] == ""
- env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == ""
- env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == ""
-
- begin
- status, headers, body = app.call(env)
- begin
- send_headers request.out, status, headers
- send_body request.out, body
- ensure
- body.close if body.respond_to? :close
- end
- ensure
- rack_input.close
- request.finish
- end
- end
-
- def self.send_headers(out, status, headers)
- out.print "Status: #{status}\r\n"
- headers.each { |k, vs|
- vs.split("\n").each { |v|
- out.print "#{k}: #{v}\r\n"
- }
- }
- out.print "\r\n"
- out.flush
- end
-
- def self.send_body(out, body)
- body.each { |part|
- out.print part
- out.flush
- }
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/lsws.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/lsws.rb
deleted file mode 100644
index 7231336d7b..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/lsws.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-require 'lsapi'
-require 'rack/content_length'
-
-module Rack
- module Handler
- class LSWS
- def self.run(app, options=nil)
- while LSAPI.accept != nil
- serve app
- end
- end
- def self.serve(app)
- app = Rack::ContentLength.new(app)
-
- env = ENV.to_hash
- env.delete "HTTP_CONTENT_LENGTH"
- env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
- env.update({"rack.version" => [1,0],
- "rack.input" => StringIO.new($stdin.read.to_s),
- "rack.errors" => $stderr,
- "rack.multithread" => false,
- "rack.multiprocess" => true,
- "rack.run_once" => false,
- "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
- })
- env["QUERY_STRING"] ||= ""
- env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
- env["REQUEST_PATH"] ||= "/"
- status, headers, body = app.call(env)
- begin
- send_headers status, headers
- send_body body
- ensure
- body.close if body.respond_to? :close
- end
- end
- def self.send_headers(status, headers)
- print "Status: #{status}\r\n"
- headers.each { |k, vs|
- vs.split("\n").each { |v|
- print "#{k}: #{v}\r\n"
- }
- }
- print "\r\n"
- STDOUT.flush
- end
- def self.send_body(body)
- body.each { |part|
- print part
- STDOUT.flush
- }
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/mongrel.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/mongrel.rb
deleted file mode 100644
index 3a5ef32d4b..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/mongrel.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-require 'mongrel'
-require 'stringio'
-require 'rack/content_length'
-require 'rack/chunked'
-
-module Rack
- module Handler
- class Mongrel < ::Mongrel::HttpHandler
- def self.run(app, options={})
- server = ::Mongrel::HttpServer.new(options[:Host] || '0.0.0.0',
- options[:Port] || 8080)
- # Acts like Rack::URLMap, utilizing Mongrel's own path finding methods.
- # Use is similar to #run, replacing the app argument with a hash of
- # { path=>app, ... } or an instance of Rack::URLMap.
- if options[:map]
- if app.is_a? Hash
- app.each do |path, appl|
- path = '/'+path unless path[0] == ?/
- server.register(path, Rack::Handler::Mongrel.new(appl))
- end
- elsif app.is_a? URLMap
- app.instance_variable_get(:@mapping).each do |(host, path, appl)|
- next if !host.nil? && !options[:Host].nil? && options[:Host] != host
- path = '/'+path unless path[0] == ?/
- server.register(path, Rack::Handler::Mongrel.new(appl))
- end
- else
- raise ArgumentError, "first argument should be a Hash or URLMap"
- end
- else
- server.register('/', Rack::Handler::Mongrel.new(app))
- end
- yield server if block_given?
- server.run.join
- end
-
- def initialize(app)
- @app = Rack::Chunked.new(Rack::ContentLength.new(app))
- end
-
- def process(request, response)
- env = {}.replace(request.params)
- env.delete "HTTP_CONTENT_TYPE"
- env.delete "HTTP_CONTENT_LENGTH"
-
- env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
-
- env.update({"rack.version" => [1,0],
- "rack.input" => request.body || StringIO.new(""),
- "rack.errors" => $stderr,
-
- "rack.multithread" => true,
- "rack.multiprocess" => false, # ???
- "rack.run_once" => false,
-
- "rack.url_scheme" => "http",
- })
- env["QUERY_STRING"] ||= ""
- env.delete "PATH_INFO" if env["PATH_INFO"] == ""
-
- status, headers, body = @app.call(env)
-
- begin
- response.status = status.to_i
- response.send_status(nil)
-
- headers.each { |k, vs|
- vs.split("\n").each { |v|
- response.header[k] = v
- }
- }
- response.send_header
-
- body.each { |part|
- response.write part
- response.socket.flush
- }
- ensure
- body.close if body.respond_to? :close
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/scgi.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/scgi.rb
deleted file mode 100644
index 6c4932df95..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/scgi.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'scgi'
-require 'stringio'
-require 'rack/content_length'
-require 'rack/chunked'
-
-module Rack
- module Handler
- class SCGI < ::SCGI::Processor
- attr_accessor :app
-
- def self.run(app, options=nil)
- new(options.merge(:app=>app,
- :host=>options[:Host],
- :port=>options[:Port],
- :socket=>options[:Socket])).listen
- end
-
- def initialize(settings = {})
- @app = Rack::Chunked.new(Rack::ContentLength.new(settings[:app]))
- @log = Object.new
- def @log.info(*args); end
- def @log.error(*args); end
- super(settings)
- end
-
- def process_request(request, input_body, socket)
- env = {}.replace(request)
- env.delete "HTTP_CONTENT_TYPE"
- env.delete "HTTP_CONTENT_LENGTH"
- env["REQUEST_PATH"], env["QUERY_STRING"] = env["REQUEST_URI"].split('?', 2)
- env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
- env["PATH_INFO"] = env["REQUEST_PATH"]
- env["QUERY_STRING"] ||= ""
- env["SCRIPT_NAME"] = ""
- env.update({"rack.version" => [1,0],
- "rack.input" => StringIO.new(input_body),
- "rack.errors" => $stderr,
-
- "rack.multithread" => true,
- "rack.multiprocess" => true,
- "rack.run_once" => false,
-
- "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
- })
- status, headers, body = app.call(env)
- begin
- socket.write("Status: #{status}\r\n")
- headers.each do |k, vs|
- vs.split("\n").each { |v| socket.write("#{k}: #{v}\r\n")}
- end
- socket.write("\r\n")
- body.each {|s| socket.write(s)}
- ensure
- body.close if body.respond_to? :close
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/swiftiplied_mongrel.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/swiftiplied_mongrel.rb
deleted file mode 100644
index 4bafd0b953..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/swiftiplied_mongrel.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-require 'swiftcore/swiftiplied_mongrel'
-
-module Rack
- module Handler
- class SwiftipliedMongrel < Handler::Mongrel
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/thin.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/thin.rb
deleted file mode 100644
index 3d4fedff75..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/thin.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require "thin"
-require "rack/content_length"
-require "rack/chunked"
-
-module Rack
- module Handler
- class Thin
- def self.run(app, options={})
- app = Rack::Chunked.new(Rack::ContentLength.new(app))
- server = ::Thin::Server.new(options[:Host] || '0.0.0.0',
- options[:Port] || 8080,
- app)
- yield server if block_given?
- server.start
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/webrick.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/webrick.rb
deleted file mode 100644
index 2bdc83a9ff..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/handler/webrick.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-require 'webrick'
-require 'stringio'
-require 'rack/content_length'
-
-module Rack
- module Handler
- class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
- def self.run(app, options={})
- server = ::WEBrick::HTTPServer.new(options)
- server.mount "/", Rack::Handler::WEBrick, app
- trap(:INT) { server.shutdown }
- yield server if block_given?
- server.start
- end
-
- def initialize(server, app)
- super server
- @app = Rack::ContentLength.new(app)
- end
-
- def service(req, res)
- env = req.meta_vars
- env.delete_if { |k, v| v.nil? }
-
- env.update({"rack.version" => [1,0],
- "rack.input" => StringIO.new(req.body.to_s),
- "rack.errors" => $stderr,
-
- "rack.multithread" => true,
- "rack.multiprocess" => false,
- "rack.run_once" => false,
-
- "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
- })
-
- env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
- env["QUERY_STRING"] ||= ""
- env["REQUEST_PATH"] ||= "/"
- if env["PATH_INFO"] == ""
- env.delete "PATH_INFO"
- else
- path, n = req.request_uri.path, env["SCRIPT_NAME"].length
- env["PATH_INFO"] = path[n, path.length-n]
- end
-
- status, headers, body = @app.call(env)
- begin
- res.status = status.to_i
- headers.each { |k, vs|
- if k.downcase == "set-cookie"
- res.cookies.concat vs.split("\n")
- else
- vs.split("\n").each { |v|
- res[k] = v
- }
- end
- }
- body.each { |part|
- res.body << part
- }
- ensure
- body.close if body.respond_to? :close
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/head.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/head.rb
deleted file mode 100644
index deab822a99..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/head.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module Rack
-
-class Head
- def initialize(app)
- @app = app
- end
-
- def call(env)
- status, headers, body = @app.call(env)
-
- if env["REQUEST_METHOD"] == "HEAD"
- [status, headers, []]
- else
- [status, headers, body]
- end
- end
-end
-
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/lint.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/lint.rb
deleted file mode 100644
index bf2e9787a1..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/lint.rb
+++ /dev/null
@@ -1,537 +0,0 @@
-require 'rack/utils'
-
-module Rack
- # Rack::Lint validates your application and the requests and
- # responses according to the Rack spec.
-
- class Lint
- def initialize(app)
- @app = app
- end
-
- # :stopdoc:
-
- class LintError < RuntimeError; end
- module Assertion
- def assert(message, &block)
- unless block.call
- raise LintError, message
- end
- end
- end
- include Assertion
-
- ## This specification aims to formalize the Rack protocol. You
- ## can (and should) use Rack::Lint to enforce it.
- ##
- ## When you develop middleware, be sure to add a Lint before and
- ## after to catch all mistakes.
-
- ## = Rack applications
-
- ## A Rack application is an Ruby object (not a class) that
- ## responds to +call+.
- def call(env=nil)
- dup._call(env)
- end
-
- def _call(env)
- ## It takes exactly one argument, the *environment*
- assert("No env given") { env }
- check_env env
-
- env['rack.input'] = InputWrapper.new(env['rack.input'])
- env['rack.errors'] = ErrorWrapper.new(env['rack.errors'])
-
- ## and returns an Array of exactly three values:
- status, headers, @body = @app.call(env)
- ## The *status*,
- check_status status
- ## the *headers*,
- check_headers headers
- ## and the *body*.
- check_content_type status, headers
- check_content_length status, headers, env
- [status, headers, self]
- end
-
- ## == The Environment
- def check_env(env)
- ## The environment must be an true instance of Hash (no
- ## subclassing allowed) that includes CGI-like headers.
- ## The application is free to modify the environment.
- assert("env #{env.inspect} is not a Hash, but #{env.class}") {
- env.instance_of? Hash
- }
-
- ##
- ## The environment is required to include these variables
- ## (adopted from PEP333), except when they'd be empty, but see
- ## below.
-
- ## <tt>REQUEST_METHOD</tt>:: The HTTP request method, such as
- ## "GET" or "POST". This cannot ever
- ## be an empty string, and so is
- ## always required.
-
- ## <tt>SCRIPT_NAME</tt>:: The initial portion of the request
- ## URL's "path" that corresponds to the
- ## application object, so that the
- ## application knows its virtual
- ## "location". This may be an empty
- ## string, if the application corresponds
- ## to the "root" of the server.
-
- ## <tt>PATH_INFO</tt>:: The remainder of the request URL's
- ## "path", designating the virtual
- ## "location" of the request's target
- ## within the application. This may be an
- ## empty string, if the request URL targets
- ## the application root and does not have a
- ## trailing slash. This value may be
- ## percent-encoded when I originating from
- ## a URL.
-
- ## <tt>QUERY_STRING</tt>:: The portion of the request URL that
- ## follows the <tt>?</tt>, if any. May be
- ## empty, but is always required!
-
- ## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>:: When combined with <tt>SCRIPT_NAME</tt> and <tt>PATH_INFO</tt>, these variables can be used to complete the URL. Note, however, that <tt>HTTP_HOST</tt>, if present, should be used in preference to <tt>SERVER_NAME</tt> for reconstructing the request URL. <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt> can never be empty strings, and so are always required.
-
- ## <tt>HTTP_</tt> Variables:: Variables corresponding to the
- ## client-supplied HTTP request
- ## headers (i.e., variables whose
- ## names begin with <tt>HTTP_</tt>). The
- ## presence or absence of these
- ## variables should correspond with
- ## the presence or absence of the
- ## appropriate HTTP header in the
- ## request.
-
- ## In addition to this, the Rack environment must include these
- ## Rack-specific variables:
-
- ## <tt>rack.version</tt>:: The Array [1,0], representing this version of Rack.
- ## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the request URL.
- ## <tt>rack.input</tt>:: See below, the input stream.
- ## <tt>rack.errors</tt>:: See below, the error stream.
- ## <tt>rack.multithread</tt>:: true if the application object may be simultaneously invoked by another thread in the same process, false otherwise.
- ## <tt>rack.multiprocess</tt>:: true if an equivalent application object may be simultaneously invoked by another process, false otherwise.
- ## <tt>rack.run_once</tt>:: true if the server expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. Normally, this will only be true for a server based on CGI (or something similar).
- ##
-
- ## Additional environment specifications have approved to
- ## standardized middleware APIs. None of these are required to
- ## be implemented by the server.
-
- ## <tt>rack.session</tt>:: A hash like interface for storing request session data.
- ## The store must implement:
- if session = env['rack.session']
- ## store(key, value) (aliased as []=);
- assert("session #{session.inspect} must respond to store and []=") {
- session.respond_to?(:store) && session.respond_to?(:[]=)
- }
-
- ## fetch(key, default = nil) (aliased as []);
- assert("session #{session.inspect} must respond to fetch and []") {
- session.respond_to?(:fetch) && session.respond_to?(:[])
- }
-
- ## delete(key);
- assert("session #{session.inspect} must respond to delete") {
- session.respond_to?(:delete)
- }
-
- ## clear;
- assert("session #{session.inspect} must respond to clear") {
- session.respond_to?(:clear)
- }
- end
-
- ## The server or the application can store their own data in the
- ## environment, too. The keys must contain at least one dot,
- ## and should be prefixed uniquely. The prefix <tt>rack.</tt>
- ## is reserved for use with the Rack core distribution and other
- ## accepted specifications and must not be used otherwise.
- ##
-
- %w[REQUEST_METHOD SERVER_NAME SERVER_PORT
- QUERY_STRING
- rack.version rack.input rack.errors
- rack.multithread rack.multiprocess rack.run_once].each { |header|
- assert("env missing required key #{header}") { env.include? header }
- }
-
- ## The environment must not contain the keys
- ## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
- ## (use the versions without <tt>HTTP_</tt>).
- %w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
- assert("env contains #{header}, must use #{header[5,-1]}") {
- not env.include? header
- }
- }
-
- ## The CGI keys (named without a period) must have String values.
- env.each { |key, value|
- next if key.include? "." # Skip extensions
- assert("env variable #{key} has non-string value #{value.inspect}") {
- value.instance_of? String
- }
- }
-
- ##
- ## There are the following restrictions:
-
- ## * <tt>rack.version</tt> must be an array of Integers.
- assert("rack.version must be an Array, was #{env["rack.version"].class}") {
- env["rack.version"].instance_of? Array
- }
- ## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
- assert("rack.url_scheme unknown: #{env["rack.url_scheme"].inspect}") {
- %w[http https].include? env["rack.url_scheme"]
- }
-
- ## * There must be a valid input stream in <tt>rack.input</tt>.
- check_input env["rack.input"]
- ## * There must be a valid error stream in <tt>rack.errors</tt>.
- check_error env["rack.errors"]
-
- ## * The <tt>REQUEST_METHOD</tt> must be a valid token.
- assert("REQUEST_METHOD unknown: #{env["REQUEST_METHOD"]}") {
- env["REQUEST_METHOD"] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
- }
-
- ## * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
- assert("SCRIPT_NAME must start with /") {
- !env.include?("SCRIPT_NAME") ||
- env["SCRIPT_NAME"] == "" ||
- env["SCRIPT_NAME"] =~ /\A\//
- }
- ## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
- assert("PATH_INFO must start with /") {
- !env.include?("PATH_INFO") ||
- env["PATH_INFO"] == "" ||
- env["PATH_INFO"] =~ /\A\//
- }
- ## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
- assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") {
- !env.include?("CONTENT_LENGTH") || env["CONTENT_LENGTH"] =~ /\A\d+\z/
- }
-
- ## * One of <tt>SCRIPT_NAME</tt> or <tt>PATH_INFO</tt> must be
- ## set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
- ## <tt>SCRIPT_NAME</tt> is empty.
- assert("One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)") {
- env["SCRIPT_NAME"] || env["PATH_INFO"]
- }
- ## <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
- assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") {
- env["SCRIPT_NAME"] != "/"
- }
- end
-
- ## === The Input Stream
- ##
- ## The input stream is an IO-like object which contains the raw HTTP
- ## POST data. If it is a file then it must be opened in binary mode.
- def check_input(input)
- ## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
- [:gets, :each, :read, :rewind].each { |method|
- assert("rack.input #{input} does not respond to ##{method}") {
- input.respond_to? method
- }
- }
- end
-
- class InputWrapper
- include Assertion
-
- def initialize(input)
- @input = input
- end
-
- def size
- @input.size
- end
-
- ## * +gets+ must be called without arguments and return a string,
- ## or +nil+ on EOF.
- def gets(*args)
- assert("rack.input#gets called with arguments") { args.size == 0 }
- v = @input.gets
- assert("rack.input#gets didn't return a String") {
- v.nil? or v.instance_of? String
- }
- v
- end
-
- ## * +read+ behaves like IO#read. Its signature is <tt>read([length, [buffer]])</tt>.
- ## If given, +length+ must be an non-negative Integer (>= 0) or +nil+, and +buffer+ must
- ## be a String and may not be nil. If +length+ is given and not nil, then this method
- ## reads at most +length+ bytes from the input stream. If +length+ is not given or nil,
- ## then this method reads all data until EOF.
- ## When EOF is reached, this method returns nil if +length+ is given and not nil, or ""
- ## if +length+ is not given or is nil.
- ## If +buffer+ is given, then the read data will be placed into +buffer+ instead of a
- ## newly created String object.
- def read(*args)
- assert("rack.input#read called with too many arguments") {
- args.size <= 2
- }
- if args.size >= 1
- assert("rack.input#read called with non-integer and non-nil length") {
- args.first.kind_of?(Integer) || args.first.nil?
- }
- assert("rack.input#read called with a negative length") {
- args.first.nil? || args.first >= 0
- }
- end
- if args.size >= 2
- assert("rack.input#read called with non-String buffer") {
- args[1].kind_of?(String)
- }
- end
-
- v = @input.read(*args)
-
- assert("rack.input#read didn't return nil or a String") {
- v.nil? or v.instance_of? String
- }
- if args[0].nil?
- assert("rack.input#read(nil) returned nil on EOF") {
- !v.nil?
- }
- end
-
- v
- end
-
- ## * +each+ must be called without arguments and only yield Strings.
- def each(*args)
- assert("rack.input#each called with arguments") { args.size == 0 }
- @input.each { |line|
- assert("rack.input#each didn't yield a String") {
- line.instance_of? String
- }
- yield line
- }
- end
-
- ## * +rewind+ must be called without arguments. It rewinds the input
- ## stream back to the beginning. It must not raise Errno::ESPIPE:
- ## that is, it may not be a pipe or a socket. Therefore, handler
- ## developers must buffer the input data into some rewindable object
- ## if the underlying input stream is not rewindable.
- def rewind(*args)
- assert("rack.input#rewind called with arguments") { args.size == 0 }
- assert("rack.input#rewind raised Errno::ESPIPE") {
- begin
- @input.rewind
- true
- rescue Errno::ESPIPE
- false
- end
- }
- end
-
- ## * +close+ must never be called on the input stream.
- def close(*args)
- assert("rack.input#close must not be called") { false }
- end
- end
-
- ## === The Error Stream
- def check_error(error)
- ## The error stream must respond to +puts+, +write+ and +flush+.
- [:puts, :write, :flush].each { |method|
- assert("rack.error #{error} does not respond to ##{method}") {
- error.respond_to? method
- }
- }
- end
-
- class ErrorWrapper
- include Assertion
-
- def initialize(error)
- @error = error
- end
-
- ## * +puts+ must be called with a single argument that responds to +to_s+.
- def puts(str)
- @error.puts str
- end
-
- ## * +write+ must be called with a single argument that is a String.
- def write(str)
- assert("rack.errors#write not called with a String") { str.instance_of? String }
- @error.write str
- end
-
- ## * +flush+ must be called without arguments and must be called
- ## in order to make the error appear for sure.
- def flush
- @error.flush
- end
-
- ## * +close+ must never be called on the error stream.
- def close(*args)
- assert("rack.errors#close must not be called") { false }
- end
- end
-
- ## == The Response
-
- ## === The Status
- def check_status(status)
- ## This is an HTTP status. When parsed as integer (+to_i+), it must be
- ## greater than or equal to 100.
- assert("Status must be >=100 seen as integer") { status.to_i >= 100 }
- end
-
- ## === The Headers
- def check_headers(header)
- ## The header must respond to +each+, and yield values of key and value.
- assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
- header.respond_to? :each
- }
- header.each { |key, value|
- ## The header keys must be Strings.
- assert("header key must be a string, was #{key.class}") {
- key.instance_of? String
- }
- ## The header must not contain a +Status+ key,
- assert("header must not contain Status") { key.downcase != "status" }
- ## contain keys with <tt>:</tt> or newlines in their name,
- assert("header names must not contain : or \\n") { key !~ /[:\n]/ }
- ## contain keys names that end in <tt>-</tt> or <tt>_</tt>,
- assert("header names must not end in - or _") { key !~ /[-_]\z/ }
- ## but only contain keys that consist of
- ## letters, digits, <tt>_</tt> or <tt>-</tt> and start with a letter.
- assert("invalid header name: #{key}") { key =~ /\A[a-zA-Z][a-zA-Z0-9_-]*\z/ }
-
- ## The values of the header must be Strings,
- assert("a header value must be a String, but the value of " +
- "'#{key}' is a #{value.class}") { value.kind_of? String }
- ## consisting of lines (for multiple header values, e.g. multiple
- ## <tt>Set-Cookie</tt> values) seperated by "\n".
- value.split("\n").each { |item|
- ## The lines must not contain characters below 037.
- assert("invalid header value #{key}: #{item.inspect}") {
- item !~ /[\000-\037]/
- }
- }
- }
- end
-
- ## === The Content-Type
- def check_content_type(status, headers)
- headers.each { |key, value|
- ## There must be a <tt>Content-Type</tt>, except when the
- ## +Status+ is 1xx, 204 or 304, in which case there must be none
- ## given.
- if key.downcase == "content-type"
- assert("Content-Type header found in #{status} response, not allowed") {
- not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
- }
- return
- end
- }
- assert("No Content-Type header found") {
- Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
- }
- end
-
- ## === The Content-Length
- def check_content_length(status, headers, env)
- headers.each { |key, value|
- if key.downcase == 'content-length'
- ## There must not be a <tt>Content-Length</tt> header when the
- ## +Status+ is 1xx, 204 or 304.
- assert("Content-Length header found in #{status} response, not allowed") {
- not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
- }
-
- bytes = 0
- string_body = true
-
- if @body.respond_to?(:to_ary)
- @body.each { |part|
- unless part.kind_of?(String)
- string_body = false
- break
- end
-
- bytes += Rack::Utils.bytesize(part)
- }
-
- if env["REQUEST_METHOD"] == "HEAD"
- assert("Response body was given for HEAD request, but should be empty") {
- bytes == 0
- }
- else
- if string_body
- assert("Content-Length header was #{value}, but should be #{bytes}") {
- value == bytes.to_s
- }
- end
- end
- end
-
- return
- end
- }
- end
-
- ## === The Body
- def each
- @closed = false
- ## The Body must respond to +each+
- @body.each { |part|
- ## and must only yield String values.
- assert("Body yielded non-string value #{part.inspect}") {
- part.instance_of? String
- }
- yield part
- }
- ##
- ## The Body itself should not be an instance of String, as this will
- ## break in Ruby 1.9.
- ##
- ## If the Body responds to +close+, it will be called after iteration.
- # XXX howto: assert("Body has not been closed") { @closed }
-
-
- ##
- ## If the Body responds to +to_path+, it must return a String
- ## identifying the location of a file whose contents are identical
- ## to that produced by calling +each+; this may be used by the
- ## server as an alternative, possibly more efficient way to
- ## transport the response.
-
- if @body.respond_to?(:to_path)
- assert("The file identified by body.to_path does not exist") {
- ::File.exist? @body.to_path
- }
- end
-
- ##
- ## The Body commonly is an Array of Strings, the application
- ## instance itself, or a File-like object.
- end
-
- def close
- @closed = true
- @body.close if @body.respond_to?(:close)
- end
-
- # :startdoc:
-
- end
-end
-
-## == Thanks
-## Some parts of this specification are adopted from PEP333: Python
-## Web Server Gateway Interface
-## v1.0 (http://www.python.org/dev/peps/pep-0333/). I'd like to thank
-## everyone involved in that effort.
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/lobster.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/lobster.rb
deleted file mode 100644
index f63f419a49..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/lobster.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-require 'zlib'
-
-require 'rack/request'
-require 'rack/response'
-
-module Rack
- # Paste has a Pony, Rack has a Lobster!
- class Lobster
- LobsterString = Zlib::Inflate.inflate("eJx9kEEOwyAMBO99xd7MAcytUhPlJyj2
- P6jy9i4k9EQyGAnBarEXeCBqSkntNXsi/ZCvC48zGQoZKikGrFMZvgS5ZHd+aGWVuWwhVF0
- t1drVmiR42HcWNz5w3QanT+2gIvTVCiE1lm1Y0eU4JGmIIbaKwextKn8rvW+p5PIwFl8ZWJ
- I8jyiTlhTcYXkekJAzTyYN6E08A+dk8voBkAVTJQ==".delete("\n ").unpack("m*")[0])
-
- LambdaLobster = lambda { |env|
- if env["QUERY_STRING"].include?("flip")
- lobster = LobsterString.split("\n").
- map { |line| line.ljust(42).reverse }.
- join("\n")
- href = "?"
- else
- lobster = LobsterString
- href = "?flip"
- end
-
- content = ["<title>Lobstericious!</title>",
- "<pre>", lobster, "</pre>",
- "<a href='#{href}'>flip!</a>"]
- length = content.inject(0) { |a,e| a+e.size }.to_s
- [200, {"Content-Type" => "text/html", "Content-Length" => length}, content]
- }
-
- def call(env)
- req = Request.new(env)
- if req.GET["flip"] == "left"
- lobster = LobsterString.split("\n").
- map { |line| line.ljust(42).reverse }.
- join("\n")
- href = "?flip=right"
- elsif req.GET["flip"] == "crash"
- raise "Lobster crashed"
- else
- lobster = LobsterString
- href = "?flip=left"
- end
-
- res = Response.new
- res.write "<title>Lobstericious!</title>"
- res.write "<pre>"
- res.write lobster
- res.write "</pre>"
- res.write "<p><a href='#{href}'>flip!</a></p>"
- res.write "<p><a href='?flip=crash'>crash!</a></p>"
- res.finish
- end
-
- end
-end
-
-if $0 == __FILE__
- require 'rack'
- require 'rack/showexceptions'
- Rack::Handler::WEBrick.run \
- Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)),
- :Port => 9292
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/lock.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/lock.rb
deleted file mode 100644
index 93238528c4..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/lock.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-module Rack
- class Lock
- FLAG = 'rack.multithread'.freeze
-
- def initialize(app, lock = Mutex.new)
- @app, @lock = app, lock
- end
-
- def call(env)
- old, env[FLAG] = env[FLAG], false
- @lock.synchronize { @app.call(env) }
- ensure
- env[FLAG] = old
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/methodoverride.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/methodoverride.rb
deleted file mode 100644
index 0eed29f471..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/methodoverride.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-module Rack
- class MethodOverride
- HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS)
-
- METHOD_OVERRIDE_PARAM_KEY = "_method".freeze
- HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE".freeze
-
- def initialize(app)
- @app = app
- end
-
- def call(env)
- if env["REQUEST_METHOD"] == "POST"
- req = Request.new(env)
- method = req.POST[METHOD_OVERRIDE_PARAM_KEY] ||
- env[HTTP_METHOD_OVERRIDE_HEADER]
- method = method.to_s.upcase
- if HTTP_METHODS.include?(method)
- env["rack.methodoverride.original_method"] = env["REQUEST_METHOD"]
- env["REQUEST_METHOD"] = method
- end
- end
-
- @app.call(env)
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/mime.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/mime.rb
deleted file mode 100644
index 5a6a73a97b..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/mime.rb
+++ /dev/null
@@ -1,204 +0,0 @@
-module Rack
- module Mime
- # Returns String with mime type if found, otherwise use +fallback+.
- # +ext+ should be filename extension in the '.ext' format that
- # File.extname(file) returns.
- # +fallback+ may be any object
- #
- # Also see the documentation for MIME_TYPES
- #
- # Usage:
- # Rack::Mime.mime_type('.foo')
- #
- # This is a shortcut for:
- # Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream')
-
- def mime_type(ext, fallback='application/octet-stream')
- MIME_TYPES.fetch(ext, fallback)
- end
- module_function :mime_type
-
- # List of most common mime-types, selected various sources
- # according to their usefulness in a webserving scope for Ruby
- # users.
- #
- # To amend this list with your local mime.types list you can use:
- #
- # require 'webrick/httputils'
- # list = WEBrick::HTTPUtils.load_mime_types('/etc/mime.types')
- # Rack::Mime::MIME_TYPES.merge!(list)
- #
- # To add the list mongrel provides, use:
- #
- # require 'mongrel/handlers'
- # Rack::Mime::MIME_TYPES.merge!(Mongrel::DirHandler::MIME_TYPES)
-
- MIME_TYPES = {
- ".3gp" => "video/3gpp",
- ".a" => "application/octet-stream",
- ".ai" => "application/postscript",
- ".aif" => "audio/x-aiff",
- ".aiff" => "audio/x-aiff",
- ".asc" => "application/pgp-signature",
- ".asf" => "video/x-ms-asf",
- ".asm" => "text/x-asm",
- ".asx" => "video/x-ms-asf",
- ".atom" => "application/atom+xml",
- ".au" => "audio/basic",
- ".avi" => "video/x-msvideo",
- ".bat" => "application/x-msdownload",
- ".bin" => "application/octet-stream",
- ".bmp" => "image/bmp",
- ".bz2" => "application/x-bzip2",
- ".c" => "text/x-c",
- ".cab" => "application/vnd.ms-cab-compressed",
- ".cc" => "text/x-c",
- ".chm" => "application/vnd.ms-htmlhelp",
- ".class" => "application/octet-stream",
- ".com" => "application/x-msdownload",
- ".conf" => "text/plain",
- ".cpp" => "text/x-c",
- ".crt" => "application/x-x509-ca-cert",
- ".css" => "text/css",
- ".csv" => "text/csv",
- ".cxx" => "text/x-c",
- ".deb" => "application/x-debian-package",
- ".der" => "application/x-x509-ca-cert",
- ".diff" => "text/x-diff",
- ".djv" => "image/vnd.djvu",
- ".djvu" => "image/vnd.djvu",
- ".dll" => "application/x-msdownload",
- ".dmg" => "application/octet-stream",
- ".doc" => "application/msword",
- ".dot" => "application/msword",
- ".dtd" => "application/xml-dtd",
- ".dvi" => "application/x-dvi",
- ".ear" => "application/java-archive",
- ".eml" => "message/rfc822",
- ".eps" => "application/postscript",
- ".exe" => "application/x-msdownload",
- ".f" => "text/x-fortran",
- ".f77" => "text/x-fortran",
- ".f90" => "text/x-fortran",
- ".flv" => "video/x-flv",
- ".for" => "text/x-fortran",
- ".gem" => "application/octet-stream",
- ".gemspec" => "text/x-script.ruby",
- ".gif" => "image/gif",
- ".gz" => "application/x-gzip",
- ".h" => "text/x-c",
- ".hh" => "text/x-c",
- ".htm" => "text/html",
- ".html" => "text/html",
- ".ico" => "image/vnd.microsoft.icon",
- ".ics" => "text/calendar",
- ".ifb" => "text/calendar",
- ".iso" => "application/octet-stream",
- ".jar" => "application/java-archive",
- ".java" => "text/x-java-source",
- ".jnlp" => "application/x-java-jnlp-file",
- ".jpeg" => "image/jpeg",
- ".jpg" => "image/jpeg",
- ".js" => "application/javascript",
- ".json" => "application/json",
- ".log" => "text/plain",
- ".m3u" => "audio/x-mpegurl",
- ".m4v" => "video/mp4",
- ".man" => "text/troff",
- ".mathml" => "application/mathml+xml",
- ".mbox" => "application/mbox",
- ".mdoc" => "text/troff",
- ".me" => "text/troff",
- ".mid" => "audio/midi",
- ".midi" => "audio/midi",
- ".mime" => "message/rfc822",
- ".mml" => "application/mathml+xml",
- ".mng" => "video/x-mng",
- ".mov" => "video/quicktime",
- ".mp3" => "audio/mpeg",
- ".mp4" => "video/mp4",
- ".mp4v" => "video/mp4",
- ".mpeg" => "video/mpeg",
- ".mpg" => "video/mpeg",
- ".ms" => "text/troff",
- ".msi" => "application/x-msdownload",
- ".odp" => "application/vnd.oasis.opendocument.presentation",
- ".ods" => "application/vnd.oasis.opendocument.spreadsheet",
- ".odt" => "application/vnd.oasis.opendocument.text",
- ".ogg" => "application/ogg",
- ".p" => "text/x-pascal",
- ".pas" => "text/x-pascal",
- ".pbm" => "image/x-portable-bitmap",
- ".pdf" => "application/pdf",
- ".pem" => "application/x-x509-ca-cert",
- ".pgm" => "image/x-portable-graymap",
- ".pgp" => "application/pgp-encrypted",
- ".pkg" => "application/octet-stream",
- ".pl" => "text/x-script.perl",
- ".pm" => "text/x-script.perl-module",
- ".png" => "image/png",
- ".pnm" => "image/x-portable-anymap",
- ".ppm" => "image/x-portable-pixmap",
- ".pps" => "application/vnd.ms-powerpoint",
- ".ppt" => "application/vnd.ms-powerpoint",
- ".ps" => "application/postscript",
- ".psd" => "image/vnd.adobe.photoshop",
- ".py" => "text/x-script.python",
- ".qt" => "video/quicktime",
- ".ra" => "audio/x-pn-realaudio",
- ".rake" => "text/x-script.ruby",
- ".ram" => "audio/x-pn-realaudio",
- ".rar" => "application/x-rar-compressed",
- ".rb" => "text/x-script.ruby",
- ".rdf" => "application/rdf+xml",
- ".roff" => "text/troff",
- ".rpm" => "application/x-redhat-package-manager",
- ".rss" => "application/rss+xml",
- ".rtf" => "application/rtf",
- ".ru" => "text/x-script.ruby",
- ".s" => "text/x-asm",
- ".sgm" => "text/sgml",
- ".sgml" => "text/sgml",
- ".sh" => "application/x-sh",
- ".sig" => "application/pgp-signature",
- ".snd" => "audio/basic",
- ".so" => "application/octet-stream",
- ".svg" => "image/svg+xml",
- ".svgz" => "image/svg+xml",
- ".swf" => "application/x-shockwave-flash",
- ".t" => "text/troff",
- ".tar" => "application/x-tar",
- ".tbz" => "application/x-bzip-compressed-tar",
- ".tcl" => "application/x-tcl",
- ".tex" => "application/x-tex",
- ".texi" => "application/x-texinfo",
- ".texinfo" => "application/x-texinfo",
- ".text" => "text/plain",
- ".tif" => "image/tiff",
- ".tiff" => "image/tiff",
- ".torrent" => "application/x-bittorrent",
- ".tr" => "text/troff",
- ".txt" => "text/plain",
- ".vcf" => "text/x-vcard",
- ".vcs" => "text/x-vcalendar",
- ".vrml" => "model/vrml",
- ".war" => "application/java-archive",
- ".wav" => "audio/x-wav",
- ".wma" => "audio/x-ms-wma",
- ".wmv" => "video/x-ms-wmv",
- ".wmx" => "video/x-ms-wmx",
- ".wrl" => "model/vrml",
- ".wsdl" => "application/wsdl+xml",
- ".xbm" => "image/x-xbitmap",
- ".xhtml" => "application/xhtml+xml",
- ".xls" => "application/vnd.ms-excel",
- ".xml" => "application/xml",
- ".xpm" => "image/x-xpixmap",
- ".xsl" => "application/xml",
- ".xslt" => "application/xslt+xml",
- ".yaml" => "text/yaml",
- ".yml" => "text/yaml",
- ".zip" => "application/zip",
- }
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/mock.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/mock.rb
deleted file mode 100644
index fdefb0340a..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/mock.rb
+++ /dev/null
@@ -1,184 +0,0 @@
-require 'uri'
-require 'stringio'
-require 'rack/lint'
-require 'rack/utils'
-require 'rack/response'
-
-module Rack
- # Rack::MockRequest helps testing your Rack application without
- # actually using HTTP.
- #
- # After performing a request on a URL with get/post/put/delete, it
- # returns a MockResponse with useful helper methods for effective
- # testing.
- #
- # You can pass a hash with additional configuration to the
- # get/post/put/delete.
- # <tt>:input</tt>:: A String or IO-like to be used as rack.input.
- # <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
- # <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
-
- class MockRequest
- class FatalWarning < RuntimeError
- end
-
- class FatalWarner
- def puts(warning)
- raise FatalWarning, warning
- end
-
- def write(warning)
- raise FatalWarning, warning
- end
-
- def flush
- end
-
- def string
- ""
- end
- end
-
- DEFAULT_ENV = {
- "rack.version" => [1,0],
- "rack.input" => StringIO.new,
- "rack.errors" => StringIO.new,
- "rack.multithread" => true,
- "rack.multiprocess" => true,
- "rack.run_once" => false,
- }
-
- def initialize(app)
- @app = app
- end
-
- def get(uri, opts={}) request("GET", uri, opts) end
- def post(uri, opts={}) request("POST", uri, opts) end
- def put(uri, opts={}) request("PUT", uri, opts) end
- def delete(uri, opts={}) request("DELETE", uri, opts) end
-
- def request(method="GET", uri="", opts={})
- env = self.class.env_for(uri, opts.merge(:method => method))
-
- if opts[:lint]
- app = Rack::Lint.new(@app)
- else
- app = @app
- end
-
- errors = env["rack.errors"]
- MockResponse.new(*(app.call(env) + [errors]))
- end
-
- # Return the Rack environment used for a request to +uri+.
- def self.env_for(uri="", opts={})
- uri = URI(uri)
- uri.path = "/#{uri.path}" unless uri.path[0] == ?/
-
- env = DEFAULT_ENV.dup
-
- env["REQUEST_METHOD"] = opts[:method] ? opts[:method].to_s.upcase : "GET"
- env["SERVER_NAME"] = uri.host || "example.org"
- env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
- env["QUERY_STRING"] = uri.query.to_s
- env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path
- env["rack.url_scheme"] = uri.scheme || "http"
- env["HTTPS"] = env["rack.url_scheme"] == "https" ? "on" : "off"
-
- env["SCRIPT_NAME"] = opts[:script_name] || ""
-
- if opts[:fatal]
- env["rack.errors"] = FatalWarner.new
- else
- env["rack.errors"] = StringIO.new
- end
-
- if params = opts[:params]
- if env["REQUEST_METHOD"] == "GET"
- params = Utils.parse_nested_query(params) if params.is_a?(String)
- params.update(Utils.parse_nested_query(env["QUERY_STRING"]))
- env["QUERY_STRING"] = Utils.build_nested_query(params)
- elsif !opts.has_key?(:input)
- opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
- if params.is_a?(Hash)
- if data = Utils::Multipart.build_multipart(params)
- opts[:input] = data
- opts["CONTENT_LENGTH"] ||= data.length.to_s
- opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Utils::Multipart::MULTIPART_BOUNDARY}"
- else
- opts[:input] = Utils.build_nested_query(params)
- end
- else
- opts[:input] = params
- end
- end
- end
-
- opts[:input] ||= ""
- if String === opts[:input]
- env["rack.input"] = StringIO.new(opts[:input])
- else
- env["rack.input"] = opts[:input]
- end
-
- env["CONTENT_LENGTH"] ||= env["rack.input"].length.to_s
-
- opts.each { |field, value|
- env[field] = value if String === field
- }
-
- env
- end
- end
-
- # Rack::MockResponse provides useful helpers for testing your apps.
- # Usually, you don't create the MockResponse on your own, but use
- # MockRequest.
-
- class MockResponse
- def initialize(status, headers, body, errors=StringIO.new(""))
- @status = status.to_i
-
- @original_headers = headers
- @headers = Rack::Utils::HeaderHash.new
- headers.each { |field, values|
- @headers[field] = values
- @headers[field] = "" if values.empty?
- }
-
- @body = ""
- body.each { |part| @body << part }
-
- @errors = errors.string if errors.respond_to?(:string)
- end
-
- # Status
- attr_reader :status
-
- # Headers
- attr_reader :headers, :original_headers
-
- def [](field)
- headers[field]
- end
-
-
- # Body
- attr_reader :body
-
- def =~(other)
- @body =~ other
- end
-
- def match(other)
- @body.match other
- end
-
-
- # Errors
- attr_accessor :errors
-
-
- include Response::Helpers
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/recursive.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/recursive.rb
deleted file mode 100644
index bf8b965925..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/recursive.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-require 'uri'
-
-module Rack
- # Rack::ForwardRequest gets caught by Rack::Recursive and redirects
- # the current request to the app at +url+.
- #
- # raise ForwardRequest.new("/not-found")
- #
-
- class ForwardRequest < Exception
- attr_reader :url, :env
-
- def initialize(url, env={})
- @url = URI(url)
- @env = env
-
- @env["PATH_INFO"] = @url.path
- @env["QUERY_STRING"] = @url.query if @url.query
- @env["HTTP_HOST"] = @url.host if @url.host
- @env["HTTP_PORT"] = @url.port if @url.port
- @env["rack.url_scheme"] = @url.scheme if @url.scheme
-
- super "forwarding to #{url}"
- end
- end
-
- # Rack::Recursive allows applications called down the chain to
- # include data from other applications (by using
- # <tt>rack['rack.recursive.include'][...]</tt> or raise a
- # ForwardRequest to redirect internally.
-
- class Recursive
- def initialize(app)
- @app = app
- end
-
- def call(env)
- @script_name = env["SCRIPT_NAME"]
- @app.call(env.merge('rack.recursive.include' => method(:include)))
- rescue ForwardRequest => req
- call(env.merge(req.env))
- end
-
- def include(env, path)
- unless path.index(@script_name) == 0 && (path[@script_name.size] == ?/ ||
- path[@script_name.size].nil?)
- raise ArgumentError, "can only include below #{@script_name}, not #{path}"
- end
-
- env = env.merge("PATH_INFO" => path, "SCRIPT_NAME" => @script_name,
- "REQUEST_METHOD" => "GET",
- "CONTENT_LENGTH" => "0", "CONTENT_TYPE" => "",
- "rack.input" => StringIO.new(""))
- @app.call(env)
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/reloader.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/reloader.rb
deleted file mode 100644
index aa2f060be5..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/reloader.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-# Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com
-# All files in this distribution are subject to the terms of the Ruby license.
-
-require 'pathname'
-
-module Rack
-
- # High performant source reloader
- #
- # This class acts as Rack middleware.
- #
- # What makes it especially suited for use in a production environment is that
- # any file will only be checked once and there will only be made one system
- # call stat(2).
- #
- # Please note that this will not reload files in the background, it does so
- # only when actively called.
- #
- # It is performing a check/reload cycle at the start of every request, but
- # also respects a cool down time, during which nothing will be done.
- class Reloader
- def initialize(app, cooldown = 10, backend = Stat)
- @app = app
- @cooldown = cooldown
- @last = (Time.now - cooldown)
- @cache = {}
- @mtimes = {}
-
- extend backend
- end
-
- def call(env)
- if @cooldown and Time.now > @last + @cooldown
- if Thread.list.size > 1
- Thread.exclusive{ reload! }
- else
- reload!
- end
-
- @last = Time.now
- end
-
- @app.call(env)
- end
-
- def reload!(stderr = $stderr)
- rotation do |file, mtime|
- previous_mtime = @mtimes[file] ||= mtime
- safe_load(file, mtime, stderr) if mtime > previous_mtime
- end
- end
-
- # A safe Kernel::load, issuing the hooks depending on the results
- def safe_load(file, mtime, stderr = $stderr)
- load(file)
- stderr.puts "#{self.class}: reloaded `#{file}'"
- file
- rescue LoadError, SyntaxError => ex
- stderr.puts ex
- ensure
- @mtimes[file] = mtime
- end
-
- module Stat
- def rotation
- files = [$0, *$LOADED_FEATURES].uniq
- paths = ['./', *$LOAD_PATH].uniq
-
- files.map{|file|
- next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
-
- found, stat = figure_path(file, paths)
- next unless found and stat and mtime = stat.mtime
-
- @cache[file] = found
-
- yield(found, mtime)
- }.compact
- end
-
- # Takes a relative or absolute +file+ name, a couple possible +paths+ that
- # the +file+ might reside in. Returns the full path and File::Stat for the
- # path.
- def figure_path(file, paths)
- found = @cache[file]
- found = file if !found and Pathname.new(file).absolute?
- found, stat = safe_stat(found)
- return found, stat if found
-
- paths.each do |possible_path|
- path = ::File.join(possible_path, file)
- found, stat = safe_stat(path)
- return ::File.expand_path(found), stat if found
- end
- end
-
- def safe_stat(file)
- return unless file
- stat = ::File.stat(file)
- return file, stat if stat.file?
- rescue Errno::ENOENT, Errno::ENOTDIR
- @cache.delete(file) and false
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/request.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/request.rb
deleted file mode 100644
index 0bff7af038..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/request.rb
+++ /dev/null
@@ -1,254 +0,0 @@
-require 'rack/utils'
-
-module Rack
- # Rack::Request provides a convenient interface to a Rack
- # environment. It is stateless, the environment +env+ passed to the
- # constructor will be directly modified.
- #
- # req = Rack::Request.new(env)
- # req.post?
- # req.params["data"]
- #
- # The environment hash passed will store a reference to the Request object
- # instantiated so that it will only instantiate if an instance of the Request
- # object doesn't already exist.
-
- class Request
- # The environment of the request.
- attr_reader :env
-
- def self.new(env, *args)
- if self == Rack::Request
- env["rack.request"] ||= super
- else
- super
- end
- end
-
- def initialize(env)
- @env = env
- end
-
- def body; @env["rack.input"] end
- def scheme; @env["rack.url_scheme"] end
- def script_name; @env["SCRIPT_NAME"].to_s end
- def path_info; @env["PATH_INFO"].to_s end
- def port; @env["SERVER_PORT"].to_i end
- def request_method; @env["REQUEST_METHOD"] end
- def query_string; @env["QUERY_STRING"].to_s end
- def content_length; @env['CONTENT_LENGTH'] end
- def content_type; @env['CONTENT_TYPE'] end
- def session; @env['rack.session'] ||= {} end
- def session_options; @env['rack.session.options'] ||= {} end
-
- # The media type (type/subtype) portion of the CONTENT_TYPE header
- # without any media type parameters. e.g., when CONTENT_TYPE is
- # "text/plain;charset=utf-8", the media-type is "text/plain".
- #
- # For more information on the use of media types in HTTP, see:
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
- def media_type
- content_type && content_type.split(/\s*[;,]\s*/, 2).first.downcase
- end
-
- # The media type parameters provided in CONTENT_TYPE as a Hash, or
- # an empty Hash if no CONTENT_TYPE or media-type parameters were
- # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
- # this method responds with the following Hash:
- # { 'charset' => 'utf-8' }
- def media_type_params
- return {} if content_type.nil?
- content_type.split(/\s*[;,]\s*/)[1..-1].
- collect { |s| s.split('=', 2) }.
- inject({}) { |hash,(k,v)| hash[k.downcase] = v ; hash }
- end
-
- # The character set of the request body if a "charset" media type
- # parameter was given, or nil if no "charset" was specified. Note
- # that, per RFC2616, text/* media types that specify no explicit
- # charset are to be considered ISO-8859-1.
- def content_charset
- media_type_params['charset']
- end
-
- def host
- # Remove port number.
- (@env["HTTP_HOST"] || @env["SERVER_NAME"]).gsub(/:\d+\z/, '')
- end
-
- def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
- def path_info=(s); @env["PATH_INFO"] = s.to_s end
-
- def get?; request_method == "GET" end
- def post?; request_method == "POST" end
- def put?; request_method == "PUT" end
- def delete?; request_method == "DELETE" end
- def head?; request_method == "HEAD" end
-
- # The set of form-data media-types. Requests that do not indicate
- # one of the media types presents in this list will not be eligible
- # for form-data / param parsing.
- FORM_DATA_MEDIA_TYPES = [
- nil,
- 'application/x-www-form-urlencoded',
- 'multipart/form-data'
- ]
-
- # The set of media-types. Requests that do not indicate
- # one of the media types presents in this list will not be eligible
- # for param parsing like soap attachments or generic multiparts
- PARSEABLE_DATA_MEDIA_TYPES = [
- 'multipart/related',
- 'multipart/mixed'
- ]
-
- # Determine whether the request body contains form-data by checking
- # the request media_type against registered form-data media-types:
- # "application/x-www-form-urlencoded" and "multipart/form-data". The
- # list of form-data media types can be modified through the
- # +FORM_DATA_MEDIA_TYPES+ array.
- def form_data?
- FORM_DATA_MEDIA_TYPES.include?(media_type)
- end
-
- # Determine whether the request body contains data by checking
- # the request media_type against registered parse-data media-types
- def parseable_data?
- PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
- end
-
- # Returns the data recieved in the query string.
- def GET
- if @env["rack.request.query_string"] == query_string
- @env["rack.request.query_hash"]
- else
- @env["rack.request.query_string"] = query_string
- @env["rack.request.query_hash"] =
- Utils.parse_nested_query(query_string)
- end
- end
-
- # Returns the data recieved in the request body.
- #
- # This method support both application/x-www-form-urlencoded and
- # multipart/form-data.
- def POST
- if @env["rack.request.form_input"].eql? @env["rack.input"]
- @env["rack.request.form_hash"]
- elsif form_data? || parseable_data?
- @env["rack.request.form_input"] = @env["rack.input"]
- unless @env["rack.request.form_hash"] =
- Utils::Multipart.parse_multipart(env)
- form_vars = @env["rack.input"].read
-
- # Fix for Safari Ajax postings that always append \0
- form_vars.sub!(/\0\z/, '')
-
- @env["rack.request.form_vars"] = form_vars
- @env["rack.request.form_hash"] = Utils.parse_nested_query(form_vars)
-
- @env["rack.input"].rewind
- end
- @env["rack.request.form_hash"]
- else
- {}
- end
- end
-
- # The union of GET and POST data.
- def params
- self.put? ? self.GET : self.GET.update(self.POST)
- rescue EOFError => e
- self.GET
- end
-
- # shortcut for request.params[key]
- def [](key)
- params[key.to_s]
- end
-
- # shortcut for request.params[key] = value
- def []=(key, value)
- params[key.to_s] = value
- end
-
- # like Hash#values_at
- def values_at(*keys)
- keys.map{|key| params[key] }
- end
-
- # the referer of the client or '/'
- def referer
- @env['HTTP_REFERER'] || '/'
- end
- alias referrer referer
-
-
- def cookies
- return {} unless @env["HTTP_COOKIE"]
-
- if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
- @env["rack.request.cookie_hash"]
- else
- @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
- # According to RFC 2109:
- # If multiple cookies satisfy the criteria above, they are ordered in
- # the Cookie header such that those with more specific Path attributes
- # precede those with less specific. Ordering with respect to other
- # attributes (e.g., Domain) is unspecified.
- @env["rack.request.cookie_hash"] =
- Utils.parse_query(@env["rack.request.cookie_string"], ';,').inject({}) {|h,(k,v)|
- h[k] = Array === v ? v.first : v
- h
- }
- end
- end
-
- def xhr?
- @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
- end
-
- # Tries to return a remake of the original request URL as a string.
- def url
- url = scheme + "://"
- url << host
-
- if scheme == "https" && port != 443 ||
- scheme == "http" && port != 80
- url << ":#{port}"
- end
-
- url << fullpath
-
- url
- end
-
- def path
- script_name + path_info
- end
-
- def fullpath
- query_string.empty? ? path : "#{path}?#{query_string}"
- end
-
- def accept_encoding
- @env["HTTP_ACCEPT_ENCODING"].to_s.split(/,\s*/).map do |part|
- m = /^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$/.match(part) # From WEBrick
-
- if m
- [m[1], (m[2] || 1.0).to_f]
- else
- raise "Invalid value for Accept-Encoding: #{part.inspect}"
- end
- end
- end
-
- def ip
- if addr = @env['HTTP_X_FORWARDED_FOR']
- addr.split(',').last.strip
- else
- @env['REMOTE_ADDR']
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/response.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/response.rb
deleted file mode 100644
index 28b4d8302f..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/response.rb
+++ /dev/null
@@ -1,183 +0,0 @@
-require 'rack/request'
-require 'rack/utils'
-
-module Rack
- # Rack::Response provides a convenient interface to create a Rack
- # response.
- #
- # It allows setting of headers and cookies, and provides useful
- # defaults (a OK response containing HTML).
- #
- # You can use Response#write to iteratively generate your response,
- # but note that this is buffered by Rack::Response until you call
- # +finish+. +finish+ however can take a block inside which calls to
- # +write+ are syncronous with the Rack response.
- #
- # Your application's +call+ should end returning Response#finish.
-
- class Response
- attr_accessor :length
-
- def initialize(body=[], status=200, header={}, &block)
- @status = status
- @header = Utils::HeaderHash.new({"Content-Type" => "text/html"}.
- merge(header))
-
- @writer = lambda { |x| @body << x }
- @block = nil
- @length = 0
-
- @body = []
-
- if body.respond_to? :to_str
- write body.to_str
- elsif body.respond_to?(:each)
- body.each { |part|
- write part.to_s
- }
- else
- raise TypeError, "stringable or iterable required"
- end
-
- yield self if block_given?
- end
-
- attr_reader :header
- attr_accessor :status, :body
-
- def [](key)
- header[key]
- end
-
- def []=(key, value)
- header[key] = value
- end
-
- def set_cookie(key, value)
- case value
- when Hash
- domain = "; domain=" + value[:domain] if value[:domain]
- path = "; path=" + value[:path] if value[:path]
- # According to RFC 2109, we need dashes here.
- # N.B.: cgi.rb uses spaces...
- expires = "; expires=" + value[:expires].clone.gmtime.
- strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
- secure = "; secure" if value[:secure]
- httponly = "; HttpOnly" if value[:httponly]
- value = value[:value]
- end
- value = [value] unless Array === value
- cookie = Utils.escape(key) + "=" +
- value.map { |v| Utils.escape v }.join("&") +
- "#{domain}#{path}#{expires}#{secure}#{httponly}"
-
- case self["Set-Cookie"]
- when Array
- self["Set-Cookie"] << cookie
- when String
- self["Set-Cookie"] = [self["Set-Cookie"], cookie]
- when nil
- self["Set-Cookie"] = cookie
- end
- end
-
- def delete_cookie(key, value={})
- unless Array === self["Set-Cookie"]
- self["Set-Cookie"] = [self["Set-Cookie"]].compact
- end
-
- self["Set-Cookie"].reject! { |cookie|
- cookie =~ /\A#{Utils.escape(key)}=/
- }
-
- set_cookie(key,
- {:value => '', :path => nil, :domain => nil,
- :expires => Time.at(0) }.merge(value))
- end
-
- def redirect(target, status=302)
- self.status = status
- self["Location"] = target
- end
-
- def finish(&block)
- @block = block
-
- if [204, 304].include?(status.to_i)
- header.delete "Content-Type"
- [status.to_i, header.to_hash, []]
- else
- [status.to_i, header.to_hash, self]
- end
- end
- alias to_a finish # For *response
-
- def each(&callback)
- @body.each(&callback)
- @writer = callback
- @block.call(self) if @block
- end
-
- # Append to body and update Content-Length.
- #
- # NOTE: Do not mix #write and direct #body access!
- #
- def write(str)
- s = str.to_s
- @length += Rack::Utils.bytesize(s)
- @writer.call s
-
- header["Content-Length"] = @length.to_s
- str
- end
-
- def close
- body.close if body.respond_to?(:close)
- end
-
- def empty?
- @block == nil && @body.empty?
- end
-
- alias headers header
-
- module Helpers
- def invalid?; @status < 100 || @status >= 600; end
-
- def informational?; @status >= 100 && @status < 200; end
- def successful?; @status >= 200 && @status < 300; end
- def redirection?; @status >= 300 && @status < 400; end
- def client_error?; @status >= 400 && @status < 500; end
- def server_error?; @status >= 500 && @status < 600; end
-
- def ok?; @status == 200; end
- def forbidden?; @status == 403; end
- def not_found?; @status == 404; end
-
- def redirect?; [301, 302, 303, 307].include? @status; end
- def empty?; [201, 204, 304].include? @status; end
-
- # Headers
- attr_reader :headers, :original_headers
-
- def include?(header)
- !!headers[header]
- end
-
- def content_type
- headers["Content-Type"]
- end
-
- def content_length
- cl = headers["Content-Length"]
- cl ? cl.to_i : cl
- end
-
- def location
- headers["Location"]
- end
- end
-
- include Helpers
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/rewindable_input.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/rewindable_input.rb
deleted file mode 100644
index 9e9b21ff99..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/rewindable_input.rb
+++ /dev/null
@@ -1,98 +0,0 @@
-require 'tempfile'
-
-module Rack
- # Class which can make any IO object rewindable, including non-rewindable ones. It does
- # this by buffering the data into a tempfile, which is rewindable.
- #
- # rack.input is required to be rewindable, so if your input stream IO is non-rewindable
- # by nature (e.g. a pipe or a socket) then you can wrap it in an object of this class
- # to easily make it rewindable.
- #
- # Don't forget to call #close when you're done. This frees up temporary resources that
- # RewindableInput uses, though it does *not* close the original IO object.
- class RewindableInput
- def initialize(io)
- @io = io
- @rewindable_io = nil
- @unlinked = false
- end
-
- def gets
- make_rewindable unless @rewindable_io
- @rewindable_io.gets
- end
-
- def read(*args)
- make_rewindable unless @rewindable_io
- @rewindable_io.read(*args)
- end
-
- def each(&block)
- make_rewindable unless @rewindable_io
- @rewindable_io.each(&block)
- end
-
- def rewind
- make_rewindable unless @rewindable_io
- @rewindable_io.rewind
- end
-
- # Closes this RewindableInput object without closing the originally
- # wrapped IO oject. Cleans up any temporary resources that this RewindableInput
- # has created.
- #
- # This method may be called multiple times. It does nothing on subsequent calls.
- def close
- if @rewindable_io
- if @unlinked
- @rewindable_io.close
- else
- @rewindable_io.close!
- end
- @rewindable_io = nil
- end
- end
-
- private
-
- # Ruby's Tempfile class has a bug. Subclass it and fix it.
- class Tempfile < ::Tempfile
- def _close
- @tmpfile.close if @tmpfile
- @data[1] = nil if @data
- @tmpfile = nil
- end
- end
-
- def make_rewindable
- # Buffer all data into a tempfile. Since this tempfile is private to this
- # RewindableInput object, we chmod it so that nobody else can read or write
- # it. On POSIX filesystems we also unlink the file so that it doesn't
- # even have a file entry on the filesystem anymore, though we can still
- # access it because we have the file handle open.
- @rewindable_io = Tempfile.new('RackRewindableInput')
- @rewindable_io.chmod(0000)
- if filesystem_has_posix_semantics?
- @rewindable_io.unlink
- @unlinked = true
- end
-
- buffer = ""
- while @io.read(1024 * 4, buffer)
- entire_buffer_written_out = false
- while !entire_buffer_written_out
- written = @rewindable_io.write(buffer)
- entire_buffer_written_out = written == buffer.size
- if !entire_buffer_written_out
- buffer.slice!(0 .. written - 1)
- end
- end
- end
- @rewindable_io.rewind
- end
-
- def filesystem_has_posix_semantics?
- RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/abstract/id.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/abstract/id.rb
deleted file mode 100644
index 218144c17f..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/abstract/id.rb
+++ /dev/null
@@ -1,142 +0,0 @@
-# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
-# bugrep: Andreas Zehnder
-
-require 'time'
-require 'rack/request'
-require 'rack/response'
-
-module Rack
-
- module Session
-
- module Abstract
-
- # ID sets up a basic framework for implementing an id based sessioning
- # service. Cookies sent to the client for maintaining sessions will only
- # contain an id reference. Only #get_session and #set_session are
- # required to be overwritten.
- #
- # All parameters are optional.
- # * :key determines the name of the cookie, by default it is
- # 'rack.session'
- # * :path, :domain, :expire_after, :secure, and :httponly set the related
- # cookie options as by Rack::Response#add_cookie
- # * :defer will not set a cookie in the response.
- # * :renew (implementation dependent) will prompt the generation of a new
- # session id, and migration of data to be referenced at the new id. If
- # :defer is set, it will be overridden and the cookie will be set.
- # * :sidbits sets the number of bits in length that a generated session
- # id will be.
- #
- # These options can be set on a per request basis, at the location of
- # env['rack.session.options']. Additionally the id of the session can be
- # found within the options hash at the key :id. It is highly not
- # recommended to change its value.
- #
- # Is Rack::Utils::Context compatible.
-
- class ID
- DEFAULT_OPTIONS = {
- :path => '/',
- :domain => nil,
- :expire_after => nil,
- :secure => false,
- :httponly => true,
- :defer => false,
- :renew => false,
- :sidbits => 128
- }
-
- attr_reader :key, :default_options
- def initialize(app, options={})
- @app = app
- @key = options[:key] || "rack.session"
- @default_options = self.class::DEFAULT_OPTIONS.merge(options)
- end
-
- def call(env)
- context(env)
- end
-
- def context(env, app=@app)
- load_session(env)
- status, headers, body = app.call(env)
- commit_session(env, status, headers, body)
- end
-
- private
-
- # Generate a new session id using Ruby #rand. The size of the
- # session id is controlled by the :sidbits option.
- # Monkey patch this to use custom methods for session id generation.
-
- def generate_sid
- "%0#{@default_options[:sidbits] / 4}x" %
- rand(2**@default_options[:sidbits] - 1)
- end
-
- # Extracts the session id from provided cookies and passes it and the
- # environment to #get_session. It then sets the resulting session into
- # 'rack.session', and places options and session metadata into
- # 'rack.session.options'.
-
- def load_session(env)
- request = Rack::Request.new(env)
- session_id = request.cookies[@key]
-
- begin
- session_id, session = get_session(env, session_id)
- env['rack.session'] = session
- rescue
- env['rack.session'] = Hash.new
- end
-
- env['rack.session.options'] = @default_options.
- merge(:id => session_id)
- end
-
- # Acquires the session from the environment and the session id from
- # the session options and passes them to #set_session. If successful
- # and the :defer option is not true, a cookie will be added to the
- # response with the session's id.
-
- def commit_session(env, status, headers, body)
- session = env['rack.session']
- options = env['rack.session.options']
- session_id = options[:id]
-
- if not session_id = set_session(env, session_id, session, options)
- env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
- [status, headers, body]
- elsif options[:defer] and not options[:renew]
- env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
- [status, headers, body]
- else
- cookie = Hash.new
- cookie[:value] = session_id
- cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
- response = Rack::Response.new(body, status, headers)
- response.set_cookie(@key, cookie.merge(options))
- response.to_a
- end
- end
-
- # All thread safety and session retrival proceedures should occur here.
- # Should return [session_id, session].
- # If nil is provided as the session id, generation of a new valid id
- # should occur within.
-
- def get_session(env, sid)
- raise '#get_session not implemented.'
- end
-
- # All thread safety and session storage proceedures should occur here.
- # Should return true or false dependant on whether or not the session
- # was saved or not.
- def set_session(env, sid, session, options)
- raise '#set_session not implemented.'
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/cookie.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/cookie.rb
deleted file mode 100644
index eace9bd0c6..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/cookie.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-require 'openssl'
-require 'rack/request'
-require 'rack/response'
-
-module Rack
-
- module Session
-
- # Rack::Session::Cookie provides simple cookie based session management.
- # The session is a Ruby Hash stored as base64 encoded marshalled data
- # set to :key (default: rack.session).
- # When the secret key is set, cookie data is checked for data integrity.
- #
- # Example:
- #
- # use Rack::Session::Cookie, :key => 'rack.session',
- # :domain => 'foo.com',
- # :path => '/',
- # :expire_after => 2592000,
- # :secret => 'change_me'
- #
- # All parameters are optional.
-
- class Cookie
-
- def initialize(app, options={})
- @app = app
- @key = options[:key] || "rack.session"
- @secret = options[:secret]
- @default_options = {:domain => nil,
- :path => "/",
- :expire_after => nil}.merge(options)
- end
-
- def call(env)
- load_session(env)
- status, headers, body = @app.call(env)
- commit_session(env, status, headers, body)
- end
-
- private
-
- def load_session(env)
- request = Rack::Request.new(env)
- session_data = request.cookies[@key]
-
- if @secret && session_data
- session_data, digest = session_data.split("--")
- session_data = nil unless digest == generate_hmac(session_data)
- end
-
- begin
- session_data = session_data.unpack("m*").first
- session_data = Marshal.load(session_data)
- env["rack.session"] = session_data
- rescue
- env["rack.session"] = Hash.new
- end
-
- env["rack.session.options"] = @default_options.dup
- end
-
- def commit_session(env, status, headers, body)
- session_data = Marshal.dump(env["rack.session"])
- session_data = [session_data].pack("m*")
-
- if @secret
- session_data = "#{session_data}--#{generate_hmac(session_data)}"
- end
-
- if session_data.size > (4096 - @key.size)
- env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.")
- [status, headers, body]
- else
- options = env["rack.session.options"]
- cookie = Hash.new
- cookie[:value] = session_data
- cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
- response = Rack::Response.new(body, status, headers)
- response.set_cookie(@key, cookie.merge(options))
- response.to_a
- end
- end
-
- def generate_hmac(data)
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, @secret, data)
- end
-
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/memcache.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/memcache.rb
deleted file mode 100644
index 4a65cbf35d..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/memcache.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
-
-require 'rack/session/abstract/id'
-require 'memcache'
-
-module Rack
- module Session
- # Rack::Session::Memcache provides simple cookie based session management.
- # Session data is stored in memcached. The corresponding session key is
- # maintained in the cookie.
- # You may treat Session::Memcache as you would Session::Pool with the
- # following caveats.
- #
- # * Setting :expire_after to 0 would note to the Memcache server to hang
- # onto the session data until it would drop it according to it's own
- # specifications. However, the cookie sent to the client would expire
- # immediately.
- #
- # Note that memcache does drop data before it may be listed to expire. For
- # a full description of behaviour, please see memcache's documentation.
-
- class Memcache < Abstract::ID
- attr_reader :mutex, :pool
- DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
- :namespace => 'rack:session',
- :memcache_server => 'localhost:11211'
-
- def initialize(app, options={})
- super
-
- @mutex = Mutex.new
- @pool = MemCache.
- new @default_options[:memcache_server], @default_options
- raise 'No memcache servers' unless @pool.servers.any?{|s|s.alive?}
- end
-
- def generate_sid
- loop do
- sid = super
- break sid unless @pool.get(sid, true)
- end
- end
-
- def get_session(env, sid)
- session = @pool.get(sid) if sid
- @mutex.lock if env['rack.multithread']
- unless sid and session
- env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
- session = {}
- sid = generate_sid
- ret = @pool.add sid, session
- raise "Session collision on '#{sid.inspect}'" unless /^STORED/ =~ ret
- end
- session.instance_variable_set('@old', {}.merge(session))
- return [sid, session]
- rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted
- warn "#{self} is unable to find server."
- warn $!.inspect
- return [ nil, {} ]
- ensure
- @mutex.unlock if env['rack.multithread']
- end
-
- def set_session(env, session_id, new_session, options)
- expiry = options[:expire_after]
- expiry = expiry.nil? ? 0 : expiry + 1
-
- @mutex.lock if env['rack.multithread']
- session = @pool.get(session_id) || {}
- if options[:renew] or options[:drop]
- @pool.delete session_id
- return false if options[:drop]
- session_id = generate_sid
- @pool.add session_id, 0 # so we don't worry about cache miss on #set
- end
- old_session = new_session.instance_variable_get('@old') || {}
- session = merge_sessions session_id, old_session, new_session, session
- @pool.set session_id, session, expiry
- return session_id
- rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted
- warn "#{self} is unable to find server."
- warn $!.inspect
- return false
- ensure
- @mutex.unlock if env['rack.multithread']
- end
-
- private
-
- def merge_sessions sid, old, new, cur=nil
- cur ||= {}
- unless Hash === old and Hash === new
- warn 'Bad old or new sessions provided.'
- return cur
- end
-
- delete = old.keys - new.keys
- warn "//@#{sid}: delete #{delete*','}" if $VERBOSE and not delete.empty?
- delete.each{|k| cur.delete k }
-
- update = new.keys.select{|k| new[k] != old[k] }
- warn "//@#{sid}: update #{update*','}" if $VERBOSE and not update.empty?
- update.each{|k| cur[k] = new[k] }
-
- cur
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/pool.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/pool.rb
deleted file mode 100644
index f6f87408bb..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/session/pool.rb
+++ /dev/null
@@ -1,100 +0,0 @@
-# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
-# THANKS:
-# apeiros, for session id generation, expiry setup, and threadiness
-# sergio, threadiness and bugreps
-
-require 'rack/session/abstract/id'
-require 'thread'
-
-module Rack
- module Session
- # Rack::Session::Pool provides simple cookie based session management.
- # Session data is stored in a hash held by @pool.
- # In the context of a multithreaded environment, sessions being
- # committed to the pool is done in a merging manner.
- #
- # The :drop option is available in rack.session.options if you with to
- # explicitly remove the session from the session cache.
- #
- # Example:
- # myapp = MyRackApp.new
- # sessioned = Rack::Session::Pool.new(myapp,
- # :domain => 'foo.com',
- # :expire_after => 2592000
- # )
- # Rack::Handler::WEBrick.run sessioned
-
- class Pool < Abstract::ID
- attr_reader :mutex, :pool
- DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :drop => false
-
- def initialize(app, options={})
- super
- @pool = Hash.new
- @mutex = Mutex.new
- end
-
- def generate_sid
- loop do
- sid = super
- break sid unless @pool.key? sid
- end
- end
-
- def get_session(env, sid)
- session = @pool[sid] if sid
- @mutex.lock if env['rack.multithread']
- unless sid and session
- env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
- session = {}
- sid = generate_sid
- @pool.store sid, session
- end
- session.instance_variable_set('@old', {}.merge(session))
- return [sid, session]
- ensure
- @mutex.unlock if env['rack.multithread']
- end
-
- def set_session(env, session_id, new_session, options)
- @mutex.lock if env['rack.multithread']
- session = @pool[session_id]
- if options[:renew] or options[:drop]
- @pool.delete session_id
- return false if options[:drop]
- session_id = generate_sid
- @pool.store session_id, 0
- end
- old_session = new_session.instance_variable_get('@old') || {}
- session = merge_sessions session_id, old_session, new_session, session
- @pool.store session_id, session
- return session_id
- rescue
- warn "#{new_session.inspect} has been lost."
- warn $!.inspect
- ensure
- @mutex.unlock if env['rack.multithread']
- end
-
- private
-
- def merge_sessions sid, old, new, cur=nil
- cur ||= {}
- unless Hash === old and Hash === new
- warn 'Bad old or new sessions provided.'
- return cur
- end
-
- delete = old.keys - new.keys
- warn "//@#{sid}: dropping #{delete*','}" if $DEBUG and not delete.empty?
- delete.each{|k| cur.delete k }
-
- update = new.keys.select{|k| new[k] != old[k] }
- warn "//@#{sid}: updating #{update*','}" if $DEBUG and not update.empty?
- update.each{|k| cur[k] = new[k] }
-
- cur
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/showexceptions.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/showexceptions.rb
deleted file mode 100644
index 697bc41fdb..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/showexceptions.rb
+++ /dev/null
@@ -1,349 +0,0 @@
-require 'ostruct'
-require 'erb'
-require 'rack/request'
-require 'rack/utils'
-
-module Rack
- # Rack::ShowExceptions catches all exceptions raised from the app it
- # wraps. It shows a useful backtrace with the sourcefile and
- # clickable context, the whole Rack environment and the request
- # data.
- #
- # Be careful when you use this on public-facing sites as it could
- # reveal information helpful to attackers.
-
- class ShowExceptions
- CONTEXT = 7
-
- def initialize(app)
- @app = app
- @template = ERB.new(TEMPLATE)
- end
-
- def call(env)
- @app.call(env)
- rescue StandardError, LoadError, SyntaxError => e
- backtrace = pretty(env, e)
- [500,
- {"Content-Type" => "text/html",
- "Content-Length" => backtrace.join.size.to_s},
- backtrace]
- end
-
- def pretty(env, exception)
- req = Rack::Request.new(env)
- path = (req.script_name + req.path_info).squeeze("/")
-
- frames = exception.backtrace.map { |line|
- frame = OpenStruct.new
- if line =~ /(.*?):(\d+)(:in `(.*)')?/
- frame.filename = $1
- frame.lineno = $2.to_i
- frame.function = $4
-
- begin
- lineno = frame.lineno-1
- lines = ::File.readlines(frame.filename)
- frame.pre_context_lineno = [lineno-CONTEXT, 0].max
- frame.pre_context = lines[frame.pre_context_lineno...lineno]
- frame.context_line = lines[lineno].chomp
- frame.post_context_lineno = [lineno+CONTEXT, lines.size].min
- frame.post_context = lines[lineno+1..frame.post_context_lineno]
- rescue
- end
-
- frame
- else
- nil
- end
- }.compact
-
- env["rack.errors"].puts "#{exception.class}: #{exception.message}"
- env["rack.errors"].puts exception.backtrace.map { |l| "\t" + l }
- env["rack.errors"].flush
-
- [@template.result(binding)]
- end
-
- def h(obj) # :nodoc:
- case obj
- when String
- Utils.escape_html(obj)
- else
- Utils.escape_html(obj.inspect)
- end
- end
-
- # :stopdoc:
-
-# adapted from Django <djangoproject.com>
-# Copyright (c) 2005, the Lawrence Journal-World
-# Used under the modified BSD license:
-# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
-TEMPLATE = <<'HTML'
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
-<html lang="en">
-<head>
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <meta name="robots" content="NONE,NOARCHIVE" />
- <title><%=h exception.class %> at <%=h path %></title>
- <style type="text/css">
- html * { padding:0; margin:0; }
- body * { padding:10px 20px; }
- body * * { padding:0; }
- body { font:small sans-serif; }
- body>div { border-bottom:1px solid #ddd; }
- h1 { font-weight:normal; }
- h2 { margin-bottom:.8em; }
- h2 span { font-size:80%; color:#666; font-weight:normal; }
- h3 { margin:1em 0 .5em 0; }
- h4 { margin:0 0 .5em 0; font-weight: normal; }
- table {
- border:1px solid #ccc; border-collapse: collapse; background:white; }
- tbody td, tbody th { vertical-align:top; padding:2px 3px; }
- thead th {
- padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
- font-weight:normal; font-size:11px; border:1px solid #ddd; }
- tbody th { text-align:right; color:#666; padding-right:.5em; }
- table.vars { margin:5px 0 2px 40px; }
- table.vars td, table.req td { font-family:monospace; }
- table td.code { width:100%;}
- table td.code div { overflow:hidden; }
- table.source th { color:#666; }
- table.source td {
- font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
- ul.traceback { list-style-type:none; }
- ul.traceback li.frame { margin-bottom:1em; }
- div.context { margin: 10px 0; }
- div.context ol {
- padding-left:30px; margin:0 10px; list-style-position: inside; }
- div.context ol li {
- font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
- div.context ol.context-line li { color:black; background-color:#ccc; }
- div.context ol.context-line li span { float: right; }
- div.commands { margin-left: 40px; }
- div.commands a { color:black; text-decoration:none; }
- #summary { background: #ffc; }
- #summary h2 { font-weight: normal; color: #666; }
- #summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
- #summary ul#quicklinks li { float: left; padding: 0 1em; }
- #summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
- #explanation { background:#eee; }
- #template, #template-not-exist { background:#f6f6f6; }
- #template-not-exist ul { margin: 0 0 0 20px; }
- #traceback { background:#eee; }
- #requestinfo { background:#f6f6f6; padding-left:120px; }
- #summary table { border:none; background:transparent; }
- #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
- #requestinfo h3 { margin-bottom:-1em; }
- .error { background: #ffc; }
- .specific { color:#cc3300; font-weight:bold; }
- </style>
- <script type="text/javascript">
- //<!--
- function getElementsByClassName(oElm, strTagName, strClassName){
- // Written by Jonathan Snook, http://www.snook.ca/jon;
- // Add-ons by Robert Nyman, http://www.robertnyman.com
- var arrElements = (strTagName == "*" && document.all)? document.all :
- oElm.getElementsByTagName(strTagName);
- var arrReturnElements = new Array();
- strClassName = strClassName.replace(/\-/g, "\\-");
- var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$$)");
- var oElement;
- for(var i=0; i<arrElements.length; i++){
- oElement = arrElements[i];
- if(oRegExp.test(oElement.className)){
- arrReturnElements.push(oElement);
- }
- }
- return (arrReturnElements)
- }
- function hideAll(elems) {
- for (var e = 0; e < elems.length; e++) {
- elems[e].style.display = 'none';
- }
- }
- window.onload = function() {
- hideAll(getElementsByClassName(document, 'table', 'vars'));
- hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
- hideAll(getElementsByClassName(document, 'ol', 'post-context'));
- }
- function toggle() {
- for (var i = 0; i < arguments.length; i++) {
- var e = document.getElementById(arguments[i]);
- if (e) {
- e.style.display = e.style.display == 'none' ? 'block' : 'none';
- }
- }
- return false;
- }
- function varToggle(link, id) {
- toggle('v' + id);
- var s = link.getElementsByTagName('span')[0];
- var uarr = String.fromCharCode(0x25b6);
- var darr = String.fromCharCode(0x25bc);
- s.innerHTML = s.innerHTML == uarr ? darr : uarr;
- return false;
- }
- //-->
- </script>
-</head>
-<body>
-
-<div id="summary">
- <h1><%=h exception.class %> at <%=h path %></h1>
- <h2><%=h exception.message %></h2>
- <table><tr>
- <th>Ruby</th>
- <td><code><%=h frames.first.filename %></code>: in <code><%=h frames.first.function %></code>, line <%=h frames.first.lineno %></td>
- </tr><tr>
- <th>Web</th>
- <td><code><%=h req.request_method %> <%=h(req.host + path)%></code></td>
- </tr></table>
-
- <h3>Jump to:</h3>
- <ul id="quicklinks">
- <li><a href="#get-info">GET</a></li>
- <li><a href="#post-info">POST</a></li>
- <li><a href="#cookie-info">Cookies</a></li>
- <li><a href="#env-info">ENV</a></li>
- </ul>
-</div>
-
-<div id="traceback">
- <h2>Traceback <span>(innermost first)</span></h2>
- <ul class="traceback">
-<% frames.each { |frame| %>
- <li class="frame">
- <code><%=h frame.filename %></code>: in <code><%=h frame.function %></code>
-
- <% if frame.context_line %>
- <div class="context" id="c<%=h frame.object_id %>">
- <% if frame.pre_context %>
- <ol start="<%=h frame.pre_context_lineno+1 %>" class="pre-context" id="pre<%=h frame.object_id %>">
- <% frame.pre_context.each { |line| %>
- <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
- <% } %>
- </ol>
- <% end %>
-
- <ol start="<%=h frame.lineno %>" class="context-line">
- <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h frame.context_line %><span>...</span></li></ol>
-
- <% if frame.post_context %>
- <ol start='<%=h frame.lineno+1 %>' class="post-context" id="post<%=h frame.object_id %>">
- <% frame.post_context.each { |line| %>
- <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
- <% } %>
- </ol>
- <% end %>
- </div>
- <% end %>
- </li>
-<% } %>
- </ul>
-</div>
-
-<div id="requestinfo">
- <h2>Request information</h2>
-
- <h3 id="get-info">GET</h3>
- <% unless req.GET.empty? %>
- <table class="req">
- <thead>
- <tr>
- <th>Variable</th>
- <th>Value</th>
- </tr>
- </thead>
- <tbody>
- <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %>
- <tr>
- <td><%=h key %></td>
- <td class="code"><div><%=h val.inspect %></div></td>
- </tr>
- <% } %>
- </tbody>
- </table>
- <% else %>
- <p>No GET data.</p>
- <% end %>
-
- <h3 id="post-info">POST</h3>
- <% unless req.POST.empty? %>
- <table class="req">
- <thead>
- <tr>
- <th>Variable</th>
- <th>Value</th>
- </tr>
- </thead>
- <tbody>
- <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %>
- <tr>
- <td><%=h key %></td>
- <td class="code"><div><%=h val.inspect %></div></td>
- </tr>
- <% } %>
- </tbody>
- </table>
- <% else %>
- <p>No POST data.</p>
- <% end %>
-
-
- <h3 id="cookie-info">COOKIES</h3>
- <% unless req.cookies.empty? %>
- <table class="req">
- <thead>
- <tr>
- <th>Variable</th>
- <th>Value</th>
- </tr>
- </thead>
- <tbody>
- <% req.cookies.each { |key, val| %>
- <tr>
- <td><%=h key %></td>
- <td class="code"><div><%=h val.inspect %></div></td>
- </tr>
- <% } %>
- </tbody>
- </table>
- <% else %>
- <p>No cookie data.</p>
- <% end %>
-
- <h3 id="env-info">Rack ENV</h3>
- <table class="req">
- <thead>
- <tr>
- <th>Variable</th>
- <th>Value</th>
- </tr>
- </thead>
- <tbody>
- <% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
- <tr>
- <td><%=h key %></td>
- <td class="code"><div><%=h val %></div></td>
- </tr>
- <% } %>
- </tbody>
- </table>
-
-</div>
-
-<div id="explanation">
- <p>
- You're seeing this error because you use <code>Rack::ShowExceptions</code>.
- </p>
-</div>
-
-</body>
-</html>
-HTML
-
- # :startdoc:
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/showstatus.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/showstatus.rb
deleted file mode 100644
index 28258c7c89..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/showstatus.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-require 'erb'
-require 'rack/request'
-require 'rack/utils'
-
-module Rack
- # Rack::ShowStatus catches all empty responses the app it wraps and
- # replaces them with a site explaining the error.
- #
- # Additional details can be put into <tt>rack.showstatus.detail</tt>
- # and will be shown as HTML. If such details exist, the error page
- # is always rendered, even if the reply was not empty.
-
- class ShowStatus
- def initialize(app)
- @app = app
- @template = ERB.new(TEMPLATE)
- end
-
- def call(env)
- status, headers, body = @app.call(env)
- headers = Utils::HeaderHash.new(headers)
- empty = headers['Content-Length'].to_i <= 0
-
- # client or server error, or explicit message
- if (status.to_i >= 400 && empty) || env["rack.showstatus.detail"]
- req = Rack::Request.new(env)
- message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
- detail = env["rack.showstatus.detail"] || message
- body = @template.result(binding)
- size = Rack::Utils.bytesize(body)
- [status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]]
- else
- [status, headers, body]
- end
- end
-
- def h(obj) # :nodoc:
- case obj
- when String
- Utils.escape_html(obj)
- else
- Utils.escape_html(obj.inspect)
- end
- end
-
- # :stopdoc:
-
-# adapted from Django <djangoproject.com>
-# Copyright (c) 2005, the Lawrence Journal-World
-# Used under the modified BSD license:
-# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
-TEMPLATE = <<'HTML'
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
-<html lang="en">
-<head>
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <title><%=h message %> at <%=h req.script_name + req.path_info %></title>
- <meta name="robots" content="NONE,NOARCHIVE" />
- <style type="text/css">
- html * { padding:0; margin:0; }
- body * { padding:10px 20px; }
- body * * { padding:0; }
- body { font:small sans-serif; background:#eee; }
- body>div { border-bottom:1px solid #ddd; }
- h1 { font-weight:normal; margin-bottom:.4em; }
- h1 span { font-size:60%; color:#666; font-weight:normal; }
- table { border:none; border-collapse: collapse; width:100%; }
- td, th { vertical-align:top; padding:2px 3px; }
- th { width:12em; text-align:right; color:#666; padding-right:.5em; }
- #info { background:#f6f6f6; }
- #info ol { margin: 0.5em 4em; }
- #info ol li { font-family: monospace; }
- #summary { background: #ffc; }
- #explanation { background:#eee; border-bottom: 0px none; }
- </style>
-</head>
-<body>
- <div id="summary">
- <h1><%=h message %> <span>(<%= status.to_i %>)</span></h1>
- <table class="meta">
- <tr>
- <th>Request Method:</th>
- <td><%=h req.request_method %></td>
- </tr>
- <tr>
- <th>Request URL:</th>
- <td><%=h req.url %></td>
- </tr>
- </table>
- </div>
- <div id="info">
- <p><%= detail %></p>
- </div>
-
- <div id="explanation">
- <p>
- You're seeing this error because you use <code>Rack::ShowStatus</code>.
- </p>
- </div>
-</body>
-</html>
-HTML
-
- # :startdoc:
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/static.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/static.rb
deleted file mode 100644
index 168e8f83b2..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/static.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-module Rack
-
- # The Rack::Static middleware intercepts requests for static files
- # (javascript files, images, stylesheets, etc) based on the url prefixes
- # passed in the options, and serves them using a Rack::File object. This
- # allows a Rack stack to serve both static and dynamic content.
- #
- # Examples:
- # use Rack::Static, :urls => ["/media"]
- # will serve all requests beginning with /media from the "media" folder
- # located in the current directory (ie media/*).
- #
- # use Rack::Static, :urls => ["/css", "/images"], :root => "public"
- # will serve all requests beginning with /css or /images from the folder
- # "public" in the current directory (ie public/css/* and public/images/*)
-
- class Static
-
- def initialize(app, options={})
- @app = app
- @urls = options[:urls] || ["/favicon.ico"]
- root = options[:root] || Dir.pwd
- @file_server = Rack::File.new(root)
- end
-
- def call(env)
- path = env["PATH_INFO"]
- can_serve = @urls.any? { |url| path.index(url) == 0 }
-
- if can_serve
- @file_server.call(env)
- else
- @app.call(env)
- end
- end
-
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/urlmap.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/urlmap.rb
deleted file mode 100644
index fcf6616c58..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/urlmap.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-module Rack
- # Rack::URLMap takes a hash mapping urls or paths to apps, and
- # dispatches accordingly. Support for HTTP/1.1 host names exists if
- # the URLs start with <tt>http://</tt> or <tt>https://</tt>.
- #
- # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part
- # relevant for dispatch is in the SCRIPT_NAME, and the rest in the
- # PATH_INFO. This should be taken care of when you need to
- # reconstruct the URL in order to create links.
- #
- # URLMap dispatches in such a way that the longest paths are tried
- # first, since they are most specific.
-
- class URLMap
- def initialize(map = {})
- remap(map)
- end
-
- def remap(map)
- @mapping = map.map { |location, app|
- if location =~ %r{\Ahttps?://(.*?)(/.*)}
- host, location = $1, $2
- else
- host = nil
- end
-
- unless location[0] == ?/
- raise ArgumentError, "paths need to start with /"
- end
- location = location.chomp('/')
-
- [host, location, app]
- }.sort_by { |(h, l, a)| [h ? -h.size : (-1.0 / 0.0), -l.size] } # Longest path first
- end
-
- def call(env)
- path = env["PATH_INFO"].to_s.squeeze("/")
- script_name = env['SCRIPT_NAME']
- hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
- @mapping.each { |host, location, app|
- next unless (hHost == host || sName == host \
- || (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
- next unless location == path[0, location.size]
- next unless path[location.size] == nil || path[location.size] == ?/
-
- return app.call(
- env.merge(
- 'SCRIPT_NAME' => (script_name + location),
- 'PATH_INFO' => path[location.size..-1]))
- }
- [404, {"Content-Type" => "text/plain"}, ["Not Found: #{path}"]]
- end
- end
-end
-
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/utils.rb b/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/utils.rb
deleted file mode 100644
index 42e2e698f4..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-1.1.pre/rack/utils.rb
+++ /dev/null
@@ -1,516 +0,0 @@
-# -*- encoding: binary -*-
-
-require 'set'
-require 'tempfile'
-
-module Rack
- # Rack::Utils contains a grab-bag of useful methods for writing web
- # applications adopted from all kinds of Ruby libraries.
-
- module Utils
- # Performs URI escaping so that you can construct proper
- # query strings faster. Use this rather than the cgi.rb
- # version since it's faster. (Stolen from Camping).
- def escape(s)
- s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
- '%'+$1.unpack('H2'*$1.size).join('%').upcase
- }.tr(' ', '+')
- end
- module_function :escape
-
- # Unescapes a URI escaped string. (Stolen from Camping).
- def unescape(s)
- s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
- [$1.delete('%')].pack('H*')
- }
- end
- module_function :unescape
-
- # Stolen from Mongrel, with some small modifications:
- # Parses a query string by breaking it up at the '&'
- # and ';' characters. You can also use this to parse
- # cookies by changing the characters used in the second
- # parameter (which defaults to '&;').
- def parse_query(qs, d = '&;')
- params = {}
-
- (qs || '').split(/[#{d}] */n).each do |p|
- k, v = unescape(p).split('=', 2)
-
- if cur = params[k]
- if cur.class == Array
- params[k] << v
- else
- params[k] = [cur, v]
- end
- else
- params[k] = v
- end
- end
-
- return params
- end
- module_function :parse_query
-
- def parse_nested_query(qs, d = '&;')
- params = {}
-
- (qs || '').split(/[#{d}] */n).each do |p|
- k, v = unescape(p).split('=', 2)
- normalize_params(params, k, v)
- end
-
- return params
- end
- module_function :parse_nested_query
-
- def normalize_params(params, name, v = nil)
- name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
- k = $1 || ''
- after = $' || ''
-
- return if k.empty?
-
- if after == ""
- params[k] = v
- elsif after == "[]"
- params[k] ||= []
- raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
- params[k] << v
- elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
- child_key = $1
- params[k] ||= []
- raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
- if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
- normalize_params(params[k].last, child_key, v)
- else
- params[k] << normalize_params({}, child_key, v)
- end
- else
- params[k] ||= {}
- raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
- params[k] = normalize_params(params[k], after, v)
- end
-
- return params
- end
- module_function :normalize_params
-
- def build_query(params)
- params.map { |k, v|
- if v.class == Array
- build_query(v.map { |x| [k, x] })
- else
- escape(k) + "=" + escape(v)
- end
- }.join("&")
- end
- module_function :build_query
-
- def build_nested_query(value, prefix = nil)
- case value
- when Array
- value.map { |v|
- build_nested_query(v, "#{prefix}[]")
- }.join("&")
- when Hash
- value.map { |k, v|
- build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
- }.join("&")
- when String
- raise ArgumentError, "value must be a Hash" if prefix.nil?
- "#{prefix}=#{escape(value)}"
- else
- prefix
- end
- end
- module_function :build_nested_query
-
- # Escape ampersands, brackets and quotes to their HTML/XML entities.
- def escape_html(string)
- string.to_s.gsub("&", "&amp;").
- gsub("<", "&lt;").
- gsub(">", "&gt;").
- gsub("'", "&#39;").
- gsub('"', "&quot;")
- end
- module_function :escape_html
-
- def select_best_encoding(available_encodings, accept_encoding)
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
-
- expanded_accept_encoding =
- accept_encoding.map { |m, q|
- if m == "*"
- (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
- else
- [[m, q]]
- end
- }.inject([]) { |mem, list|
- mem + list
- }
-
- encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
-
- unless encoding_candidates.include?("identity")
- encoding_candidates.push("identity")
- end
-
- expanded_accept_encoding.find_all { |m, q|
- q == 0.0
- }.each { |m, _|
- encoding_candidates.delete(m)
- }
-
- return (encoding_candidates & available_encodings)[0]
- end
- module_function :select_best_encoding
-
- # Return the bytesize of String; uses String#length under Ruby 1.8 and
- # String#bytesize under 1.9.
- if ''.respond_to?(:bytesize)
- def bytesize(string)
- string.bytesize
- end
- else
- def bytesize(string)
- string.size
- end
- end
- module_function :bytesize
-
- # Context allows the use of a compatible middleware at different points
- # in a request handling stack. A compatible middleware must define
- # #context which should take the arguments env and app. The first of which
- # would be the request environment. The second of which would be the rack
- # application that the request would be forwarded to.
- class Context
- attr_reader :for, :app
-
- def initialize(app_f, app_r)
- raise 'running context does not respond to #context' unless app_f.respond_to? :context
- @for, @app = app_f, app_r
- end
-
- def call(env)
- @for.context(env, @app)
- end
-
- def recontext(app)
- self.class.new(@for, app)
- end
-
- def context(env, app=@app)
- recontext(app).call(env)
- end
- end
-
- # A case-insensitive Hash that preserves the original case of a
- # header when set.
- class HeaderHash < Hash
- def initialize(hash={})
- @names = {}
- hash.each { |k, v| self[k] = v }
- end
-
- def to_hash
- inject({}) do |hash, (k,v)|
- if v.respond_to? :to_ary
- hash[k] = v.to_ary.join("\n")
- else
- hash[k] = v
- end
- hash
- end
- end
-
- def [](k)
- super @names[k.downcase]
- end
-
- def []=(k, v)
- delete k
- @names[k.downcase] = k
- super k, v
- end
-
- def delete(k)
- super @names.delete(k.downcase)
- end
-
- def include?(k)
- @names.has_key? k.downcase
- end
-
- alias_method :has_key?, :include?
- alias_method :member?, :include?
- alias_method :key?, :include?
-
- def merge!(other)
- other.each { |k, v| self[k] = v }
- self
- end
-
- def merge(other)
- hash = dup
- hash.merge! other
- end
- end
-
- # Every standard HTTP code mapped to the appropriate message.
- # Stolen from Mongrel.
- HTTP_STATUS_CODES = {
- 100 => 'Continue',
- 101 => 'Switching Protocols',
- 200 => 'OK',
- 201 => 'Created',
- 202 => 'Accepted',
- 203 => 'Non-Authoritative Information',
- 204 => 'No Content',
- 205 => 'Reset Content',
- 206 => 'Partial Content',
- 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 Large',
- 415 => 'Unsupported Media Type',
- 416 => 'Requested Range Not Satisfiable',
- 417 => 'Expectation Failed',
- 500 => 'Internal Server Error',
- 501 => 'Not Implemented',
- 502 => 'Bad Gateway',
- 503 => 'Service Unavailable',
- 504 => 'Gateway Timeout',
- 505 => 'HTTP Version Not Supported'
- }
-
- # Responses with HTTP status codes that should not have an entity body
- STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304)
-
- # A multipart form data parser, adapted from IOWA.
- #
- # Usually, Rack::Request#POST takes care of calling this.
-
- module Multipart
- class UploadedFile
- # The filename, *not* including the path, of the "uploaded" file
- attr_reader :original_filename
-
- # The content type of the "uploaded" file
- attr_accessor :content_type
-
- def initialize(path, content_type = "text/plain", binary = false)
- raise "#{path} file does not exist" unless ::File.exist?(path)
- @content_type = content_type
- @original_filename = ::File.basename(path)
- @tempfile = Tempfile.new(@original_filename)
- @tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
- @tempfile.binmode if binary
- FileUtils.copy_file(path, @tempfile.path)
- end
-
- def path
- @tempfile.path
- end
- alias_method :local_path, :path
-
- def method_missing(method_name, *args, &block) #:nodoc:
- @tempfile.__send__(method_name, *args, &block)
- end
- end
-
- EOL = "\r\n"
- MULTIPART_BOUNDARY = "AaB03x"
-
- def self.parse_multipart(env)
- unless env['CONTENT_TYPE'] =~
- %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
- nil
- else
- boundary = "--#{$1}"
-
- params = {}
- buf = ""
- content_length = env['CONTENT_LENGTH'].to_i
- input = env['rack.input']
- input.rewind
-
- boundary_size = Utils.bytesize(boundary) + EOL.size
- bufsize = 16384
-
- content_length -= boundary_size
-
- read_buffer = ''
-
- status = input.read(boundary_size, read_buffer)
- raise EOFError, "bad content body" unless status == boundary + EOL
-
- rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n
-
- loop {
- head = nil
- body = ''
- filename = content_type = name = nil
-
- until head && buf =~ rx
- if !head && i = buf.index(EOL+EOL)
- head = buf.slice!(0, i+2) # First \r\n
- buf.slice!(0, 2) # Second \r\n
-
- filename = head[/Content-Disposition:.* filename="?([^\";]*)"?/ni, 1]
- content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
- name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
-
- if content_type || filename
- body = Tempfile.new("RackMultipart")
- body.binmode if body.respond_to?(:binmode)
- end
-
- next
- end
-
- # Save the read body part.
- if head && (boundary_size+4 < buf.size)
- body << buf.slice!(0, buf.size - (boundary_size+4))
- end
-
- c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer)
- raise EOFError, "bad content body" if c.nil? || c.empty?
- buf << c
- content_length -= c.size
- end
-
- # Save the rest.
- if i = buf.index(rx)
- body << buf.slice!(0, i)
- buf.slice!(0, boundary_size+2)
-
- content_length = -1 if $1 == "--"
- end
-
- if filename == ""
- # filename is blank which means no file has been selected
- data = nil
- elsif filename
- body.rewind
-
- # 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.
- filename =~ /^(?:.*[:\\\/])?(.*)/m
- filename = $1
-
- data = {:filename => filename, :type => content_type,
- :name => name, :tempfile => body, :head => head}
- elsif !filename && content_type
- body.rewind
-
- # Generic multipart cases, not coming from a form
- data = {:type => content_type,
- :name => name, :tempfile => body, :head => head}
- else
- data = body
- end
-
- Utils.normalize_params(params, name, data) unless data.nil?
-
- break if buf.empty? || content_length == -1
- }
-
- input.rewind
-
- params
- end
- end
-
- def self.build_multipart(params, first = true)
- if first
- unless params.is_a?(Hash)
- raise ArgumentError, "value must be a Hash"
- end
-
- multipart = false
- query = lambda { |value|
- case value
- when Array
- value.each(&query)
- when Hash
- value.values.each(&query)
- when UploadedFile
- multipart = true
- end
- }
- params.values.each(&query)
- return nil unless multipart
- end
-
- flattened_params = Hash.new
-
- params.each do |key, value|
- k = first ? key.to_s : "[#{key}]"
-
- case value
- when Array
- value.map { |v|
- build_multipart(v, false).each { |subkey, subvalue|
- flattened_params["#{k}[]#{subkey}"] = subvalue
- }
- }
- when Hash
- build_multipart(value, false).each { |subkey, subvalue|
- flattened_params[k + subkey] = subvalue
- }
- else
- flattened_params[k] = value
- end
- end
-
- if first
- flattened_params.map { |name, file|
- if file.respond_to?(:original_filename)
- ::File.open(file.path, "rb") do |f|
- f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
-<<-EOF
---#{MULTIPART_BOUNDARY}\r
-Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r
-Content-Type: #{file.content_type}\r
-Content-Length: #{::File.stat(file.path).size}\r
-\r
-#{f.read}\r
-EOF
- end
- else
-<<-EOF
---#{MULTIPART_BOUNDARY}\r
-Content-Disposition: form-data; name="#{name}"\r
-\r
-#{file}\r
-EOF
- end
- }.join + "--#{MULTIPART_BOUNDARY}--\r"
- else
- flattened_params
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-test/rack/mock_session.rb b/actionpack/lib/action_dispatch/vendor/rack-test/rack/mock_session.rb
deleted file mode 100644
index eba6226538..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-test/rack/mock_session.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-module Rack
-
- class MockSession
- attr_writer :cookie_jar
- attr_reader :last_response
-
- def initialize(app, default_host = Rack::Test::DEFAULT_HOST)
- @app = app
- @default_host = default_host
- end
-
- def clear_cookies
- @cookie_jar = Rack::Test::CookieJar.new([], @default_host)
- end
-
- def set_cookie(cookie, uri = nil)
- cookie_jar.merge(cookie, uri)
- end
-
- def request(uri, env)
- env["HTTP_COOKIE"] ||= cookie_jar.for(uri)
- @last_request = Rack::Request.new(env)
- status, headers, body = @app.call(@last_request.env)
- @last_response = MockResponse.new(status, headers, body, env["rack.errors"].flush)
- cookie_jar.merge(last_response.headers["Set-Cookie"], uri)
-
- @last_response
- end
-
- # Return the last request issued in the session. Raises an error if no
- # requests have been sent yet.
- def last_request
- raise Rack::Test::Error.new("No request yet. Request a page first.") unless @last_request
- @last_request
- end
-
- # Return the last response received in the session. Raises an error if
- # no requests have been sent yet.
- def last_response
- raise Rack::Test::Error.new("No response yet. Request a page first.") unless @last_response
- @last_response
- end
-
- def cookie_jar
- @cookie_jar ||= Rack::Test::CookieJar.new([], @default_host)
- end
-
- end
-
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-test/rack/test.rb b/actionpack/lib/action_dispatch/vendor/rack-test/rack/test.rb
deleted file mode 100644
index 70384b1d76..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-test/rack/test.rb
+++ /dev/null
@@ -1,239 +0,0 @@
-unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__) + "/.."))
- $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/.."))
-end
-
-require "uri"
-require "rack"
-require "rack/mock_session"
-require "rack/test/cookie_jar"
-require "rack/test/mock_digest_request"
-require "rack/test/utils"
-require "rack/test/methods"
-require "rack/test/uploaded_file"
-
-module Rack
- module Test
-
- VERSION = "0.3.0"
-
- DEFAULT_HOST = "example.org"
- MULTIPART_BOUNDARY = "----------XnJLe9ZIbbGUYtzPQJ16u1"
-
- # The common base class for exceptions raised by Rack::Test
- class Error < StandardError; end
-
- class Session
- extend Forwardable
- include Rack::Test::Utils
-
- def_delegators :@rack_mock_session, :clear_cookies, :set_cookie, :last_response, :last_request
-
- # Initialize a new session for the given Rack app
- def initialize(app, default_host = DEFAULT_HOST)
- @headers = {}
- @default_host = default_host
- @rack_mock_session = Rack::MockSession.new(app, default_host)
- end
-
- # Issue a GET request for the given URI with the given params and Rack
- # environment. Stores the issues request object in #last_request and
- # the app's response in #last_response. Yield #last_response to a block
- # if given.
- #
- # Example:
- # get "/"
- def get(uri, params = {}, env = {}, &block)
- env = env_for(uri, env.merge(:method => "GET", :params => params))
- process_request(uri, env, &block)
- end
-
- # Issue a POST request for the given URI. See #get
- #
- # Example:
- # post "/signup", "name" => "Bryan"
- def post(uri, params = {}, env = {}, &block)
- env = env_for(uri, env.merge(:method => "POST", :params => params))
- process_request(uri, env, &block)
- end
-
- # Issue a PUT request for the given URI. See #get
- #
- # Example:
- # put "/"
- def put(uri, params = {}, env = {}, &block)
- env = env_for(uri, env.merge(:method => "PUT", :params => params))
- process_request(uri, env, &block)
- end
-
- # Issue a DELETE request for the given URI. See #get
- #
- # Example:
- # delete "/"
- def delete(uri, params = {}, env = {}, &block)
- env = env_for(uri, env.merge(:method => "DELETE", :params => params))
- process_request(uri, env, &block)
- end
-
- # Issue a HEAD request for the given URI. See #get
- #
- # Example:
- # head "/"
- def head(uri, params = {}, env = {}, &block)
- env = env_for(uri, env.merge(:method => "HEAD", :params => params))
- process_request(uri, env, &block)
- end
-
- # Issue a request to the Rack app for the given URI and optional Rack
- # environment. Stores the issues request object in #last_request and
- # the app's response in #last_response. Yield #last_response to a block
- # if given.
- #
- # Example:
- # request "/"
- def request(uri, env = {}, &block)
- env = env_for(uri, env)
- process_request(uri, env, &block)
- end
-
- # Set a header to be included on all subsequent requests through the
- # session. Use a value of nil to remove a previously configured header.
- #
- # Example:
- # header "User-Agent", "Firefox"
- def header(name, value)
- if value.nil?
- @headers.delete(name)
- else
- @headers[name] = value
- end
- end
-
- # Set the username and password for HTTP Basic authorization, to be
- # included in subsequent requests in the HTTP_AUTHORIZATION header.
- #
- # Example:
- # basic_authorize "bryan", "secret"
- def basic_authorize(username, password)
- encoded_login = ["#{username}:#{password}"].pack("m*")
- header('HTTP_AUTHORIZATION', "Basic #{encoded_login}")
- end
-
- alias_method :authorize, :basic_authorize
-
- def digest_authorize(username, password)
- @digest_username = username
- @digest_password = password
- end
-
- # Rack::Test will not follow any redirects automatically. This method
- # will follow the redirect returned in the last response. If the last
- # response was not a redirect, an error will be raised.
- def follow_redirect!
- unless last_response.redirect?
- raise Error.new("Last response was not a redirect. Cannot follow_redirect!")
- end
-
- get(last_response["Location"])
- end
-
- private
-
- def env_for(path, env)
- uri = URI.parse(path)
- uri.host ||= @default_host
-
- env = default_env.merge(env)
-
- env.update("HTTPS" => "on") if URI::HTTPS === uri
- env["X-Requested-With"] = "XMLHttpRequest" if env[:xhr]
-
- if (env[:method] == "POST" || env["REQUEST_METHOD"] == "POST") && !env.has_key?(:input)
- env["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
-
- multipart = (Hash === env[:params]) &&
- env[:params].any? { |_, v| UploadedFile === v }
-
- if multipart
- env[:input] = multipart_body(env.delete(:params))
- env["CONTENT_LENGTH"] ||= env[:input].length.to_s
- env["CONTENT_TYPE"] = "multipart/form-data; boundary=#{MULTIPART_BOUNDARY}"
- else
- env[:input] = params_to_string(env.delete(:params))
- end
- end
-
- params = env[:params] || {}
- params.update(parse_query(uri.query))
-
- uri.query = requestify(params)
-
- if env.has_key?(:cookie)
- set_cookie(env.delete(:cookie), uri)
- end
-
- Rack::MockRequest.env_for(uri.to_s, env)
- end
-
- def process_request(uri, env)
- uri = URI.parse(uri)
- uri.host ||= @default_host
-
- @rack_mock_session.request(uri, env)
-
- if retry_with_digest_auth?(env)
- auth_env = env.merge({
- "HTTP_AUTHORIZATION" => digest_auth_header,
- "rack-test.digest_auth_retry" => true
- })
- auth_env.delete('rack.request')
- process_request(uri.path, auth_env)
- else
- yield last_response if block_given?
-
- last_response
- end
- end
-
- def digest_auth_header
- challenge = last_response["WWW-Authenticate"].split(" ", 2).last
- params = Rack::Auth::Digest::Params.parse(challenge)
-
- params.merge!({
- "username" => @digest_username,
- "nc" => "00000001",
- "cnonce" => "nonsensenonce",
- "uri" => last_request.path_info,
- "method" => last_request.env["REQUEST_METHOD"],
- })
-
- params["response"] = MockDigestRequest.new(params).response(@digest_password)
-
- "Digest #{params}"
- end
-
- def retry_with_digest_auth?(env)
- last_response.status == 401 &&
- digest_auth_configured? &&
- !env["rack-test.digest_auth_retry"]
- end
-
- def digest_auth_configured?
- @digest_username
- end
-
- def default_env
- { "rack.test" => true, "REMOTE_ADDR" => "127.0.0.1" }.merge(@headers)
- end
-
- def params_to_string(params)
- case params
- when Hash then requestify(params)
- when nil then ""
- else params
- end
- end
-
- end
-
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-test/rack/test/cookie_jar.rb b/actionpack/lib/action_dispatch/vendor/rack-test/rack/test/cookie_jar.rb
deleted file mode 100644
index d58c914c9b..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-test/rack/test/cookie_jar.rb
+++ /dev/null
@@ -1,169 +0,0 @@
-require "uri"
-module Rack
- module Test
-
- class Cookie
- include Rack::Utils
-
- # :api: private
- attr_reader :name, :value
-
- # :api: private
- def initialize(raw, uri = nil, default_host = DEFAULT_HOST)
- @default_host = default_host
- uri ||= default_uri
-
- # separate the name / value pair from the cookie options
- @name_value_raw, options = raw.split(/[;,] */n, 2)
-
- @name, @value = parse_query(@name_value_raw, ';').to_a.first
- @options = parse_query(options, ';')
-
- @options["domain"] ||= (uri.host || default_host)
- @options["path"] ||= uri.path.sub(/\/[^\/]*\Z/, "")
- end
-
- def replaces?(other)
- [name.downcase, domain, path] == [other.name.downcase, other.domain, other.path]
- end
-
- # :api: private
- def raw
- @name_value_raw
- end
-
- # :api: private
- def empty?
- @value.nil? || @value.empty?
- end
-
- # :api: private
- def domain
- @options["domain"]
- end
-
- def secure?
- @options.has_key?("secure")
- end
-
- # :api: private
- def path
- @options["path"].strip || "/"
- end
-
- # :api: private
- def expires
- Time.parse(@options["expires"]) if @options["expires"]
- end
-
- # :api: private
- def expired?
- expires && expires < Time.now
- end
-
- # :api: private
- def valid?(uri)
- uri ||= default_uri
-
- if uri.host.nil?
- uri.host = @default_host
- end
-
- (!secure? || (secure? && uri.scheme == "https")) &&
- uri.host =~ Regexp.new("#{Regexp.escape(domain)}$", Regexp::IGNORECASE) &&
- uri.path =~ Regexp.new("^#{Regexp.escape(path)}")
- end
-
- # :api: private
- def matches?(uri)
- ! expired? && valid?(uri)
- end
-
- # :api: private
- def <=>(other)
- # Orders the cookies from least specific to most
- [name, path, domain.reverse] <=> [other.name, other.path, other.domain.reverse]
- end
-
- protected
-
- def default_uri
- URI.parse("//" + @default_host + "/")
- end
-
- end
-
- class CookieJar
-
- # :api: private
- def initialize(cookies = [], default_host = DEFAULT_HOST)
- @default_host = default_host
- @cookies = cookies
- @cookies.sort!
- end
-
- def [](name)
- cookies = hash_for(nil)
- # TODO: Should be case insensitive
- cookies[name] && cookies[name].value
- end
-
- def []=(name, value)
- # TODO: needs proper escaping
- merge("#{name}=#{value}")
- end
-
- def merge(raw_cookies, uri = nil)
- return unless raw_cookies
-
- raw_cookies.each_line do |raw_cookie|
- cookie = Cookie.new(raw_cookie, uri, @default_host)
- self << cookie if cookie.valid?(uri)
- end
- end
-
- def <<(new_cookie)
- @cookies.reject! do |existing_cookie|
- new_cookie.replaces?(existing_cookie)
- end
-
- @cookies << new_cookie
- @cookies.sort!
- end
-
- # :api: private
- def for(uri)
- hash_for(uri).values.map { |c| c.raw }.join(';')
- end
-
- def to_hash
- cookies = {}
-
- hash_for(nil).each do |name, cookie|
- cookies[name] = cookie.value
- end
-
- return cookies
- end
-
- protected
-
- def hash_for(uri = nil)
- cookies = {}
-
- # The cookies are sorted by most specific first. So, we loop through
- # all the cookies in order and add it to a hash by cookie name if
- # the cookie can be sent to the current URI. It's added to the hash
- # so that when we are done, the cookies will be unique by name and
- # we'll have grabbed the most specific to the URI.
- @cookies.each do |cookie|
- cookies[cookie.name] = cookie if cookie.matches?(uri)
- end
-
- return cookies
- end
-
- end
-
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-test/rack/test/methods.rb b/actionpack/lib/action_dispatch/vendor/rack-test/rack/test/methods.rb
deleted file mode 100644
index a191fa23d8..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-test/rack/test/methods.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require "forwardable"
-
-module Rack
- module Test
- module Methods
- extend Forwardable
-
- def rack_test_session
- @_rack_test_session ||= Rack::Test::Session.new(app)
- end
-
- def rack_mock_session
- @_rack_mock_session ||= Rack::MockSession.new(app)
- end
-
- METHODS = [
- :request,
-
- # HTTP verbs
- :get,
- :post,
- :put,
- :delete,
- :head,
-
- # Redirects
- :follow_redirect!,
-
- # Header-related features
- :header,
- :set_cookie,
- :clear_cookies,
- :authorize,
- :basic_authorize,
- :digest_authorize,
-
- # Expose the last request and response
- :last_response,
- :last_request
- ]
-
- def_delegators :rack_test_session, *METHODS
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-test/rack/test/mock_digest_request.rb b/actionpack/lib/action_dispatch/vendor/rack-test/rack/test/mock_digest_request.rb
deleted file mode 100644
index 81c398ba51..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-test/rack/test/mock_digest_request.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-module Rack
- module Test
-
- class MockDigestRequest
- def initialize(params)
- @params = params
- end
-
- def method_missing(sym)
- if @params.has_key? k = sym.to_s
- return @params[k]
- end
-
- super
- end
-
- def method
- @params['method']
- end
-
- def response(password)
- Rack::Auth::Digest::MD5.new(nil).send :digest, self, password
- end
- end
-
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-test/rack/test/uploaded_file.rb b/actionpack/lib/action_dispatch/vendor/rack-test/rack/test/uploaded_file.rb
deleted file mode 100644
index 239302fbe4..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-test/rack/test/uploaded_file.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-require "tempfile"
-
-module Rack
- module Test
-
- class UploadedFile
- # The filename, *not* including the path, of the "uploaded" file
- attr_reader :original_filename
-
- # The content type of the "uploaded" file
- attr_accessor :content_type
-
- def initialize(path, content_type = "text/plain", binary = false)
- raise "#{path} file does not exist" unless ::File.exist?(path)
- @content_type = content_type
- @original_filename = ::File.basename(path)
- @tempfile = Tempfile.new(@original_filename)
- @tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
- @tempfile.binmode if binary
- FileUtils.copy_file(path, @tempfile.path)
- end
-
- def path
- @tempfile.path
- end
-
- alias_method :local_path, :path
-
- def method_missing(method_name, *args, &block) #:nodoc:
- @tempfile.__send__(method_name, *args, &block)
- end
-
- end
-
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-test/rack/test/utils.rb b/actionpack/lib/action_dispatch/vendor/rack-test/rack/test/utils.rb
deleted file mode 100644
index d25b849709..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-test/rack/test/utils.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-module Rack
- module Test
-
- module Utils
- include Rack::Utils
-
- def requestify(value, prefix = nil)
- case value
- when Array
- value.map do |v|
- requestify(v, "#{prefix}[]")
- end.join("&")
- when Hash
- value.map do |k, v|
- requestify(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
- end.join("&")
- else
- "#{prefix}=#{escape(value)}"
- end
- end
-
- module_function :requestify
-
- def multipart_requestify(params, first=true)
- p = Hash.new
-
- params.each do |key, value|
- k = first ? key.to_s : "[#{key}]"
-
- if Hash === value
- multipart_requestify(value, false).each do |subkey, subvalue|
- p[k + subkey] = subvalue
- end
- else
- p[k] = value
- end
- end
-
- return p
- end
-
- module_function :multipart_requestify
-
- def multipart_body(params)
- multipart_requestify(params).map do |key, value|
- if value.respond_to?(:original_filename)
- ::File.open(value.path, "rb") do |f|
- f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
-
- <<-EOF
---#{MULTIPART_BOUNDARY}\r
-Content-Disposition: form-data; name="#{key}"; filename="#{escape(value.original_filename)}"\r
-Content-Type: #{value.content_type}\r
-Content-Length: #{::File.stat(value.path).size}\r
-\r
-#{f.read}\r
-EOF
- end
- else
-<<-EOF
---#{MULTIPART_BOUNDARY}\r
-Content-Disposition: form-data; name="#{key}"\r
-\r
-#{value}\r
-EOF
- end
- end.join("")+"--#{MULTIPART_BOUNDARY}--\r"
- end
-
- module_function :multipart_body
-
- end
-
- end
-end
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index c171a5a8f5..ec1b07797b 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -175,6 +175,17 @@ module ActionView #:nodoc:
attr_accessor :controller
attr_internal :captures
+ def reset_formats(formats)
+ @formats = formats
+
+ if defined?(ActionController)
+ # This is expensive, but we need to reset this when the format is updated,
+ # which currently only happens
+ Thread.current[:format_locale_key] =
+ ActionController::HashKey.get(self.class, formats, I18n.locale)
+ end
+ end
+
class << self
delegate :erb_trim_mode=, :to => 'ActionView::TemplateHandlers::ERB'
delegate :logger, :to => 'ActionController::Base', :allow_nil => true
@@ -240,7 +251,7 @@ module ActionView #:nodoc:
end
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil, formats = nil)#:nodoc:
- @formats = formats || [:html]
+ @formats = formats
@assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) }
@controller = controller
@helpers = self.class.helpers || Module.new
@@ -255,15 +266,6 @@ module ActionView #:nodoc:
@view_paths = self.class.process_view_paths(paths)
end
- def with_template(current_template)
- _evaluate_assigns_and_ivars
- last_template, self.template = template, current_template
- last_formats, self.formats = formats, current_template.formats
- yield
- ensure
- self.template, self.formats = last_template, last_formats
- end
-
def punctuate_body!(part)
flush_output_buffer
response.body_parts << part
@@ -272,18 +274,11 @@ module ActionView #:nodoc:
# Evaluates the local assigns and controller ivars, pushes them to the view.
def _evaluate_assigns_and_ivars #:nodoc:
- @assigns_added ||= _copy_ivars_from_controller
- end
-
- private
-
- def _copy_ivars_from_controller #:nodoc:
if @controller
variables = @controller.instance_variable_names
variables -= @controller.protected_instance_variables if @controller.respond_to?(:protected_instance_variables)
variables.each { |name| instance_variable_set(name, @controller.instance_variable_get(name)) }
end
- true
end
end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 72f162cb20..81029102b1 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -738,6 +738,7 @@ module ActionView
options = options.stringify_keys
tag_value = options.delete("value")
name_and_id = options.dup
+ name_and_id["id"] = name_and_id["for"]
add_default_name_and_id_for_value(tag_value, name_and_id)
options.delete("index")
options["for"] ||= name_and_id["id"]
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 8cb5882aab..3db5202e7d 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -162,6 +162,60 @@ module ActionView
InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
end
+
+ # Returns <tt><select></tt>, <tt><optgroup></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.
+ #
+ # Parameters:
+ # * +object+ - The instance of the class to be used for the select tag
+ # * +method+ - The attribute of +object+ corresponding to the select tag
+ # * +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.
+ #
+ # 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
+ # class City < ActiveRecord::Base
+ # belongs_to :country
+ # # attribs: id, name, country_id
+ # end
+ #
+ # Sample usage:
+ # grouped_collection_select(:city, :country_id, @continents, :countries, :name, :id, :name)
+ #
+ # Possible output:
+ # <select name="city[country_id]">
+ # <optgroup label="Africa">
+ # <option value="1">South Africa</option>
+ # <option value="3">Somalia</option>
+ # </optgroup>
+ # <optgroup label="Europe">
+ # <option value="7" selected="selected">Denmark</option>
+ # <option value="2">Ireland</option>
+ # </optgroup>
+ # </select>
+ #
+ def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
+ InstanceTag.new(object, method, self, options.delete(:object)).to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, 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.
#
@@ -490,6 +544,15 @@ module ActionView
)
end
+ def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_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(option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value), 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)
@@ -508,7 +571,8 @@ module ActionView
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
+ prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('support.select.prompt', :default => 'Please select')
+ "<option value=\"\">#{prompt}</option>\n" + option_tags
else
option_tags
end
@@ -524,6 +588,10 @@ module ActionView
@template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
end
+ def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
+ @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options))
+ end
+
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
@template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
end
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index 624b537ad2..03f1dabb4e 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -991,12 +991,13 @@ module ActionView
def render(*options_for_render)
old_formats = @context && @context.formats
- @context.formats = [:html] if @context
+
+ @context.reset_formats([:html]) if @context
Hash === options_for_render.first ?
@context.render(*options_for_render) :
options_for_render.first.to_s
ensure
- @context.formats = old_formats if @context
+ @context.reset_formats(old_formats) if @context
end
def javascript_object_for(object)
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index c5a6d1f084..b07304e361 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -568,7 +568,7 @@ module ActionView
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;"
+ "if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method, url, href)} };return false;"
when confirm
"return #{confirm_javascript_function(confirm)};"
when method
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index afe35691bc..c82cd07ec2 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -108,3 +108,7 @@
# The variable :count is also available
body: "There were problems with the following fields:"
+ support:
+ select:
+ # default value for :prompt => true in FormOptionsHelper
+ prompt: "Please select" \ No newline at end of file
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
index 4001757a9b..5524a3219a 100644
--- a/actionpack/lib/action_view/paths.rb
+++ b/actionpack/lib/action_view/paths.rb
@@ -34,7 +34,6 @@ module ActionView #:nodoc:
end
def find(path, details = {}, prefix = nil, partial = false)
- # template_path = path.sub(/^\//, '')
template_path = path
each do |load_path|
@@ -43,8 +42,6 @@ module ActionView #:nodoc:
end
end
- # TODO: Have a fallback absolute path?
- extension = details[:formats] || []
raise ActionView::MissingTemplate.new(self, "#{prefix}/#{path} - #{details.inspect} - partial: #{!!partial}")
end
diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb
index 64f08c447d..7f10f54d2e 100644
--- a/actionpack/lib/action_view/render/partials.rb
+++ b/actionpack/lib/action_view/render/partials.rb
@@ -173,77 +173,114 @@ module ActionView
extend ActiveSupport::Concern
class PartialRenderer
- def self.partial_names
- @partial_names ||= Hash.new {|h,k| h[k] = ActiveSupport::ConcurrentHash.new }
- end
+ PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} }
+ TEMPLATES = Hash.new {|h,k| h[k] = {} }
- def self.formats
- @formats ||= Hash.new {|h,k| h[k] = Hash.new{|h,k| h[k] = Hash.new {|h,k| h[k] = {}}}}
- end
+ attr_reader :template
def initialize(view_context, options, block)
- partial = options[:partial]
-
@view = view_context
- @options = options
- @locals = options[:locals] || {}
- @block = block
-
- # Set up some instance variables to speed up memoizing
- @partial_names = self.class.partial_names[@view.controller.class]
- @templates = self.class.formats
- @format = view_context.formats
-
- # Set up the object and path
- @object = partial.is_a?(String) ? options[:object] : partial
- @path = partial_path(partial)
+ @partial_names = PARTIAL_NAMES[@view.controller.class]
+
+ key = Thread.current[:format_locale_key]
+ @templates = TEMPLATES[key] if key
+
+ setup(options, block)
+ end
+
+ def setup(options, block)
+ partial = options[:partial]
+
+ @options = options
+ @locals = options[:locals] || {}
+ @block = block
+
+ if String === partial
+ @object = options[:object]
+ @path = partial
+ else
+ @object = partial
+ @path = partial_path(partial)
+ end
end
def render
- return render_collection if collection
-
- template = find_template
- render_template(template, @object || @locals[template.variable_name])
+ if @collection = collection
+ render_collection
+ else
+ @template = template = find_template
+ render_template(template, @object || @locals[template.variable_name])
+ end
end
def render_collection
- # Even if no template is rendered, this will ensure that the MIME type
- # for the empty response is the same as the provided template
- @options[:_template] = default_template = find_template
+ @template = template = find_template
- return nil if collection.blank?
+ return nil if @collection.blank?
if @options.key?(:spacer_template)
spacer = find_template(@options[:spacer_template]).render(@view, @locals)
end
- segments = []
+ result = template ? collection_with_template(template) : collection_without_template
+ result.join(spacer)
+ end
+
+ def collection_with_template(template)
+ options = @options
+
+ segments, locals, as = [], @locals, options[:as] || template.variable_name
+
+ counter_name = template.counter_name
+ locals[counter_name] = -1
+
+ @collection.each do |object|
+ locals[counter_name] += 1
+ locals[as] = object
+
+ segments << template.render(@view, locals)
+ end
+
+ @template = template
+ segments
+ end
+
+ def collection_without_template
+ options = @options
+
+ segments, locals, as = [], @locals, options[:as]
+ index, template = -1, nil
- collection.each_with_index do |object, index|
- template = default_template || find_template(partial_path(object))
- @locals[template.counter_name] = index
- segments << render_template(template, object)
+ @collection.each do |object|
+ template = find_template(partial_path(object))
+ locals[template.counter_name] = (index += 1)
+ locals[template.variable_name] = object
+
+ segments << template.render(@view, locals)
end
- segments.join(spacer)
+ @template = template
+ segments
end
def render_template(template, object = @object)
- @options[:_template] ||= template
+ options, locals, view = @options, @locals, @view
+ locals[options[:as] || template.variable_name] = object
- # TODO: is locals[:object] really necessary?
- @locals[:object] = @locals[template.variable_name] = object
- @locals[@options[:as]] = object if @options[:as]
+ content = template.render(view, locals) do |*name|
+ @view._layout_for(*name, &@block)
+ end
- content = @view._render_single_template(template, @locals, &@block)
- return content if @block || !@options[:layout]
- find_template(@options[:layout]).render(@view, @locals) { content }
+ if @block || !options[:layout]
+ content
+ else
+ find_template(options[:layout]).render(@view, @locals) { content }
+ end
end
-
private
def collection
- @collection ||= if @object.respond_to?(:to_ary)
+ if @object.respond_to?(:to_ary)
@object
elsif @options.key?(:collection)
@options[:collection] || []
@@ -251,15 +288,19 @@ module ActionView
end
def find_template(path = @path)
- return if !path
- @templates[path][@view.controller_path][@format][I18n.locale] ||= begin
- prefix = @view.controller.controller_path unless path.include?(?/)
- @view.find(path, {:formats => @view.formats}, prefix, true)
+ unless @templates
+ path && _find_template(path)
+ else
+ path && @templates[path] ||= _find_template(path)
end
end
+
+ def _find_template(path)
+ prefix = @view.controller.controller_path unless path.include?(?/)
+ @view.find(path, {:formats => @view.formats}, prefix, true)
+ end
def partial_path(object = @object)
- return object if object.is_a?(String)
@partial_names[object.class] ||= begin
return nil unless object.respond_to?(:to_model)
@@ -272,14 +313,26 @@ module ActionView
end
def render_partial(options)
- @assigns_added = false
- # TODO: Handle other details here.
- self.formats = options[:_details][:formats] if options[:_details]
- _render_partial(options)
+ _evaluate_assigns_and_ivars
+
+ details = options[:_details]
+
+ # Is this needed
+ self.formats = details[:formats] if details
+ renderer = PartialRenderer.new(self, options, nil)
+ text = renderer.render
+ options[:_template] = renderer.template
+ text
end
def _render_partial(options, &block) #:nodoc:
- PartialRenderer.new(self, options, block).render
+ if @renderer
+ @renderer.setup(options, block)
+ else
+ @renderer = PartialRenderer.new(self, options, block)
+ end
+
+ @renderer.render
end
end
diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb
index c7afc56e3b..b0b75918b7 100644
--- a/actionpack/lib/action_view/render/rendering.rb
+++ b/actionpack/lib/action_view/render/rendering.rb
@@ -12,8 +12,6 @@ module ActionView
# as the locals hash.
def render(options = {}, locals = {}, &block) #:nodoc:
case options
- when String, NilClass
- _render_partial(:partial => options, :locals => locals || {})
when Hash
layout = options[:layout]
@@ -35,26 +33,8 @@ module ActionView
end
when :update
update_page(&block)
- end
- end
-
- def _render_content(content, layout, locals)
- return content unless layout
-
- locals ||= {}
-
- if controller && layout
- @_layout = layout.identifier
- logger.info("Rendering template within #{layout.identifier}") if logger
- end
-
- begin
- old_content, @_content_for[:layout] = @_content_for[:layout], content
-
- @cached_content_for_layout = @_content_for[:layout]
- _render_single_template(layout, locals)
- ensure
- @_content_for[:layout] = old_content
+ else
+ _render_partial(:partial => options, :locals => locals)
end
end
@@ -90,48 +70,25 @@ module ActionView
# In this case, the layout would receive the block passed into <tt>render :layout</tt>,
# and the Struct specified in the layout would be passed into the block. The result
# would be <html>Hello David</html>.
- def _layout_for(names, &block)
- with_output_buffer do
- # This is due to the potentially ambiguous use of yield when
- # a block is passed in to a template *and* there is a content_for()
- # of the same name. Suggested solution: require explicit use of content_for
- # in these ambiguous cases.
- #
- # We would be able to continue supporting yield in all non-ambiguous
- # cases. Question: should we deprecate yield in favor of content_for
- # and reserve yield for cases where there is a yield into a real block?
- if @_content_for.key?(names.first) || !block_given?
- return @_content_for[names.first || :layout]
- else
- return yield(names)
- end
- end
- end
+ def _layout_for(name = nil)
+ return @_content_for[name || :layout] if !block_given? || name
- def _render_single_template(template, locals = {}, &block)
- with_template(template) do
- template.render(self, locals) do |*names|
- _layout_for(names, &block)
- end
- end
- rescue Exception => e
- if e.is_a?(TemplateError)
- e.sub_template_of(template)
- raise e
- else
- raise TemplateError.new(template, assigns, e)
+ with_output_buffer do
+ return yield
end
end
def _render_inline(inline, layout, options)
handler = Template.handler_class_for_extension(options[:type] || "erb")
template = Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {})
- content = _render_single_template(template, options[:locals] || {})
- layout ? _render_content(content, layout, options[:locals]) : content
+ locals = options[:locals] || {}
+ content = template.render(self, locals)
+ content = layout.render(self, locals) {|*name| _layout_for(*name) { content } } if layout
+ content
end
def _render_text(text, layout, options)
- layout ? _render_content(text, layout, options[:locals]) : text
+ text = layout.render(self, options[:locals]) { text } if layout
end
# This is the API to render a ViewContext's template from a controller.
@@ -141,7 +98,7 @@ module ActionView
# _layout:: The layout, if any, to wrap the Template in
# _partial:: true if the template is a partial
def render_template(options)
- @assigns_added = nil
+ _evaluate_assigns_and_ivars
template, layout, partial = options.values_at(:_template, :_layout, :_partial)
_render_template(template, layout, options, partial)
end
@@ -158,10 +115,18 @@ module ActionView
content = if partial
_render_partial_object(template, options)
else
- _render_single_template(template, locals)
+ template.render(self, locals)
end
- _render_content(content, layout, locals)
+ @cached_content_for_layout = content
+ @_content_for[:layout] = content
+
+ if layout
+ @_layout = layout.identifier
+ logger.info("Rendering template within #{layout.identifier}") if logger
+ content = layout.render(self, locals) {|*name| _layout_for(*name) }
+ end
+ content
end
end
end \ No newline at end of file
diff --git a/actionpack/lib/action_view/template/handler.rb b/actionpack/lib/action_view/template/handler.rb
index 3071c78174..4bf58b9fa8 100644
--- a/actionpack/lib/action_view/template/handler.rb
+++ b/actionpack/lib/action_view/template/handler.rb
@@ -26,11 +26,7 @@ module ActionView
self.default_format = Mime::HTML
def self.call(template)
- "#{name}.new(self).render(template, local_assigns)"
- end
-
- def initialize(view = nil)
- @view = view
+ raise "Need to implement #{self.class.name}#call(template)"
end
def render(template, local_assigns)
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index ebfc6cc8ce..0b4c62d4d0 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -4,7 +4,7 @@ require "action_view/template/template"
module ActionView
# Abstract superclass
class Resolver
- def initialize(options)
+ def initialize(options = {})
@cache = options[:cache]
@cached = {}
end
@@ -41,8 +41,10 @@ module ActionView
end
def handler_glob
- e = TemplateHandlers.extensions.map{|h| ".#{h},"}.join
- "{#{e}}"
+ @handler_glob ||= begin
+ e = TemplateHandlers.extensions.map{|h| ".#{h}"}.join(",")
+ "{#{e}}"
+ end
end
def formats_glob
@@ -60,6 +62,10 @@ module ActionView
class FileSystemResolver < Resolver
+ def self.cached_glob
+ @@cached_glob ||= {}
+ end
+
def initialize(path, options = {})
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
super(options)
@@ -105,20 +111,22 @@ module ActionView
# :api: plugin
def details_to_glob(name, details, prefix, partial, root)
- path = ""
- path << "#{prefix}/" unless prefix.empty?
- path << (partial ? "_#{name}" : name)
-
- extensions = ""
- [:locales, :formats].each do |k|
- extensions << if exts = details[k]
- '{' + exts.map {|e| ".#{e},"}.join + '}'
- else
- k == :formats ? formats_glob : ''
+ self.class.cached_glob[[name, prefix, partial, details, root]] ||= begin
+ path = ""
+ path << "#{prefix}/" unless prefix.empty?
+ path << (partial ? "_#{name}" : name)
+
+ extensions = ""
+ [:locales, :formats].each do |k|
+ extensions << if exts = details[k]
+ '{' + exts.map {|e| ".#{e},"}.join + '}'
+ else
+ k == :formats ? formats_glob : ''
+ end
end
- end
- "#{root}#{path}#{extensions}#{handler_glob}"
+ "#{root}#{path}#{extensions}#{handler_glob}"
+ end
end
# TODO: fix me
diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb
index 33d3f79ad3..7d6964e3e3 100644
--- a/actionpack/lib/action_view/template/template.rb
+++ b/actionpack/lib/action_view/template/template.rb
@@ -26,9 +26,16 @@ module ActionView
@details[:formats] = Array.wrap(format.to_sym)
end
- def render(view, locals, &blk)
+ def render(view, locals, &block)
method_name = compile(locals, view)
- view.send(method_name, locals, &blk)
+ view.send(method_name, locals, &block)
+ rescue Exception => e
+ if e.is_a?(TemplateError)
+ e.sub_template_of(self)
+ raise e
+ else
+ raise TemplateError.new(self, view.assigns, e)
+ end
end
# TODO: Figure out how to abstract this
@@ -90,7 +97,22 @@ module ActionView
raise ActionView::TemplateError.new(self, {}, e)
end
end
-
+
+ class LocalsKey
+ @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {} } }
+
+ def self.get(*locals)
+ @hash_keys[*locals] ||= new(klass, format, locale)
+ end
+
+ attr_accessor :hash
+ def initialize(klass, format, locale)
+ @hash = locals.hash
+ end
+
+ alias_method :eql?, :equal?
+ end
+
def build_method_name(locals)
# TODO: is locals.keys.hash reliably the same?
@method_names[locals.keys.hash] ||=
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index e51744d095..c2ccd1d3a5 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -9,14 +9,16 @@ module ActionView
end
attr_internal :rendered
- alias_method :_render_template_without_template_tracking, :_render_single_template
- def _render_single_template(template, local_assigns, &block)
- if template.respond_to?(:identifier) && template.present?
- @_rendered[:partials][template] += 1 if template.partial?
- @_rendered[:template] ||= []
- @_rendered[:template] << template
- end
- _render_template_without_template_tracking(template, local_assigns, &block)
+ end
+
+ class Template
+ alias_method :render_without_tracking, :render
+ def render(view, locals, &blk)
+ rendered = view.rendered
+ rendered[:partials][self] += 1 if partial?
+ rendered[:template] ||= []
+ rendered[:template] << self
+ render_without_tracking(view, locals, &blk)
end
end
@@ -68,9 +70,8 @@ module ActionView
def initialize
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
-
+
@params = {}
- send(:initialize_current_url)
end
end
diff --git a/actionpack/test/abstract_controller/abstract_controller_test.rb b/actionpack/test/abstract_controller/abstract_controller_test.rb
index 9438a4dfc9..7991436703 100644
--- a/actionpack/test/abstract_controller/abstract_controller_test.rb
+++ b/actionpack/test/abstract_controller/abstract_controller_test.rb
@@ -19,8 +19,9 @@ module AbstractController
class TestBasic < ActiveSupport::TestCase
test "dispatching works" do
- result = Me.new.process(:index)
- assert_equal "Hello world", result.response_body
+ controller = Me.new
+ controller.process(:index)
+ assert_equal "Hello world", controller.response_body
end
end
@@ -67,29 +68,33 @@ module AbstractController
end
class TestRenderingController < ActiveSupport::TestCase
+ def setup
+ @controller = Me2.new
+ end
+
test "rendering templates works" do
- result = Me2.new.process(:index)
- assert_equal "Hello from index.erb", result.response_body
+ @controller.process(:index)
+ assert_equal "Hello from index.erb", @controller.response_body
end
test "rendering passes ivars to the view" do
- result = Me2.new.process(:action_with_ivars)
- assert_equal "Hello from index_with_ivars.erb", result.response_body
+ @controller.process(:action_with_ivars)
+ assert_equal "Hello from index_with_ivars.erb", @controller.response_body
end
test "rendering with no template name" do
- result = Me2.new.process(:naked_render)
- assert_equal "Hello from naked_render.erb", result.response_body
+ @controller.process(:naked_render)
+ assert_equal "Hello from naked_render.erb", @controller.response_body
end
test "rendering to a rack body" do
- result = Me2.new.process(:rendering_to_body)
- assert_equal "Hello from naked_render.erb", result.response_body
+ @controller.process(:rendering_to_body)
+ assert_equal "Hello from naked_render.erb", @controller.response_body
end
test "rendering to a string" do
- result = Me2.new.process(:rendering_to_string)
- assert_equal "Hello from naked_render.erb", result.response_body
+ @controller.process(:rendering_to_string)
+ assert_equal "Hello from naked_render.erb", @controller.response_body
end
end
@@ -119,14 +124,18 @@ module AbstractController
end
class TestPrefixedViews < ActiveSupport::TestCase
+ def setup
+ @controller = Me3.new
+ end
+
test "templates are located inside their 'prefix' folder" do
- result = Me3.new.process(:index)
- assert_equal "Hello from me3/index.erb", result.response_body
+ @controller.process(:index)
+ assert_equal "Hello from me3/index.erb", @controller.response_body
end
test "templates included their format" do
- result = Me3.new.process(:formatted)
- assert_equal "Hello from me3/formatted.html.erb", result.response_body
+ @controller.process(:formatted)
+ assert_equal "Hello from me3/formatted.html.erb", @controller.response_body
end
end
@@ -168,8 +177,9 @@ module AbstractController
class TestLayouts < ActiveSupport::TestCase
test "layouts are included" do
- result = Me4.new.process(:index)
- assert_equal "Me4 Enter : Hello from me4/index.erb : Exit", result.response_body
+ controller = Me4.new
+ result = controller.process(:index)
+ assert_equal "Me4 Enter : Hello from me4/index.erb : Exit", controller.response_body
end
end
@@ -203,10 +213,11 @@ module AbstractController
end
class TestRespondToAction < ActiveSupport::TestCase
-
+
def assert_dispatch(klass, body = "success", action = :index)
- response = klass.new.process(action).response_body
- assert_equal body, response
+ controller = klass.new
+ controller.process(action)
+ assert_equal body, controller.response_body
end
test "an arbitrary method is available as an action by default" do
diff --git a/actionpack/test/abstract_controller/callbacks_test.rb b/actionpack/test/abstract_controller/callbacks_test.rb
index 817f60f7d1..8f62adce8c 100644
--- a/actionpack/test/abstract_controller/callbacks_test.rb
+++ b/actionpack/test/abstract_controller/callbacks_test.rb
@@ -19,10 +19,11 @@ module AbstractController
end
end
- class TestCallbacks < ActiveSupport::TestCase
+ class TestCallbacks1 < ActiveSupport::TestCase
test "basic callbacks work" do
- result = Callback1.new.process(:index)
- assert_equal "Hello world", result.response_body
+ controller = Callback1.new
+ result = controller.process(:index)
+ assert_equal "Hello world", controller.response_body
end
end
@@ -50,20 +51,24 @@ module AbstractController
end
end
- class TestCallbacks < ActiveSupport::TestCase
+ class TestCallbacks2 < ActiveSupport::TestCase
+ def setup
+ @controller = Callback2.new
+ end
+
test "before_filter works" do
- result = Callback2.new.process(:index)
- assert_equal "Hello world", result.response_body
+ result = @controller.process(:index)
+ assert_equal "Hello world", @controller.response_body
end
test "after_filter works" do
- result = Callback2.new.process(:index)
- assert_equal "Goodbye", result.instance_variable_get("@second")
+ @controller.process(:index)
+ assert_equal "Goodbye", @controller.instance_variable_get("@second")
end
test "around_filter works" do
- result = Callback2.new.process(:index)
- assert_equal "FIRSTSECOND", result.instance_variable_get("@aroundz")
+ @controller.process(:index)
+ assert_equal "FIRSTSECOND", @controller.instance_variable_get("@aroundz")
end
end
@@ -81,15 +86,19 @@ module AbstractController
end
end
- class TestCallbacks < ActiveSupport::TestCase
+ class TestCallbacks3 < ActiveSupport::TestCase
+ def setup
+ @controller = Callback3.new
+ end
+
test "before_filter works with procs" do
- result = Callback3.new.process(:index)
- assert_equal "Hello world", result.response_body
+ result = @controller.process(:index)
+ assert_equal "Hello world", @controller.response_body
end
test "after_filter works with procs" do
- result = Callback3.new.process(:index)
- assert_equal "Goodbye", result.instance_variable_get("@second")
+ result = @controller.process(:index)
+ assert_equal "Goodbye", @controller.instance_variable_get("@second")
end
end
@@ -116,20 +125,24 @@ module AbstractController
end
end
- class TestCallbacks < ActiveSupport::TestCase
+ class TestCallbacksWithConditions < ActiveSupport::TestCase
+ def setup
+ @controller = CallbacksWithConditions.new
+ end
+
test "when :only is specified, a before filter is triggered on that action" do
- result = CallbacksWithConditions.new.process(:index)
- assert_equal "Hello, World", result.response_body
+ @controller.process(:index)
+ assert_equal "Hello, World", @controller.response_body
end
test "when :only is specified, a before filter is not triggered on other actions" do
- result = CallbacksWithConditions.new.process(:sekrit_data)
- assert_equal "true", result.response_body
+ @controller.process(:sekrit_data)
+ assert_equal "true", @controller.response_body
end
test "when :except is specified, an after filter is not triggered on that action" do
- result = CallbacksWithConditions.new.process(:index)
- assert_nil result.instance_variable_get("@authenticated")
+ result = @controller.process(:index)
+ assert_nil @controller.instance_variable_get("@authenticated")
end
end
@@ -156,20 +169,24 @@ module AbstractController
end
end
- class TestCallbacks < ActiveSupport::TestCase
+ class TestCallbacksWithArrayConditions < ActiveSupport::TestCase
+ def setup
+ @controller = CallbacksWithArrayConditions.new
+ end
+
test "when :only is specified with an array, a before filter is triggered on that action" do
- result = CallbacksWithArrayConditions.new.process(:index)
- assert_equal "Hello, World", result.response_body
+ result = @controller.process(:index)
+ assert_equal "Hello, World", @controller.response_body
end
test "when :only is specified with an array, a before filter is not triggered on other actions" do
- result = CallbacksWithArrayConditions.new.process(:sekrit_data)
- assert_equal "true", result.response_body
+ result = @controller.process(:sekrit_data)
+ assert_equal "true", @controller.response_body
end
test "when :except is specified with an array, an after filter is not triggered on that action" do
- result = CallbacksWithArrayConditions.new.process(:index)
- assert_nil result.instance_variable_get("@authenticated")
+ result = @controller.process(:index)
+ assert_nil @controller.instance_variable_get("@authenticated")
end
end
@@ -181,15 +198,19 @@ module AbstractController
end
end
- class TestCallbacks < ActiveSupport::TestCase
+ class TestCallbacksWithChangedConditions < ActiveSupport::TestCase
+ def setup
+ @controller = ChangedConditions.new
+ end
+
test "when a callback is modified in a child with :only, it works for the :only action" do
- result = ChangedConditions.new.process(:index)
- assert_equal "Hello world", result.response_body
+ result = @controller.process(:index)
+ assert_equal "Hello world", @controller.response_body
end
test "when a callback is modified in a child with :only, it does not work for other actions" do
- result = ChangedConditions.new.process(:not_index)
- assert_equal "", result.response_body
+ result = @controller.process(:not_index)
+ assert_equal "", @controller.response_body
end
end
@@ -207,8 +228,9 @@ module AbstractController
class TestHalting < ActiveSupport::TestCase
test "when a callback sets the response body, the action should not be invoked" do
- result = SetsResponseBody.new.process(:index)
- assert_equal "Success", result.response_body
+ controller = SetsResponseBody.new
+ controller.process(:index)
+ assert_equal "Success", controller.response_body
end
end
diff --git a/actionpack/test/abstract_controller/helper_test.rb b/actionpack/test/abstract_controller/helper_test.rb
index e9a60c0307..34a10cecc9 100644
--- a/actionpack/test/abstract_controller/helper_test.rb
+++ b/actionpack/test/abstract_controller/helper_test.rb
@@ -34,8 +34,9 @@ module AbstractController
class TestHelpers < ActiveSupport::TestCase
def test_helpers
- result = MyHelpers1.new.process(:index)
- assert_equal "Hello World : Included", result.response_body
+ controller = MyHelpers1.new
+ controller.process(:index)
+ assert_equal "Hello World : Included", controller.response_body
end
end
diff --git a/actionpack/test/abstract_controller/layouts_test.rb b/actionpack/test/abstract_controller/layouts_test.rb
index 42f73faa61..995aac7fad 100644
--- a/actionpack/test/abstract_controller/layouts_test.rb
+++ b/actionpack/test/abstract_controller/layouts_test.rb
@@ -143,13 +143,15 @@ module AbstractControllerTests
class TestBase < ActiveSupport::TestCase
test "when no layout is specified, and no default is available, render without a layout" do
- result = Blank.new.process(:index)
- assert_equal "Hello blank!", result.response_body
+ controller = Blank.new
+ controller.process(:index)
+ assert_equal "Hello blank!", controller.response_body
end
test "when layout is specified as a string, render with that layout" do
- result = WithString.new.process(:index)
- assert_equal "With String Hello string!", result.response_body
+ controller = WithString.new
+ controller.process(:index)
+ assert_equal "With String Hello string!", controller.response_body
end
test "when layout is specified as a string, but the layout is missing, raise an exception" do
@@ -157,23 +159,27 @@ module AbstractControllerTests
end
test "when layout is specified as false, do not use a layout" do
- result = WithFalseLayout.new.process(:index)
- assert_equal "Hello false!", result.response_body
+ controller = WithFalseLayout.new
+ controller.process(:index)
+ assert_equal "Hello false!", controller.response_body
end
test "when layout is specified as nil, do not use a layout" do
- result = WithNilLayout.new.process(:index)
- assert_equal "Hello nil!", result.response_body
+ controller = WithNilLayout.new
+ controller.process(:index)
+ assert_equal "Hello nil!", controller.response_body
end
test "when layout is specified as a symbol, call the requested method and use the layout returned" do
- result = WithSymbol.new.process(:index)
- assert_equal "OMGHI2U Hello symbol!", result.response_body
+ controller = WithSymbol.new
+ controller.process(:index)
+ assert_equal "OMGHI2U Hello symbol!", controller.response_body
end
test "when layout is specified as a symbol and the method returns nil, don't use a layout" do
- result = WithSymbolReturningNil.new.process(:index)
- assert_equal "Hello nilz!", result.response_body
+ controller = WithSymbolReturningNil.new
+ controller.process(:index)
+ assert_equal "Hello nilz!", controller.response_body
end
test "when the layout is specified as a symbol and the method doesn't exist, raise an exception" do
@@ -185,29 +191,34 @@ module AbstractControllerTests
end
test "when a child controller does not have a layout, use the parent controller layout" do
- result = WithStringChild.new.process(:index)
- assert_equal "With String Hello string!", result.response_body
+ controller = WithStringChild.new
+ controller.process(:index)
+ assert_equal "With String Hello string!", controller.response_body
end
test "when a child controller has specified a layout, use that layout and not the parent controller layout" do
- result = WithStringOverriddenChild.new.process(:index)
- assert_equal "With Override Hello string!", result.response_body
+ controller = WithStringOverriddenChild.new
+ controller.process(:index)
+ assert_equal "With Override Hello string!", controller.response_body
end
test "when a child controller has an implied layout, use that layout and not the parent controller layout" do
- result = WithStringImpliedChild.new.process(:index)
- assert_equal "With Implied Hello string!", result.response_body
+ controller = WithStringImpliedChild.new
+ controller.process(:index)
+ assert_equal "With Implied Hello string!", controller.response_body
end
test "when a child controller specifies layout nil, do not use the parent layout" do
- result = WithNilChild.new.process(:index)
- assert_equal "Hello string!", result.response_body
+ controller = WithNilChild.new
+ controller.process(:index)
+ assert_equal "Hello string!", controller.response_body
end
test "when a grandchild has no layout specified, the child has an implied layout, and the " \
"parent has specified a layout, use the child controller layout" do
- result = WithChildOfImplied.new.process(:index)
- assert_equal "With Implied Hello string!", result.response_body
+ controller = WithChildOfImplied.new
+ controller.process(:index)
+ assert_equal "With Implied Hello string!", controller.response_body
end
test "raises an exception when specifying layout true" do
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index b062a71442..07ba37c51c 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -6,6 +6,8 @@ $:.unshift(File.dirname(__FILE__) + '/lib')
$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers')
$:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers')
+ENV['TMPDIR'] = File.join(File.dirname(__FILE__), 'tmp')
+
ENV['new_base'] = "true"
$stderr.puts "Running old tests on new_base"
@@ -60,20 +62,33 @@ module ActionController
}
Base.session_store = nil
+ class << Routing
+ def possible_controllers
+ @@possible_controllers ||= []
+ end
+ end
+
class Base
include ActionController::Testing
+
+ def self.inherited(klass)
+ name = klass.name.underscore.sub(/_controller$/, '')
+ ActionController::Routing.possible_controllers << name unless name.blank?
+ super
+ end
end
Base.view_paths = FIXTURE_LOAD_PATH
class TestCase
include TestProcess
+
setup do
ActionController::Routing::Routes.draw do |map|
map.connect ':controller/:action/:id'
end
end
-
+
def assert_template(options = {}, message = nil)
validate_request!
diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb
index 47f8496181..a46ce7a0aa 100644
--- a/actionpack/test/activerecord/active_record_store_test.rb
+++ b/actionpack/test/activerecord/active_record_store_test.rb
@@ -1,12 +1,6 @@
require 'active_record_unit'
class ActiveRecordStoreTest < ActionController::IntegrationTest
- DispatcherApp = ActionController::Dispatcher.new
- SessionApp = ActiveRecord::SessionStore.new(DispatcherApp,
- :key => '_session_id')
- SessionAppWithFixation = ActiveRecord::SessionStore.new(DispatcherApp,
- :key => '_session_id', :cookie_only => false)
-
class TestController < ActionController::Base
def no_session_access
head :ok
@@ -39,7 +33,7 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
def setup
ActiveRecord::SessionStore.session_class.create_table!
- @integration_session = open_session(SessionApp)
+ reset_app!
end
def teardown
@@ -138,9 +132,9 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
end
def test_allows_session_fixation
- @integration_session = open_session(SessionAppWithFixation)
-
with_test_route_set do
+ reset_with_fixation!
+
get '/set_session_value'
assert_response :success
assert cookies['_session_id']
@@ -151,8 +145,7 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
session_id = cookies['_session_id']
assert session_id
- reset!
- @integration_session = open_session(SessionAppWithFixation)
+ reset_with_fixation!
get '/set_session_value', :_session_id => session_id, :foo => "baz"
assert_response :success
@@ -166,6 +159,16 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
end
private
+ def reset_app!
+ app = ActiveRecord::SessionStore.new(ActionController::Dispatcher.new, :key => '_session_id')
+ @integration_session = open_session(app)
+ end
+
+ def reset_with_fixation!
+ app = ActiveRecord::SessionStore.new(ActionController::Dispatcher.new, :key => '_session_id', :cookie_only => false)
+ @integration_session = open_session(app)
+ end
+
def with_test_route_set
with_routing do |set|
set.draw do |map|
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index ecbaba39d1..453812c128 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -182,18 +182,6 @@ end
# a test case to exercise the new capabilities TestRequest & TestResponse
class ActionPackAssertionsControllerTest < ActionController::TestCase
- # let's get this party started
- def setup
- super
- ActionController::Routing::Routes.reload
- ActionController::Routing.use_controllers!(%w(action_pack_assertions admin/inner_module user content admin/user))
- end
-
- def teardown
- super
- ActionController::Routing::Routes.reload
- end
-
# -- assertion-based testing ------------------------------------------------
def test_assert_tag_and_url_for
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 8877057070..b97ceb4594 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -177,17 +177,17 @@ class DefaultUrlOptionsTest < ActionController::TestCase
end
def test_default_url_options_are_used_if_set
- ActionController::Routing::Routes.draw do |map|
- map.default_url_options 'default_url_options', :controller => 'default_url_options'
- map.connect ':controller/:action/:id'
- end
+ with_routing do |set|
+ set.draw do |map|
+ map.default_url_options 'default_url_options', :controller => 'default_url_options'
+ map.connect ':controller/:action/:id'
+ end
- get :default_url_options_action # Make a dummy request so that the controller is initialized properly.
+ get :default_url_options_action # Make a dummy request so that the controller is initialized properly.
- assert_equal 'http://www.override.com/default_url_options/new?bacon=chunky', @controller.url_for(:controller => 'default_url_options')
- assert_equal 'http://www.override.com/default_url_options?bacon=chunky', @controller.send(:default_url_options_url)
- ensure
- ActionController::Routing::Routes.load!
+ assert_equal 'http://www.override.com/default_url_options/new?bacon=chunky', @controller.url_for(:controller => 'default_url_options')
+ assert_equal 'http://www.override.com/default_url_options?bacon=chunky', @controller.send(:default_url_options_url)
+ end
end
end
@@ -206,15 +206,15 @@ class EmptyUrlOptionsTest < ActionController::TestCase
end
end
-class EnsureNamedRoutesWorksTicket22BugTest < Test::Unit::TestCase
+class EnsureNamedRoutesWorksTicket22BugTest < ActionController::TestCase
def test_named_routes_still_work
- ActionController::Routing::Routes.draw do |map|
- map.resources :things
- end
- EmptyController.send :include, ActionController::UrlWriter
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :things
+ end
+ EmptyController.send :include, ActionController::UrlWriter
- assert_equal '/things', EmptyController.new.send(:things_path)
- ensure
- ActionController::Routing::Routes.load!
+ assert_equal '/things', EmptyController.new.send(:things_path)
+ end
end
end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 68529cc8f7..82c790bc19 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -48,10 +48,8 @@ class PageCachingTest < ActionController::TestCase
super
ActionController::Base.perform_caching = true
- ActionController::Routing::Routes.clear!
-
ActionController::Routing::Routes.draw do |map|
- map.main '', :controller => 'posts'
+ map.main '', :controller => 'posts', :format => nil
map.formatted_posts 'posts.:format', :controller => 'posts'
map.resources :posts
map.connect ':controller/:action/:id'
@@ -72,7 +70,6 @@ class PageCachingTest < ActionController::TestCase
def teardown
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
- ActionController::Routing::Routes.clear!
ActionController::Base.perform_caching = false
end
@@ -536,7 +533,6 @@ class FragmentCachingTest < ActionController::TestCase
@controller.params = @params
@controller.request = @request
@controller.response = @response
- @controller.send(:initialize_current_url)
@controller.send(:initialize_template_class, @response)
@controller.send(:assign_shortcuts, @request, @response)
end
diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb
index 511788aec8..c249788c67 100644
--- a/actionpack/test/controller/content_type_test.rb
+++ b/actionpack/test/controller/content_type_test.rb
@@ -46,7 +46,7 @@ class ContentTypeController < ActionController::Base
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.xml { render :action => "render_default_content_types_for_respond_to" }
format.js { render :text => "hello world!" }
format.rss { render :text => "hello world!", :content_type => Mime::XML }
end
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index 8319b5c573..93a815adae 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -79,29 +79,20 @@ class RespondToController < ActionController::Base
end
end
- def custom_constant_handling
- Mime::Type.register("text/x-mobile", :mobile)
+ Mime::Type.register("text/x-mobile", :mobile)
+ def custom_constant_handling
respond_to do |type|
type.html { render :text => "HTML" }
type.mobile { render :text => "Mobile" }
end
- ensure
- Mime::SET.delete(:mobile)
- 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::SET.delete(:mobile)
- Mime.module_eval { remove_const :MOBILE if const_defined?(:MOBILE) }
end
def handle_any
@@ -125,32 +116,24 @@ class RespondToController < ActionController::Base
end
end
+ Mime::Type.register_alias("text/html", :iphone)
+
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::SET.delete(:iphone)
- 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::SET.delete(:iphone)
- Mime.module_eval { remove_const :IPHONE if const_defined?(:IPHONE) }
end
def rescue_action(e)
@@ -213,18 +196,20 @@ class RespondToControllerTest < ActionController::TestCase
def test_js_or_html
@request.accept = "text/javascript, text/html"
- get :js_or_html
+ xhr :get, :js_or_html
assert_equal 'JS', @response.body
- get :html_or_xml
+ @request.accept = "text/javascript, text/html"
+ xhr :get, :html_or_xml
assert_equal 'HTML', @response.body
- get :just_xml
+ @request.accept = "text/javascript, text/html"
+ xhr :get, :just_xml
assert_response 406
end
def test_json_or_yaml
- get :json_or_yaml
+ xhr :get, :json_or_yaml
assert_equal 'JSON', @response.body
get :json_or_yaml, :format => 'json'
@@ -246,13 +231,13 @@ class RespondToControllerTest < ActionController::TestCase
def test_js_or_anything
@request.accept = "text/javascript, */*"
- get :js_or_html
+ xhr :get, :js_or_html
assert_equal 'JS', @response.body
- get :html_or_xml
+ xhr :get, :html_or_xml
assert_equal 'HTML', @response.body
- get :just_xml
+ xhr :get, :just_xml
assert_equal 'XML', @response.body
end
@@ -291,14 +276,16 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_with_atom_content_type
+ @request.accept = ""
@request.env["CONTENT_TYPE"] = "application/atom+xml"
- get :made_for_content_type
+ xhr :get, :made_for_content_type
assert_equal "ATOM", @response.body
end
def test_with_rss_content_type
+ @request.accept = ""
@request.env["CONTENT_TYPE"] = "application/rss+xml"
- get :made_for_content_type
+ xhr :get, :made_for_content_type
assert_equal "RSS", @response.body
end
@@ -497,8 +484,12 @@ class RespondWithController < ActionController::Base
respond_with(Customer.new("david", 13))
end
+ def using_resource_with_collection
+ respond_with([Customer.new("david", 13), Customer.new("jamis", 9)])
+ end
+
def using_resource_with_parent
- respond_with([Quiz::Store.new("developer?", 11), Customer.new("david", 13)])
+ respond_with(Quiz::Store.new("developer?", 11), Customer.new("david", 13))
end
def using_resource_with_status_and_location
@@ -506,7 +497,7 @@ class RespondWithController < ActionController::Base
end
def using_resource_with_responder
- responder = proc { |c, r, o| c.render :text => "Resource name is #{r.name}" }
+ responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" }
respond_with(Customer.new("david", 13), :responder => responder)
end
@@ -540,6 +531,7 @@ class RespondWithControllerTest < ActionController::TestCase
ActionController::Routing::Routes.draw do |map|
map.resources :customers
map.resources :quiz_stores, :has_many => :customers
+ map.connect ":controller/:action/:id"
end
end
@@ -592,7 +584,7 @@ class RespondWithControllerTest < ActionController::TestCase
@request.accept = "application/xml"
get :using_resource
assert_equal "application/xml", @response.content_type
- assert_equal "XML", @response.body
+ assert_equal "<name>david</name>", @response.body
@request.accept = "application/json"
assert_raise ActionView::MissingTemplate do
@@ -622,7 +614,7 @@ class RespondWithControllerTest < ActionController::TestCase
post :using_resource
assert_equal "application/xml", @response.content_type
assert_equal 201, @response.status
- assert_equal "XML", @response.body
+ assert_equal "<name>david</name>", @response.body
assert_equal "http://www.example.com/customers/13", @response.location
errors = { :name => :invalid }
@@ -689,7 +681,7 @@ class RespondWithControllerTest < ActionController::TestCase
get :using_resource_with_parent
assert_equal "application/xml", @response.content_type
assert_equal 200, @response.status
- assert_equal "XML", @response.body
+ assert_equal "<name>david</name>", @response.body
end
def test_using_resource_with_parent_for_post
@@ -698,7 +690,7 @@ class RespondWithControllerTest < ActionController::TestCase
post :using_resource_with_parent
assert_equal "application/xml", @response.content_type
assert_equal 201, @response.status
- assert_equal "XML", @response.body
+ assert_equal "<name>david</name>", @response.body
assert_equal "http://www.example.com/quiz_stores/11/customers/13", @response.location
errors = { :name => :invalid }
@@ -710,6 +702,15 @@ class RespondWithControllerTest < ActionController::TestCase
assert_nil @response.location
end
+ def test_using_resource_with_collection
+ @request.accept = "application/xml"
+ get :using_resource_with_collection
+ assert_equal "application/xml", @response.content_type
+ assert_equal 200, @response.status
+ assert_match /<name>david<\/name>/, @response.body
+ assert_match /<name>jamis<\/name>/, @response.body
+ end
+
def test_clear_respond_to
@controller = InheritedRespondWithController.new
@request.accept = "text/html"
@@ -722,7 +723,14 @@ class RespondWithControllerTest < ActionController::TestCase
@request.accept = "*/*"
get :index
assert_equal "application/xml", @response.content_type
- assert_equal "XML", @response.body
+ assert_equal "<name>david</name>", @response.body
+ end
+
+ def test_block_inside_respond_with_is_rendered
+ @controller = InheritedRespondWithController.new
+ @request.accept = "application/json"
+ get :index
+ assert_equal "JSON", @response.body
end
def test_no_double_render_is_raised
@@ -782,12 +790,8 @@ class PostController < AbstractPostController
protected
def with_iphone
- Mime::Type.register_alias("text/html", :iphone)
request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone"
yield
- ensure
- Mime::SET.delete(:iphone)
- Mime.module_eval { remove_const :IPHONE if const_defined?(:IPHONE) }
end
end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 7755af592d..ea278fd8f0 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -231,18 +231,20 @@ class RedirectTest < ActionController::TestCase
end
def test_redirect_to_record
- ActionController::Routing::Routes.draw do |map|
- map.resources :workshops
- map.connect ':controller/:action/:id'
+ with_routing do |set|
+ set.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
+ assert_redirected_to Workshop.new(5, false)
+
+ get :redirect_to_new_record
+ assert_equal "http://test.host/workshops", redirect_to_url
+ assert_redirected_to Workshop.new(5, true)
end
-
- get :redirect_to_existing_record
- assert_equal "http://test.host/workshops/5", redirect_to_url
- assert_redirected_to Workshop.new(5, false)
-
- get :redirect_to_new_record
- assert_equal "http://test.host/workshops", redirect_to_url
- assert_redirected_to Workshop.new(5, true)
end
def test_redirect_to_nil
diff --git a/actionpack/test/controller/render_js_test.rb b/actionpack/test/controller/render_js_test.rb
index d02fd3fd4c..bc850de733 100644
--- a/actionpack/test/controller/render_js_test.rb
+++ b/actionpack/test/controller/render_js_test.rb
@@ -13,7 +13,7 @@ class TestController < ActionController::Base
# let's just rely on the template
end
- def partial
+ def show_partial
render :partial => 'partial'
end
end
@@ -33,7 +33,7 @@ class RenderTest < ActionController::TestCase
end
def test_should_render_js_partial
- xhr :get, :partial, :format => 'js'
+ xhr :get, :show_partial, :format => 'js'
assert_equal 'partial js', @response.body
- end
+ end
end \ No newline at end of file
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 0c0599679c..abcc8bf384 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -486,10 +486,6 @@ class TestController < ActionController::Base
render :action => "using_layout_around_block"
end
- def render_using_layout_around_block_with_args
- render :action => "using_layout_around_block_with_args"
- end
-
def render_using_layout_around_block_in_main_layout_and_within_content_for_layout
render :action => "using_layout_around_block", :layout => "layouts/block_with_layout"
end
@@ -1090,15 +1086,17 @@ class RenderTest < ActionController::TestCase
end
def test_head_with_location_object
- ActionController::Routing::Routes.draw do |map|
- map.resources :customers
- map.connect ':controller/:action/:id'
- end
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :customers
+ map.connect ':controller/:action/:id'
+ end
- get :head_with_location_object
- assert @response.body.blank?
- assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"]
- assert_response :ok
+ get :head_with_location_object
+ assert @response.body.blank?
+ assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"]
+ assert_response :ok
+ end
end
def test_head_with_custom_header
@@ -1161,11 +1159,6 @@ class RenderTest < ActionController::TestCase
assert_equal "Before (Anthony)\nInside from first block in layout\nAfter\nBefore (David)\nInside from block\nAfter\nBefore (Ramm)\nInside from second block in layout\nAfter\n", @response.body
end
- def test_using_layout_around_block_with_args
- get :render_using_layout_around_block_with_args
- assert_equal "Before\narg1arg2\nAfter", @response.body
- end
-
def test_partial_only
get :partial_only
assert_equal "only partial", @response.body
diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb
index 139f55d8bd..e96e8a4d57 100644
--- a/actionpack/test/controller/render_xml_test.rb
+++ b/actionpack/test/controller/render_xml_test.rb
@@ -55,13 +55,15 @@ class RenderTest < ActionController::TestCase
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
+ with_routing do |set|
+ set.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"]
+ get :render_with_object_location
+ assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"]
+ end
end
def test_should_render_formatted_xml_erb_template
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 83925ed4db..7111796f8d 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -1,10 +1,6 @@
require 'abstract_unit'
require 'digest/sha1'
-ActionController::Routing::Routes.draw do |map|
- map.connect ':controller/:action/:id'
-end
-
# common controller actions
module RequestForgeryProtectionActions
def index
@@ -50,7 +46,7 @@ module RequestForgeryProtectionTests
def teardown
ActionController::Base.request_forgery_protection_token = nil
end
-
+
def test_should_render_form_with_token_tag
get :index
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 30ab110ef7..0b639e363d 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -76,6 +76,50 @@ class ResourcesTest < ActionController::TestCase
end
end
+ def test_override_paths_for_member_and_collection_methods
+ collection_methods = { 'rss' => :get, 'reorder' => :post, 'csv' => :post }
+ member_methods = { 'rss' => :get, :atom => :get, :upload => :post, :fix => :post }
+ path_names = {:new => 'nuevo', 'rss' => 'canal', :fix => 'corrigir' }
+
+ with_restful_routing :messages,
+ :collection => collection_methods,
+ :member => member_methods,
+ :path_names => path_names do
+
+ assert_restful_routes_for :messages,
+ :collection => collection_methods,
+ :member => member_methods,
+ :path_names => path_names do |options|
+ member_methods.each do |action, method|
+ assert_recognizes(options.merge(:action => action.to_s, :id => '1'),
+ :path => "/messages/1/#{path_names[action] || action}",
+ :method => method)
+ end
+
+ collection_methods.each do |action, method|
+ assert_recognizes(options.merge(:action => action),
+ :path => "/messages/#{path_names[action] || action}",
+ :method => method)
+ end
+ end
+
+ assert_restful_named_routes_for :messages,
+ :collection => collection_methods,
+ :member => member_methods,
+ :path_names => path_names do |options|
+
+ collection_methods.keys.each do |action|
+ assert_named_route "/messages/#{path_names[action] || action}", "#{action}_messages_path", :action => action
+ end
+
+ member_methods.keys.each do |action|
+ assert_named_route "/messages/1/#{path_names[action] || action}", "#{action}_message_path", :action => action, :id => "1"
+ end
+
+ end
+ end
+ end
+
def test_override_paths_for_default_restful_actions
resource = ActionController::Resources::Resource.new(:messages,
:path_names => {:new => 'nuevo', :edit => 'editar'})
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 5f9ae6292c..d20684296f 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -9,7 +9,6 @@ class MilestonesController < ActionController::Base
def rescue_action(e) raise e end
end
-RunTimeTests = ARGV.include? 'time'
ROUTING = ActionController::Routing
class ROUTING::RouteBuilder
@@ -45,11 +44,11 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase
end
def test_route_recognition_unescapes_path_components
- options = { :controller => "controller",
+ options = { :controller => "content",
:action => "act#{@segment}ion",
:variable => "var#{@segment}iable",
:additional => ["add#{@segment}itional-1", "add#{@segment}itional-2"] }
- assert_equal options, @set.recognize_path("/controller/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2")
+ assert_equal options, @set.recognize_path("/content/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2")
end
def test_route_generation_allows_passing_non_string_values_to_generated_helper
@@ -60,644 +59,7 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase
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("Hello")
- assert_equal "Hello", eval(s.interpolation_statement([]))
- assert_equal "HelloHello", eval(s.interpolation_statement([s]))
-
- s2 = ROUTING::StaticSegment.new("-")
- assert_equal "Hello-Hello", eval(s.interpolation_statement([s, s2]))
-
- s3 = ROUTING::StaticSegment.new("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('Hello World')
- assert !s.raw?
- assert_equal 'Hello%20World', s.interpolation_chunk
-
- s = ROUTING::StaticSegment.new('Hello World', :raw => true)
- assert s.raw?
- assert_equal 'Hello World', s.interpolation_chunk
- end
-
- def test_value_should_not_be_double_unescaped
- s = ROUTING::StaticSegment.new('%D0%9A%D0%B0%D1%80%D1%82%D0%B0') # Карта
- assert_equal '%D0%9A%D0%B0%D1%80%D1%82%D0%B0', s.interpolation_chunk
- end
-
- def test_regexp_chunk_should_escape_specials
- s = ROUTING::StaticSegment.new('Hello*World')
- assert_equal 'Hello\*World', s.regexp_chunk
-
- s = ROUTING::StaticSegment.new('HelloWorld')
- assert_equal 'HelloWorld', s.regexp_chunk
- end
-
- def test_regexp_chunk_should_add_question_mark_for_optionals
- s = ROUTING::StaticSegment.new("/", :optional => true)
- assert_equal "/?", s.regexp_chunk
-
- s = ROUTING::StaticSegment.new("hello", :optional => true)
- assert_equal "(?:hello)?", s.regexp_chunk
- end
-end
-
-class DynamicSegmentTest < Test::Unit::TestCase
- def segment(options = {})
- unless @segment
- @segment = ROUTING::DynamicSegment.new(:a, options)
- end
- @segment
- end
-
- def test_extract_value
- s = ROUTING::DynamicSegment.new(: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 = segment(:regexp => /\d+/)
-
- a_value = nil
- assert !eval(segment.value_check)
- end
-
- def test_optional_regexp_value_check_should_accept_nil
- segment = segment(:regexp => /\d+/, :optional => true)
-
- a_value = nil
- assert eval(segment.value_check)
- end
-
- def test_regexp_value_check_rejects_no_match
- segment = 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 = 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 = segment(:optional => true)
-
- a_value = nil
- assert_equal nil, segment.value_check
- end
-
- def test_regexp_value_check_should_accept_match_with_default
- segment = segment(:regexp => /\d+/, :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_raise(LocalJumpError) { eval(segment.extraction_code) }
- end
-
- def test_extraction_code_should_return_on_mismatch
- segment = segment(:regexp => /\d+/)
- hash = merged = {:a => 'Hi', :b => '3'}
- options = {:b => '3'}
- a_value = nil
-
- # Local jump because of return inside eval.
- assert_raise(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 = 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 = 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(nil, :regexp => /\d+/)
- 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(nil, :regexp => /(\d+)(.*?)/)
- assert_equal "((\\d+)(.*?))stuff", a_segment.build_pattern('stuff')
- end
-
- def test_optionality_implied
- a_segment = ROUTING::DynamicSegment.new(:id)
- assert a_segment.optionality_implied?
-
- a_segment = ROUTING::DynamicSegment.new(:action)
- assert a_segment.optionality_implied?
- end
-
- def test_modifiers_must_be_handled_sensibly
- a_segment = ROUTING::DynamicSegment.new(nil, :regexp => /david|jamis/i)
- assert_equal "((?i-mx:david|jamis))stuff", a_segment.build_pattern('stuff')
- a_segment = ROUTING::DynamicSegment.new(nil, :regexp => /david|jamis/x)
- assert_equal "((?x-mi:david|jamis))stuff", a_segment.build_pattern('stuff')
- a_segment = ROUTING::DynamicSegment.new(nil, :regexp => /david|jamis/)
- assert_equal "(david|jamis)stuff", a_segment.build_pattern('stuff')
- 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
-
-class PathSegmentTest < Test::Unit::TestCase
- def segment(options = {})
- unless @segment
- @segment = ROUTING::PathSegment.new(:path, options)
- end
- @segment
- end
-
- def test_regexp_chunk_should_return_string
- segment = segment(:regexp => /[a-z]+/)
- assert_kind_of String, segment.regexp_chunk
- end
-
- def test_regexp_chunk_should_be_wrapped_with_parenthesis
- segment = segment(:regexp => /[a-z]+/)
- assert_equal "([a-z]+)", segment.regexp_chunk
- end
-
- def test_regexp_chunk_should_respect_options
- segment = segment(:regexp => /[a-z]+/i)
- assert_equal "((?i-mx:[a-z]+))", segment.regexp_chunk
- end
-end
-
-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_raise 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
- segments[-4] = ROUTING::DynamicSegment.new(:action, :regexp => /show|in/)
-
- 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 RoutingTest < Test::Unit::TestCase
- def test_possible_controllers
- true_controller_paths = ActionController::Routing.controller_paths
-
- ActionController::Routing.use_controllers! nil
-
- Object.send(:remove_const, :RAILS_ROOT) if defined?(::RAILS_ROOT)
- Object.const_set(:RAILS_ROOT, File.dirname(__FILE__) + '/controller_fixtures')
-
- 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.controller_paths = [
- root, root + '/app/controllers', root + '/vendor/plugins/bad_plugin/lib'
- ]
- ActionController::Routing::Routes.load!
-
- 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 .foo/../.bar foo.bar/../config)
paths = ActionController::Routing.normalize_paths(load_paths)
@@ -759,7 +121,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
ActionController::Routing.use_controllers! %w(content admin/user admin/news_feed)
end
-
+
def teardown
@rs.clear!
end
@@ -815,52 +177,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase
map.resources :pages
map.connect ':controller/:action/:id'
}
- n = 1000
- if RunTimeTests
- GC.start
- rectime = Benchmark.realtime do
- n.times do
- rs.recognize_path("/videos/1234567", {:method => :get})
- rs.recognize_path("/videos/1234567/abuse", {:method => :get})
- rs.recognize_path("/users/1234567/settings", {:method => :get})
- rs.recognize_path("/channels/1234567", {:method => :get})
- rs.recognize_path("/session/new", {:method => :get})
- rs.recognize_path("/admin/user/show/10", {:method => :get})
- end
- end
- puts "\n\nRecognition (#{rs.routes.size} routes):"
- 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
@@ -1138,8 +454,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
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)
+ assert_equal '/pages/boo', rs.generate({}, :controller => 'content', :action => 'show_file', :path => %w(pages boo))
end
def test_backwards
@@ -1455,162 +770,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
end
end
-class RouteTest < Test::Unit::TestCase
- def setup
- @route = ROUTING::Route.new
- end
-
- def slash_segment(is_optional = false)
- ROUTING::DividerSegment.new('/', :optional => is_optional)
- end
-
- def default_route
- unless defined?(@default_route)
- segments = []
- segments << ROUTING::StaticSegment.new('/', :raw => true)
- segments << ROUTING::DynamicSegment.new(:controller)
- segments << slash_segment(:optional)
- segments << ROUTING::DynamicSegment.new(:action, :default => 'index', :optional => true)
- segments << slash_segment(:optional)
- segments << ROUTING::DynamicSegment.new(:id, :optional => true)
- segments << slash_segment(:optional)
- @default_route = ROUTING::Route.new(segments).freeze
- 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_raise(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
- segments = []
- segments << ROUTING::StaticSegment.new('/', :raw => true)
- segments << ROUTING::StaticSegment.new('user')
- segments << ROUTING::StaticSegment.new('/', :raw => true, :optional => true)
- segments << ROUTING::DynamicSegment.new(:user)
- segments << ROUTING::StaticSegment.new('/', :raw => true, :optional => true)
-
- requirements = {:controller => 'users', :action => 'show'}
-
- user_url = ROUTING::Route.new(segments, requirements)
- 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
-
-class RouteSetTest < Test::Unit::TestCase
+class RouteSetTest < ActiveSupport::TestCase
def set
@set ||= ROUTING::RouteSet.new
end
@@ -1619,6 +779,19 @@ class RouteSetTest < Test::Unit::TestCase
@request ||= ActionController::TestRequest.new
end
+ def default_route_set
+ @default_route_set ||= begin
+ set = nil
+ ActionController::Routing.with_controllers(['accounts']) do
+ set = ROUTING::RouteSet.new
+ set.draw do |map|
+ map.connect '/:controller/:action/:id/'
+ end
+ end
+ set
+ end
+ 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")
@@ -2185,16 +1358,6 @@ class RouteSetTest < Test::Unit::TestCase
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 }
@@ -2502,6 +1665,211 @@ class RouteSetTest < Test::Unit::TestCase
assert_equal({:controller => 'pages', :action => 'show', :name => :as_symbol}, set.recognize_path('/named'))
end
+
+ def test_interpolation_chunk_should_respect_raw
+ ActionController::Routing.with_controllers(['hello']) do
+ set.draw do |map|
+ map.connect '/Hello World', :controller => 'hello'
+ end
+
+ assert_equal '/Hello%20World', set.generate(:controller => 'hello')
+ assert_equal({:controller => "hello", :action => "index"}, set.recognize_path('/Hello World'))
+ assert_raise(ActionController::RoutingError) { set.recognize_path('/Hello%20World') }
+ end
+ end
+
+ def test_value_should_not_be_double_unescaped
+ ActionController::Routing.with_controllers(['foo']) do
+ set.draw do |map|
+ map.connect '/Карта', :controller => 'foo'
+ end
+
+ assert_equal '/%D0%9A%D0%B0%D1%80%D1%82%D0%B0', set.generate(:controller => 'foo')
+ assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/Карта'))
+ assert_raise(ActionController::RoutingError) { set.recognize_path('/%D0%9A%D0%B0%D1%80%D1%82%D0%B0') }
+ end
+ end
+
+ def test_regexp_chunk_should_escape_specials
+ ActionController::Routing.with_controllers(['foo', 'bar']) do
+ set.draw do |map|
+ map.connect '/Hello*World', :controller => 'foo'
+ map.connect '/HelloWorld', :controller => 'bar'
+ end
+
+ assert_equal '/Hello*World', set.generate(:controller => 'foo')
+ assert_equal '/HelloWorld', set.generate(:controller => 'bar')
+
+ assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/Hello*World'))
+ assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/HelloWorld'))
+ end
+ end
+
+ def test_regexp_chunk_should_add_question_mark_for_optionals
+ ActionController::Routing.with_controllers(['foo', 'bar']) do
+ set.draw do |map|
+ map.connect '/', :controller => 'foo'
+ map.connect '/hello', :controller => 'bar'
+ end
+
+ assert_equal '/', set.generate(:controller => 'foo')
+ assert_equal '/hello', set.generate(:controller => 'bar')
+
+ assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/'))
+ assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/hello'))
+ end
+ end
+
+ def test_assign_route_options_with_anchor_chars
+ ActionController::Routing.with_controllers(['cars']) do
+ set.draw do |map|
+ map.connect '/cars/:action/:person/:car/', :controller => 'cars'
+ end
+
+ assert_equal '/cars/buy/1/2', set.generate(:controller => 'cars', :action => 'buy', :person => '1', :car => '2')
+
+ assert_equal({:controller => "cars", :action => "buy", :person => "1", :car => "2"}, set.recognize_path('/cars/buy/1/2'))
+ end
+ end
+
+ def test_segmentation_of_dot_path
+ ActionController::Routing.with_controllers(['books']) do
+ set.draw do |map|
+ map.connect '/books/:action.rss', :controller => 'books'
+ end
+
+ assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list')
+
+ assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list.rss'))
+ end
+ end
+
+ def test_segmentation_of_dynamic_dot_path
+ ActionController::Routing.with_controllers(['books']) do
+ set.draw do |map|
+ map.connect '/books/:action.:format', :controller => 'books'
+ end
+
+ assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list', :format => 'rss')
+ assert_equal '/books/list.xml', set.generate(:controller => 'books', :action => 'list', :format => 'xml')
+ assert_equal '/books/list', set.generate(:controller => 'books', :action => 'list')
+ assert_equal '/books', set.generate(:controller => 'books', :action => 'index')
+
+ assert_equal({:controller => "books", :action => "list", :format => "rss"}, set.recognize_path('/books/list.rss'))
+ assert_equal({:controller => "books", :action => "list", :format => "xml"}, set.recognize_path('/books/list.xml'))
+ assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list'))
+ assert_equal({:controller => "books", :action => "index"}, set.recognize_path('/books'))
+ end
+ end
+
+ def test_slashes_are_implied
+ ['/:controller/:action/:id/', '/:controller/:action/:id',
+ ':controller/:action/:id', '/:controller/:action/:id/'
+ ].each do |path|
+ @set = nil
+ set.draw { |map| map.connect(path) }
+
+ assert_equal '/content', set.generate(:controller => 'content', :action => 'index')
+ assert_equal '/content/list', set.generate(:controller => 'content', :action => 'list')
+ assert_equal '/content/show/1', set.generate(:controller => 'content', :action => 'show', :id => '1')
+
+ assert_equal({:controller => "content", :action => "index"}, set.recognize_path('/content'))
+ assert_equal({:controller => "content", :action => "index"}, set.recognize_path('/content/index'))
+ assert_equal({:controller => "content", :action => "list"}, set.recognize_path('/content/list'))
+ assert_equal({:controller => "content", :action => "show", :id => "1"}, set.recognize_path('/content/show/1'))
+ end
+ end
+
+ def test_default_route_recognition
+ expected = {:controller => 'accounts', :action => 'show', :id => '10'}
+ assert_equal expected, default_route_set.recognize_path('/accounts/show/10')
+ assert_equal expected, default_route_set.recognize_path('/accounts/show/10/')
+
+ expected[:id] = 'jamis'
+ assert_equal expected, default_route_set.recognize_path('/accounts/show/jamis/')
+
+ expected.delete :id
+ assert_equal expected, default_route_set.recognize_path('/accounts/show')
+ assert_equal expected, default_route_set.recognize_path('/accounts/show/')
+
+ expected[:action] = 'index'
+ assert_equal expected, default_route_set.recognize_path('/accounts/')
+ assert_equal expected, default_route_set.recognize_path('/accounts')
+
+ assert_raise(ActionController::RoutingError) { default_route_set.recognize_path('/') }
+ assert_raise(ActionController::RoutingError) { default_route_set.recognize_path('/accounts/how/goood/it/is/to/be/free') }
+ end
+
+ def test_default_route_should_omit_default_action
+ assert_equal '/accounts', default_route_set.generate({:controller => 'accounts', :action => 'index'})
+ end
+
+ def test_default_route_should_include_default_action_when_id_present
+ assert_equal '/accounts/index/20', default_route_set.generate({:controller => 'accounts', :action => 'index', :id => '20'})
+ end
+
+ def test_default_route_should_work_with_action_but_no_id
+ assert_equal '/accounts/list_all', default_route_set.generate({:controller => 'accounts', :action => 'list_all'})
+ end
+
+ def test_default_route_should_uri_escape_pluses
+ expected = { :controller => 'accounts', :action => 'show', :id => 'hello world' }
+ assert_equal expected, default_route_set.recognize_path('/accounts/show/hello world')
+ assert_equal expected, default_route_set.recognize_path('/accounts/show/hello%20world')
+ assert_equal '/accounts/show/hello%20world', default_route_set.generate(expected, expected)
+
+ expected[:id] = 'hello+world'
+ assert_equal expected, default_route_set.recognize_path('/accounts/show/hello+world')
+ assert_equal expected, default_route_set.recognize_path('/accounts/show/hello%2Bworld')
+ assert_equal '/accounts/show/hello+world', default_route_set.generate(expected, expected)
+ 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_raise(ArgumentError) do
+ ROUTING::RouteBuilder.new.build '/contact', :contoller => "contact", :action => "index"
+ end
+ end
+
+ def test_build_empty_query_string
+ assert_equal '/foo', default_route_set.generate({:controller => 'foo'})
+ end
+
+ def test_build_query_string_with_nil_value
+ assert_equal '/foo', default_route_set.generate({:controller => 'foo', :x => nil})
+ end
+
+ def test_simple_build_query_string
+ assert_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => '1', :y => '2'})
+ end
+
+ def test_convert_ints_build_query_string
+ assert_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => 1, :y => 2})
+ end
+
+ def test_escape_spaces_build_query_string
+ assert_equal '/foo?x=hello+world&y=goodbye+world', default_route_set.generate({:controller => 'foo', :x => 'hello world', :y => 'goodbye world'})
+ end
+
+ def test_expand_array_build_query_string
+ assert_equal '/foo?x%5B%5D=1&x%5B%5D=2', default_route_set.generate({:controller => 'foo', :x => [1, 2]})
+ end
+
+ def test_escape_spaces_build_query_string_selected_keys
+ assert_equal '/foo?x=hello+world', default_route_set.generate({:controller => 'foo', :x => 'hello world'})
+ end
end
class RouteLoadingTest < Test::Unit::TestCase
@@ -2565,10 +1933,10 @@ class RouteLoadingTest < Test::Unit::TestCase
routes.reload
end
-
+
def test_load_multiple_configurations
routes.add_configuration_file("engines.rb")
-
+
File.expects(:stat).at_least_once.returns(@stat)
routes.expects(:load).with('./config/routes.rb')
diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb
index 9e88188b9a..73870a56bb 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_test.rb
@@ -123,13 +123,6 @@ XML
@controller = TestController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
- ActionController::Routing.use_controllers! %w(content admin/user test_test/test)
- ActionController::Routing::Routes.load_routes!
- end
-
- def teardown
- super
- ActionController::Routing::Routes.reload
end
def test_raw_post_handling
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index 863f8414c5..9b8d07222b 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -46,6 +46,20 @@ class UrlRewriterTests < ActionController::TestCase
)
end
+ def test_anchor_should_call_to_param
+ assert_equal(
+ 'http://test.host/c/a/i#anchor',
+ @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anchor'))
+ )
+ end
+
+ def test_anchor_should_be_cgi_escaped
+ assert_equal(
+ 'http://test.host/c/a/i#anc%2Fhor',
+ @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anc/hor'))
+ )
+ end
+
def test_overwrite_params
@params[:controller] = 'hi'
@params[:action] = 'bye'
@@ -110,6 +124,18 @@ class UrlWriterTests < ActionController::TestCase
)
end
+ def test_anchor_should_call_to_param
+ assert_equal('/c/a#anchor',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor'))
+ )
+ end
+
+ def test_anchor_should_be_cgi_escaped
+ assert_equal('/c/a#anc%2Fhor',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anc/hor'))
+ )
+ end
+
def test_default_host
add_host!
assert_equal('http://www.basecamphq.com/c/a/i',
@@ -195,61 +221,62 @@ class UrlWriterTests < ActionController::TestCase
end
def test_named_routes
- 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'
+ with_routing do |set|
+ set.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'))
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_relative_url_root_is_respected_for_named_routes
orig_relative_url_root = ActionController::Base.relative_url_root
ActionController::Base.relative_url_root = '/subdir'
- ActionController::Routing::Routes.draw do |map|
- map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index'
- end
+ with_routing do |set|
+ set.draw do |map|
+ map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index'
+ end
- kls = Class.new { include ActionController::UrlWriter }
- controller = kls.new
+ kls = Class.new { include ActionController::UrlWriter }
+ controller = kls.new
- assert_equal 'http://www.basecamphq.com/subdir/home/sweet/home/again',
- controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again')
+ assert_equal 'http://www.basecamphq.com/subdir/home/sweet/home/again',
+ controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again')
+ end
ensure
- ActionController::Routing::Routes.load!
ActionController::Base.relative_url_root = orig_relative_url_root
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'
+ with_routing do |set|
+ set.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'))
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
@@ -302,41 +329,41 @@ class UrlWriterTests < ActionController::TestCase
end
def test_named_routes_with_nil_keys
- ActionController::Routing::Routes.clear!
- ActionController::Routing::Routes.draw do |map|
- map.main '', :controller => 'posts'
- map.resources :posts
- map.connect ':controller/:action/:id'
+ with_routing do |set|
+ set.draw do |map|
+ map.main '', :controller => 'posts', :format => nil
+ map.resources :posts
+ 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 }
+ kls.default_url_options[:host] = 'www.basecamphq.com'
+
+ controller = kls.new
+ params = {:action => :index, :controller => :posts, :format => :xml}
+ assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params))
+ params[:format] = nil
+ assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params))
end
- # We need to create a new class in order to install the new named route.
- kls = Class.new { include ActionController::UrlWriter }
- kls.default_url_options[:host] = 'www.basecamphq.com'
-
- controller = kls.new
- params = {:action => :index, :controller => :posts, :format => :xml}
- assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params))
- params[:format] = nil
- assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params))
- ensure
- ActionController::Routing::Routes.load!
end
def test_formatted_url_methods_are_deprecated
- ActionController::Routing::Routes.draw do |map|
- map.resources :posts
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :posts
+ 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
+ params = {:id => 1, :format => :xml}
+ assert_deprecated do
+ assert_equal("/posts/1.xml", controller.send(:formatted_post_path, params))
+ end
+ assert_deprecated do
+ assert_equal("/posts/1.xml", controller.send(:formatted_post_path, 1, :xml))
+ end
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
- params = {:id => 1, :format => :xml}
- assert_deprecated do
- assert_equal("/posts/1.xml", controller.send(:formatted_post_path, params))
- end
- assert_deprecated do
- assert_equal("/posts/1.xml", controller.send(:formatted_post_path, 1, :xml))
- end
- ensure
- ActionController::Routing::Routes.load!
end
def test_multiple_includes_maintain_distinct_options
diff --git a/actionpack/test/controller/verification_test.rb b/actionpack/test/controller/verification_test.rb
index d568030e41..ee558f3465 100644
--- a/actionpack/test/controller/verification_test.rb
+++ b/actionpack/test/controller/verification_test.rb
@@ -112,7 +112,10 @@ class VerificationTest < ActionController::TestCase
tests TestController
setup do
- ActionController::Routing::Routes.add_named_route :foo, '/foo', :controller => 'test', :action => 'foo'
+ ActionController::Routing::Routes.draw do |map|
+ map.foo '/foo', :controller => 'test', :action => 'foo'
+ map.connect ":controller/:action/:id"
+ end
end
def test_using_symbol_back_with_no_referrer
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index 9bf8da7276..916124e221 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -24,11 +24,6 @@ class WebServiceTest < ActionController::IntegrationTest
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
@@ -110,58 +105,61 @@ class WebServiceTest < ActionController::IntegrationTest
def test_post_xml_using_an_attributted_node_named_type
with_test_route_set do
- ActionController::Base.param_parsers[Mime::XML] = Proc.new { |data| Hash.from_xml(data)['request'].with_indifferent_access }
- post "/", '<request><type type="string">Arial,12</type><z>3</z></request>',
- {'CONTENT_TYPE' => 'application/xml'}
-
- assert_equal 'type, z', @controller.response.body
- assert @controller.params.has_key?(:type)
- assert_equal 'Arial,12', @controller.params['type'], @controller.params.inspect
- assert_equal '3', @controller.params['z'], @controller.params.inspect
+ with_params_parsers Mime::XML => Proc.new { |data| Hash.from_xml(data)['request'].with_indifferent_access } do
+ post "/", '<request><type type="string">Arial,12</type><z>3</z></request>',
+ {'CONTENT_TYPE' => 'application/xml'}
+
+ assert_equal 'type, z', @controller.response.body
+ assert @controller.params.has_key?(:type)
+ assert_equal 'Arial,12', @controller.params['type'], @controller.params.inspect
+ assert_equal '3', @controller.params['z'], @controller.params.inspect
+ end
end
end
def test_register_and_use_yaml
with_test_route_set do
- ActionController::Base.param_parsers[Mime::YAML] = Proc.new { |d| YAML.load(d) }
- post "/", {"entry" => "loaded from yaml"}.to_yaml,
- {'CONTENT_TYPE' => 'application/x-yaml'}
+ with_params_parsers Mime::YAML => Proc.new { |d| YAML.load(d) } do
+ post "/", {"entry" => "loaded from yaml"}.to_yaml,
+ {'CONTENT_TYPE' => 'application/x-yaml'}
- assert_equal 'entry', @controller.response.body
- assert @controller.params.has_key?(:entry)
- assert_equal 'loaded from yaml', @controller.params["entry"]
+ assert_equal 'entry', @controller.response.body
+ assert @controller.params.has_key?(:entry)
+ assert_equal 'loaded from yaml', @controller.params["entry"]
+ end
end
end
def test_register_and_use_yaml_as_symbol
with_test_route_set do
- ActionController::Base.param_parsers[Mime::YAML] = :yaml
- post "/", {"entry" => "loaded from yaml"}.to_yaml,
- {'CONTENT_TYPE' => 'application/x-yaml'}
+ with_params_parsers Mime::YAML => :yaml do
+ post "/", {"entry" => "loaded from yaml"}.to_yaml,
+ {'CONTENT_TYPE' => 'application/x-yaml'}
- assert_equal 'entry', @controller.response.body
- assert @controller.params.has_key?(:entry)
- assert_equal 'loaded from yaml', @controller.params["entry"]
+ assert_equal 'entry', @controller.response.body
+ assert @controller.params.has_key?(:entry)
+ assert_equal 'loaded from yaml', @controller.params["entry"]
+ end
end
end
def test_register_and_use_xml_simple
with_test_route_set do
- ActionController::Base.param_parsers[Mime::XML] = Proc.new { |data| Hash.from_xml(data)['request'].with_indifferent_access }
- post "/", '<request><summary>content...</summary><title>SimpleXml</title></request>',
- {'CONTENT_TYPE' => 'application/xml'}
-
- 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"]
+ with_params_parsers Mime::XML => Proc.new { |data| Hash.from_xml(data)['request'].with_indifferent_access } do
+ post "/", '<request><summary>content...</summary><title>SimpleXml</title></request>',
+ {'CONTENT_TYPE' => 'application/xml'}
+
+ 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
end
end
def test_use_xml_ximple_with_empty_request
with_test_route_set do
- ActionController::Base.param_parsers[Mime::XML] = :xml_simple
assert_nothing_raised { post "/", "", {'CONTENT_TYPE' => 'application/xml'} }
assert @controller.response.body.blank?
end
@@ -169,7 +167,6 @@ class WebServiceTest < ActionController::IntegrationTest
def test_dasherized_keys_as_xml
with_test_route_set do
- ActionController::Base.param_parsers[Mime::XML] = :xml_simple
post "/?full=1", "<first-key>\n<sub-key>...</sub-key>\n</first-key>",
{'CONTENT_TYPE' => 'application/xml'}
assert_equal 'action, controller, first_key(sub_key), full', @controller.response.body
@@ -179,7 +176,6 @@ class WebServiceTest < ActionController::IntegrationTest
def test_typecast_as_xml
with_test_route_set do
- ActionController::Base.param_parsers[Mime::XML] = :xml_simple
xml = <<-XML
<data>
<a type="integer">15</a>
@@ -208,7 +204,6 @@ class WebServiceTest < ActionController::IntegrationTest
def test_entities_unescaped_as_xml_simple
with_test_route_set do
- ActionController::Base.param_parsers[Mime::XML] = :xml_simple
xml = <<-XML
<data>&lt;foo &quot;bar&apos;s&quot; &amp; friends&gt;</data>
XML
@@ -219,34 +214,44 @@ class WebServiceTest < ActionController::IntegrationTest
def test_typecast_as_yaml
with_test_route_set do
- ActionController::Base.param_parsers[Mime::YAML] = :yaml
- 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
- post "/", yaml, {'CONTENT_TYPE' => 'application/x-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]
+ with_params_parsers Mime::YAML => :yaml do
+ 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
+ post "/", yaml, {'CONTENT_TYPE' => 'application/x-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
end
end
private
+ def with_params_parsers(parsers = {})
+ old_session = @integration_session
+ app = ActionDispatch::ParamsParser.new(ActionController::Routing::Routes, parsers)
+ @integration_session = open_session(app)
+ yield
+ ensure
+ @integration_session = old_session
+ end
+
def with_test_route_set
with_routing do |set|
set.draw do |map|
@@ -254,6 +259,7 @@ class WebServiceTest < ActionController::IntegrationTest
c.connect "/", :action => "assign_parameters"
end
end
+ reset!
yield
end
end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index 4ea0fedb8f..0832943d4c 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -56,7 +56,7 @@ class MimeTypeTest < ActiveSupport::TestCase
test "type convenience methods" do
# Don't test Mime::ALL, since it Mime::ALL#html? == true
- types = Mime::SET.symbols.uniq - [:all]
+ types = Mime::SET.symbols.uniq - [:all, :iphone]
# Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE
types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) }
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index a3dde72c4e..995f36bb29 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -30,16 +30,37 @@ class JsonParamsParsingTest < ActionController::IntegrationTest
)
end
+ test "logs error if parsing unsuccessful" do
+ with_test_routing do
+ begin
+ $stderr = StringIO.new
+ json = "[\"person]\": {\"name\": \"David\"}}"
+ post "/parse", json, {'CONTENT_TYPE' => 'application/json'}
+ assert_response :error
+ $stderr.rewind && err = $stderr.read
+ assert err =~ /Error occurred while parsing request parameters/
+ ensure
+ $stderr = STDERR
+ end
+ end
+ end
+
private
def assert_parses(expected, actual, headers = {})
+ with_test_routing do
+ post "/parse", actual, headers
+ assert_response :ok
+ assert_equal(expected, TestController.last_request_parameters)
+ end
+ end
+
+ def with_test_routing
with_routing do |set|
set.draw do |map|
map.connect ':action', :controller => "json_params_parsing_test/test"
end
-
- post "/parse", actual, headers
- assert_response :ok
- assert_equal(expected, TestController.last_request_parameters)
+ reset!
+ yield
end
end
end
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 301080842e..d4ee4362eb 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -153,6 +153,7 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest
set.draw do |map|
map.connect ':action', :controller => "multipart_params_parsing_test/test"
end
+ reset!
yield
end
end
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
index a31e326ddf..2261934e45 100644
--- a/actionpack/test/dispatch/request/query_string_parsing_test.rb
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -111,6 +111,7 @@ class QueryStringParsingTest < ActionController::IntegrationTest
set.draw do |map|
map.connect ':action', :controller => "query_string_parsing_test/test"
end
+ reset!
get "/parse", actual
assert_response :ok
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
index 7167cdafac..6c9967d26e 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -132,6 +132,7 @@ class UrlEncodedParamsParsingTest < ActionController::IntegrationTest
set.draw do |map|
map.connect ':action', :controller => "url_encoded_params_parsing_test/test"
end
+ reset!
yield
end
end
diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
index ee764e726e..2f2dd695c4 100644
--- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
@@ -38,6 +38,21 @@ class XmlParamsParsingTest < ActionController::IntegrationTest
end
end
+ test "logs error if parsing unsuccessful" do
+ with_test_routing do
+ begin
+ $stderr = StringIO.new
+ xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar></pineapple>"
+ post "/parse", xml, default_headers
+ assert_response :error
+ $stderr.rewind && err = $stderr.read
+ assert err =~ /Error occurred while parsing request parameters/
+ ensure
+ $stderr = STDERR
+ end
+ end
+ end
+
test "parses multiple files" do
xml = <<-end_body
<person>
@@ -71,6 +86,7 @@ class XmlParamsParsingTest < ActionController::IntegrationTest
set.draw do |map|
map.connect ':action', :controller => "xml_params_parsing_test/test"
end
+ reset!
yield
end
end
@@ -85,4 +101,4 @@ class LegacyXmlParamsParsingTest < XmlParamsParsingTest
def default_headers
{'HTTP_X_POST_DATA_FORMAT' => 'xml'}
end
-end
+end \ No newline at end of file
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index f3500fca34..239fda98e0 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -366,12 +366,12 @@ class RequestTest < ActiveSupport::TestCase
end
test "XMLHttpRequest" do
- with_accept_header false do
- request = stub_request 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'
- request.expects(:parameters).at_least_once.returns({})
- assert request.xhr?
- assert_equal Mime::JS, request.format
- end
+ request = stub_request 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest',
+ 'HTTP_ACCEPT' =>
+ [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(",")
+ request.expects(:parameters).at_least_once.returns({})
+ assert request.xhr?
+ assert_equal Mime::JS, request.format
end
test "content type" do
@@ -420,37 +420,34 @@ class RequestTest < ActiveSupport::TestCase
end
test "formats with accept header" do
- with_accept_header true do
- request = stub_request 'HTTP_ACCEPT' => 'text/html'
- request.expects(:parameters).at_least_once.returns({})
- assert_equal [ Mime::HTML ], request.formats
-
- request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8'
- request.expects(:parameters).at_least_once.returns({})
- assert_equal with_set(Mime::XML, Mime::HTML), request.formats
- end
+ request = stub_request 'HTTP_ACCEPT' => 'text/html'
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal [ Mime::HTML ], request.formats
- with_accept_header false do
- request = stub_request
- request.expects(:parameters).at_least_once.returns({ :format => :txt })
- assert_equal with_set(Mime::TEXT), request.formats
- end
+ request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8',
+ 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal with_set(Mime::XML), request.formats
+
+ request = stub_request
+ request.expects(:parameters).at_least_once.returns({ :format => :txt })
+ assert_equal with_set(Mime::TEXT), request.formats
end
test "negotiate_mime" do
- with_accept_header true do
- request = stub_request 'HTTP_ACCEPT' => 'text/html'
- request.expects(:parameters).at_least_once.returns({})
-
- assert_equal nil, request.negotiate_mime([Mime::XML, Mime::JSON])
- assert_equal Mime::HTML, request.negotiate_mime([Mime::XML, Mime::HTML])
- assert_equal Mime::HTML, request.negotiate_mime([Mime::XML, Mime::ALL])
-
- request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8'
- request.expects(:parameters).at_least_once.returns({})
- assert_equal Mime::XML, request.negotiate_mime([Mime::XML, Mime::CSV])
- assert_equal Mime::CSV, request.negotiate_mime([Mime::CSV, Mime::YAML])
- end
+ request = stub_request 'HTTP_ACCEPT' => 'text/html',
+ 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+
+ request.expects(:parameters).at_least_once.returns({})
+
+ assert_equal nil, request.negotiate_mime([Mime::XML, Mime::JSON])
+ assert_equal Mime::HTML, request.negotiate_mime([Mime::XML, Mime::HTML])
+ assert_equal Mime::HTML, request.negotiate_mime([Mime::XML, Mime::ALL])
+
+ request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8',
+ 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal Mime::XML, request.negotiate_mime([Mime::XML, Mime::CSV])
end
protected
@@ -460,7 +457,7 @@ protected
end
def with_set(*args)
- args + Mime::SET
+ args
end
def with_accept_header(value)
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index 2db76818ac..0723a76d2b 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -8,10 +8,6 @@ class CookieStoreTest < ActionController::IntegrationTest
# Make sure Session middleware doesnt get included in the middleware stack
ActionController::Base.session_store = nil
- DispatcherApp = ActionController::Dispatcher.new
- CookieStoreApp = ActionDispatch::Session::CookieStore.new(DispatcherApp,
- :key => SessionKey, :secret => SessionSecret)
-
Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, 'SHA1')
SignedBar = Verifier.generate(:foo => "bar", :session_id => ActiveSupport::SecureRandom.hex(16))
@@ -51,7 +47,7 @@ class CookieStoreTest < ActionController::IntegrationTest
end
def setup
- @integration_session = open_session(CookieStoreApp)
+ reset_app!
end
def test_raises_argument_error_if_missing_session_key
@@ -197,10 +193,10 @@ class CookieStoreTest < ActionController::IntegrationTest
end
def test_session_store_with_expire_after
- app = ActionDispatch::Session::CookieStore.new(DispatcherApp, :key => SessionKey, :secret => SessionSecret, :expire_after => 5.hours)
- @integration_session = open_session(app)
-
with_test_route_set do
+ app = ActionDispatch::Session::CookieStore.new(ActionController::Dispatcher.new, :key => SessionKey, :secret => SessionSecret, :expire_after => 5.hours)
+ @integration_session = open_session(app)
+
# First request accesses the session
time = Time.local(2008, 4, 24)
Time.stubs(:now).returns(time)
@@ -230,6 +226,12 @@ class CookieStoreTest < ActionController::IntegrationTest
end
private
+ def reset_app!
+ app = ActionDispatch::Session::CookieStore.new(ActionController::Dispatcher.new,
+ :key => SessionKey, :secret => SessionSecret)
+ @integration_session = open_session(app)
+ end
+
def with_test_route_set
with_routing do |set|
set.draw do |map|
@@ -237,6 +239,7 @@ class CookieStoreTest < ActionController::IntegrationTest
c.connect "/:action"
end
end
+ reset_app!
yield
end
end
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index 7561c93e4a..1588918be7 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -32,14 +32,7 @@ class MemCacheStoreTest < ActionController::IntegrationTest
end
begin
- DispatcherApp = ActionController::Dispatcher.new
- MemCacheStoreApp = ActionDispatch::Session::MemCacheStore.new(
- DispatcherApp, :key => '_session_id')
-
-
- def setup
- @integration_session = open_session(MemCacheStoreApp)
- end
+ App = ActionDispatch::Session::MemCacheStore.new(ActionController::Dispatcher.new, :key => '_session_id')
def test_setting_and_getting_session_value
with_test_route_set do
@@ -114,6 +107,12 @@ class MemCacheStoreTest < ActionController::IntegrationTest
end
private
+ def reset_app!
+ app = ActionDispatch::Session::MemCacheStore.new(
+ ActionController::Dispatcher.new, :key => '_session_id')
+ @integration_session = open_session(app)
+ end
+
def with_test_route_set
with_routing do |set|
set.draw do |map|
@@ -121,6 +120,7 @@ class MemCacheStoreTest < ActionController::IntegrationTest
c.connect "/:action"
end
end
+ reset_app!
yield
end
end
diff --git a/actionpack/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml b/actionpack/test/fixtures/content_type/render_default_content_types_for_respond_to.xml.erb
index 25dc746886..25dc746886 100644
--- a/actionpack/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml
+++ b/actionpack/test/fixtures/content_type/render_default_content_types_for_respond_to.xml.erb
diff --git a/actionpack/test/fixtures/test/_customer_with_var.erb b/actionpack/test/fixtures/test/_customer_with_var.erb
index 3379246b7e..00047dd20e 100644
--- a/actionpack/test/fixtures/test/_customer_with_var.erb
+++ b/actionpack/test/fixtures/test/_customer_with_var.erb
@@ -1 +1 @@
-<%= customer.name %> <%= object.name %> <%= customer_with_var.name %> \ No newline at end of file
+<%= customer.name %> <%= customer.name %> <%= customer.name %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_hash_object.erb b/actionpack/test/fixtures/test/_hash_object.erb
index 55c03afb27..34a92c6a56 100644
--- a/actionpack/test/fixtures/test/_hash_object.erb
+++ b/actionpack/test/fixtures/test/_hash_object.erb
@@ -1,2 +1,2 @@
<%= hash_object[:first_name] %>
-<%= object[:first_name].reverse %>
+<%= hash_object[:first_name].reverse %>
diff --git a/actionpack/test/fixtures/test/using_layout_around_block_with_args.html.erb b/actionpack/test/fixtures/test/using_layout_around_block_with_args.html.erb
deleted file mode 100644
index 71b1f30ad0..0000000000
--- a/actionpack/test/fixtures/test/using_layout_around_block_with_args.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<% render(:layout => "layout_for_block_with_args") do |*args| %><%= args.join %><% end %> \ No newline at end of file
diff --git a/actionpack/test/lib/controller/fake_controllers.rb b/actionpack/test/lib/controller/fake_controllers.rb
index 75c114c103..6e02e2d21b 100644
--- a/actionpack/test/lib/controller/fake_controllers.rb
+++ b/actionpack/test/lib/controller/fake_controllers.rb
@@ -1,15 +1,16 @@
class << Object; alias_method :const_available?, :const_defined?; end
-
-class ContentController < Class.new(ActionController::Base)
+
+class ContentController < 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
+ class UserController < ActionController::Base; end
+ class NewsFeedController < ActionController::Base; end
end
+
# For speed test
class SpeedController < ActionController::Base; end
class SearchController < SpeedController; end
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index 0faf8f3f9a..18eff7516b 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -10,12 +10,16 @@ class Customer < Struct.new(:name, :id)
id.to_s
end
- def to_xml
- "XML"
+ def to_xml(options={})
+ if options[:builder]
+ options[:builder].name name
+ else
+ "<name>#{name}</name>"
+ end
end
- def to_js
- "JS"
+ def to_js(options={})
+ "name: #{name.inspect}"
end
def errors
diff --git a/actionpack/test/lib/fixture_template.rb b/actionpack/test/lib/fixture_template.rb
index ee526b5de5..8da92180d1 100644
--- a/actionpack/test/lib/fixture_template.rb
+++ b/actionpack/test/lib/fixture_template.rb
@@ -4,7 +4,7 @@ module ActionView #:nodoc:
super(options)
@hash = hash
end
-
+
def find_templates(name, details, prefix, partial)
if regexp = details_to_regexp(name, details, prefix, partial)
cached(regexp) do
@@ -16,26 +16,26 @@ module ActionView #:nodoc:
end
end
end
-
+
private
-
+
def formats_regexp
@formats_regexp ||= begin
formats = Mime::SET.symbols
'(?:' + formats.map { |l| "\\.#{Regexp.escape(l.to_s)}" }.join('|') + ')?'
end
end
-
+
def handler_regexp
e = TemplateHandlers.extensions.map{|h| "\\.#{Regexp.escape(h.to_s)}"}.join("|")
- "(?:#{e})?"
+ "(?:#{e})"
end
-
+
def details_to_regexp(name, details, prefix, partial)
path = ""
path << "#{prefix}/" unless prefix.empty?
path << (partial ? "_#{name}" : name)
-
+
extensions = ""
[:locales, :formats].each do |k|
extensions << if exts = details[k]
@@ -47,7 +47,7 @@ module ActionView #:nodoc:
%r'^#{Regexp.escape(path)}#{extensions}#{handler_regexp}$'
end
-
+
# TODO: fix me
# :api: plugin
def path_to_details(path)
@@ -56,10 +56,10 @@ module ActionView #:nodoc:
partial = m[1] == '_'
details = (m[2]||"").split('.').reject { |e| e.empty? }
handler = Template.handler_class_for_extension(m[3])
-
+
format = Mime[details.last] && details.pop.to_sym
locale = details.last && details.pop.to_sym
-
+
return handler, :format => format, :locale => locale, :partial => partial
end
end
diff --git a/actionpack/test/new_base/content_type_test.rb b/actionpack/test/new_base/content_type_test.rb
index cfc03a3024..ceee508224 100644
--- a/actionpack/test/new_base/content_type_test.rb
+++ b/actionpack/test/new_base/content_type_test.rb
@@ -75,7 +75,7 @@ module ContentType
end
test "sets Content-Type as application/xml when rendering *.xml.erb" do
- get "/content_type/implied/i_am_xml_erb"
+ get "/content_type/implied/i_am_xml_erb", "format" => "xml"
assert_header "Content-Type", "application/xml; charset=utf-8"
end
@@ -87,7 +87,7 @@ module ContentType
end
test "sets Content-Type as application/xml when rendering *.xml.builder" do
- get "/content_type/implied/i_am_xml_builder"
+ get "/content_type/implied/i_am_xml_builder", "format" => "xml"
assert_header "Content-Type", "application/xml; charset=utf-8"
end
diff --git a/actionpack/test/new_base/metal_test.rb b/actionpack/test/new_base/metal_test.rb
new file mode 100644
index 0000000000..2b7720863a
--- /dev/null
+++ b/actionpack/test/new_base/metal_test.rb
@@ -0,0 +1,44 @@
+require File.join(File.expand_path(File.dirname(__FILE__)), "test_helper")
+
+module MetalTest
+ class MetalMiddleware < ActionController::Middleware
+ def call(env)
+ if env["PATH_INFO"] =~ /authed/
+ app.call(env)
+ else
+ [401, headers, "Not authed!"]
+ end
+ end
+ end
+
+ class Endpoint
+ def call(env)
+ [200, {}, "Hello World"]
+ end
+ end
+
+ class TestMiddleware < ActiveSupport::TestCase
+ def setup
+ @app = Rack::Builder.new do
+ use MetalMiddleware
+ run Endpoint.new
+ end.to_app
+ end
+
+ test "it can call the next app by using @app" do
+ env = Rack::MockRequest.env_for("/authed")
+ response = @app.call(env)
+
+ assert_equal "Hello World", response[2]
+ end
+
+ test "it can return a response using the normal AC::Metal techniques" do
+ env = Rack::MockRequest.env_for("/")
+ response = @app.call(env)
+
+ assert_equal "Not authed!", response[2]
+ assert_equal 401, response[0]
+ end
+ end
+end
+
diff --git a/actionpack/test/new_base/render_layout_test.rb b/actionpack/test/new_base/render_layout_test.rb
index 279b807a5f..933eef58e7 100644
--- a/actionpack/test/new_base/render_layout_test.rb
+++ b/actionpack/test/new_base/render_layout_test.rb
@@ -83,7 +83,7 @@ module ControllerLayouts
testing ControllerLayouts::MismatchFormatController
test "if JS is selected, an HTML template is not also selected" do
- get :index
+ get :index, "format" => "js"
assert_response "$(\"test\").omg();"
end
diff --git a/actionpack/test/new_base/render_rjs_test.rb b/actionpack/test/new_base/render_rjs_test.rb
index bd4c87b3bf..3d3e516905 100644
--- a/actionpack/test/new_base/render_rjs_test.rb
+++ b/actionpack/test/new_base/render_rjs_test.rb
@@ -21,24 +21,23 @@ module RenderRjs
def index_locale
old_locale, I18n.locale = I18n.locale, :da
end
-
end
class TestBasic < SimpleRouteCase
testing BasicController
test "rendering a partial in an RJS template should pick the JS template over the HTML one" do
- get :index
+ get :index, "format" => "js"
assert_response("$(\"customer\").update(\"JS Partial\");")
end
test "replacing an element with a partial in an RJS template should pick the HTML template over the JS one" do
- get :index_html
+ get :index_html, "format" => "js"
assert_response("$(\"customer\").update(\"HTML Partial\");")
end
test "replacing an element with a partial in an RJS template with a locale should pick the localed HTML template" do
- get :index_locale, :format => :js
+ get :index_locale, "format" => "js"
assert_response("$(\"customer\").update(\"Danish HTML Partial\");")
end
diff --git a/actionpack/test/new_base/render_template_test.rb b/actionpack/test/new_base/render_template_test.rb
index 94ea38fc7b..967cbd07b0 100644
--- a/actionpack/test/new_base/render_template_test.rb
+++ b/actionpack/test/new_base/render_template_test.rb
@@ -73,7 +73,7 @@ module RenderTemplate
end
test "rendering a builder template" do
- get :builder_template
+ get :builder_template, "format" => "xml"
assert_response "<html>\n <p>Hello</p>\n</html>\n"
end
end
diff --git a/actionpack/test/new_base/test_helper.rb b/actionpack/test/new_base/test_helper.rb
index 9271b2dd59..b7ccd3db8d 100644
--- a/actionpack/test/new_base/test_helper.rb
+++ b/actionpack/test/new_base/test_helper.rb
@@ -35,12 +35,6 @@ class Rack::TestCase < ActionController::IntegrationTest
setup do
ActionController::Base.session_options[:key] = "abc"
ActionController::Base.session_options[:secret] = ("*" * 30)
-
- controllers = ActionController::Base.subclasses.map do |k|
- k.underscore.sub(/_controller$/, '')
- end
-
- ActionController::Routing.use_controllers!(controllers)
end
def app
@@ -91,10 +85,26 @@ end
class ::ApplicationController < ActionController::Base
end
+module ActionController
+ class << Routing
+ def possible_controllers
+ @@possible_controllers ||= []
+ end
+ end
+
+ class Base
+ def self.inherited(klass)
+ name = klass.name.underscore.sub(/_controller$/, '')
+ ActionController::Routing.possible_controllers << name unless name.blank?
+ super
+ end
+ end
+end
+
class SimpleRouteCase < Rack::TestCase
setup do
ActionController::Routing::Routes.draw do |map|
map.connect ':controller/:action/:id'
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 2b1d80b1bf..8fd018f86d 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -157,6 +157,22 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, "for" => "my_for"))
end
+ def test_label_with_id_attribute_as_symbol
+ assert_dom_equal('<label for="post_title" id="my_id">Title</label>', label(:post, :title, nil, :id => "my_id"))
+ end
+
+ def test_label_with_id_attribute_as_string
+ assert_dom_equal('<label for="post_title" id="my_id">Title</label>', label(:post, :title, nil, "id" => "my_id"))
+ end
+
+ def test_label_with_for_and_id_attributes_as_symbol
+ assert_dom_equal('<label for="my_for" id="my_id">Title</label>', label(:post, :title, nil, :for => "my_for", :id => "my_id"))
+ end
+
+ def test_label_with_for_and_id_attributes_as_string
+ assert_dom_equal('<label for="my_for" id="my_id">Title</label>', label(:post, :title, nil, "for" => "my_for", "id" => "my_id"))
+ end
+
def test_label_for_radio_buttons_with_value
assert_dom_equal('<label for="post_title_great_title">The title goes here</label>', label("post", "title", "The title goes here", :value => "great_title"))
assert_dom_equal('<label for="post_title_great_title">The title goes here</label>', label("post", "title", "The title goes here", :value => "great title"))
diff --git a/actionpack/test/template/form_options_helper_i18n_test.rb b/actionpack/test/template/form_options_helper_i18n_test.rb
new file mode 100644
index 0000000000..91e370efa7
--- /dev/null
+++ b/actionpack/test/template/form_options_helper_i18n_test.rb
@@ -0,0 +1,27 @@
+require 'abstract_unit'
+
+class FormOptionsHelperI18nTests < ActionView::TestCase
+ tests ActionView::Helpers::FormOptionsHelper
+
+ def setup
+ @prompt_message = 'Select!'
+ I18n.backend.send(:init_translations)
+ I18n.backend.store_translations :en, :support => { :select => { :prompt => @prompt_message } }
+ end
+
+ def teardown
+ I18n.backend = I18n::Backend::Simple.new
+ end
+
+ def test_select_with_prompt_true_translates_prompt_message
+ I18n.expects(:translate).with('support.select.prompt', { :default => 'Please select' })
+ select('post', 'category', [], :prompt => true)
+ end
+
+ def test_select_with_translated_prompt
+ assert_dom_equal(
+ %Q(<select id="post_category" name="post[category]"><option value="">#{@prompt_message}</option>\n</select>),
+ select('post', 'category', [], :prompt => true)
+ )
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 73624406be..aa40e46aa8 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -763,6 +763,40 @@ class FormOptionsHelperTest < ActionView::TestCase
html
end
+ def test_grouped_collection_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")] )
+ ]
+
+ @post = Post.new
+ @post.origin = 'dk'
+
+ assert_dom_equal(
+ %Q{<select id="post_origin" name="post[origin]"><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></select>},
+ grouped_collection_select("post", "origin", @continents, :countries, :continent_name, :country_id, :country_name)
+ )
+ end
+
+ def test_grouped_collection_select_under_fields_for
+ @continents = [
+ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
+ Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
+ ]
+
+ @post = Post.new
+ @post.origin = 'dk'
+
+ fields_for :post, @post do |f|
+ concat f.grouped_collection_select("origin", @continents, :countries, :continent_name, :country_id, :country_name)
+ end
+
+ assert_dom_equal(
+ %Q{<select id="post_origin" name="post[origin]"><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></select>},
+ output_buffer
+ )
+ end
+
private
def dummy_posts
diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb
index 8caabfc3e1..f0f686f6e2 100644
--- a/actionpack/test/template/javascript_helper_test.rb
+++ b/actionpack/test/template/javascript_helper_test.rb
@@ -7,6 +7,10 @@ class JavaScriptHelperTest < ActionView::TestCase
attr_accessor :formats, :output_buffer
+ def reset_formats(format)
+ @format = format
+ end
+
def setup
super
@template = self
diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb
index acbf311212..313a769088 100644
--- a/actionpack/test/template/prototype_helper_test.rb
+++ b/actionpack/test/template/prototype_helper_test.rb
@@ -36,6 +36,10 @@ class Author::Nested < Author; end
class PrototypeHelperBaseTest < ActionView::TestCase
attr_accessor :formats, :output_buffer
+ def reset_formats(format)
+ @format = format
+ end
+
def setup
super
@template = self
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 7f30ae88a1..c86d5215cd 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -2,10 +2,14 @@
require 'abstract_unit'
require 'controller/fake_models'
+class TestController < ActionController::Base
+end
+
module RenderTestCases
def setup_view(paths)
@assigns = { :secret => 'in the sauce' }
@view = ActionView::Base.new(paths, @assigns)
+ @controller_view = ActionView::Base.for_controller(TestController.new)
# Reload and register danish language for testing
I18n.reload!
@@ -138,7 +142,7 @@ module RenderTestCases
end
def test_render_partial_collection_without_as
- assert_equal "local_inspector,local_inspector_counter,object",
+ assert_equal "local_inspector,local_inspector_counter",
@view.render(:partial => "test/local_inspector", :collection => [ Customer.new("mary") ])
end
@@ -158,6 +162,25 @@ module RenderTestCases
assert_nil @view.render(:partial => [])
end
+ def test_render_partial_using_string
+ assert_equal "Hello: Anonymous", @controller_view.render('customer')
+ end
+
+ def test_render_partial_with_locals_using_string
+ assert_equal "Hola: david", @controller_view.render('customer_greeting', :greeting => 'Hola', :customer_greeting => Customer.new("david"))
+ end
+
+ def test_render_partial_using_object
+ assert_equal "Hello: lifo",
+ @controller_view.render(Customer.new("lifo"), :greeting => "Hello")
+ end
+
+ def test_render_partial_using_collection
+ customers = [ Customer.new("Amazon"), Customer.new("Yahoo") ]
+ assert_equal "Hello: AmazonHello: Yahoo",
+ @controller_view.render(customers, :greeting => "Hello")
+ end
+
# TODO: The reason for this test is unclear, improve documentation
def test_render_partial_and_fallback_to_layout
assert_equal "Before (Josh)\n\nAfter", @view.render(:partial => "test/layout_for_partial", :locals => { :name => "Josh" })
@@ -167,6 +190,8 @@ module RenderTestCases
def test_render_missing_xml_partial_and_raise_missing_template
@view.formats = [:xml]
assert_raise(ActionView::MissingTemplate) { @view.render(:partial => "test/layout_for_partial") }
+ ensure
+ @view.formats = nil
end
def test_render_inline
@@ -196,17 +221,6 @@ module RenderTestCases
assert_equal 'source: "Hello, <%= name %>!"', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo)
end
- class LegacyHandler < ActionView::TemplateHandler
- def render(template, local_assigns)
- "source: #{template.source}; locals: #{local_assigns.inspect}"
- end
- end
-
- def test_render_legacy_handler_with_custom_type
- ActionView::Template.register_template_handler :foo, LegacyHandler
- assert_equal 'source: Hello, <%= name %>!; locals: {:name=>"Josh"}', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo)
- end
-
def test_render_ignores_templates_with_malformed_template_handlers
%w(malformed malformed.erb malformed.html.erb malformed.en.html.erb).each do |name|
assert_raise(ActionView::MissingTemplate) { @view.render(:file => "test/malformed/#{name}") }
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index b7823b9394..08143ba680 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -1,6 +1,10 @@
require 'abstract_unit'
require 'testing_sandbox'
-require 'redcloth'
+begin
+ require 'redcloth'
+rescue LoadError
+ $stderr.puts "Skipping textilize tests. `gem install RedCloth` to enable."
+end
class TextHelperTest < ActionView::TestCase
tests ActionView::Helpers::TextHelper
@@ -530,19 +534,21 @@ class TextHelperTest < ActionView::TestCase
assert_equal(%w{Specialized Fuji Giant}, @cycles)
end
- def test_textilize
- assert_equal("<p><strong>This is Textile!</strong> Rejoice!</p>", textilize("*This is Textile!* Rejoice!"))
- end
+ if defined? RedCloth
+ def test_textilize
+ assert_equal("<p><strong>This is Textile!</strong> Rejoice!</p>", textilize("*This is Textile!* Rejoice!"))
+ end
- def test_textilize_with_blank
- assert_equal("", textilize(""))
- end
+ def test_textilize_with_blank
+ assert_equal("", textilize(""))
+ end
- def test_textilize_with_options
- assert_equal("<p>This is worded &lt;strong&gt;strongly&lt;/strong&gt;</p>", textilize("This is worded <strong>strongly</strong>", :filter_html))
- end
+ def test_textilize_with_options
+ assert_equal("<p>This is worded &lt;strong&gt;strongly&lt;/strong&gt;</p>", textilize("This is worded <strong>strongly</strong>", :filter_html))
+ end
- def test_textilize_with_hard_breaks
- assert_equal("<p>This is one scary world.<br />\n True.</p>", textilize("This is one scary world.\n True."))
+ def test_textilize_with_hard_breaks
+ assert_equal("<p>This is one scary world.<br />\n True.</p>", textilize("This is one scary world.\n True."))
+ end
end
end
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index 9eeb26831c..0e24fbd24d 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -220,6 +220,14 @@ class UrlHelperTest < ActionView::TestCase
)
end
+ def test_link_tag_using_delete_javascript_and_href_and_confirm
+ assert_dom_equal(
+ "<a href='\#' onclick=\"if (confirm('Are you serious?')) { 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 => '#', :confirm => "Are you serious?"),
+ "When specifying url, form should be generated with it, but not this.href"
+ )
+ end
+
def test_link_tag_using_post_javascript_and_popup
assert_raise(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :method => :post, :confirm => "Are you serious?") }
end
diff --git a/actionpack/test/tmp/.gitignore b/actionpack/test/tmp/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/tmp/.gitignore
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
new file mode 100644
index 0000000000..26500568ee
--- /dev/null
+++ b/activemodel/CHANGELOG
@@ -0,0 +1,11 @@
+*Edge*
+
+* Add validates_format_of :without => /regexp/ option. #430 [Elliot Winkler, Peer Allan]
+
+ Example :
+
+ validates_format_of :subdomain, :without => /www|admin|mail/
+
+* Introduce validates_with to encapsulate attribute validations in a class. #2630 [Jeff Dean]
+
+* Extracted from Active Record and Active Resource.
diff --git a/activemodel/CHANGES b/activemodel/CHANGES
index a9f9c27507..217a6d6bf7 100644
--- a/activemodel/CHANGES
+++ b/activemodel/CHANGES
@@ -9,4 +9,4 @@ Changes from extracting bits to ActiveModel
klass.add_observer(self)
klass.class_eval 'def after_find() end' unless
klass.respond_to?(:after_find)
- end \ No newline at end of file
+ end
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 9bb4cf8b54..5bb931be7f 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -29,12 +29,14 @@ module ActiveModel
autoload :AttributeMethods, 'active_model/attribute_methods'
autoload :Conversion, 'active_model/conversion'
autoload :DeprecatedErrorMethods, 'active_model/deprecated_error_methods'
+ autoload :Dirty, 'active_model/dirty'
autoload :Errors, 'active_model/errors'
+ autoload :Lint, 'active_model/lint'
autoload :Name, 'active_model/naming'
autoload :Naming, 'active_model/naming'
autoload :Observer, 'active_model/observing'
autoload :Observing, 'active_model/observing'
- autoload :Serializer, 'active_model/serializer'
+ autoload :Serialization, 'active_model/serialization'
autoload :StateMachine, 'active_model/state_machine'
autoload :TestCase, 'active_model/test_case'
autoload :Validations, 'active_model/validations'
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index de80559036..1091ad3095 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -133,18 +133,31 @@ module ActiveModel
undefine_attribute_methods
end
+ def alias_attribute(new_name, old_name)
+ attribute_method_matchers.each do |matcher|
+ module_eval <<-STR, __FILE__, __LINE__+1
+ def #{matcher.method_name(new_name)}(*args)
+ send(:#{matcher.method_name(old_name)}, *args)
+ end
+ STR
+ end
+ end
+
def define_attribute_methods(attr_names)
return if attribute_methods_generated?
- attr_names.each do |name|
- attribute_method_matchers.each do |method|
- method_name = "#{method.prefix}#{name}#{method.suffix}"
- unless instance_method_already_implemented?(method_name)
- generate_method = "define_method_#{method.prefix}attribute#{method.suffix}"
+ attr_names.each do |attr_name|
+ attribute_method_matchers.each do |matcher|
+ unless instance_method_already_implemented?(matcher.method_name(attr_name))
+ generate_method = "define_method_#{matcher.prefix}attribute#{matcher.suffix}"
if respond_to?(generate_method)
- send(generate_method, name)
+ send(generate_method, attr_name)
else
- generated_attribute_methods.module_eval("def #{method_name}(*args); send(:#{method.prefix}attribute#{method.suffix}, '#{name}', *args); end", __FILE__, __LINE__)
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__+1
+ def #{matcher.method_name(attr_name)}(*args)
+ send(:#{matcher.method_missing_target}, '#{attr_name}', *args)
+ end
+ STR
end
end
end
@@ -180,7 +193,7 @@ module ActiveModel
class AttributeMethodMatcher
attr_reader :prefix, :suffix
- AttributeMethodMatch = Struct.new(:prefix, :base, :suffix)
+ AttributeMethodMatch = Struct.new(:target, :attr_name)
def initialize(options = {})
options.symbolize_keys!
@@ -190,11 +203,19 @@ module ActiveModel
def match(method_name)
if matchdata = @regex.match(method_name)
- AttributeMethodMatch.new(matchdata[1], matchdata[2], matchdata[3])
+ AttributeMethodMatch.new(method_missing_target, matchdata[2])
else
nil
end
end
+
+ def method_name(attr_name)
+ "#{prefix}#{attr_name}#{suffix}"
+ end
+
+ def method_missing_target
+ :"#{prefix}attribute#{suffix}"
+ end
end
def attribute_method_matchers #:nodoc:
@@ -214,7 +235,7 @@ module ActiveModel
method_name = method_id.to_s
if match = match_attribute_method?(method_name)
guard_private_attribute_method!(method_name, args)
- return __send__("#{match.prefix}attribute#{match.suffix}", match.base, *args, &block)
+ return __send__(match.target, match.attr_name, *args, &block)
end
super
end
@@ -246,7 +267,7 @@ module ActiveModel
# The struct's attributes are prefix, base and suffix.
def match_attribute_method?(method_name)
self.class.send(:attribute_method_matchers).each do |method|
- if (match = method.match(method_name)) && attribute_method?(match.base)
+ if (match = method.match(method_name)) && attribute_method?(match.attr_name)
return match
end
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
new file mode 100644
index 0000000000..735c61df74
--- /dev/null
+++ b/activemodel/lib/active_model/dirty.rb
@@ -0,0 +1,126 @@
+module ActiveModel
+ # Track unsaved attribute changes.
+ #
+ # A newly instantiated object is unchanged:
+ # person = Person.find_by_name('Uncle Bob')
+ # person.changed? # => false
+ #
+ # Change the name:
+ # person.name = 'Bob'
+ # person.changed? # => true
+ # person.name_changed? # => true
+ # person.name_was # => 'Uncle Bob'
+ # person.name_change # => ['Uncle Bob', 'Bob']
+ # person.name = 'Bill'
+ # person.name_change # => ['Uncle Bob', 'Bill']
+ #
+ # Save the changes:
+ # person.save
+ # person.changed? # => false
+ # person.name_changed? # => false
+ #
+ # Assigning the same value leaves the attribute unchanged:
+ # person.name = 'Bill'
+ # person.name_changed? # => false
+ # person.name_change # => nil
+ #
+ # Which attributes have changed?
+ # person.name = 'Bob'
+ # person.changed # => ['name']
+ # person.changes # => { 'name' => ['Bill', 'Bob'] }
+ #
+ # Resetting an attribute returns it to its original state:
+ # person.reset_name! # => 'Bill'
+ # person.changed? # => false
+ # person.name_changed? # => false
+ # person.name # => 'Bill'
+ #
+ # Before modifying an attribute in-place:
+ # person.name_will_change!
+ # person.name << 'y'
+ # person.name_change # => ['Bill', 'Billy']
+ module Dirty
+ extend ActiveSupport::Concern
+ include ActiveModel::AttributeMethods
+
+ included do
+ attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
+ attribute_method_affix :prefix => 'reset_', :suffix => '!'
+ end
+
+ # Do any attributes have unsaved changes?
+ # person.changed? # => false
+ # person.name = 'bob'
+ # person.changed? # => true
+ def changed?
+ !changed_attributes.empty?
+ end
+
+ # List of attributes with unsaved changes.
+ # person.changed # => []
+ # person.name = 'bob'
+ # person.changed # => ['name']
+ def changed
+ changed_attributes.keys
+ end
+
+ # Map of changed attrs => [original value, new value].
+ # person.changes # => {}
+ # person.name = 'bob'
+ # person.changes # => { 'name' => ['bill', 'bob'] }
+ def changes
+ changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
+ end
+
+ # Map of attributes that were changed when the model was saved.
+ # person.name # => 'bob'
+ # person.name = 'robert'
+ # person.save
+ # person.previous_changes # => {'name' => ['bob, 'robert']}
+ def previous_changes
+ previously_changed_attributes
+ end
+
+ private
+ # Map of change <tt>attr => original value</tt>.
+ def changed_attributes
+ @changed_attributes ||= {}
+ end
+
+ # Map of fields that were changed when the model was saved
+ def previously_changed_attributes
+ @previously_changed || {}
+ end
+
+ # Handle <tt>*_changed?</tt> for +method_missing+.
+ def attribute_changed?(attr)
+ changed_attributes.include?(attr)
+ end
+
+ # Handle <tt>*_change</tt> for +method_missing+.
+ def attribute_change(attr)
+ [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
+ end
+
+ # Handle <tt>*_was</tt> for +method_missing+.
+ def attribute_was(attr)
+ attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
+ end
+
+ # Handle <tt>*_will_change!</tt> for +method_missing+.
+ def attribute_will_change!(attr)
+ begin
+ value = __send__(attr)
+ value = value.duplicable? ? value.clone : value
+ rescue TypeError, NoMethodError
+ end
+
+ changed_attributes[attr] = value
+ end
+
+ # Handle <tt>reset_*!</tt> for +method_missing+.
+ def reset_attribute!(attr)
+ __send__("#{attr}=", changed_attributes[attr]) if attribute_changed?(attr)
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
new file mode 100644
index 0000000000..46af8ca9de
--- /dev/null
+++ b/activemodel/lib/active_model/lint.rb
@@ -0,0 +1,96 @@
+require "test/unit"
+require "test/unit/ui/console/testrunner"
+
+# You can test whether an object is compliant with the ActiveModel API by
+# calling ActiveModel::Compliance.test(object). It will emit a Test::Unit
+# output that tells you whether your object is fully compliant, or if not,
+# which aspects of the API are not implemented.
+#
+# These tests do not attempt to determine the semantic correctness of the
+# returned values. For instance, you could implement valid? to always
+# return true, and the tests would pass. It is up to you to ensure that
+# the values are semantically meaningful.
+#
+# Objects you pass in are expected to return a compliant object from a
+# call to to_model. It is perfectly fine for to_model to return self.
+
+module ActiveModel
+ module Lint
+ def self.test(object, verbosity = 2, output = STDOUT)
+ test_class = Class.new(::Test::Unit::TestCase) do
+ include Test
+
+ define_method(:setup) do
+ assert object.respond_to?(:to_model), "The object should respond_to :to_model"
+ @object = object.to_model
+ super
+ end
+ end
+
+ ::Test::Unit::UI::Console::TestRunner.new(test_class, verbosity, output).start
+ end
+
+ module Test
+ def assert_boolean(name, result)
+ assert result == true || result == false, "#{name} should be a boolean"
+ end
+
+ # valid?
+ # ------
+ #
+ # Returns a boolean that specifies whether the object is in a valid or invalid
+ # state.
+ def test_valid?
+ assert @object.respond_to?(:valid?), "The model should respond to valid?"
+ assert_boolean "valid?", @object.valid?
+ end
+
+ # new_record?
+ # -----------
+ #
+ # Returns a boolean that specifies whether the object has been persisted yet.
+ # This is used when calculating the URL for an object. If the object is
+ # not persisted, a form for that object, for instance, will be POSTed to the
+ # collection. If it is persisted, a form for the object will put PUTed to the
+ # URL for the object.
+ def test_new_record?
+ assert @object.respond_to?(:new_record?), "The model should respond to new_record?"
+ assert_boolean "new_record?", @object.new_record?
+ end
+
+ def test_destroyed?
+ assert @object.respond_to?(:new_record?), "The model should respond to destroyed?"
+ assert_boolean "destroyed?", @object.destroyed?
+ end
+
+ # errors
+ # ------
+ #
+ # Returns an object that has :[] and :full_messages defined on it. See below
+ # for more details.
+ def setup
+ assert @object.respond_to?(:errors), "The model should respond to errors"
+ @errors = @object.errors
+ end
+
+ # This module tests the #errors object
+ module Errors
+ # Returns an Array of Strings that are the errors for the attribute in
+ # question. If localization is used, the Strings should be localized
+ # for the current locale. If no error is present, this method should
+ # return an empty Array.
+ def test_errors_aref
+ assert @errors[:hello].is_a?(Array), "errors#[] should return an Array"
+ end
+
+ # Returns an Array of all error messages for the object. Each message
+ # should contain information about the field, if applicable.
+ def test_errors_full_messages
+ assert @errors.full_messages.is_a?(Array), "errors#full_messages should return an Array"
+ end
+ end
+
+ include Errors
+ end
+ end
+end \ No newline at end of file
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
new file mode 100644
index 0000000000..4c0073f687
--- /dev/null
+++ b/activemodel/lib/active_model/serialization.rb
@@ -0,0 +1,30 @@
+require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/hash/slice'
+
+module ActiveModel
+ module Serialization
+ def serializable_hash(options = nil)
+ options ||= {}
+
+ options[:only] = Array.wrap(options[:only]).map { |n| n.to_s }
+ options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }
+
+ attribute_names = attributes.keys.sort
+ if options[:only].any?
+ attribute_names &= options[:only]
+ elsif options[:except].any?
+ attribute_names -= options[:except]
+ end
+
+ method_names = Array.wrap(options[:methods]).inject([]) do |methods, name|
+ methods << name if respond_to?(name.to_s)
+ methods
+ end
+
+ (attribute_names + method_names).inject({}) { |hash, name|
+ hash[name] = send(name)
+ hash
+ }
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb
deleted file mode 100644
index 5b603bdbd7..0000000000
--- a/activemodel/lib/active_model/serializer.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require 'active_support/core_ext/hash/except'
-require 'active_support/core_ext/hash/slice'
-
-module ActiveModel
- class Serializer
- attr_reader :options
-
- def initialize(serializable, options = nil)
- @serializable = serializable
- @options = options ? options.dup : {}
-
- @options[:only] = Array.wrap(@options[:only]).map { |n| n.to_s }
- @options[:except] = Array.wrap(@options[:except]).map { |n| n.to_s }
- end
-
- def serialize
- raise NotImplemented
- end
-
- def to_s(&block)
- serialize(&block)
- end
-
- # To replicate the behavior in ActiveRecord#attributes,
- # <tt>:except</tt> takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set
- # for a N level model but is set for the N+1 level models,
- # then because <tt>:except</tt> is set to a default value, the second
- # level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if
- # <tt>:only</tt> is set, always delete <tt>:except</tt>.
- def serializable_attribute_names
- attribute_names = @serializable.attributes.keys.sort
-
- if options[:only].any?
- attribute_names &= options[:only]
- elsif options[:except].any?
- attribute_names -= options[:except]
- end
-
- attribute_names
- end
-
- def serializable_method_names
- Array.wrap(options[:methods]).inject([]) do |methods, name|
- methods << name if @serializable.respond_to?(name.to_s)
- methods
- end
- end
-
- def serializable_names
- serializable_attribute_names + serializable_method_names
- end
-
- def serializable_hash
- serializable_names.inject({}) { |hash, name|
- hash[name] = @serializable.send(name)
- hash
- }
- end
- end
-end
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index e94512fd64..ee6d48bfc6 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -5,6 +5,7 @@ module ActiveModel
module Serializers
module JSON
extend ActiveSupport::Concern
+ include ActiveModel::Serialization
included do
extend ActiveModel::Naming
@@ -12,19 +13,6 @@ module ActiveModel
cattr_accessor :include_root_in_json, :instance_writer => false
end
- class Serializer < ActiveModel::Serializer
- def serializable_hash
- model = super
- @serializable.include_root_in_json ?
- { @serializable.class.model_name.element => model } :
- model
- end
-
- def serialize
- ActiveSupport::JSON.encode(serializable_hash)
- end
- end
-
# Returns a JSON string representing the model. Some configuration is
# available through +options+.
#
@@ -92,7 +80,9 @@ module ActiveModel
# {"comments": [{"body": "Don't think too hard"}],
# "title": "So I was thinking"}]}
def encode_json(encoder)
- Serializer.new(self, encoder.options).to_s
+ hash = serializable_hash(encoder.options)
+ hash = { self.class.model_name.element => hash } if include_root_in_json
+ ActiveSupport::JSON.encode(hash)
end
def as_json(options = nil)
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 4508a39347..86149f1e5f 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -5,8 +5,9 @@ module ActiveModel
module Serializers
module Xml
extend ActiveSupport::Concern
+ include ActiveModel::Serialization
- class Serializer < ActiveModel::Serializer #:nodoc:
+ class Serializer #:nodoc:
class Attribute #:nodoc:
attr_reader :name, :value, :type
@@ -74,32 +75,32 @@ module ActiveModel
end
end
- def builder
- @builder ||= begin
- require 'builder' unless defined? ::Builder
- options[:indent] ||= 2
- builder = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
+ attr_reader :options
- unless options[:skip_instruct]
- builder.instruct!
- options[:skip_instruct] = true
- end
+ def initialize(serializable, options = nil)
+ @serializable = serializable
+ @options = options ? options.dup : {}
- builder
- end
+ @options[:only] = Array.wrap(@options[:only]).map { |n| n.to_s }
+ @options[:except] = Array.wrap(@options[:except]).map { |n| n.to_s }
end
- def root
- root = (options[:root] || @serializable.class.model_name.singular).to_s
- reformat_name(root)
- end
-
- def dasherize?
- !options.has_key?(:dasherize) || options[:dasherize]
- end
+ # To replicate the behavior in ActiveRecord#attributes,
+ # <tt>:except</tt> takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set
+ # for a N level model but is set for the N+1 level models,
+ # then because <tt>:except</tt> is set to a default value, the second
+ # level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if
+ # <tt>:only</tt> is set, always delete <tt>:except</tt>.
+ def serializable_attribute_names
+ attribute_names = @serializable.attributes.keys.sort
+
+ if options[:only].any?
+ attribute_names &= options[:only]
+ elsif options[:except].any?
+ attribute_names -= options[:except]
+ end
- def camelize?
- options.has_key?(:camelize) && options[:camelize]
+ attribute_names
end
def serializable_attributes
@@ -134,6 +135,34 @@ module ActiveModel
end
private
+ def builder
+ @builder ||= begin
+ require 'builder' unless defined? ::Builder
+ options[:indent] ||= 2
+ builder = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
+
+ unless options[:skip_instruct]
+ builder.instruct!
+ options[:skip_instruct] = true
+ end
+
+ builder
+ end
+ end
+
+ def root
+ root = (options[:root] || @serializable.class.model_name.singular).to_s
+ reformat_name(root)
+ end
+
+ def dasherize?
+ !options.has_key?(:dasherize) || options[:dasherize]
+ end
+
+ def camelize?
+ options.has_key?(:camelize) && options[:camelize]
+ end
+
def reformat_name(name)
name = name.camelize if camelize?
dasherize? ? name.dasherize : name
@@ -163,8 +192,7 @@ module ActiveModel
end
def to_xml(options = {}, &block)
- serializer = Serializer.new(self, options)
- block_given? ? serializer.to_s(&block) : serializer.to_s
+ Serializer.new(self, options).serialize(&block)
end
def from_xml(xml)
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index 6f3b668bf0..c670dafc7c 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -1,22 +1,30 @@
module ActiveModel
module Validations
module ClassMethods
- # Validates whether the value of the specified attribute is of the correct form by matching it against the regular expression
- # provided.
+ # Validates whether the value of the specified attribute is of the correct form, going by the regular expression provided.
+ # You can require that the attribute matches the regular expression:
#
# class Person < ActiveRecord::Base
# validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
# end
#
+ # Alternatively, you can require that the specified attribute does _not_ match the regular expression:
+ #
+ # class Person < ActiveRecord::Base
+ # validates_format_of :email, :without => /NOSPAM/
+ # end
+ #
# Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
#
- # A regular expression must be provided or else an exception will be raised.
+ # You must pass either <tt>:with</tt> or <tt>:without</tt> as an option. In addition, both must be a regular expression,
+ # or else an exception will be raised.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
- # * <tt>:with</tt> - The regular expression used to validate the format with (note: must be supplied!).
+ # * <tt>:with</tt> - Regular expression that if the attribute matches will result in a successful validation.
+ # * <tt>:without</tt> - Regular expression that if the attribute does not match will result in a successful validation.
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
@@ -25,14 +33,27 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_format_of(*attr_names)
- configuration = { :with => nil }
- configuration.update(attr_names.extract_options!)
+ configuration = attr_names.extract_options!
+
+ unless configuration.include?(:with) ^ configuration.include?(:without) # ^ == xor, or "exclusive or"
+ raise ArgumentError, "Either :with or :without must be supplied (but not both)"
+ end
- raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
+ if configuration[:with] && !configuration[:with].is_a?(Regexp)
+ raise ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash"
+ end
- validates_each(attr_names, configuration) do |record, attr_name, value|
- unless value.to_s =~ configuration[:with]
- record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
+ if configuration[:without] && !configuration[:without].is_a?(Regexp)
+ raise ArgumentError, "A regular expression must be supplied as the :without option of the configuration hash"
+ end
+
+ if configuration[:with]
+ validates_each(attr_names, configuration) do |record, attr_name, value|
+ record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value) if value.to_s !~ configuration[:with]
+ end
+ elsif configuration[:without]
+ validates_each(attr_names, configuration) do |record, attr_name, value|
+ record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value) if value.to_s =~ configuration[:without]
end
end
end
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index ada6e28594..32dbcd82d0 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -31,6 +31,21 @@ module ActiveModel
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
+ #
+ # The following checks can also be supplied with a proc or a symbol which corresponds to a method:
+ # * <tt>:greater_than</tt>
+ # * <tt>:greater_than_or_equal_to</tt>
+ # * <tt>:equal_to</tt>
+ # * <tt>:less_than</tt>
+ # * <tt>:less_than_or_equal_to</tt>
+ #
+ # class Person < ActiveRecord::Base
+ # validates_numericality_of :width, :less_than => Proc.new { |person| person.height }
+ # validates_numericality_of :width, :greater_than => :minimum_weight
+ # end
+ #
+ #
+
def validates_numericality_of(*attr_names)
configuration = { :only_integer => false, :allow_nil => false }
configuration.update(attr_names.extract_options!)
@@ -38,7 +53,8 @@ module ActiveModel
numericality_options = ALL_NUMERICALITY_CHECKS.keys & configuration.keys
(numericality_options - [ :odd, :even ]).each do |option|
- raise ArgumentError, ":#{option} must be a number" unless configuration[option].is_a?(Numeric)
+ value = configuration[option]
+ raise ArgumentError, ":#{option} must be a number, a symbol or a proc" unless value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol)
end
validates_each(attr_names,configuration) do |record, attr_name, value|
@@ -74,6 +90,9 @@ module ActiveModel
record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message])
end
else
+ configuration[option] = configuration[option].call(record) if configuration[option].is_a? Proc
+ configuration[option] = record.method(configuration[option]).call if configuration[option].is_a? Symbol
+
unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
record.errors.add(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option])
end
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
new file mode 100644
index 0000000000..851cdfebf0
--- /dev/null
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -0,0 +1,64 @@
+module ActiveModel
+ module Validations
+ module ClassMethods
+
+ # Passes the record off to the class or classes specified and allows them to add errors based on more complex conditions.
+ #
+ # class Person < ActiveRecord::Base
+ # validates_with MyValidator
+ # end
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def validate
+ # if some_complex_logic
+ # record.errors[:base] << "This record is invalid"
+ # end
+ # end
+ #
+ # private
+ # def some_complex_logic
+ # # ...
+ # end
+ # end
+ #
+ # You may also pass it multiple classes, like so:
+ #
+ # class Person < ActiveRecord::Base
+ # validates_with MyValidator, MyOtherValidator, :on => :create
+ # end
+ #
+ # Configuration options:
+ # * <tt>on</tt> - Specifies when this validation is active (<tt>:create</tt> or <tt>:update</tt>
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
+ # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
+ # The method, proc or string should return or evaluate to a true or false value.
+ # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
+ # The method, proc or string should return or evaluate to a true or false value.
+ #
+ # If you pass any additional configuration options, they will be passed to the class and available as <tt>options</tt>:
+ #
+ # class Person < ActiveRecord::Base
+ # validates_with MyValidator, :my_custom_key => "my custom value"
+ # end
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def validate
+ # options[:my_custom_key] # => "my custom value"
+ # end
+ # end
+ #
+ def validates_with(*args)
+ configuration = args.extract_options!
+
+ send(validation_method(configuration[:on]), configuration) do |record|
+ args.each do |klass|
+ klass.new(record, configuration.except(:on, :if, :unless)).validate
+ end
+ end
+ end
+ end
+ end
+end
+
+
diff --git a/activemodel/test/cases/lint_test.rb b/activemodel/test/cases/lint_test.rb
new file mode 100644
index 0000000000..165c353045
--- /dev/null
+++ b/activemodel/test/cases/lint_test.rb
@@ -0,0 +1,50 @@
+require "cases/helper"
+
+class TestLint < Test::Unit::TestCase
+ class CompliantObject
+ def to_model
+ self
+ end
+
+ def valid?() true end
+ def new_record?() true end
+ def destroyed?() true end
+
+ def errors
+ obj = Object.new
+ def obj.[](key) [] end
+ def obj.full_messages() [] end
+ obj
+ end
+ end
+
+ def assert_output(object, failures, errors, *test_names)
+ ActiveModel::Lint.test(object, 3, output = StringIO.new)
+ regex = %r{#{failures} failures, #{errors} errors}
+ assert_match regex, output.string
+
+ test_names.each do |test_name|
+ assert_match test_name, output.string
+ end
+ end
+
+ def test_valid
+ assert_output(CompliantObject.new, 0, 0, /test_valid/)
+ end
+
+ def test_new_record
+ assert_output(CompliantObject.new, 0, 0, /test_new_record?/)
+ end
+
+ def test_destroyed
+ assert_output(CompliantObject.new, 0, 0, /test_destroyed/)
+ end
+
+ def test_errors_aref
+ assert_output(CompliantObject.new, 0, 0, /test_errors_aref/)
+ end
+
+ def test_errors_full_messages
+ assert_output(CompliantObject.new, 0, 0, /test_errors_aref/)
+ end
+end \ No newline at end of file
diff --git a/activemodel/test/cases/tests_database.rb b/activemodel/test/cases/tests_database.rb
index 0f4475fa2d..8dd00ea147 100644
--- a/activemodel/test/cases/tests_database.rb
+++ b/activemodel/test/cases/tests_database.rb
@@ -27,15 +27,10 @@ module ActiveModel
def self.setup_connection
defaults = { :database => ':memory:' }
- begin
- adapter = defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'
- options = defaults.merge :adapter => adapter, :timeout => 500
- ActiveRecord::Base.establish_connection(options)
- rescue Exception
- $stderr.puts 'SQLite 3 unavailable; trying SQLite 2.'
- options = defaults.merge :adapter => 'sqlite'
- ActiveRecord::Base.establish_connection(options)
- end
+
+ adapter = defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'
+ options = defaults.merge :adapter => adapter, :timeout => 500
+ ActiveRecord::Base.establish_connection(options)
end
end
end
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index 2c06a9dd02..e19e4bf7b3 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -71,6 +71,35 @@ class PresenceValidationTest < ActiveModel::TestCase
assert_equal ["can't be Invalid title"], t.errors[:title]
end
+ def test_validate_format_with_not_option
+ Topic.validates_format_of(:title, :without => /foo/, :message => "should not contain foo")
+ t = Topic.new
+
+ t.title = "foobar"
+ t.valid?
+ assert_equal ["should not contain foo"], t.errors[:title]
+
+ t.title = "something else"
+ t.valid?
+ assert_equal [], t.errors[:title]
+ end
+
+ def test_validate_format_of_without_any_regexp_should_raise_error
+ assert_raise(ArgumentError) { Topic.validates_format_of(:title) }
+ end
+
+ def test_validates_format_of_with_both_regexps_should_raise_error
+ assert_raise(ArgumentError) { Topic.validates_format_of(:title, :with => /this/, :without => /that/) }
+ end
+
+ def test_validates_format_of_when_with_isnt_a_regexp_should_raise_error
+ assert_raise(ArgumentError) { Topic.validates_format_of(:title, :with => "clearly not a regexp") }
+ end
+
+ def test_validates_format_of_when_not_isnt_a_regexp_should_raise_error
+ assert_raise(ArgumentError) { Topic.validates_format_of(:title, :without => "clearly not a regexp") }
+ end
+
def test_validates_format_of_with_custom_error_using_quotes
repair_validations(Developer) do
Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "format 'single' and \"double\" quotes"
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index 0af6eb69ce..d3201966dc 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -106,6 +106,24 @@ class NumericalityValidationTest < ActiveModel::TestCase
valid!([2])
end
+ def test_validates_numericality_with_proc
+ Topic.send(:define_method, :min_approved, lambda { 5 })
+ Topic.validates_numericality_of :approved, :greater_than_or_equal_to => Proc.new {|topic| topic.min_approved }
+
+ invalid!([3, 4])
+ valid!([5, 6])
+ Topic.send(:remove_method, :min_approved)
+ end
+
+ def test_validates_numericality_with_symbol
+ Topic.send(:define_method, :max_approved, lambda { 5 })
+ Topic.validates_numericality_of :approved, :less_than_or_equal_to => :max_approved
+
+ invalid!([6])
+ valid!([4, 5])
+ Topic.send(:remove_method, :max_approved)
+ end
+
def test_validates_numericality_with_numeric_message
Topic.validates_numericality_of :approved, :less_than => 4, :message => "smaller than {{count}}"
topic = Topic.new("title" => "numeric test", "approved" => 10)
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
new file mode 100644
index 0000000000..f55fdc5864
--- /dev/null
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -0,0 +1,116 @@
+# encoding: utf-8
+require 'cases/helper'
+
+require 'models/topic'
+
+class ValidatesWithTest < ActiveRecord::TestCase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ ERROR_MESSAGE = "Validation error from validator"
+ OTHER_ERROR_MESSAGE = "Validation error from other validator"
+
+ class ValidatorThatAddsErrors < ActiveRecord::Validator
+ def validate()
+ record.errors[:base] << ERROR_MESSAGE
+ end
+ end
+
+ class OtherValidatorThatAddsErrors < ActiveRecord::Validator
+ def validate()
+ record.errors[:base] << OTHER_ERROR_MESSAGE
+ end
+ end
+
+ class ValidatorThatDoesNotAddErrors < ActiveRecord::Validator
+ def validate()
+ end
+ end
+
+ class ValidatorThatValidatesOptions < ActiveRecord::Validator
+ def validate()
+ if options[:field] == :first_name
+ record.errors[:base] << ERROR_MESSAGE
+ end
+ end
+ end
+
+ test "vaidation with class that adds errors" do
+ Topic.validates_with(ValidatorThatAddsErrors)
+ topic = Topic.new
+ assert !topic.valid?, "A class that adds errors causes the record to be invalid"
+ assert topic.errors[:base].include?(ERROR_MESSAGE)
+ end
+
+ test "with a class that returns valid" do
+ Topic.validates_with(ValidatorThatDoesNotAddErrors)
+ topic = Topic.new
+ assert topic.valid?, "A class that does not add errors does not cause the record to be invalid"
+ end
+
+ test "with a class that adds errors on update and a new record" do
+ Topic.validates_with(ValidatorThatAddsErrors, :on => :update)
+ topic = Topic.new
+ assert topic.valid?, "Validation doesn't run on create if 'on' is set to update"
+ end
+
+ test "with a class that adds errors on create and a new record" do
+ Topic.validates_with(ValidatorThatAddsErrors, :on => :create)
+ topic = Topic.new
+ assert !topic.valid?, "Validation does run on create if 'on' is set to create"
+ assert topic.errors[:base].include?(ERROR_MESSAGE)
+ end
+
+ test "with multiple classes" do
+ Topic.validates_with(ValidatorThatAddsErrors, OtherValidatorThatAddsErrors)
+ topic = Topic.new
+ assert !topic.valid?
+ assert topic.errors[:base].include?(ERROR_MESSAGE)
+ assert topic.errors[:base].include?(OTHER_ERROR_MESSAGE)
+ end
+
+ test "with if statements that return false" do
+ Topic.validates_with(ValidatorThatAddsErrors, :if => "1 == 2")
+ topic = Topic.new
+ assert topic.valid?
+ end
+
+ test "with if statements that return true" do
+ Topic.validates_with(ValidatorThatAddsErrors, :if => "1 == 1")
+ topic = Topic.new
+ assert !topic.valid?
+ assert topic.errors[:base].include?(ERROR_MESSAGE)
+ end
+
+ test "with unless statements that return true" do
+ Topic.validates_with(ValidatorThatAddsErrors, :unless => "1 == 1")
+ topic = Topic.new
+ assert topic.valid?
+ end
+
+ test "with unless statements that returns false" do
+ Topic.validates_with(ValidatorThatAddsErrors, :unless => "1 == 2")
+ topic = Topic.new
+ assert !topic.valid?
+ assert topic.errors[:base].include?(ERROR_MESSAGE)
+ end
+
+ test "passes all non-standard configuration options to the validator class" do
+ topic = Topic.new
+ validator = mock()
+ validator.expects(:new).with(topic, {:foo => :bar}).returns(validator)
+ validator.expects(:validate)
+
+ Topic.validates_with(validator, :if => "1 == 1", :foo => :bar)
+ assert topic.valid?
+ end
+
+ test "validates_with with options" do
+ Topic.validates_with(ValidatorThatValidatesOptions, :field => :first_name)
+ topic = Topic.new
+ assert !topic.valid?
+ assert topic.errors[:base].include?(ERROR_MESSAGE)
+ end
+
+end
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 9adc6b887f..d8bfb1916d 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,9 @@
*Edge*
+* Remove support for SQLite 2. Please upgrade to SQLite 3+ or install the plugin from git://github.com/rails/sqlite2_adapter.git [Pratik Naik]
+
+* PostgreSQL: XML datatype support. #1874 [Leonardo Borges]
+
* quoted_date converts time-like objects to ActiveRecord::Base.default_timezone before serialization. This allows you to use Time.now in find conditions and have it correctly be serialized as the current time in UTC when default_timezone == :utc. #2946 [Geoff Buesing]
* SQLite: drop support for 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper]
@@ -2197,7 +2201,7 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Escape database name in MySQL adapter when creating and dropping databases. #3409 [anna@wota.jp]
-* Disambiguate table names for columns in validates_uniquness_of's WHERE clause. #3423 [alex.borovsky@gmail.com]
+* Disambiguate table names for columns in validates_uniqueness_of's WHERE clause. #3423 [alex.borovsky@gmail.com]
* .with_scope imposed create parameters now bypass attr_protected [Tobias Lütke]
@@ -3712,7 +3716,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Escape database name in MySQL adapter when creating and dropping databases. #3409 [anna@wota.jp]
-* Disambiguate table names for columns in validates_uniquness_of's WHERE clause. #3423 [alex.borovsky@gmail.com]
+* Disambiguate table names for columns in validates_uniqueness_of's WHERE clause. #3423 [alex.borovsky@gmail.com]
* .with_scope imposed create parameters now bypass attr_protected [Tobias Lütke]
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 09dbc5ad6d..a9e4a92e37 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -24,19 +24,39 @@ PKG_FILES = FileList[
"lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "Rakefile"
].exclude(/\bCVS\b|~$/)
+def run_without_aborting(*tasks)
+ errors = []
+
+ tasks.each do |task|
+ begin
+ Rake::Task[task].invoke
+ rescue Exception
+ errors << task
+ end
+ end
+
+ abort "Errors running #{errors.join(', ')}" if errors.any?
+end
desc 'Run mysql, sqlite, and postgresql tests by default'
task :default => :test
desc 'Run mysql, sqlite, and postgresql tests'
-task :test => defined?(JRUBY_VERSION) ?
- %w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
- %w(test_mysql test_sqlite3 test_postgresql)
-task :isolated_test => defined?(JRUBY_VERSION) ?
- %w(isolated_test_jdbcmysql isolated_test_jdbcsqlite3 isolated_test_jdbcpostgresql) :
- %w(isolated_test_mysql isolated_test_sqlite3 isolated_test_postgresql)
-
-%w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
+task :test do
+ tasks = defined?(JRUBY_VERSION) ?
+ %w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
+ %w(test_mysql test_sqlite3 test_postgresql)
+ run_without_aborting(*tasks)
+end
+
+task :isolated_test do
+ tasks = defined?(JRUBY_VERSION) ?
+ %w(isolated_test_jdbcmysql isolated_test_jdbcsqlite3 isolated_test_jdbcpostgresql) :
+ %w(isolated_test_mysql isolated_test_sqlite3 isolated_test_postgresql)
+ run_without_aborting(*tasks)
+end
+
+%w( mysql postgresql sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
Rake::TestTask.new("test_#{adapter}") { |t|
connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}"
adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
@@ -86,8 +106,8 @@ task :rebuild_mysql_databases => 'mysql:rebuild_databases'
namespace :postgresql do
desc 'Build the PostgreSQL test databases'
task :build_databases do
- %x( createdb activerecord_unittest )
- %x( createdb activerecord_unittest2 )
+ %x( createdb -E UTF8 activerecord_unittest )
+ %x( createdb -E UTF8 activerecord_unittest2 )
end
desc 'Drop the PostgreSQL test databases'
diff --git a/activerecord/examples/.gitignore b/activerecord/examples/.gitignore
new file mode 100644
index 0000000000..0dfc1cb7fb
--- /dev/null
+++ b/activerecord/examples/.gitignore
@@ -0,0 +1 @@
+performance.sql
diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb
new file mode 100755
index 0000000000..53acd62f47
--- /dev/null
+++ b/activerecord/examples/performance.rb
@@ -0,0 +1,162 @@
+#!/usr/bin/env ruby -KU
+
+TIMES = (ENV['N'] || 10000).to_i
+
+require 'rubygems'
+gem 'addressable', '~>2.0'
+gem 'faker', '~>0.3.1'
+gem 'rbench', '~>0.2.3'
+require 'addressable/uri'
+require 'faker'
+require 'rbench'
+
+__DIR__ = File.dirname(__FILE__)
+$:.unshift "#{__DIR__}/../lib"
+require 'active_record'
+
+conn = { :adapter => 'mysql',
+ :database => 'activerecord_unittest',
+ :username => 'rails', :password => '',
+ :encoding => 'utf8' }
+
+conn[:socket] = Pathname.glob(%w[
+ /opt/local/var/run/mysql5/mysqld.sock
+ /tmp/mysqld.sock
+ /tmp/mysql.sock
+ /var/mysql/mysql.sock
+ /var/run/mysqld/mysqld.sock
+]).find { |path| path.socket? }
+
+ActiveRecord::Base.establish_connection(conn)
+
+class User < ActiveRecord::Base
+ connection.create_table :users, :force => true do |t|
+ t.string :name, :email
+ t.timestamps
+ end
+
+ has_many :exhibits
+end
+
+class Exhibit < ActiveRecord::Base
+ connection.create_table :exhibits, :force => true do |t|
+ t.belongs_to :user
+ t.string :name
+ t.text :notes
+ t.timestamps
+ end
+
+ belongs_to :user
+
+ def look; attributes end
+ def feel; look; user.name end
+
+ def self.look(exhibits) exhibits.each { |e| e.look } end
+ def self.feel(exhibits) exhibits.each { |e| e.feel } end
+end
+
+sqlfile = "#{__DIR__}/performance.sql"
+
+if File.exists?(sqlfile)
+ mysql_bin = %w[mysql mysql5].select { |bin| `which #{bin}`.length > 0 }
+ `#{mysql_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} < #{sqlfile}`
+else
+ puts 'Generating data...'
+
+ # pre-compute the insert statements and fake data compilation,
+ # so the benchmarks below show the actual runtime for the execute
+ # method, minus the setup steps
+
+ # Using the same paragraph for all exhibits because it is very slow
+ # to generate unique paragraphs for all exhibits.
+ notes = Faker::Lorem.paragraphs.join($/)
+ today = Date.today
+
+ puts 'Inserting 10,000 users and exhibits...'
+ 10_000.times do
+ user = User.create(
+ :created_at => today,
+ :name => Faker::Name.name,
+ :email => Faker::Internet.email
+ )
+
+ Exhibit.create(
+ :created_at => today,
+ :name => Faker::Company.name,
+ :user => user,
+ :notes => notes
+ )
+ end
+
+ mysqldump_bin = %w[mysqldump mysqldump5].select { |bin| `which #{bin}`.length > 0 }
+ `#{mysqldump_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} exhibits users > #{sqlfile}`
+end
+
+RBench.run(TIMES) do
+ column :times
+ column :ar
+
+ report 'Model#id', (TIMES * 100).ceil do
+ ar_obj = Exhibit.find(1)
+
+ ar { ar_obj.id }
+ end
+
+ report 'Model.new (instantiation)' do
+ ar { Exhibit.new }
+ end
+
+ report 'Model.new (setting attributes)' do
+ attrs = { :name => 'sam' }
+ ar { Exhibit.new(attrs) }
+ end
+
+ report 'Model.first' do
+ ar { Exhibit.first.look }
+ end
+
+ report 'Model.all limit(100)', (TIMES / 10).ceil do
+ ar { Exhibit.look Exhibit.all(:limit => 100) }
+ end
+
+ report 'Model.all limit(100) with relationship', (TIMES / 10).ceil do
+ ar { Exhibit.feel Exhibit.all(:limit => 100, :include => :user) }
+ end
+
+ report 'Model.all limit(10,000)', (TIMES / 1000).ceil do
+ ar { Exhibit.look Exhibit.all(:limit => 10000) }
+ end
+
+ exhibit = {
+ :name => Faker::Company.name,
+ :notes => Faker::Lorem.paragraphs.join($/),
+ :created_at => Date.today
+ }
+
+ report 'Model.create' do
+ ar { Exhibit.create(exhibit) }
+ end
+
+ report 'Resource#attributes=' do
+ attrs_first = { :name => 'sam' }
+ attrs_second = { :name => 'tom' }
+ ar { exhibit = Exhibit.new(attrs_first); exhibit.attributes = attrs_second }
+ end
+
+ report 'Resource#update' do
+ ar { Exhibit.first.update_attributes(:name => 'bob') }
+ end
+
+ report 'Resource#destroy' do
+ ar { Exhibit.first.destroy }
+ end
+
+ report 'Model.transaction' do
+ ar { Exhibit.transaction { Exhibit.new } }
+ end
+
+ summary 'Total'
+end
+
+ActiveRecord::Migration.drop_table "exhibits"
+ActiveRecord::Migration.drop_table "users"
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 68b0251982..2f1e7573d8 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -69,6 +69,7 @@ module ActiveRecord
autoload :TestCase, 'active_record/test_case'
autoload :Timestamp, 'active_record/timestamp'
autoload :Transactions, 'active_record/transactions'
+ autoload :Validator, 'active_record/validator'
autoload :Validations, 'active_record/validations'
module AttributeMethods
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 7f299b2aa5..f494e38e2f 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -42,11 +42,12 @@ module ActiveRecord
end
end
- class HasManyThroughCantAssociateThroughHasManyReflection < ActiveRecordError #:nodoc:
+ class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
end
end
+
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
@@ -416,6 +417,32 @@ module ActiveRecord
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
# @firm.invoices # selects all invoices by going through the Client join model.
#
+ # Similarly you can go through a +has_one+ association on the join model:
+ #
+ # class Group < ActiveRecord::Base
+ # has_many :users
+ # has_many :avatars, :through => :users
+ # end
+ #
+ # class User < ActiveRecord::Base
+ # belongs_to :group
+ # has_one :avatar
+ # end
+ #
+ # class Avatar < ActiveRecord::Base
+ # belongs_to :user
+ # end
+ #
+ # @group = Group.first
+ # @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group
+ # @group.avatars # selects all avatars by going through the User join model.
+ #
+ # An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are
+ # *read-only*. For example, the following would not work following the previous example:
+ #
+ # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around.
+ # @group.avatars.delete(@group.avatars.last) # so would this
+ #
# === Polymorphic Associations
#
# Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
@@ -819,7 +846,7 @@ module ActiveRecord
# [:through]
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
- # or <tt>has_many</tt> association on the join model.
+ # <tt>has_one</tt> or <tt>has_many</tt> association on the join model.
# [:source]
# Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index e67ccfb228..1b7bf42b91 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -208,6 +208,7 @@ module ActiveRecord
# Note that this method will _always_ remove records from the database
# ignoring the +:dependent+ option.
def destroy(*records)
+ records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)}
remove_records(records) do |records, old_records|
old_records.each { |record| record.destroy }
end
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index fd23e59e82..d91c555dad 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -1,6 +1,11 @@
module ActiveRecord
module Associations
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
+ def initialize(owner, reflection)
+ super
+ @primary_key_list = {}
+ end
+
def create(attributes = {})
create_record(attributes) { |record| insert_record(record) }
end
@@ -17,6 +22,12 @@ module ActiveRecord
@reflection.reset_column_information
end
+ def has_primary_key?
+ return @has_primary_key unless @has_primary_key.nil?
+ @has_primary_key = (ActiveRecord::Base.connection.supports_primary_key? &&
+ ActiveRecord::Base.connection.primary_key(@reflection.options[:join_table]))
+ end
+
protected
def construct_find_options!(options)
options[:joins] = @join_sql
@@ -29,6 +40,11 @@ module ActiveRecord
end
def insert_record(record, force = true, validate = true)
+ if has_primary_key?
+ raise ActiveRecord::ConfigurationError,
+ "Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})."
+ end
+
if record.new_record?
if force
record.save!
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index e4b631bc54..73d3c23cd3 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -74,6 +74,7 @@ module ActiveRecord
"#{@reflection.primary_key_name} = NULL",
"#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
)
+ @owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index f4507c979c..829f0ac0c5 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -8,21 +8,11 @@ module ActiveRecord
alias_method :new, :build
def create!(attrs = nil)
- ensure_owner_is_not_new
-
- transaction do
- self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association! } : @reflection.create_association!)
- object
- end
+ create_record(attrs, true)
end
def create(attrs = nil)
- ensure_owner_is_not_new
-
- transaction do
- self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association)
- object
- end
+ create_record(attrs, false)
end
def destroy(*records)
@@ -40,8 +30,18 @@ module ActiveRecord
return @target.size if loaded?
return count
end
-
+
protected
+ def create_record(attrs, force = true)
+ ensure_owner_is_not_new
+
+ transaction do
+ object = @reflection.klass.new(attrs)
+ add_record_to_target_with_callbacks(object) {|r| insert_record(object, force) }
+ object
+ end
+ end
+
def target_reflection_has_associated_record?
if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.primary_key_name].blank?
false
@@ -65,9 +65,10 @@ module ActiveRecord
return false unless record.save(validate)
end
end
- through_reflection = @reflection.through_reflection
- klass = through_reflection.klass
- @owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(record)) { through_reflection.create_association! }
+
+ through_association = @owner.send(@reflection.through_reflection.name)
+ through_record = through_association.create!(construct_join_attributes(record))
+ through_association.proxy_target << through_record
end
# TODO - add dependent option support
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index 830aa1808a..a79bf943d1 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -18,9 +18,15 @@ module ActiveRecord
current_object = @owner.send(@reflection.through_reflection.name)
if current_object
- new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
- else
- @owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value))) if new_value
+ new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
+ elsif new_value
+ if @owner.new_record?
+ self.target = new_value
+ through_association = @owner.send(:association_instance_get, @reflection.through_reflection.name)
+ through_association.build(construct_join_attributes(new_value))
+ else
+ @owner.send(@reflection.through_reflection.name, klass.create(construct_join_attributes(new_value)))
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb
index 8e7ce33814..16b6123439 100644
--- a/activerecord/lib/active_record/associations/through_association_scope.rb
+++ b/activerecord/lib/active_record/associations/through_association_scope.rb
@@ -93,7 +93,7 @@ module ActiveRecord
# Construct attributes for :through pointing to owner and associate.
def construct_join_attributes(associate)
# TODO: revist this to allow it for deletion, supposing dependent option is supported
- raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many
+ raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 911c908c8b..4df0f1af69 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -1,95 +1,25 @@
+require 'active_support/core_ext/object/tap'
+
module ActiveRecord
module AttributeMethods
- # Track unsaved attribute changes.
- #
- # A newly instantiated object is unchanged:
- # person = Person.find_by_name('Uncle Bob')
- # person.changed? # => false
- #
- # Change the name:
- # person.name = 'Bob'
- # person.changed? # => true
- # person.name_changed? # => true
- # person.name_was # => 'Uncle Bob'
- # person.name_change # => ['Uncle Bob', 'Bob']
- # person.name = 'Bill'
- # person.name_change # => ['Uncle Bob', 'Bill']
- #
- # Save the changes:
- # person.save
- # person.changed? # => false
- # person.name_changed? # => false
- #
- # Assigning the same value leaves the attribute unchanged:
- # person.name = 'Bill'
- # person.name_changed? # => false
- # person.name_change # => nil
- #
- # Which attributes have changed?
- # person.name = 'Bob'
- # person.changed # => ['name']
- # person.changes # => { 'name' => ['Bill', 'Bob'] }
- #
- # Resetting an attribute returns it to its original state:
- # person.reset_name! # => 'Bill'
- # person.changed? # => false
- # person.name_changed? # => false
- # person.name # => 'Bill'
- #
- # Before modifying an attribute in-place:
- # person.name_will_change!
- # person.name << 'y'
- # person.name_change # => ['Bill', 'Billy']
module Dirty
extend ActiveSupport::Concern
-
- DIRTY_AFFIXES = [
- { :suffix => '_changed?' },
- { :suffix => '_change' },
- { :suffix => '_will_change!' },
- { :suffix => '_was' },
- { :prefix => 'reset_', :suffix => '!' }
- ]
+ include ActiveModel::Dirty
included do
- attribute_method_affix *DIRTY_AFFIXES
-
- alias_method_chain :save, :dirty
- alias_method_chain :save!, :dirty
- alias_method_chain :update, :dirty
- alias_method_chain :reload, :dirty
+ alias_method_chain :save, :dirty
+ alias_method_chain :save!, :dirty
+ alias_method_chain :update, :dirty
+ alias_method_chain :reload, :dirty
superclass_delegating_accessor :partial_updates
self.partial_updates = true
end
- # Do any attributes have unsaved changes?
- # person.changed? # => false
- # person.name = 'bob'
- # person.changed? # => true
- def changed?
- !changed_attributes.empty?
- end
-
- # List of attributes with unsaved changes.
- # person.changed # => []
- # person.name = 'bob'
- # person.changed # => ['name']
- def changed
- changed_attributes.keys
- end
-
- # Map of changed attrs => [original value, new value].
- # person.changes # => {}
- # person.name = 'bob'
- # person.changes # => { 'name' => ['bill', 'bob'] }
- def changes
- changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
- end
-
# Attempts to +save+ the record and clears changed attributes if successful.
def save_with_dirty(*args) #:nodoc:
if status = save_without_dirty(*args)
+ @previously_changed = changes
changed_attributes.clear
end
status
@@ -97,49 +27,21 @@ module ActiveRecord
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
def save_with_dirty!(*args) #:nodoc:
- status = save_without_dirty!(*args)
- changed_attributes.clear
- status
+ save_without_dirty!(*args).tap do
+ @previously_changed = changes
+ changed_attributes.clear
+ end
end
# <tt>reload</tt> the record and clears changed attributes.
def reload_with_dirty(*args) #:nodoc:
- record = reload_without_dirty(*args)
- changed_attributes.clear
- record
+ reload_without_dirty(*args).tap do
+ previously_changed_attributes.clear
+ changed_attributes.clear
+ end
end
private
- # Map of change <tt>attr => original value</tt>.
- def changed_attributes
- @changed_attributes ||= {}
- end
-
- # Handle <tt>*_changed?</tt> for +method_missing+.
- def attribute_changed?(attr)
- changed_attributes.include?(attr)
- end
-
- # Handle <tt>*_change</tt> for +method_missing+.
- def attribute_change(attr)
- [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
- end
-
- # Handle <tt>*_was</tt> for +method_missing+.
- def attribute_was(attr)
- attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
- end
-
- # Handle <tt>reset_*!</tt> for +method_missing+.
- def reset_attribute!(attr)
- self[attr] = changed_attributes[attr] if attribute_changed?(attr)
- end
-
- # Handle <tt>*_will_change!</tt> for +method_missing+.
- def attribute_will_change!(attr)
- changed_attributes[attr] = clone_attribute_value(:read_attribute, attr)
- end
-
# Wrap write_attribute to remember original attribute value.
def write_attribute(attr, value)
attr = attr.to_s
@@ -182,23 +84,6 @@ module ActiveRecord
old != value
end
-
- module ClassMethods
- def self.extended(base)
- class << base
- alias_method_chain :alias_attribute, :dirty
- end
- end
-
- def alias_attribute_with_dirty(new_name, old_name)
- alias_attribute_without_dirty(new_name, old_name)
- DIRTY_AFFIXES.each do |affixes|
- module_eval <<-STR, __FILE__, __LINE__+1
- def #{affixes[:prefix]}#{new_name}#{affixes[:suffix]}; self.#{affixes[:prefix]}#{old_name}#{affixes[:suffix]}; end # def reset_subject!; self.reset_title!; end
- STR
- end
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 531a698f77..c5be561ea3 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1394,7 +1394,7 @@ module ActiveRecord #:nodoc:
end
defaults << options[:default] if options[:default]
defaults.flatten!
- defaults << attribute_key_name.humanize
+ defaults << attribute_key_name.to_s.humanize
options[:count] ||= 1
I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes]))
end
@@ -2294,20 +2294,24 @@ module ActiveRecord #:nodoc:
# And for value objects on a composed_of relationship:
# { :address => Address.new("123 abc st.", "chicago") }
# # => "address_street='123 abc st.' and address_city='chicago'"
- def sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name)
+ def sanitize_sql_hash_for_conditions(attrs, default_table_name = quoted_table_name)
attrs = expand_hash_conditions_for_aggregates(attrs)
conditions = attrs.map do |attr, value|
+ table_name = default_table_name
+
unless value.is_a?(Hash)
attr = attr.to_s
# Extract table name from qualified attribute names.
if attr.include?('.')
- table_name, attr = attr.split('.', 2)
- table_name = connection.quote_table_name(table_name)
+ attr_table_name, attr = attr.split('.', 2)
+ attr_table_name = connection.quote_table_name(attr_table_name)
+ else
+ attr_table_name = table_name
end
- attribute_condition("#{table_name}.#{connection.quote_column_name(attr)}", value)
+ attribute_condition("#{attr_table_name}.#{connection.quote_column_name(attr)}", value)
else
sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
end
@@ -3013,16 +3017,22 @@ module ActiveRecord #:nodoc:
def execute_callstack_for_multiparameter_attributes(callstack)
errors = []
- callstack.each do |name, values|
+ callstack.each do |name, values_with_empty_parameters|
begin
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
+ # in order to allow a date to be set without a year, we must keep the empty values.
+ # Otherwise, we wouldn't be able to distinguish it from a date with an empty day.
+ values = values_with_empty_parameters.reject(&:nil?)
+
if values.empty?
send(name + "=", nil)
else
+
value = if Time == klass
instantiate_time_object(name, values)
elsif Date == klass
begin
+ values = values_with_empty_parameters.collect do |v| v.nil? ? 1 : v end
Date.new(*values)
rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
@@ -3050,10 +3060,8 @@ module ActiveRecord #:nodoc:
attribute_name = multiparameter_name.split("(").first
attributes[attribute_name] = [] unless attributes.include?(attribute_name)
- unless value.empty?
- attributes[attribute_name] <<
- [ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
- end
+ parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
+ attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_value ]
end
attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index 4a88c43dff..646fed1a0b 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -197,7 +197,7 @@ module ActiveRecord
sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
if options[:from]
sql << " FROM #{options[:from]} "
- elsif scope && scope[:from]
+ elsif scope && scope[:from] && !use_workaround
sql << " FROM #{scope[:from]} "
else
sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index f346e3ebc8..520f3c8c0c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -315,6 +315,20 @@ module ActiveRecord
@base = base
end
+ #Handles non supported datatypes - e.g. XML
+ def method_missing(symbol, *args)
+ if symbol.to_s == 'xml'
+ xml_column_fallback(args)
+ end
+ end
+
+ def xml_column_fallback(*args)
+ case @base.adapter_name.downcase
+ when 'sqlite', 'mysql'
+ options = args.extract_options!
+ column(args[0], :text, options)
+ end
+ end
# Appends a primary key definition to the table definition.
# Can be called multiple times, but this is probably not a good idea.
def primary_key(name)
@@ -705,3 +719,4 @@ module ActiveRecord
end
end
+
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 2473c772e3..20787ec510 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -107,7 +107,7 @@ module ActiveRecord
# See also TableDefinition#column for details on how to create columns.
def create_table(table_name, options = {})
table_definition = TableDefinition.new(self)
- table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name)) unless options[:id] == false
+ table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
yield table_definition if block_given?
@@ -329,7 +329,7 @@ module ActiveRecord
schema_migrations_table.column :version, :string, :null => false
end
add_index sm_table, :version, :unique => true,
- :name => 'unique_schema_migrations'
+ :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
# Backwards-compatibility: if we find schema_info, assume we've
# migrated up to that point:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index c533d4cdb6..fab70f34b9 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -56,6 +56,13 @@ module ActiveRecord
false
end
+ # Can this adapter determine the primary key for tables not attached
+ # to an ActiveRecord class, such as join tables? Backend specific, as
+ # the abstract adapter always returns +false+.
+ def supports_primary_key?
+ false
+ end
+
# Does this adapter support using DISTINCT within COUNT? This is +true+
# for all adapters except sqlite.
def supports_count_distinct?
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 2b882a1f25..4edb64c2c0 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -53,12 +53,7 @@ module ActiveRecord
socket = config[:socket]
username = config[:username] ? config[:username].to_s : 'root'
password = config[:password].to_s
-
- if config.has_key?(:database)
- database = config[:database]
- else
- raise ArgumentError, "No database specified. Missing argument: database."
- end
+ database = config[:database]
# Require the MySQL driver and define Mysql::Result.all_hashes
unless defined? Mysql
@@ -81,7 +76,7 @@ module ActiveRecord
module ConnectionAdapters
class MysqlColumn < Column #:nodoc:
def extract_default(default)
- if type == :binary || type == :text
+ if sql_type =~ /blob/i || type == :text
if default.blank?
return null ? nil : ''
else
@@ -95,7 +90,7 @@ module ActiveRecord
end
def has_default?
- return false if type == :binary || type == :text #mysql forbids defaults on blob and text columns
+ return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
super
end
@@ -213,6 +208,10 @@ module ActiveRecord
true
end
+ def supports_primary_key? #:nodoc:
+ true
+ end
+
def supports_savepoints? #:nodoc:
true
end
@@ -555,6 +554,12 @@ module ActiveRecord
keys.length == 1 ? [keys.first, nil] : nil
end
+ # Returns just a table's primary key
+ def primary_key(table)
+ pk_and_sequence = pk_and_sequence_for(table)
+ pk_and_sequence && pk_and_sequence.first
+ end
+
def case_sensitive_equality_operator
"= BINARY"
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index e77ae93c21..84cf1ad9fd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -40,6 +40,12 @@ module ActiveRecord
end
module ConnectionAdapters
+ class TableDefinition
+ def xml(*args)
+ options = args.extract_options!
+ column(args[0], 'xml', options)
+ end
+ end
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
# Instantiates a new PostgreSQL column definition in a table.
@@ -68,7 +74,7 @@ module ActiveRecord
# depending on the server specifics
super
end
-
+
# Maps PostgreSQL-specific data types to logical Rails types.
def simplified_type(field_type)
case field_type
@@ -100,10 +106,10 @@ module ActiveRecord
:string
# XML type
when /^xml$/
- :string
+ :xml
# Arrays
when /^\D+\[\]$/
- :string
+ :string
# Object identifier types
when /^oid$/
:integer
@@ -112,7 +118,7 @@ module ActiveRecord
super
end
end
-
+
# Extracts the value from a PostgreSQL column default definition.
def self.extract_value_from_default(default)
case default
@@ -195,7 +201,8 @@ module ActiveRecord
:time => { :name => "time" },
:date => { :name => "date" },
:binary => { :name => "bytea" },
- :boolean => { :name => "boolean" }
+ :boolean => { :name => "boolean" },
+ :xml => { :name => "xml" }
}
# Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -250,6 +257,11 @@ module ActiveRecord
true
end
+ # Does PostgreSQL support finding primary key on non-ActiveRecord tables?
+ def supports_primary_key? #:nodoc:
+ true
+ end
+
# Does PostgreSQL support standard conforming strings?
def supports_standard_conforming_strings?
# Temporarily set the client message level above error to prevent unintentional
@@ -273,7 +285,7 @@ module ActiveRecord
def supports_ddl_transactions?
true
end
-
+
def supports_savepoints?
true
end
@@ -365,7 +377,7 @@ module ActiveRecord
if value.kind_of?(String) && column && column.type == :binary
"#{quoted_string_prefix}'#{escape_bytea(value)}'"
elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
- "xml '#{quote_string(value)}'"
+ "xml E'#{quote_string(value)}'"
elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
# Not truly string input, so doesn't require (or allow) escape string syntax.
"'#{value.to_s}'"
@@ -564,7 +576,7 @@ module ActiveRecord
def rollback_db_transaction
execute "ROLLBACK"
end
-
+
if defined?(PGconn::PQTRANS_IDLE)
# The ruby-pg driver supports inspecting the transaction status,
# while the ruby-postgres driver does not.
@@ -811,6 +823,12 @@ module ActiveRecord
nil
end
+ # Returns just a table's primary key
+ def primary_key(table)
+ pk_and_sequence = pk_and_sequence_for(table)
+ pk_and_sequence && pk_and_sequence.first
+ end
+
# Renames a table.
def rename_table(name, new_name)
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
@@ -909,18 +927,18 @@ module ActiveRecord
sql = "DISTINCT ON (#{columns}) #{columns}, "
sql << order_columns * ', '
end
-
+
# Returns an ORDER BY clause for the passed order option.
- #
+ #
# PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
# by wrapping the +sql+ string as a sub-select and ordering in that query.
def add_order_by_for_association_limiting!(sql, options) #:nodoc:
return sql if options[:order].blank?
-
+
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
-
+
sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
end
@@ -1044,7 +1062,7 @@ module ActiveRecord
if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID
# Because money output is formatted according to the locale, there are two
# cases to consider (note the decimal separators):
- # (1) $12,345,678.12
+ # (1) $12,345,678.12
# (2) $12.345.678,12
case column = row[cell_index]
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
@@ -1104,3 +1122,4 @@ module ActiveRecord
end
end
end
+
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 5eef692d05..d933bc924d 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -24,11 +24,6 @@ module ActiveRecord
module ConnectionAdapters #:nodoc:
class SQLite3Adapter < SQLiteAdapter # :nodoc:
- def table_structure(table_name)
- structure = @connection.table_info(quote_table_name(table_name))
- raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
- structure
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index c0f5046bff..5ed7094169 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -4,27 +4,6 @@ require 'active_support/core_ext/kernel/requires'
module ActiveRecord
class Base
class << self
- # Establishes a connection to the database that's used by all Active Record objects
- def sqlite_connection(config) # :nodoc:
- parse_sqlite_config!(config)
-
- unless self.class.const_defined?(:SQLite)
- require_library_or_gem(config[:adapter])
-
- db = SQLite::Database.new(config[:database], 0)
- db.show_datatypes = "ON" if !defined? SQLite::Version
- db.results_as_hash = true if defined? SQLite::Version
- db.type_translation = false
-
- # "Downgrade" deprecated sqlite API
- if SQLite.const_defined?(:Version)
- ConnectionAdapters::SQLite2Adapter.new(db, logger, config)
- else
- ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger, config)
- end
- end
- end
-
private
def parse_sqlite_config!(config)
# Require database.
@@ -101,6 +80,10 @@ module ActiveRecord
true
end
+ def supports_primary_key? #:nodoc:
+ true
+ end
+
def requires_reloading?
true
end
@@ -324,9 +307,9 @@ module ActiveRecord
end
def table_structure(table_name)
- returning structure = execute("PRAGMA table_info(#{quote_table_name(table_name)})") do
- raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
- end
+ structure = @connection.table_info(quote_table_name(table_name))
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
+ structure
end
def alter_table(table_name, options = {}) #:nodoc:
@@ -441,18 +424,5 @@ module ActiveRecord
end
end
-
- class SQLite2Adapter < SQLiteAdapter # :nodoc:
- def rename_table(name, new_name)
- move_table(name, new_name)
- end
- end
-
- class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc:
- def insert(sql, name = nil, pk = nil, id_value = nil)
- execute(sql, name = nil)
- id_value || @connection.last_insert_rowid
- end
- end
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 6eeeddc9e1..99b812b5fc 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -622,7 +622,8 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
targets.each do |target|
join_fixtures["#{label}_#{target}"] = Fixture.new(
{ association.primary_key_name => row[primary_key_name],
- association.association_foreign_key => Fixtures.identify(target) }, nil)
+ association.association_foreign_key => Fixtures.identify(target) },
+ nil, @connection)
end
end
end
@@ -706,12 +707,12 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
yaml_value.each do |fixture|
raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
- fixture.each do |name, data|
+ fixture.each do |name, data|
unless data
raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
end
- self[name] = Fixture.new(data, model_class)
+ self[name] = Fixture.new(data, model_class, @connection)
end
end
end
@@ -724,7 +725,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
reader.each do |row|
data = {}
row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
- self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class)
+ self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class, @connection)
end
end
@@ -762,7 +763,8 @@ class Fixture #:nodoc:
attr_reader :model_class
- def initialize(fixture, model_class)
+ def initialize(fixture, model_class, connection = ActiveRecord::Base.connection)
+ @connection = connection
@fixture = fixture
@model_class = model_class.is_a?(Class) ? model_class : model_class.constantize rescue nil
end
@@ -784,14 +786,14 @@ class Fixture #:nodoc:
end
def key_list
- columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) }
+ columns = @fixture.keys.collect{ |column_name| @connection.quote_column_name(column_name) }
columns.join(", ")
end
def value_list
list = @fixture.inject([]) do |fixtures, (key, value)|
col = model_class.columns_hash[key] if model_class.respond_to?(:ancestors) && model_class.ancestors.include?(ActiveRecord::Base)
- fixtures << ActiveRecord::Base.connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
+ fixtures << @connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
end
list * ', '
end
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index bf8a71d236..092f5f0023 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -23,6 +23,7 @@ en:
less_than_or_equal_to: "must be less than or equal to {{count}}"
odd: "must be odd"
even: "must be even"
+ record_invalid: "Validation failed: {{errors}}"
# Append your own errors here or at the model/attributes scope.
# You can define own errors for models or model attributes.
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 0baa9654b7..db5d2b25ed 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -314,7 +314,7 @@ module ActiveRecord
raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
end
- unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
+ unless [:belongs_to, :has_many, :has_one].include?(source_reflection.macro) && source_reflection.options[:through].nil?
raise HasManyThroughSourceAssociationMacroError.new(self)
end
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 94f1e8f1fd..b49471f7ab 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -1,60 +1,58 @@
module ActiveRecord #:nodoc:
module Serialization
- module RecordSerializer #:nodoc:
- def initialize(*args)
- super
- options[:except] |= Array.wrap(@serializable.class.inheritance_column)
+ extend ActiveSupport::Concern
+ include ActiveModel::Serializers::JSON
+
+ def serializable_hash(options = nil)
+ options ||= {}
+
+ options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }
+ options[:except] |= Array.wrap(self.class.inheritance_column)
+
+ hash = super(options)
+
+ serializable_add_includes(options) do |association, records, opts|
+ hash[association] = records.is_a?(Enumerable) ?
+ records.map { |r| r.serializable_hash(opts) } :
+ records.serializable_hash(opts)
end
+ hash
+ end
+
+ private
# Add associations specified via the <tt>:includes</tt> option.
# Expects a block that takes as arguments:
# +association+ - name of the association
# +records+ - the association record(s) to be serialized
# +opts+ - options for the association records
- def add_includes(&block)
- if include_associations = options.delete(:include)
- base_only_or_except = { :except => options[:except],
- :only => options[:only] }
-
- include_has_options = include_associations.is_a?(Hash)
- associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
-
- for association in associations
- records = case @serializable.class.reflect_on_association(association).macro
- when :has_many, :has_and_belongs_to_many
- @serializable.send(association).to_a
- when :has_one, :belongs_to
- @serializable.send(association)
- end
-
- unless records.nil?
- association_options = include_has_options ? include_associations[association] : base_only_or_except
- opts = options.merge(association_options)
- yield(association, records, opts)
- end
- end
+ def serializable_add_includes(options = {})
+ return unless include_associations = options.delete(:include)
- options[:include] = include_associations
- end
- end
+ base_only_or_except = { :except => options[:except],
+ :only => options[:only] }
+
+ include_has_options = include_associations.is_a?(Hash)
+ associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
- def serializable_hash
- hash = super
+ for association in associations
+ records = case self.class.reflect_on_association(association).macro
+ when :has_many, :has_and_belongs_to_many
+ send(association).to_a
+ when :has_one, :belongs_to
+ send(association)
+ end
- add_includes do |association, records, opts|
- hash[association] =
- if records.is_a?(Enumerable)
- records.collect { |r| self.class.new(r, opts).serializable_hash }
- else
- self.class.new(records, opts).serializable_hash
- end
+ unless records.nil?
+ association_options = include_has_options ? include_associations[association] : base_only_or_except
+ opts = options.merge(association_options)
+ yield(association, records, opts)
+ end
end
- hash
+ options[:include] = include_associations
end
- end
end
end
require 'active_record/serializers/xml_serializer'
-require 'active_record/serializers/json_serializer'
diff --git a/activerecord/lib/active_record/serializers/json_serializer.rb b/activerecord/lib/active_record/serializers/json_serializer.rb
deleted file mode 100644
index 63bf42c09d..0000000000
--- a/activerecord/lib/active_record/serializers/json_serializer.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-module ActiveRecord #:nodoc:
- module Serialization
- extend ActiveSupport::Concern
- include ActiveModel::Serializers::JSON
-
- class JSONSerializer < ActiveModel::Serializers::JSON::Serializer
- include Serialization::RecordSerializer
- end
-
- def encode_json(encoder)
- JSONSerializer.new(self, encoder.options).to_s
- end
- end
-end
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index 4e172bd2b6..b19920741e 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -2,6 +2,8 @@ require 'active_support/core_ext/hash/conversions'
module ActiveRecord #:nodoc:
module Serialization
+ include ActiveModel::Serializers::Xml
+
# Builds an XML document to represent the model. Some configuration is
# available through +options+. However more complicated cases should
# override ActiveRecord::Base#to_xml.
@@ -169,18 +171,15 @@ module ActiveRecord #:nodoc:
# end
# end
def to_xml(options = {}, &block)
- serializer = XmlSerializer.new(self, options)
- block_given? ? serializer.to_s(&block) : serializer.to_s
- end
-
- def from_xml(xml)
- self.attributes = Hash.from_xml(xml).values.first
- self
+ XmlSerializer.new(self, options).serialize(&block)
end
end
class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
- include Serialization::RecordSerializer
+ def initialize(*args)
+ super
+ options[:except] |= Array.wrap(@serializable.class.inheritance_column)
+ end
def serializable_attributes
serializable_attribute_names.collect { |name| Attribute.new(name, @serializable) }
@@ -235,7 +234,9 @@ module ActiveRecord #:nodoc:
builder.tag!(*args) do
add_attributes
procs = options.delete(:procs)
- add_includes { |association, records, opts| add_associations(association, records, opts) }
+ @serializable.send(:serializable_add_includes, options) { |association, records, opts|
+ add_associations(association, records, opts)
+ }
options[:procs] = procs
add_procs
yield builder if block_given?
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index a7fa98756e..5fc41cf054 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -12,7 +12,8 @@ module ActiveRecord
attr_reader :record
def initialize(record)
@record = record
- super("Validation failed: #{@record.errors.full_messages.join(", ")}")
+ errors = @record.errors.full_messages.join(I18n.t('support.array.words_connector', :default => ', '))
+ super(I18n.t('activerecord.errors.messages.record_invalid', :errors => errors))
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index edec4e9e43..711086dc2c 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -119,7 +119,7 @@ module ActiveRecord
comparison_operator = "IS ?"
elsif column.text?
comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
- value = column.limit ? value.to_s[0, column.limit] : value.to_s
+ value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s
else
comparison_operator = "= ?"
end
diff --git a/activerecord/lib/active_record/validator.rb b/activerecord/lib/active_record/validator.rb
new file mode 100644
index 0000000000..83a33f4dcd
--- /dev/null
+++ b/activerecord/lib/active_record/validator.rb
@@ -0,0 +1,68 @@
+module ActiveRecord #:nodoc:
+
+ # A simple base class that can be used along with ActiveRecord::Base.validates_with
+ #
+ # class Person < ActiveRecord::Base
+ # validates_with MyValidator
+ # end
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def validate
+ # if some_complex_logic
+ # record.errors[:base] = "This record is invalid"
+ # end
+ # end
+ #
+ # private
+ # def some_complex_logic
+ # # ...
+ # end
+ # end
+ #
+ # Any class that inherits from ActiveRecord::Validator will have access to <tt>record</tt>,
+ # which is an instance of the record being validated, and must implement a method called <tt>validate</tt>.
+ #
+ # class Person < ActiveRecord::Base
+ # validates_with MyValidator
+ # end
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def validate
+ # record # => The person instance being validated
+ # options # => Any non-standard options passed to validates_with
+ # end
+ # end
+ #
+ # To cause a validation error, you must add to the <tt>record<tt>'s errors directly
+ # from within the validators message
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def validate
+ # record.errors[:base] << "This is some custom error message"
+ # record.errors[:first_name] << "This is some complex validation"
+ # # etc...
+ # end
+ # end
+ #
+ # To add behavior to the initialize method, use the following signature:
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def initialize(record, options)
+ # super
+ # @my_custom_field = options[:field_name] || :first_name
+ # end
+ # end
+ #
+ class Validator
+ attr_reader :record, :options
+
+ def initialize(record, options)
+ @record = record
+ @options = options
+ end
+
+ def validate
+ raise "You must override this method"
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 88136597e3..9463b7b14a 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -63,6 +63,18 @@ class AdapterTest < ActiveRecord::TestCase
def test_show_nonexistent_variable_returns_nil
assert_nil @connection.show_variable('foo_bar_baz')
end
+
+ def test_not_specifying_database_name_for_cross_database_selects
+ assert_nothing_raised do
+ ActiveRecord::Base.establish_connection({
+ :adapter => 'mysql',
+ :username => 'rails'
+ })
+ ActiveRecord::Base.connection.execute "SELECT activerecord_unittest.pirates.*, activerecord_unittest2.courses.* FROM activerecord_unittest.pirates, activerecord_unittest2.courses"
+ end
+
+ ActiveRecord::Base.establish_connection 'arunit'
+ end
end
if current_adapter?(:PostgreSQLAdapter)
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 784c484178..2a77eed1b5 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -249,24 +249,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 1, Topic.find(topic.id)[:replies_count]
end
- def test_belongs_to_counter_after_save
- topic = Topic.create("title" => "monday night")
- topic.replies.create("title" => "re: monday night", "content" => "football")
- assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
-
- topic.save
- assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
- end
-
- def test_belongs_to_counter_after_update_attributes
- topic = Topic.create("title" => "37s")
- topic.replies.create("title" => "re: 37s", "content" => "rails")
- assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
-
- topic.update_attributes("title" => "37signals")
- assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
- end
-
def test_assignment_before_child_saved
final_cut = Client.new("name" => "Final Cut")
firm = Firm.find(1)
diff --git a/activerecord/test/cases/associations/habtm_join_table_test.rb b/activerecord/test/cases/associations/habtm_join_table_test.rb
new file mode 100644
index 0000000000..bf3e04c3eb
--- /dev/null
+++ b/activerecord/test/cases/associations/habtm_join_table_test.rb
@@ -0,0 +1,56 @@
+require 'cases/helper'
+
+class MyReader < ActiveRecord::Base
+ has_and_belongs_to_many :my_books
+end
+
+class MyBook < ActiveRecord::Base
+ has_and_belongs_to_many :my_readers
+end
+
+class HabtmJoinTableTest < ActiveRecord::TestCase
+ def setup
+ ActiveRecord::Base.connection.create_table :my_books, :force => true do |t|
+ t.string :name
+ end
+ assert ActiveRecord::Base.connection.table_exists?(:my_books)
+
+ ActiveRecord::Base.connection.create_table :my_readers, :force => true do |t|
+ t.string :name
+ end
+ assert ActiveRecord::Base.connection.table_exists?(:my_readers)
+
+ ActiveRecord::Base.connection.create_table :my_books_my_readers, :force => true do |t|
+ t.integer :my_book_id
+ t.integer :my_reader_id
+ end
+ assert ActiveRecord::Base.connection.table_exists?(:my_books_my_readers)
+ end
+
+ def teardown
+ ActiveRecord::Base.connection.drop_table :my_books
+ ActiveRecord::Base.connection.drop_table :my_readers
+ ActiveRecord::Base.connection.drop_table :my_books_my_readers
+ end
+
+ uses_transaction :test_should_raise_exception_when_join_table_has_a_primary_key
+ def test_should_raise_exception_when_join_table_has_a_primary_key
+ if ActiveRecord::Base.connection.supports_primary_key?
+ assert_raise ActiveRecord::ConfigurationError do
+ jaime = MyReader.create(:name=>"Jaime")
+ jaime.my_books << MyBook.create(:name=>'Great Expectations')
+ end
+ end
+ end
+
+ uses_transaction :test_should_cache_result_of_primary_key_check
+ def test_should_cache_result_of_primary_key_check
+ if ActiveRecord::Base.connection.supports_primary_key?
+ ActiveRecord::Base.connection.stubs(:primary_key).with('my_books_my_readers').returns(false).once
+ weaz = MyReader.create(:name=>'Weaz')
+
+ weaz.my_books << MyBook.create(:name=>'Great Expectations')
+ weaz.my_books << MyBook.create(:name=>'Greater Expectations')
+ end
+ end
+end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index a3d92c3bdb..f7178f2c5e 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -10,11 +10,12 @@ require 'models/author'
require 'models/comment'
require 'models/person'
require 'models/reader'
+require 'models/tagging'
class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :comments, :author_addresses,
- :people, :posts, :readers
+ :people, :posts, :readers, :taggings
def setup
Client.destroyed_client_ids.clear
@@ -287,6 +288,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id")
end
+ def test_find_all_with_include_and_conditions
+ assert_nothing_raised do
+ Developer.find(:all, :joins => :audit_logs, :conditions => {'audit_logs.message' => nil, :name => 'Smith'})
+ end
+ end
+
def test_find_in_collection
assert_equal Client.find(2).name, companies(:first_firm).clients.find(2).name
assert_raise(ActiveRecord::RecordNotFound) { companies(:first_firm).clients.find(6) }
@@ -510,6 +517,23 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 0, new_firm.clients_of_firm.size
end
+ def test_deleting_updates_counter_cache
+ topic = Topic.first
+ assert_equal topic.replies.to_a.size, topic.replies_count
+
+ topic.replies.delete(topic.replies.first)
+ topic.reload
+ assert_equal topic.replies.to_a.size, topic.replies_count
+ end
+
+ def test_deleting_updates_counter_cache_without_dependent_destroy
+ post = posts(:welcome)
+
+ assert_difference "post.reload.taggings_count", -1 do
+ post.taggings.delete(post.taggings.first)
+ end
+ end
+
def test_deleting_a_collection
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
@@ -555,6 +579,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_clearing_updates_counter_cache
+ topic = Topic.first
+
+ topic.replies.clear
+ topic.reload
+ assert_equal 0, topic.replies_count
+ end
+
def test_clearing_a_dependent_association_collection
firm = companies(:first_firm)
client_id = firm.dependent_clients_of_firm.first.id
@@ -699,6 +731,28 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 0, companies(:first_firm).clients_of_firm(true).size
end
+ def test_destroying_by_fixnum_id
+ force_signal37_to_load_all_clients_of_firm
+
+ assert_difference "Client.count", -1 do
+ companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id)
+ end
+
+ assert_equal 0, companies(:first_firm).reload.clients_of_firm.size
+ assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ end
+
+ def test_destroying_by_string_id
+ force_signal37_to_load_all_clients_of_firm
+
+ assert_difference "Client.count", -1 do
+ companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id.to_s)
+ end
+
+ assert_equal 0, companies(:first_firm).reload.clients_of_firm.size
+ assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ end
+
def test_destroying_a_collection
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
@@ -870,7 +924,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
lambda { authors(:mary).comments = [comments(:greetings), comments(:more_greetings)] },
lambda { authors(:mary).comments << Comment.create!(:body => "Yay", :post_id => 424242) },
lambda { authors(:mary).comments.delete(authors(:mary).comments.first) },
- ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection, &block) }
+ ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
end
def test_dynamic_find_should_respect_association_order_for_through
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 799ab52025..5f13b66d11 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -11,9 +11,12 @@ require 'models/author'
require 'models/owner'
require 'models/pet'
require 'models/toy'
+require 'models/contract'
+require 'models/company'
+require 'models/developer'
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys, :jobs, :references
+ fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys, :jobs, :references, :companies
def test_associate_existing
assert_queries(2) { posts(:thinking);people(:david) }
@@ -176,6 +179,44 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordNotSaved) { p.people.create!(:first_name => "snow") }
end
+ def test_associate_with_create_and_invalid_options
+ firm = companies(:first_firm)
+ assert_no_difference('firm.developers.count') { assert_nothing_raised { firm.developers.create(:name => '0') } }
+ end
+
+ def test_associate_with_create_and_valid_options
+ firm = companies(:first_firm)
+ assert_difference('firm.developers.count', 1) { firm.developers.create(:name => 'developer') }
+ end
+
+ def test_associate_with_create_bang_and_invalid_options
+ firm = companies(:first_firm)
+ assert_no_difference('firm.developers.count') { assert_raises(ActiveRecord::RecordInvalid) { firm.developers.create!(:name => '0') } }
+ end
+
+ def test_associate_with_create_bang_and_valid_options
+ firm = companies(:first_firm)
+ assert_difference('firm.developers.count', 1) { firm.developers.create!(:name => 'developer') }
+ end
+
+ def test_push_with_invalid_record
+ firm = companies(:first_firm)
+ assert_raises(ActiveRecord::RecordInvalid) { firm.developers << Developer.new(:name => '0') }
+ end
+
+ def test_push_with_invalid_join_record
+ repair_validations(Contract) do
+ Contract.validate {|r| r.errors[:base] << 'Invalid Contract' }
+
+ firm = companies(:first_firm)
+ lifo = Developer.new(:name => 'lifo')
+ assert_raises(ActiveRecord::RecordInvalid) { firm.developers << lifo }
+
+ lifo = Developer.create!(:name => 'lifo')
+ assert_raises(ActiveRecord::RecordInvalid) { firm.developers << lifo }
+ end
+ end
+
def test_clear_associations
assert_queries(2) { posts(:welcome);posts(:welcome).people(true) }
@@ -304,4 +345,16 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
post_with_no_comments = people(:michael).posts_with_no_comments.first
assert_equal post_with_no_comments, posts(:authorless)
end
+
+ def test_has_many_through_has_one_reflection
+ assert_equal [comments(:eager_sti_on_associations_vs_comment)], authors(:david).very_special_comments
+ end
+
+ def test_modifying_has_many_through_has_one_reflection_should_raise
+ [
+ lambda { authors(:david).very_special_comments = [VerySpecialComment.create!(:body => "Gorp!", :post_id => 1011), VerySpecialComment.create!(:body => "Eep!", :post_id => 1012)] },
+ lambda { authors(:david).very_special_comments << VerySpecialComment.create!(:body => "Hoohah!", :post_id => 1013) },
+ lambda { authors(:david).very_special_comments.delete(authors(:david).very_special_comments.first) },
+ ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index ab6e6d20fc..9aef3eb374 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -28,6 +28,16 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_not_nil new_member.current_membership
assert_not_nil new_member.club
end
+
+ def test_creating_association_builds_through_record_for_new
+ new_member = Member.new(:name => "Jane")
+ new_member.club = clubs(:moustache_club)
+ assert new_member.current_membership
+ assert_equal clubs(:moustache_club), new_member.current_membership.club
+ assert_equal clubs(:moustache_club), new_member.club
+ assert new_member.save
+ assert_equal clubs(:moustache_club), new_member.club
+ end
def test_replace_target_record
new_club = Club.create(:name => "Marx Bros")
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 9da7fc2639..e9af5a60d8 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -319,11 +319,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_belongs_to_polymorphic_with_counter_cache
- assert_equal 0, posts(:welcome)[:taggings_count]
+ assert_equal 1, posts(:welcome)[:taggings_count]
tagging = posts(:welcome).taggings.create(:tag => tags(:general))
- assert_equal 1, posts(:welcome, :reload)[:taggings_count]
+ assert_equal 2, posts(:welcome, :reload)[:taggings_count]
tagging.destroy
- assert posts(:welcome, :reload)[:taggings_count].zero?
+ assert_equal 1, posts(:welcome, :reload)[:taggings_count]
end
def test_unavailable_through_reflection
@@ -381,7 +381,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_polymorphic_has_one
- assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tagging }
+ assert_equal Tagging.find(1,2).sort_by { |t| t.id }, authors(:david).tagging
end
def test_has_many_through_polymorphic_has_many
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 16364141df..8421a8fb07 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1070,7 +1070,25 @@ class BasicsTest < ActiveRecord::TestCase
assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date
end
- def test_multiparameter_attributes_on_date_with_empty_date
+ def test_multiparameter_attributes_on_date_with_empty_year
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "24" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_date_from_db Date.new(1, 6, 24), topic.last_read.to_date
+ end
+
+ def test_multiparameter_attributes_on_date_with_empty_month
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "24" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_date_from_db Date.new(2004, 1, 24), topic.last_read.to_date
+ end
+
+ def test_multiparameter_attributes_on_date_with_empty_day
attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
topic = Topic.find(1)
topic.attributes = attributes
@@ -1079,6 +1097,33 @@ class BasicsTest < ActiveRecord::TestCase
assert_date_from_db Date.new(2004, 6, 1), topic.last_read.to_date
end
+ def test_multiparameter_attributes_on_date_with_empty_day_and_year
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_date_from_db Date.new(1, 6, 1), topic.last_read.to_date
+ end
+
+ def test_multiparameter_attributes_on_date_with_empty_day_and_month
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_date_from_db Date.new(2004, 1, 1), topic.last_read.to_date
+ end
+
+ def test_multiparameter_attributes_on_date_with_empty_year_and_month
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "24" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_date_from_db Date.new(1, 1, 24), topic.last_read.to_date
+ end
+
def test_multiparameter_attributes_on_date_with_all_empty
attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" }
topic = Topic.find(1)
@@ -1730,17 +1775,15 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal res4, res5
- unless current_adapter?(:SQLite2Adapter, :DeprecatedSQLiteAdapter)
- res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
- res7 = nil
- assert_nothing_raised do
- res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
- :joins => "p, comments co",
- :select => "p.id",
- :distinct => true)
- end
- assert_equal res6, res7
+ res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
+ res7 = nil
+ assert_nothing_raised do
+ res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
+ :joins => "p, comments co",
+ :select => "p.id",
+ :distinct => true)
end
+ assert_equal res6, res7
end
def test_clear_association_cache_stored
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index 98abc8eac8..fc9a0ac96e 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -33,4 +33,38 @@ class ColumnDefinitionTest < ActiveRecord::TestCase
column.limit, column.precision, column.scale, column.default, column.null)
assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, column_def.to_sql
end
+
+ if current_adapter?(:MysqlAdapter)
+ def test_should_set_default_for_mysql_binary_data_types
+ binary_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "a", "binary(1)")
+ assert_equal "a", binary_column.default
+
+ varbinary_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "a", "varbinary(1)")
+ assert_equal "a", varbinary_column.default
+ end
+
+ def test_should_not_set_default_for_blob_and_text_data_types
+ assert_raise ArgumentError do
+ ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "a", "blob")
+ end
+
+ assert_raise ArgumentError do
+ ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "Hello", "text")
+ end
+
+ text_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "text")
+ assert_equal nil, text_column.default
+
+ not_null_text_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "text", false)
+ assert_equal "", not_null_text_column.default
+ end
+
+ def test_has_default_should_return_false_for_blog_and_test_data_types
+ blob_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "blob")
+ assert !blob_column.has_default?
+
+ text_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "text")
+ assert !text_column.has_default?
+ end
+ end
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 74571d923a..f456d273fe 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -308,6 +308,84 @@ class DirtyTest < ActiveRecord::TestCase
end
end
+ def test_previous_changes
+ # original values should be in previous_changes
+ pirate = Pirate.new
+
+ assert_equal Hash.new, pirate.previous_changes
+ pirate.catchphrase = "arrr"
+ pirate.save!
+
+ assert_equal 4, pirate.previous_changes.size
+ assert_equal [nil, "arrr"], pirate.previous_changes['catchphrase']
+ assert_equal [nil, pirate.id], pirate.previous_changes['id']
+ assert_nil pirate.previous_changes['updated_on'][0]
+ assert_not_nil pirate.previous_changes['updated_on'][1]
+ assert_nil pirate.previous_changes['created_on'][0]
+ assert_not_nil pirate.previous_changes['created_on'][1]
+ assert !pirate.previous_changes.key?('parrot_id')
+
+ # original values should be in previous_changes
+ pirate = Pirate.new
+
+ assert_equal Hash.new, pirate.previous_changes
+ pirate.catchphrase = "arrr"
+ pirate.save
+
+ assert_equal 4, pirate.previous_changes.size
+ assert_equal [nil, "arrr"], pirate.previous_changes['catchphrase']
+ assert_equal [nil, pirate.id], pirate.previous_changes['id']
+ assert pirate.previous_changes.include?('updated_on')
+ assert pirate.previous_changes.include?('created_on')
+ assert !pirate.previous_changes.key?('parrot_id')
+
+ pirate.catchphrase = "Yar!!"
+ pirate.reload
+ assert_equal Hash.new, pirate.previous_changes
+
+ pirate = Pirate.find_by_catchphrase("arrr")
+ pirate.catchphrase = "Me Maties!"
+ pirate.save!
+
+ assert_equal 2, pirate.previous_changes.size
+ assert_equal ["arrr", "Me Maties!"], pirate.previous_changes['catchphrase']
+ assert_not_nil pirate.previous_changes['updated_on'][0]
+ assert_not_nil pirate.previous_changes['updated_on'][1]
+ assert !pirate.previous_changes.key?('parrot_id')
+ assert !pirate.previous_changes.key?('created_on')
+
+ pirate = Pirate.find_by_catchphrase("Me Maties!")
+ pirate.catchphrase = "Thar She Blows!"
+ pirate.save
+
+ assert_equal 2, pirate.previous_changes.size
+ assert_equal ["Me Maties!", "Thar She Blows!"], pirate.previous_changes['catchphrase']
+ assert_not_nil pirate.previous_changes['updated_on'][0]
+ assert_not_nil pirate.previous_changes['updated_on'][1]
+ assert !pirate.previous_changes.key?('parrot_id')
+ assert !pirate.previous_changes.key?('created_on')
+
+ pirate = Pirate.find_by_catchphrase("Thar She Blows!")
+ pirate.update_attributes(:catchphrase => "Ahoy!")
+
+ assert_equal 2, pirate.previous_changes.size
+ assert_equal ["Thar She Blows!", "Ahoy!"], pirate.previous_changes['catchphrase']
+ assert_not_nil pirate.previous_changes['updated_on'][0]
+ assert_not_nil pirate.previous_changes['updated_on'][1]
+ assert !pirate.previous_changes.key?('parrot_id')
+ assert !pirate.previous_changes.key?('created_on')
+
+ pirate = Pirate.find_by_catchphrase("Ahoy!")
+ pirate.update_attribute(:catchphrase, "Ninjas suck!")
+
+ assert_equal 2, pirate.previous_changes.size
+ assert_equal ["Ahoy!", "Ninjas suck!"], pirate.previous_changes['catchphrase']
+ assert_not_nil pirate.previous_changes['updated_on'][0]
+ assert_not_nil pirate.previous_changes['updated_on'][1]
+ assert !pirate.previous_changes.key?('parrot_id')
+ assert !pirate.previous_changes.key?('created_on')
+ end
+
private
def with_partial_updates(klass, on = true)
old = klass.partial_updates?
diff --git a/activerecord/test/cases/i18n_test.rb b/activerecord/test/cases/i18n_test.rb
index b1db662eca..d59c53cec8 100644
--- a/activerecord/test/cases/i18n_test.rb
+++ b/activerecord/test/cases/i18n_test.rb
@@ -12,6 +12,11 @@ class ActiveRecordI18nTests < Test::Unit::TestCase
I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } }
assert_equal 'topic title attribute', Topic.human_attribute_name('title')
end
+
+ def test_translated_model_attributes_with_symbols
+ I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } }
+ assert_equal 'topic title attribute', Topic.human_attribute_name(:title)
+ end
def test_translated_model_attributes_with_sti
I18n.backend.store_translations 'en', :activerecord => {:attributes => {:reply => {:title => 'reply title attribute'} } }
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index f0f21615e0..6d3f938799 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -25,6 +25,24 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
+ class MigrationTableAndIndexTest < ActiveRecord::TestCase
+ def test_add_schema_info_respects_prefix_and_suffix
+ conn = ActiveRecord::Base.connection
+
+ conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+ ActiveRecord::Base.table_name_prefix = 'foo_'
+ ActiveRecord::Base.table_name_suffix = '_bar'
+ conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+
+ conn.initialize_schema_migrations_table
+
+ assert_equal "foo_unique_schema_migrations_bar", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name]
+ ensure
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_suffix = ""
+ end
+ end
+
class MigrationTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
@@ -224,7 +242,7 @@ if ActiveRecord::Base.connection.supports_migrations?
t.column :foo, :string
end
- assert_equal %w(foo testings_id), Person.connection.columns(:testings).map { |c| c.name }.sort
+ assert_equal %w(foo testing_id), Person.connection.columns(:testings).map { |c| c.name }.sort
ensure
Person.connection.drop_table :testings rescue nil
ActiveRecord::Base.primary_key_prefix_type = nil
@@ -237,7 +255,7 @@ if ActiveRecord::Base.connection.supports_migrations?
t.column :foo, :string
end
- assert_equal %w(foo testingsid), Person.connection.columns(:testings).map { |c| c.name }.sort
+ assert_equal %w(foo testingid), Person.connection.columns(:testings).map { |c| c.name }.sort
ensure
Person.connection.drop_table :testings rescue nil
ActiveRecord::Base.primary_key_prefix_type = nil
@@ -396,7 +414,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal 9, wealth_column.precision
assert_equal 7, wealth_column.scale
end
-
+
def test_native_types
Person.delete_all
Person.connection.add_column "people", "last_name", :string
@@ -975,9 +993,9 @@ if ActiveRecord::Base.connection.supports_migrations?
def test_migrator_one_down
ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
-
+
ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1)
-
+
Person.reset_column_information
assert Person.column_methods_hash.include?(:last_name)
assert !Reminder.table_exists?
@@ -1118,20 +1136,20 @@ if ActiveRecord::Base.connection.supports_migrations?
assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
assert_equal "hello world", Reminder.find(:first).content
end
-
+
def test_migrator_rollback
ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
assert_equal(3, ActiveRecord::Migrator.current_version)
-
+
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
assert_equal(2, ActiveRecord::Migrator.current_version)
-
+
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
assert_equal(1, ActiveRecord::Migrator.current_version)
-
+
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
assert_equal(0, ActiveRecord::Migrator.current_version)
-
+
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
assert_equal(0, ActiveRecord::Migrator.current_version)
end
@@ -1294,7 +1312,7 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
-
+
class SexyMigrationsTest < ActiveRecord::TestCase
def test_references_column_type_adds_id
with_new_table do |t|
@@ -1350,6 +1368,15 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_xml_creates_xml_column
+ with_new_table do |t|
+ t.expects(:column).with(:data, 'xml', {})
+ t.xml :data
+ end
+ end
+ end
+
protected
def with_new_table
Person.connection.create_table :delete_me, :force => true do |t|
@@ -1567,3 +1594,4 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
end
+
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 2a729f0678..bb2aba9d92 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -368,6 +368,12 @@ class NamedScopeTest < ActiveRecord::TestCase
end
end
end
+
+ def test_table_names_for_chaining_scopes_with_and_without_table_name_included
+ assert_nothing_raised do
+ Comment.for_first_post.for_first_author.all
+ end
+ end
end
class DynamicScopeMatchTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/pk_test.rb b/activerecord/test/cases/pk_test.rb
index 948a570b93..c121e0aa0f 100644
--- a/activerecord/test/cases/pk_test.rb
+++ b/activerecord/test/cases/pk_test.rb
@@ -98,4 +98,22 @@ class PrimaryKeysTest < ActiveRecord::TestCase
def test_instance_destroy_should_quote_pkey
assert_nothing_raised { MixedCaseMonkey.find(1).destroy }
end
+
+ def test_supports_primary_key
+ assert_nothing_raised NoMethodError do
+ ActiveRecord::Base.connection.supports_primary_key?
+ end
+ end
+
+ def test_primary_key_returns_value_if_it_exists
+ if ActiveRecord::Base.connection.supports_primary_key?
+ assert_equal 'id', ActiveRecord::Base.connection.primary_key('developers')
+ end
+ end
+
+ def test_primary_key_returns_nil_if_it_does_not_exist
+ if ActiveRecord::Base.connection.supports_primary_key?
+ assert_nil ActiveRecord::Base.connection.primary_key('developers_projects')
+ end
+ end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 4083b990d9..a164f5e060 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -176,8 +176,8 @@ class ReflectionTest < ActiveRecord::TestCase
def test_reflection_of_all_associations
# FIXME these assertions bust a lot
- assert_equal 29, Firm.reflect_on_all_associations.size
- assert_equal 22, Firm.reflect_on_all_associations(:has_many).size
+ assert_equal 31, Firm.reflect_on_all_associations.size
+ assert_equal 24, Firm.reflect_on_all_associations(:has_many).size
assert_equal 7, Firm.reflect_on_all_associations(:has_one).size
assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index e6a77f626b..1c43e3c5b5 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -161,7 +161,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip
assert_equal 'add_index "companies", ["firm_id", "type", "rating", "ruby_type"], :name => "company_index"', index_definition
end
-
+
def test_schema_dump_should_honor_nonstandard_primary_keys
output = standard_dump
match = output.match(%r{create_table "movies"(.*)do})
@@ -196,6 +196,15 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{:precision => 3,[[:space:]]+:scale => 2,[[:space:]]+:default => 2.78}, output
end
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_schema_dump_includes_xml_shorthand_definition
+ output = standard_dump
+ if %r{create_table "postgresql_xml_data_type"} =~ output
+ assert_match %r{t.xml "data"}, output
+ end
+ end
+ end
+
def test_schema_dump_keeps_large_precision_integer_columns_as_decimal
output = standard_dump
# Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers
@@ -214,3 +223,4 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t.string[[:space:]]+"id",[[:space:]]+:null => false$}, match[2], "non-primary key id column not preserved"
end
end
+
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index 0303147e50..73d9c7249c 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -709,3 +709,191 @@ class I18nValidationTest < ActiveRecord::TestCase
assert_equal ["I am a custom error"], @topic.errors[:title]
end
end
+
+class ActiveRecordValidationsGenerateMessageI18nTests < ActiveSupport::TestCase
+ def setup
+ reset_callbacks Topic
+ @topic = Topic.new
+ I18n.backend.store_translations :'en', {
+ :activerecord => {
+ :errors => {
+ :messages => {
+ :inclusion => "is not included in the list",
+ :exclusion => "is reserved",
+ :invalid => "is invalid",
+ :confirmation => "doesn't match confirmation",
+ :accepted => "must be accepted",
+ :empty => "can't be empty",
+ :blank => "can't be blank",
+ :too_long => "is too long (maximum is {{count}} characters)",
+ :too_short => "is too short (minimum is {{count}} characters)",
+ :wrong_length => "is the wrong length (should be {{count}} characters)",
+ :taken => "has already been taken",
+ :not_a_number => "is not a number",
+ :greater_than => "must be greater than {{count}}",
+ :greater_than_or_equal_to => "must be greater than or equal to {{count}}",
+ :equal_to => "must be equal to {{count}}",
+ :less_than => "must be less than {{count}}",
+ :less_than_or_equal_to => "must be less than or equal to {{count}}",
+ :odd => "must be odd",
+ :even => "must be even"
+ }
+ }
+ }
+ }
+ end
+
+ def reset_callbacks(*models)
+ models.each do |model|
+ model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ model.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ model.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ end
+ end
+
+ # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value)
+ def test_generate_message_inclusion_with_default_message
+ assert_equal 'is not included in the list', @topic.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_inclusion_with_custom_message
+ assert_equal 'custom message title', @topic.errors.generate_message(:title, :inclusion, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value)
+ def test_generate_message_exclusion_with_default_message
+ assert_equal 'is reserved', @topic.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_exclusion_with_custom_message
+ assert_equal 'custom message title', @topic.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
+ # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
+ def test_generate_message_invalid_with_default_message
+ assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_invalid_with_custom_message
+ assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message])
+ def test_generate_message_confirmation_with_default_message
+ assert_equal "doesn't match confirmation", @topic.errors.generate_message(:title, :confirmation, :default => nil)
+ end
+
+ def test_generate_message_confirmation_with_custom_message
+ assert_equal 'custom message', @topic.errors.generate_message(:title, :confirmation, :default => 'custom message')
+ end
+
+ # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message])
+ def test_generate_message_accepted_with_default_message
+ assert_equal "must be accepted", @topic.errors.generate_message(:title, :accepted, :default => nil)
+ end
+
+ def test_generate_message_accepted_with_custom_message
+ assert_equal 'custom message', @topic.errors.generate_message(:title, :accepted, :default => 'custom message')
+ end
+
+ # add_on_empty: generate_message(attr, :empty, :default => custom_message)
+ def test_generate_message_empty_with_default_message
+ assert_equal "can't be empty", @topic.errors.generate_message(:title, :empty, :default => nil)
+ end
+
+ def test_generate_message_empty_with_custom_message
+ assert_equal 'custom message', @topic.errors.generate_message(:title, :empty, :default => 'custom message')
+ end
+
+ # add_on_blank: generate_message(attr, :blank, :default => custom_message)
+ def test_generate_message_blank_with_default_message
+ assert_equal "can't be blank", @topic.errors.generate_message(:title, :blank, :default => nil)
+ end
+
+ def test_generate_message_blank_with_custom_message
+ assert_equal 'custom message', @topic.errors.generate_message(:title, :blank, :default => 'custom message')
+ end
+
+ # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end)
+ def test_generate_message_too_long_with_default_message
+ assert_equal "is too long (maximum is 10 characters)", @topic.errors.generate_message(:title, :too_long, :default => nil, :count => 10)
+ end
+
+ def test_generate_message_too_long_with_custom_message
+ assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_long, :default => 'custom message {{count}}', :count => 10)
+ end
+
+ # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
+ def test_generate_message_too_short_with_default_message
+ assert_equal "is too short (minimum is 10 characters)", @topic.errors.generate_message(:title, :too_short, :default => nil, :count => 10)
+ end
+
+ def test_generate_message_too_short_with_custom_message
+ assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_short, :default => 'custom message {{count}}', :count => 10)
+ end
+
+ # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value)
+ def test_generate_message_wrong_length_with_default_message
+ assert_equal "is the wrong length (should be 10 characters)", @topic.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10)
+ end
+
+ def test_generate_message_wrong_length_with_custom_message
+ assert_equal 'custom message 10', @topic.errors.generate_message(:title, :wrong_length, :default => 'custom message {{count}}', :count => 10)
+ end
+
+ # validates_uniqueness_of: generate_message(attr_name, :taken, :default => configuration[:message])
+ def test_generate_message_taken_with_default_message
+ assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_taken_with_custom_message
+ assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
+ def test_generate_message_not_a_number_with_default_message
+ assert_equal "is not a number", @topic.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_not_a_number_with_custom_message
+ assert_equal 'custom message title', @topic.errors.generate_message(:title, :not_a_number, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message])
+ def test_generate_message_greater_than_with_default_message
+ assert_equal "must be greater than 10", @topic.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_greater_than_or_equal_to_with_default_message
+ assert_equal "must be greater than or equal to 10", @topic.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_equal_to_with_default_message
+ assert_equal "must be equal to 10", @topic.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_less_than_with_default_message
+ assert_equal "must be less than 10", @topic.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_less_than_or_equal_to_with_default_message
+ assert_equal "must be less than or equal to 10", @topic.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_odd_with_default_message
+ assert_equal "must be odd", @topic.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_even_with_default_message
+ assert_equal "must be even", @topic.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10)
+ end
+ # ActiveRecord#RecordInvalid exception
+
+ test "RecordInvalid exception can be localized" do
+ topic = Topic.new
+ topic.errors.add(:title, :invalid)
+ topic.errors.add(:title, :blank)
+ assert_equal "Validation failed: Title is invalid, Title can't be blank", ActiveRecord::RecordInvalid.new(topic).message
+ end
+end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 961db51d1d..17ba4e2f8a 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -5,6 +5,7 @@ require 'models/reply'
require 'models/warehouse_thing'
require 'models/guid'
require 'models/event'
+require 'models/developer'
# The following methods in Topic are used in test_conditional_validation_*
class Topic
@@ -36,20 +37,20 @@ class Thaumaturgist < IneptWizard
end
class UniquenessValidationTest < ActiveRecord::TestCase
- fixtures :topics, 'warehouse-things'
+ fixtures :topics, 'warehouse-things', :developers
repair_validations(Topic)
def test_validate_uniqueness
Topic.validates_uniqueness_of(:title)
- t = Topic.new("title" => "I'm unique!")
+ t = Topic.new("title" => "I'm uniqué!")
assert t.save, "Should save t as unique"
t.content = "Remaining unique"
assert t.save, "Should still save t as unique"
- t2 = Topic.new("title" => "I'm unique!")
+ t2 = Topic.new("title" => "I'm uniqué!")
assert !t2.valid?, "Shouldn't be valid"
assert !t2.save, "Shouldn't save t2 as unique"
assert_equal ["has already been taken"], t2.errors[:title]
@@ -58,7 +59,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert t2.save, "Should now save t2 as unique"
end
- def test_validates_uniquness_with_newline_chars
+ def test_validates_uniqueness_with_newline_chars
Topic.validates_uniqueness_of(:title, :case_sensitive => false)
t = Topic.new("title" => "new\nline")
@@ -237,6 +238,16 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique"
end
+ def test_validate_uniqueness_with_limit_and_utf8
+ with_kcode('UTF8') do
+ # Event.title is limited to 5 characters
+ e1 = Event.create(:title => "一二三四五")
+ assert e1.valid?, "Could not create an event with a unique, 5 character title"
+ e2 = Event.create(:title => "一二三四五六七八")
+ assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique"
+ end
+ end
+
def test_validate_straight_inheritance_uniqueness
w1 = IneptWizard.create(:name => "Rincewind", :city => "Ankh-Morpork")
assert w1.valid?, "Saving w1"
diff --git a/activerecord/test/connections/native_sqlite/connection.rb b/activerecord/test/connections/native_sqlite/connection.rb
deleted file mode 100644
index fea985d8a3..0000000000
--- a/activerecord/test/connections/native_sqlite/connection.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-print "Using native SQlite\n"
-require_dependency 'models/course'
-require 'logger'
-ActiveRecord::Base.logger = Logger.new("debug.log")
-
-class SqliteError < StandardError
-end
-
-BASE_DIR = FIXTURES_ROOT
-sqlite_test_db = "#{BASE_DIR}/fixture_database.sqlite"
-sqlite_test_db2 = "#{BASE_DIR}/fixture_database_2.sqlite"
-
-def make_connection(clazz, db_file)
- ActiveRecord::Base.configurations = { clazz.name => { :adapter => 'sqlite', :database => db_file } }
- unless File.exist?(db_file)
- puts "SQLite database not found at #{db_file}. Rebuilding it."
- sqlite_command = %Q{sqlite "#{db_file}" "create table a (a integer); drop table a;"}
- puts "Executing '#{sqlite_command}'"
- raise SqliteError.new("Seems that there is no sqlite executable available") unless system(sqlite_command)
- end
- clazz.establish_connection(clazz.name)
-end
-
-make_connection(ActiveRecord::Base, sqlite_test_db)
-make_connection(Course, sqlite_test_db2)
diff --git a/activerecord/test/fixtures/posts.yml b/activerecord/test/fixtures/posts.yml
index 92e5d1908f..f817493190 100644
--- a/activerecord/test/fixtures/posts.yml
+++ b/activerecord/test/fixtures/posts.yml
@@ -4,6 +4,7 @@ welcome:
title: Welcome to the weblog
body: Such a lovely day
comments_count: 2
+ taggings_count: 1
type: Post
thinking:
@@ -11,6 +12,8 @@ thinking:
author_id: 1
title: So I was thinking
body: Like I hopefully always am
+ comments_count: 1
+ taggings_count: 1
type: SpecialPost
authorless:
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index b844c7cce0..f264f980d6 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -1,5 +1,6 @@
class Author < ActiveRecord::Base
has_many :posts
+ has_many :very_special_comments, :through => :posts
has_many :posts_with_comments, :include => :comments, :class_name => "Post"
has_many :popular_grouped_posts, :include => :comments, :class_name => "Post", :group => "type", :having => "SUM(comments_count) > 1", :select => "type"
has_many :posts_with_comments_sorted_by_comment_id, :include => :comments, :class_name => "Post", :order => 'comments.id'
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index f7f07c103f..399dea9f12 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -1,6 +1,10 @@
class Comment < ActiveRecord::Base
named_scope :containing_the_letter_e, :conditions => "comments.body LIKE '%e%'"
-
+ named_scope :for_first_post, :conditions => { :post_id => 1 }
+ named_scope :for_first_author,
+ :joins => :post,
+ :conditions => { "posts.author_id" => 1 }
+
belongs_to :post, :counter_cache => true
def self.what_are_you
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 1c05e523e0..ab09f88a9f 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -9,6 +9,8 @@ class Company < AbstractCompany
validates_presence_of :name
has_one :dummy_account, :foreign_key => "firm_id", :class_name => "Account"
+ has_many :contracts
+ has_many :developers, :through => :contracts
def arbitrary_method
"I am Jack's profound disappointment"
diff --git a/activerecord/test/models/contract.rb b/activerecord/test/models/contract.rb
new file mode 100644
index 0000000000..606c99cd4e
--- /dev/null
+++ b/activerecord/test/models/contract.rb
@@ -0,0 +1,4 @@
+class Contract < ActiveRecord::Base
+ belongs_to :company
+ belongs_to :developer
+end \ No newline at end of file
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 576a4d03c6..3d8911bfe9 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,7 +1,7 @@
ActiveRecord::Schema.define do
%w(postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings
- postgresql_oids defaults geometrics).each do |table_name|
+ postgresql_oids postgresql_xml_data_type defaults geometrics).each do |table_name|
execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
end
@@ -100,4 +100,15 @@ _SQL
obj_id OID
);
_SQL
-end \ No newline at end of file
+
+ begin
+ execute <<_SQL
+ CREATE TABLE postgresql_xml_data_type (
+ id SERIAL PRIMARY KEY,
+ data xml
+ );
+_SQL
+rescue #This version of PostgreSQL either has no XML support or is was not compiled with XML support: skipping table
+ end
+end
+
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 5f60d5e137..9ab4cf6f43 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -131,6 +131,10 @@ ActiveRecord::Schema.define do
t.integer :extendedWarranty, :null => false
end
+ create_table :contracts, :force => true do |t|
+ t.integer :developer_id
+ t.integer :company_id
+ end
create_table :customers, :force => true do |t|
t.string :name
diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG
index 1f9ecea6c5..113694e895 100644
--- a/activeresource/CHANGELOG
+++ b/activeresource/CHANGELOG
@@ -1,5 +1,11 @@
*Edge*
+* Add support for errors in JSON format. #1956 [Fabien Jakimowicz]
+
+* Recognizes 410 as Resource Gone. #2316 [Jordan Brough, Jatinder Singh]
+
+* More thorough SSL support. #2370 [Roy Nicholson]
+
* HTTP proxy support. #2133 [Marshall Huss, Sébastien Dabet]
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index 3d58660ffc..e1f221bd3e 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -110,6 +110,8 @@ module ActiveResource
#
# Many REST APIs will require authentication, usually in the form of basic
# HTTP authentication. Authentication can be specified by:
+ #
+ # === HTTP Basic Authentication
# * putting the credentials in the URL for the +site+ variable.
#
# class Person < ActiveResource::Base
@@ -130,6 +132,19 @@ module ActiveResource
# Note: Some values cannot be provided in the URL passed to site. e.g. email addresses
# as usernames. In those situations you should use the separate user and password option.
#
+ # === Certificate Authentication
+ #
+ # * End point uses an X509 certificate for authentication. <tt>See ssl_options=</tt> for all options.
+ #
+ # class Person < ActiveResource::Base
+ # self.site = "https://secure.api.people.com/"
+ # self.ssl_options = {:cert => OpenSSL::X509::Certificate.new(File.open(pem_file))
+ # :key => OpenSSL::PKey::RSA.new(File.open(pem_file)),
+ # :ca_path => "/path/to/OpenSSL/formatted/CA_Certs",
+ # :verify_mode => OpenSSL::SSL::VERIFY_PEER}
+ # end
+ #
+ #
# == Errors & Validation
#
# Error handling and validation is handled in much the same manner as you're used to seeing in
@@ -156,6 +171,7 @@ module ActiveResource
# * 404 - ActiveResource::ResourceNotFound
# * 405 - ActiveResource::MethodNotAllowed
# * 409 - ActiveResource::ResourceConflict
+ # * 410 - ActiveResource::ResourceGone
# * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors)
# * 401..499 - ActiveResource::ClientError
# * 500..599 - ActiveResource::ServerError
@@ -176,7 +192,7 @@ module ActiveResource
#
# Active Resource supports validations on resources and will return errors if any of these validations fail
# (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by
- # a response code of <tt>422</tt> and an XML representation of the validation errors. The save operation will
+ # a response code of <tt>422</tt> and an XML or JSON representation of the validation errors. The save operation will
# then fail (with a <tt>false</tt> return value) and the validation errors can be accessed on the resource in question.
#
# ryan = Person.find(1)
@@ -185,10 +201,14 @@ module ActiveResource
#
# # When
# # PUT http://api.people.com:3000/people/1.xml
+ # # or
+ # # PUT http://api.people.com:3000/people/1.json
# # is requested with invalid values, the response is:
# #
# # Response (422):
# # <errors type="array"><error>First cannot be empty</error></errors>
+ # # or
+ # # {"errors":["First cannot be empty"]}
# #
#
# ryan.errors.invalid?(:first) # => true
@@ -349,6 +369,31 @@ module ActiveResource
end
end
+ # Options that will get applied to an SSL connection.
+ #
+ # * <tt>:key</tt> - An OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
+ # * <tt>:cert</tt> - An OpenSSL::X509::Certificate object as client certificate
+ # * <tt>:ca_file</tt> - Path to a CA certification file in PEM format. The file can contrain several CA certificates.
+ # * <tt>:ca_path</tt> - Path of a CA certification directory containing certifications in PEM format.
+ # * <tt>:verify_mode</tt> - Flags for server the certification verification at begining of SSL/TLS session. (OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable)
+ # * <tt>:verify_callback</tt> - The verify callback for the server certification verification.
+ # * <tt>:verify_depth</tt> - The maximum depth for the certificate chain verification.
+ # * <tt>:cert_store</tt> - OpenSSL::X509::Store to verify peer certificate.
+ # * <tt>:ssl_timeout</tt> -The SSL timeout in seconds.
+ def ssl_options=(opts={})
+ @connection = nil
+ @ssl_options = opts
+ end
+
+ # Returns the SSL options hash.
+ def ssl_options
+ if defined?(@ssl_options)
+ @ssl_options
+ elsif superclass != Object && superclass.ssl_options
+ superclass.ssl_options
+ end
+ end
+
# An instance of ActiveResource::Connection that is the base \connection to the remote service.
# The +refresh+ parameter toggles whether or not the \connection is refreshed at every request
# or not (defaults to <tt>false</tt>).
@@ -359,6 +404,7 @@ module ActiveResource
@connection.user = user if user
@connection.password = password if password
@connection.timeout = timeout if timeout
+ @connection.ssl_options = ssl_options if ssl_options
@connection
else
superclass.connection
@@ -546,6 +592,19 @@ module ActiveResource
#
# StreetAddress.find(1, :params => { :person_id => 1 })
# # => GET /people/1/street_addresses/1.xml
+ #
+ # == Failure or missing data
+ # A failure to find the requested object raises a ResourceNotFound
+ # exception if the find was called with an id.
+ # With any other scope, find returns nil when no data is returned.
+ #
+ # Person.find(1)
+ # # => raises ResourcenotFound
+ #
+ # Person.find(:all)
+ # Person.find(:first)
+ # Person.find(:last)
+ # # => nil
def find(*arguments)
scope = arguments.slice!(0)
options = arguments.slice!(0) || {}
@@ -559,6 +618,28 @@ module ActiveResource
end
end
+
+ # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass
+ # in all the same arguments to this method as you can to
+ # <tt>find(:first)</tt>.
+ def first(*args)
+ find(:first, *args)
+ end
+
+ # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass
+ # in all the same arguments to this method as you can to
+ # <tt>find(:last)</tt>.
+ def last(*args)
+ find(:last, *args)
+ end
+
+ # This is an alias for find(:all). You can pass in all the same
+ # arguments to this method as you can to <tt>find(:all)</tt>
+ def all(*args)
+ find(:all, *args)
+ end
+
+
# Deletes the resources with the ID in the +id+ parameter.
#
# ==== Options
@@ -592,23 +673,29 @@ module ActiveResource
response.code.to_i == 200
end
# id && !find_single(id, options).nil?
- rescue ActiveResource::ResourceNotFound
+ rescue ActiveResource::ResourceNotFound, ActiveResource::ResourceGone
false
end
private
# Find every resource
def find_every(options)
- case from = options[:from]
- when Symbol
- instantiate_collection(get(from, options[:params]))
- when String
- path = "#{from}#{query_string(options[:params])}"
- instantiate_collection(connection.get(path, headers) || [])
- else
- prefix_options, query_options = split_options(options[:params])
- path = collection_path(prefix_options, query_options)
- instantiate_collection( (connection.get(path, headers) || []), prefix_options )
+ begin
+ case from = options[:from]
+ when Symbol
+ instantiate_collection(get(from, options[:params]))
+ when String
+ path = "#{from}#{query_string(options[:params])}"
+ instantiate_collection(connection.get(path, headers) || [])
+ else
+ prefix_options, query_options = split_options(options[:params])
+ path = collection_path(prefix_options, query_options)
+ instantiate_collection( (connection.get(path, headers) || []), prefix_options )
+ end
+ rescue ActiveResource::ResourceNotFound
+ # Swallowing ResourceNotFound exceptions and return nil - as per
+ # ActiveRecord.
+ nil
end
end
@@ -835,6 +922,23 @@ module ActiveResource
def save
new? ? create : update
end
+
+ # Saves the resource.
+ #
+ # If the resource is new, it is created via +POST+, otherwise the
+ # existing resource is updated via +PUT+.
+ #
+ # With <tt>save!</tt> validations always run. If any of them fail
+ # ActiveResource::ResourceInvalid gets raised, and nothing is POSTed to
+ # the remote system.
+ # See ActiveResource::Validations for more information.
+ #
+ # There's a series of callbacks associated with <tt>save!</tt>. If any
+ # of the <tt>before_*</tt> callbacks return +false+ the action is
+ # cancelled and <tt>save!</tt> raises ActiveResource::ResourceInvalid.
+ def save!
+ save || raise(ResourceInvalid.new(self))
+ end
# Deletes the resource from the remote service.
#
@@ -985,7 +1089,13 @@ module ActiveResource
case value
when Array
resource = find_or_create_resource_for_collection(key)
- value.map { |attrs| attrs.is_a?(String) ? attrs.dup : resource.new(attrs) }
+ value.map do |attrs|
+ if attrs.is_a?(String) || attrs.is_a?(Numeric)
+ attrs.duplicable? ? attrs.dup : attrs
+ else
+ resource.new(attrs)
+ end
+ end
when Hash
resource = find_or_create_resource_for(key)
resource.new(value)
diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb
index ef57c1f8b2..9d551f04e7 100644
--- a/activeresource/lib/active_resource/connection.rb
+++ b/activeresource/lib/active_resource/connection.rb
@@ -13,10 +13,11 @@ module ActiveResource
HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
:put => 'Content-Type',
:post => 'Content-Type',
- :delete => 'Accept'
+ :delete => 'Accept',
+ :head => 'Accept'
}
- attr_reader :site, :user, :password, :timeout, :proxy
+ attr_reader :site, :user, :password, :timeout, :proxy, :ssl_options
attr_accessor :format
class << self
@@ -61,6 +62,11 @@ module ActiveResource
@timeout = timeout
end
+ # Hash of options applied to Net::HTTP instance when +site+ protocol is 'https'.
+ def ssl_options=(opts={})
+ @ssl_options = opts
+ end
+
# Executes a GET request.
# Used to get (find) resources.
def get(path, headers = {})
@@ -88,7 +94,7 @@ module ActiveResource
# Executes a HEAD request.
# Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
def head(path, headers = {})
- request(:head, path, build_request_headers(headers))
+ request(:head, path, build_request_headers(headers, :head))
end
@@ -102,6 +108,8 @@ module ActiveResource
handle_response(result)
rescue Timeout::Error => e
raise TimeoutError.new(e.message)
+ rescue OpenSSL::SSL::SSLError => e
+ raise SSLError.new(e.message)
end
# Handles response and error codes from the remote service.
@@ -123,6 +131,8 @@ module ActiveResource
raise(MethodNotAllowed.new(response))
when 409
raise(ResourceConflict.new(response))
+ when 410
+ raise(ResourceGone.new(response))
when 422
raise(ResourceInvalid.new(response))
when 401...500
@@ -149,8 +159,7 @@ module ActiveResource
end
def configure_http(http)
- http.use_ssl = @site.is_a?(URI::HTTPS)
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl?
+ http = apply_ssl_options(http)
# Net::HTTP timeouts default to 60 seconds.
if @timeout
@@ -161,6 +170,29 @@ module ActiveResource
http
end
+ def apply_ssl_options(http)
+ return http unless @site.is_a?(URI::HTTPS)
+
+ http.use_ssl = true
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ return http unless defined?(@ssl_options)
+
+ http.ca_path = @ssl_options[:ca_path] if @ssl_options[:ca_path]
+ http.ca_file = @ssl_options[:ca_file] if @ssl_options[:ca_file]
+
+ http.cert = @ssl_options[:cert] if @ssl_options[:cert]
+ http.key = @ssl_options[:key] if @ssl_options[:key]
+
+ http.cert_store = @ssl_options[:cert_store] if @ssl_options[:cert_store]
+ http.ssl_timeout = @ssl_options[:ssl_timeout] if @ssl_options[:ssl_timeout]
+
+ http.verify_mode = @ssl_options[:verify_mode] if @ssl_options[:verify_mode]
+ http.verify_callback = @ssl_options[:verify_callback] if @ssl_options[:verify_callback]
+ http.verify_depth = @ssl_options[:verify_depth] if @ssl_options[:verify_depth]
+
+ http
+ end
+
def default_header
@default_header ||= {}
end
diff --git a/activeresource/lib/active_resource/exceptions.rb b/activeresource/lib/active_resource/exceptions.rb
index 5e4b1d4487..0631cdcf9f 100644
--- a/activeresource/lib/active_resource/exceptions.rb
+++ b/activeresource/lib/active_resource/exceptions.rb
@@ -20,6 +20,14 @@ module ActiveResource
def to_s; @message ;end
end
+ # Raised when a OpenSSL::SSL::SSLError occurs.
+ class SSLError < ConnectionError
+ def initialize(message)
+ @message = message
+ end
+ def to_s; @message ;end
+ end
+
# 3xx Redirection
class Redirection < ConnectionError # :nodoc:
def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
@@ -43,6 +51,9 @@ module ActiveResource
# 409 Conflict
class ResourceConflict < ClientError; end # :nodoc:
+ # 410 Gone
+ class ResourceGone < ClientError; end # :nodoc:
+
# 5xx Server Error
class ServerError < ConnectionError; end # :nodoc:
diff --git a/activeresource/lib/active_resource/validations.rb b/activeresource/lib/active_resource/validations.rb
index a2ba224998..d4d282e273 100644
--- a/activeresource/lib/active_resource/validations.rb
+++ b/activeresource/lib/active_resource/validations.rb
@@ -7,11 +7,12 @@ module ActiveResource
# Active Resource validation is reported to and from this object, which is used by Base#save
# to determine whether the object in a valid state to be saved. See usage example in Validations.
class Errors < ActiveModel::Errors
- # Grabs errors from the XML response.
- def from_xml(xml)
- clear
+ # Grabs errors from an array of messages (like ActiveRecord::Validations)
+ # The second parameter directs the errors cache to be cleared (default)
+ # or not (by passing true)
+ def from_array(messages, save_cache = false)
+ clear unless save_cache
humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) }
- messages = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue []
messages.each do |message|
attr_message = humanized_attributes.keys.detect do |attr_name|
if message[0, attr_name.size + 1] == "#{attr_name} "
@@ -22,6 +23,18 @@ module ActiveResource
self[:base] << message if attr_message.nil?
end
end
+
+ # Grabs errors from a json response.
+ def from_json(json, save_cache = false)
+ array = ActiveSupport::JSON.decode(json)['errors'] rescue []
+ from_array array, save_cache
+ end
+
+ # Grabs errors from an XML response.
+ def from_xml(xml, save_cache = false)
+ array = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue []
+ from_array array, save_cache
+ end
end
# Module to support validation and errors with Active Resource objects. The module overrides
@@ -46,21 +59,55 @@ module ActiveResource
#
module Validations
extend ActiveSupport::Concern
+ include ActiveModel::Validations
+ extend ActiveModel::Validations::ClassMethods
included do
alias_method_chain :save, :validation
end
# Validate a resource and save (POST) it to the remote web service.
- def save_with_validation
- save_without_validation
- true
+ # If any local validations fail - the save (POST) will not be attempted.
+ def save_with_validation(perform_validation = true)
+ # clear the remote validations so they don't interfere with the local
+ # ones. Otherwise we get an endless loop and can never change the
+ # fields so as to make the resource valid
+ @remote_errors = nil
+ if perform_validation && valid? || !perform_validation
+ save_without_validation
+ true
+ else
+ false
+ end
rescue ResourceInvalid => error
- errors.from_xml(error.response.body)
+ # cache the remote errors because every call to <tt>valid?</tt> clears
+ # all errors. We must keep a copy to add these back after local
+ # validations
+ @remote_errors = error
+ load_remote_errors(@remote_errors, true)
false
end
+
+ # Loads the set of remote errors into the object's Errors based on the
+ # content-type of the error-block received
+ def load_remote_errors(remote_errors, save_cache = false ) #:nodoc:
+ case remote_errors.response['Content-Type']
+ when 'application/xml'
+ errors.from_xml(remote_errors.response.body, save_cache)
+ when 'application/json'
+ errors.from_json(remote_errors.response.body, save_cache)
+ end
+ end
+
# Checks for errors on an object (i.e., is resource.errors empty?).
+ #
+ # Runs all the specified local validations and returns true if no errors
+ # were added, otherwise false.
+ # Runs local validations (eg those on your Active Resource model), and
+ # also any errors returned from the remote system the last time we
+ # saved.
+ # Remote errors can only be cleared by trying to re-save the resource.
#
# ==== Examples
# my_person = Person.create(params[:person])
@@ -70,7 +117,10 @@ module ActiveResource
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
# my_person.valid?
# # => false
+ #
def valid?
+ super
+ load_remote_errors(@remote_errors, true) if defined?(@remote_errors) && @remote_errors.present?
errors.empty?
end
diff --git a/activeresource/test/base_errors_test.rb b/activeresource/test/base_errors_test.rb
deleted file mode 100644
index 28813821df..0000000000
--- a/activeresource/test/base_errors_test.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-require 'abstract_unit'
-require "fixtures/person"
-
-class BaseErrorsTest < Test::Unit::TestCase
- def setup
- ActiveResource::HttpMock.respond_to do |mock|
- mock.post "/people.xml", {}, "<?xml version=\"1.0\" encoding=\"UTF-8\"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>", 422
- end
- @person = Person.new(:name => '', :age => '')
- assert_equal @person.save, false
- end
-
- def test_should_mark_as_invalid
- assert !@person.valid?
- end
-
- def test_should_parse_xml_errors
- assert_kind_of ActiveResource::Errors, @person.errors
- assert_equal 4, @person.errors.size
- end
-
- def test_should_parse_errors_to_individual_attributes
- assert @person.errors[:name].any?
- assert_equal ["can't be blank"], @person.errors[:age]
- assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name]
- assert_equal ["Person quota full for today."], @person.errors[:base]
- end
-
- def test_should_iterate_over_errors
- errors = []
- @person.errors.each { |attribute, message| errors << [attribute.to_s, message] }
- assert errors.include?(["name", "can't be blank"])
- end
-
- def test_should_iterate_over_full_errors
- errors = []
- @person.errors.to_a.each { |message| errors << message }
- assert errors.include?("Name can't be blank")
- end
-
- def test_should_format_full_errors
- full = @person.errors.full_messages
- assert full.include?("Age can't be blank")
- assert full.include?("Name can't be blank")
- assert full.include?("Name must start with a letter")
- assert full.include?("Person quota full for today.")
- end
-end
diff --git a/activeresource/test/authorization_test.rb b/activeresource/test/cases/authorization_test.rb
index ca25f437e3..ca25f437e3 100644
--- a/activeresource/test/authorization_test.rb
+++ b/activeresource/test/cases/authorization_test.rb
diff --git a/activeresource/test/base/custom_methods_test.rb b/activeresource/test/cases/base/custom_methods_test.rb
index 2d81549a65..2d81549a65 100644
--- a/activeresource/test/base/custom_methods_test.rb
+++ b/activeresource/test/cases/base/custom_methods_test.rb
diff --git a/activeresource/test/base/equality_test.rb b/activeresource/test/cases/base/equality_test.rb
index 84f1a7b998..84f1a7b998 100644
--- a/activeresource/test/base/equality_test.rb
+++ b/activeresource/test/cases/base/equality_test.rb
diff --git a/activeresource/test/base/load_test.rb b/activeresource/test/cases/base/load_test.rb
index 035bd965c2..1952f5b5f0 100644
--- a/activeresource/test/base/load_test.rb
+++ b/activeresource/test/cases/base/load_test.rb
@@ -51,7 +51,9 @@ class BaseLoadTest < Test::Unit::TestCase
:id => 1, :state => { :id => 1, :name => 'Oregon',
:notable_rivers => [
{ :id => 1, :name => 'Willamette' },
- { :id => 2, :name => 'Columbia', :rafted_by => @matz }] }}}
+ { :id => 2, :name => 'Columbia', :rafted_by => @matz }],
+ :postal_codes => [ 97018, 1234567890 ],
+ :places => [ "Columbia City", "Unknown" ]}}}
@person = Person.new
end
@@ -127,6 +129,19 @@ class BaseLoadTest < Test::Unit::TestCase
assert_kind_of Person::Street::State::NotableRiver, rivers.first
assert_equal @deep[:street][:state][:notable_rivers].first[:id], rivers.first.id
assert_equal @matz[:id], rivers.last.rafted_by.id
+
+ postal_codes = state.postal_codes
+ assert_kind_of Array, postal_codes
+ assert_equal 2, postal_codes.size
+ assert_kind_of Fixnum, postal_codes.first
+ assert_equal @deep[:street][:state][:postal_codes].first, postal_codes.first
+ assert_kind_of Numeric, postal_codes.last
+ assert_equal @deep[:street][:state][:postal_codes].last, postal_codes.last
+
+ places = state.places
+ assert_kind_of Array, places
+ assert_kind_of String, places.first
+ assert_equal @deep[:street][:state][:places].first, places.first
end
def test_nested_collections_within_the_same_namespace
diff --git a/activeresource/test/cases/base_errors_test.rb b/activeresource/test/cases/base_errors_test.rb
new file mode 100644
index 0000000000..eca00e9ca8
--- /dev/null
+++ b/activeresource/test/cases/base_errors_test.rb
@@ -0,0 +1,83 @@
+require 'abstract_unit'
+require "fixtures/person"
+
+class BaseErrorsTest < Test::Unit::TestCase
+ def setup
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.post "/people.xml", {}, %q(<?xml version="1.0" encoding="UTF-8"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>), 422, {'Content-Type' => 'application/xml'}
+ mock.post "/people.json", {}, %q({"errors":["Age can't be blank","Name can't be blank","Name must start with a letter","Person quota full for today."]}), 422, {'Content-Type' => 'application/json'}
+ end
+ end
+
+ def test_should_mark_as_invalid
+ [ :json, :xml ].each do |format|
+ invalid_user_using_format(format) do
+ assert !@person.valid?
+ end
+ end
+ end
+
+ def test_should_parse_xml_errors
+ [ :json, :xml ].each do |format|
+ invalid_user_using_format(format) do
+ assert_kind_of ActiveResource::Errors, @person.errors
+ assert_equal 4, @person.errors.size
+ end
+ end
+ end
+
+ def test_should_parse_errors_to_individual_attributes
+ [ :json, :xml ].each do |format|
+ invalid_user_using_format(format) do
+ assert @person.errors[:name].any?
+ assert_equal ["can't be blank"], @person.errors[:age]
+ assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name]
+ assert_equal ["Person quota full for today."], @person.errors[:base]
+ end
+ end
+ end
+
+ def test_should_iterate_over_errors
+ [ :json, :xml ].each do |format|
+ invalid_user_using_format(format) do
+ errors = []
+ @person.errors.each { |attribute, message| errors << [attribute, message] }
+ assert errors.include?([:name, "can't be blank"])
+ end
+ end
+ end
+
+ def test_should_iterate_over_full_errors
+ [ :json, :xml ].each do |format|
+ invalid_user_using_format(format) do
+ errors = []
+ @person.errors.to_a.each { |message| errors << message }
+ assert errors.include?("Name can't be blank")
+ end
+ end
+ end
+
+ def test_should_format_full_errors
+ [ :json, :xml ].each do |format|
+ invalid_user_using_format(format) do
+ full = @person.errors.full_messages
+ assert full.include?("Age can't be blank")
+ assert full.include?("Name can't be blank")
+ assert full.include?("Name must start with a letter")
+ assert full.include?("Person quota full for today.")
+ end
+ end
+ end
+
+ private
+ def invalid_user_using_format(mime_type_reference)
+ previous_format = Person.format
+ Person.format = mime_type_reference
+ @person = Person.new(:name => '', :age => '')
+ assert_equal false, @person.save
+
+ yield
+ ensure
+ Person.format = previous_format
+ end
+end
diff --git a/activeresource/test/base_test.rb b/activeresource/test/cases/base_test.rb
index e68d562d97..8c0217aad6 100644
--- a/activeresource/test/base_test.rb
+++ b/activeresource/test/cases/base_test.rb
@@ -166,6 +166,13 @@ class BaseTest < Test::Unit::TestCase
assert_equal(5, Forum.connection.timeout)
end
+ def test_should_accept_setting_ssl_options
+ expected = {:verify => 1}
+ Forum.ssl_options= expected
+ assert_equal(expected, Forum.ssl_options)
+ assert_equal(expected, Forum.connection.ssl_options)
+ end
+
def test_user_variable_can_be_reset
actor = Class.new(ActiveResource::Base)
actor.site = 'http://cinema'
@@ -196,6 +203,16 @@ class BaseTest < Test::Unit::TestCase
assert_nil actor.connection.timeout
end
+ def test_ssl_options_hash_can_be_reset
+ actor = Class.new(ActiveResource::Base)
+ actor.site = 'https://cinema'
+ assert_nil actor.ssl_options
+ actor.ssl_options = {:foo => 5}
+ actor.ssl_options = nil
+ assert_nil actor.ssl_options
+ assert_nil actor.connection.ssl_options
+ end
+
def test_credentials_from_site_are_decoded
actor = Class.new(ActiveResource::Base)
actor.site = 'http://my%40email.com:%31%32%33@cinema'
@@ -395,6 +412,40 @@ class BaseTest < Test::Unit::TestCase
assert_equal fruit.timeout, apple.timeout, 'subclass did not adopt changes from parent class'
end
+ def test_ssl_options_reader_uses_superclass_ssl_options_until_written
+ # Superclass is Object so returns nil.
+ assert_nil ActiveResource::Base.ssl_options
+ assert_nil Class.new(ActiveResource::Base).ssl_options
+ Person.ssl_options = {:foo => 'bar'}
+
+ # Subclass uses superclass ssl_options.
+ actor = Class.new(Person)
+ assert_equal Person.ssl_options, actor.ssl_options
+
+ # Changing subclass ssl_options doesn't change superclass ssl_options.
+ actor.ssl_options = {:baz => ''}
+ assert_not_equal Person.ssl_options, actor.ssl_options
+
+ # Changing superclass ssl_options doesn't overwrite subclass ssl_options.
+ Person.ssl_options = {:color => 'blue'}
+ assert_not_equal Person.ssl_options, actor.ssl_options
+
+ # Changing superclass ssl_options after subclassing changes subclass ssl_options.
+ jester = Class.new(actor)
+ actor.ssl_options = {:color => 'red'}
+ assert_equal actor.ssl_options, jester.ssl_options
+
+ # Subclasses are always equal to superclass ssl_options when not overridden.
+ fruit = Class.new(ActiveResource::Base)
+ apple = Class.new(fruit)
+
+ fruit.ssl_options = {:alpha => 'betas'}
+ assert_equal fruit.ssl_options, apple.ssl_options, 'subclass did not adopt changes from parent class'
+
+ fruit.ssl_options = {:omega => 'moos'}
+ assert_equal fruit.ssl_options, apple.ssl_options, 'subclass did not adopt changes from parent class'
+ end
+
def test_updating_baseclass_site_object_wipes_descendent_cached_connection_objects
# Subclasses are always equal to superclass site when not overridden
fruit = Class.new(ActiveResource::Base)
@@ -586,13 +637,6 @@ class BaseTest < Test::Unit::TestCase
assert_equal [:person_id].to_set, StreetAddress.__send__(:prefix_parameters)
end
- def test_find_by_id
- matz = Person.find(1)
- assert_kind_of Person, matz
- assert_equal "Matz", matz.name
- assert matz.name?
- end
-
def test_respond_to
matz = Person.find(1)
assert matz.respond_to?(:name)
@@ -601,32 +645,6 @@ class BaseTest < Test::Unit::TestCase
assert !matz.respond_to?(:super_scalable_stuff)
end
- def test_find_by_id_with_custom_prefix
- addy = StreetAddress.find(1, :params => { :person_id => 1 })
- assert_kind_of StreetAddress, addy
- assert_equal '12345 Street', addy.street
- end
-
- def test_find_all
- all = Person.find(:all)
- assert_equal 2, all.size
- assert_kind_of Person, all.first
- assert_equal "Matz", all.first.name
- assert_equal "David", all.last.name
- end
-
- def test_find_first
- matz = Person.find(:first)
- assert_kind_of Person, matz
- assert_equal "Matz", matz.name
- end
-
- def test_find_last
- david = Person.find(:last)
- assert_kind_of Person, david
- assert_equal 'David', david.name
- end
-
def test_custom_header
Person.headers['key'] = 'value'
assert_raise(ActiveResource::ResourceNotFound) { Person.find(4) }
@@ -634,52 +652,15 @@ class BaseTest < Test::Unit::TestCase
Person.headers.delete('key')
end
- def test_find_by_id_not_found
- assert_raise(ActiveResource::ResourceNotFound) { Person.find(99) }
- assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1) }
- end
-
- def test_find_all_by_from
- ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/people.xml", {}, @people_david }
-
- people = Person.find(:all, :from => "/companies/1/people.xml")
- assert_equal 1, people.size
- assert_equal "David", people.first.name
- end
-
- def test_find_all_by_from_with_options
- ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/people.xml", {}, @people_david }
-
- people = Person.find(:all, :from => "/companies/1/people.xml")
- assert_equal 1, people.size
- assert_equal "David", people.first.name
- end
-
- def test_find_all_by_symbol_from
- ActiveResource::HttpMock.respond_to { |m| m.get "/people/managers.xml", {}, @people_david }
-
- people = Person.find(:all, :from => :managers)
- assert_equal 1, people.size
- assert_equal "David", people.first.name
- end
-
- def test_find_single_by_from
- ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/manager.xml", {}, @david }
-
- david = Person.find(:one, :from => "/companies/1/manager.xml")
- assert_equal "David", david.name
- end
-
- def test_find_single_by_symbol_from
- ActiveResource::HttpMock.respond_to { |m| m.get "/people/leader.xml", {}, @david }
-
- david = Person.find(:one, :from => :leader)
- assert_equal "David", david.name
+ def test_save
+ rick = Person.new
+ assert rick.save
+ assert_equal '5', rick.id
end
- def test_save
+ def test_save!
rick = Person.new
- assert_equal true, rick.save
+ assert rick.save!
assert_equal '5', rick.id
end
@@ -848,6 +829,14 @@ class BaseTest < Test::Unit::TestCase
assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :params => { :person_id => 1 }) }
end
+ def test_destroy_with_410_gone
+ assert Person.find(1).destroy
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.get "/people/1.xml", {}, nil, 410
+ end
+ assert_raise(ActiveResource::ResourceGone) { Person.find(1).destroy }
+ end
+
def test_delete
assert Person.delete(1)
ActiveResource::HttpMock.respond_to do |mock|
@@ -863,6 +852,14 @@ class BaseTest < Test::Unit::TestCase
end
assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :params => { :person_id => 1 }) }
end
+
+ def test_delete_with_410_gone
+ assert Person.delete(1)
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.get "/people/1.xml", {}, nil, 410
+ end
+ assert_raise(ActiveResource::ResourceGone) { Person.find(1) }
+ end
def test_exists
# Class method.
@@ -915,6 +912,22 @@ class BaseTest < Test::Unit::TestCase
end
end
+ def test_exists_without_http_mock
+ http = Net::HTTP.new(Person.site.host, Person.site.port)
+ ActiveResource::Connection.any_instance.expects(:http).returns(http)
+ http.expects(:request).returns(ActiveResource::Response.new(""))
+
+ assert Person.exists?('not-mocked')
+ end
+
+ def test_exists_with_410_gone
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.head "/people/1.xml", {}, nil, 410
+ end
+
+ assert !Person.exists?(1)
+ end
+
def test_to_xml
matz = Person.find(1)
xml = matz.encode
diff --git a/activeresource/test/cases/finder_test.rb b/activeresource/test/cases/finder_test.rb
new file mode 100644
index 0000000000..535b6f4198
--- /dev/null
+++ b/activeresource/test/cases/finder_test.rb
@@ -0,0 +1,233 @@
+require 'abstract_unit'
+require "fixtures/person"
+require "fixtures/customer"
+require "fixtures/street_address"
+require "fixtures/beast"
+require "fixtures/proxy"
+require 'active_support/core_ext/hash/conversions'
+
+class FinderTest < Test::Unit::TestCase
+ def setup
+ # TODO: refactor/DRY this setup - it's a copy of the BaseTest setup.
+ # We can probably put this into abstract_unit
+ @matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person')
+ @david = { :id => 2, :name => 'David' }.to_xml(:root => 'person')
+ @greg = { :id => 3, :name => 'Greg' }.to_xml(:root => 'person')
+ @addy = { :id => 1, :street => '12345 Street' }.to_xml(:root => 'address')
+ @default_request_headers = { 'Content-Type' => 'application/xml' }
+ @rick = { :name => "Rick", :age => 25 }.to_xml(:root => "person")
+ @people = [{ :id => 1, :name => 'Matz' }, { :id => 2, :name => 'David' }].to_xml(:root => 'people')
+ @people_david = [{ :id => 2, :name => 'David' }].to_xml(:root => 'people')
+ @addresses = [{ :id => 1, :street => '12345 Street' }].to_xml(:root => 'addresses')
+
+ # - deep nested resource -
+ # - Luis (Customer)
+ # - JK (Customer::Friend)
+ # - Mateo (Customer::Friend::Brother)
+ # - Edith (Customer::Friend::Brother::Child)
+ # - Martha (Customer::Friend::Brother::Child)
+ # - Felipe (Customer::Friend::Brother)
+ # - Bryan (Customer::Friend::Brother::Child)
+ # - Luke (Customer::Friend::Brother::Child)
+ # - Eduardo (Customer::Friend)
+ # - Sebas (Customer::Friend::Brother)
+ # - Andres (Customer::Friend::Brother::Child)
+ # - Jorge (Customer::Friend::Brother::Child)
+ # - Elsa (Customer::Friend::Brother)
+ # - Natacha (Customer::Friend::Brother::Child)
+ # - Milena (Customer::Friend::Brother)
+ #
+ @luis = {:id => 1, :name => 'Luis',
+ :friends => [{:name => 'JK',
+ :brothers => [{:name => 'Mateo',
+ :children => [{:name => 'Edith'},{:name => 'Martha'}]},
+ {:name => 'Felipe',
+ :children => [{:name => 'Bryan'},{:name => 'Luke'}]}]},
+ {:name => 'Eduardo',
+ :brothers => [{:name => 'Sebas',
+ :children => [{:name => 'Andres'},{:name => 'Jorge'}]},
+ {:name => 'Elsa',
+ :children => [{:name => 'Natacha'}]},
+ {:name => 'Milena',
+ :children => []}]}]}.to_xml(:root => 'customer')
+ # - resource with yaml array of strings; for ActiveRecords using serialize :bar, Array
+ @marty = <<-eof.strip
+ <?xml version=\"1.0\" encoding=\"UTF-8\"?>
+ <person>
+ <id type=\"integer\">5</id>
+ <name>Marty</name>
+ <colors type=\"yaml\">---
+ - \"red\"
+ - \"green\"
+ - \"blue\"
+ </colors>
+ </person>
+ eof
+
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.get "/people/1.xml", {}, @matz
+ mock.get "/people/2.xml", {}, @david
+ mock.get "/people/5.xml", {}, @marty
+ mock.get "/people/Greg.xml", {}, @greg
+ mock.get "/people/4.xml", {'key' => 'value'}, nil, 404
+ mock.put "/people/1.xml", {}, nil, 204
+ mock.delete "/people/1.xml", {}, nil, 200
+ mock.delete "/people/2.xml", {}, nil, 400
+ mock.get "/people/99.xml", {}, nil, 404
+ mock.post "/people.xml", {}, @rick, 201, 'Location' => '/people/5.xml'
+ mock.get "/people.xml", {}, @people
+ mock.get "/people/1/addresses.xml", {}, @addresses
+ mock.get "/people/1/addresses/1.xml", {}, @addy
+ mock.get "/people/1/addresses/2.xml", {}, nil, 404
+ mock.get "/people/2/addresses.xml", {}, nil, 404
+ mock.get "/people/2/addresses/1.xml", {}, nil, 404
+ mock.get "/people/Greg/addresses/1.xml", {}, @addy
+ mock.put "/people/1/addresses/1.xml", {}, nil, 204
+ mock.delete "/people/1/addresses/1.xml", {}, nil, 200
+ mock.post "/people/1/addresses.xml", {}, nil, 201, 'Location' => '/people/1/addresses/5'
+ mock.get "/people//addresses.xml", {}, nil, 404
+ mock.get "/people//addresses/1.xml", {}, nil, 404
+ mock.put "/people//addresses/1.xml", {}, nil, 404
+ mock.delete "/people//addresses/1.xml", {}, nil, 404
+ mock.post "/people//addresses.xml", {}, nil, 404
+ mock.head "/people/1.xml", {}, nil, 200
+ mock.head "/people/Greg.xml", {}, nil, 200
+ mock.head "/people/99.xml", {}, nil, 404
+ mock.head "/people/1/addresses/1.xml", {}, nil, 200
+ mock.head "/people/1/addresses/2.xml", {}, nil, 404
+ mock.head "/people/2/addresses/1.xml", {}, nil, 404
+ mock.head "/people/Greg/addresses/1.xml", {}, nil, 200
+ # customer
+ mock.get "/customers/1.xml", {}, @luis
+ end
+
+ Person.user = nil
+ Person.password = nil
+ end
+
+ def test_find_by_id
+ matz = Person.find(1)
+ assert_kind_of Person, matz
+ assert_equal "Matz", matz.name
+ assert matz.name?
+ end
+
+ def test_find_by_id_with_custom_prefix
+ addy = StreetAddress.find(1, :params => { :person_id => 1 })
+ assert_kind_of StreetAddress, addy
+ assert_equal '12345 Street', addy.street
+ end
+
+ def test_find_all
+ all = Person.find(:all)
+ assert_equal 2, all.size
+ assert_kind_of Person, all.first
+ assert_equal "Matz", all.first.name
+ assert_equal "David", all.last.name
+ end
+
+ def test_all
+ all = Person.all
+ assert_equal 2, all.size
+ assert_kind_of Person, all.first
+ assert_equal "Matz", all.first.name
+ assert_equal "David", all.last.name
+ end
+
+ def test_all_with_params
+ all = StreetAddress.all(:params => { :person_id => 1 })
+ assert_equal 1, all.size
+ assert_kind_of StreetAddress, all.first
+ end
+
+ def test_find_first
+ matz = Person.find(:first)
+ assert_kind_of Person, matz
+ assert_equal "Matz", matz.name
+ end
+
+ def test_first
+ matz = Person.first
+ assert_kind_of Person, matz
+ assert_equal "Matz", matz.name
+ end
+
+ def test_first_with_params
+ addy = StreetAddress.first(:params => { :person_id => 1 })
+ assert_kind_of StreetAddress, addy
+ assert_equal '12345 Street', addy.street
+ end
+
+ def test_find_last
+ david = Person.find(:last)
+ assert_kind_of Person, david
+ assert_equal 'David', david.name
+ end
+
+ def test_last
+ david = Person.last
+ assert_kind_of Person, david
+ assert_equal 'David', david.name
+ end
+
+ def test_last_with_params
+ addy = StreetAddress.last(:params => { :person_id => 1 })
+ assert_kind_of StreetAddress, addy
+ assert_equal '12345 Street', addy.street
+ end
+
+ def test_find_by_id_not_found
+ assert_raise(ActiveResource::ResourceNotFound) { Person.find(99) }
+ assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1) }
+ end
+
+ def test_find_all_sub_objects
+ all = StreetAddress.find(:all, :params => { :person_id => 1 })
+ assert_equal 1, all.size
+ assert_kind_of StreetAddress, all.first
+ end
+
+ def test_find_all_sub_objects_not_found
+ assert_nothing_raised do
+ addys = StreetAddress.find(:all, :params => { :person_id => 2 })
+ end
+ end
+
+ def test_find_all_by_from
+ ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/people.xml", {}, @people_david }
+
+ people = Person.find(:all, :from => "/companies/1/people.xml")
+ assert_equal 1, people.size
+ assert_equal "David", people.first.name
+ end
+
+ def test_find_all_by_from_with_options
+ ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/people.xml", {}, @people_david }
+
+ people = Person.find(:all, :from => "/companies/1/people.xml")
+ assert_equal 1, people.size
+ assert_equal "David", people.first.name
+ end
+
+ def test_find_all_by_symbol_from
+ ActiveResource::HttpMock.respond_to { |m| m.get "/people/managers.xml", {}, @people_david }
+
+ people = Person.find(:all, :from => :managers)
+ assert_equal 1, people.size
+ assert_equal "David", people.first.name
+ end
+
+ def test_find_single_by_from
+ ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/manager.xml", {}, @david }
+
+ david = Person.find(:one, :from => "/companies/1/manager.xml")
+ assert_equal "David", david.name
+ end
+
+ def test_find_single_by_symbol_from
+ ActiveResource::HttpMock.respond_to { |m| m.get "/people/leader.xml", {}, @david }
+
+ david = Person.find(:one, :from => :leader)
+ assert_equal "David", david.name
+ end
+end
diff --git a/activeresource/test/format_test.rb b/activeresource/test/cases/format_test.rb
index c3733e13d8..c3733e13d8 100644
--- a/activeresource/test/format_test.rb
+++ b/activeresource/test/cases/format_test.rb
diff --git a/activeresource/test/observing_test.rb b/activeresource/test/cases/observing_test.rb
index 334b256772..334b256772 100644
--- a/activeresource/test/observing_test.rb
+++ b/activeresource/test/cases/observing_test.rb
diff --git a/activeresource/test/cases/validations_test.rb b/activeresource/test/cases/validations_test.rb
new file mode 100644
index 0000000000..a8ab7d64e7
--- /dev/null
+++ b/activeresource/test/cases/validations_test.rb
@@ -0,0 +1,55 @@
+require 'abstract_unit'
+require "fixtures/project"
+
+# The validations are tested thoroughly under ActiveModel::Validations
+# This test case simply makes sur that they are all accessible by
+# Active Resource objects.
+class ValidationsTest < ActiveModel::TestCase
+ VALID_PROJECT_HASH = { :name => "My Project", :description => "A project" }
+ def setup
+ @my_proj = VALID_PROJECT_HASH.to_xml(:root => "person")
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.post "/projects.xml", {}, @my_proj, 201, 'Location' => '/projects/5.xml'
+ end
+ end
+
+ def test_validates_presence_of
+ p = new_project(:name => nil)
+ assert !p.valid?, "should not be a valid record without name"
+ assert !p.save, "should not have saved an invalid record"
+ assert_equal ["can't be blank"], p.errors[:name], "should have an error on name"
+
+ p.name = "something"
+
+ assert p.save, "should have saved after fixing the validation, but had: #{p.errors.inspect}"
+ end
+
+ def test_fails_save!
+ p = new_project(:name => nil)
+ assert_raise(ActiveResource::ResourceInvalid) { p.save! }
+ end
+
+
+ def test_validate_callback
+ # we have a callback ensuring the description is longer than three letters
+ p = new_project(:description => 'a')
+ assert !p.valid?, "should not be a valid record when it fails a validation callback"
+ assert !p.save, "should not have saved an invalid record"
+ assert_equal ["must be greater than three letters long"], p.errors[:description], "should be an error on description"
+
+ # should now allow this description
+ p.description = 'abcd'
+ assert p.save, "should have saved after fixing the validation, but had: #{p.errors.inspect}"
+ end
+
+ protected
+
+ # quickie helper to create a new project with all the required
+ # attributes.
+ # Pass in any params you specifically want to override
+ def new_project(opts = {})
+ Project.new(VALID_PROJECT_HASH.merge(opts))
+ end
+
+end
+
diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb
index 12e8058b0c..d7466c65b4 100644
--- a/activeresource/test/connection_test.rb
+++ b/activeresource/test/connection_test.rb
@@ -56,6 +56,9 @@ class ConnectionTest < Test::Unit::TestCase
# 409 is an optimistic locking error
assert_response_raises ActiveResource::ResourceConflict, 409
+ # 410 is a removed resource
+ assert_response_raises ActiveResource::ResourceGone, 410
+
# 422 is a validation error
assert_response_raises ActiveResource::ResourceInvalid, 422
@@ -204,6 +207,24 @@ class ConnectionTest < Test::Unit::TestCase
assert_nothing_raised(Mocha::ExpectationError) { @conn.get(path, {'Accept' => 'application/xhtml+xml'}) }
end
+ def test_ssl_options_get_applied_to_http
+ http = Net::HTTP.new('')
+ @conn.site="https://secure"
+ @conn.ssl_options={:verify_mode => OpenSSL::SSL::VERIFY_PEER}
+ @conn.timeout = 10 # prevent warning about uninitialized.
+ @conn.send(:configure_http, http)
+
+ assert http.use_ssl?
+ assert_equal http.verify_mode, OpenSSL::SSL::VERIFY_PEER
+ end
+
+ def test_ssl_error
+ http = Net::HTTP.new('')
+ @conn.expects(:http).returns(http)
+ http.expects(:get).raises(OpenSSL::SSL::SSLError, 'Expired certificate')
+ assert_raise(ActiveResource::SSLError) { @conn.get('/people/1.xml') }
+ end
+
protected
def assert_response_raises(klass, code)
assert_raise(klass, "Expected response code #{code} to raise #{klass}") do
diff --git a/activeresource/test/fixtures/project.rb b/activeresource/test/fixtures/project.rb
new file mode 100644
index 0000000000..e15fa6f620
--- /dev/null
+++ b/activeresource/test/fixtures/project.rb
@@ -0,0 +1,25 @@
+# used to test validations
+class Project < ActiveResource::Base
+ self.site = "http://37s.sunrise.i:3000"
+
+ validates_presence_of :name
+ validate :description_greater_than_three_letters
+
+ # to test the validate *callback* works
+ def description_greater_than_three_letters
+ errors.add :description, 'must be greater than three letters long' if description.length < 3 unless description.blank?
+ end
+
+
+ # stop-gap accessor to default this attribute to nil
+ # Otherwise the validations fail saying that the method does not exist.
+ # In future, method_missing will be updated to not explode on a known
+ # attribute.
+ def name
+ attributes['name'] || nil
+ end
+ def description
+ attributes['description'] || nil
+ end
+end
+
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 448db538ab..e28df8efa5 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -62,19 +62,27 @@ module ActiveSupport
end
end
+ RAILS_CACHE_ID = ENV["RAILS_CACHE_ID"]
+ RAILS_APP_VERION = ENV["RAILS_APP_VERION"]
+ EXPANDED_CACHE = RAILS_CACHE_ID || RAILS_APP_VERION
+
def self.expand_cache_key(key, namespace = nil)
expanded_cache_key = namespace ? "#{namespace}/" : ""
- if ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
- expanded_cache_key << "#{ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]}/"
+ if EXPANDED_CACHE
+ expanded_cache_key << "#{RAILS_CACHE_ID || RAILS_APP_VERION}/"
end
- expanded_cache_key << case
- when key.respond_to?(:cache_key)
+ expanded_cache_key <<
+ if key.respond_to?(:cache_key)
key.cache_key
- when key.is_a?(Array)
- key.collect { |element| expand_cache_key(element) }.to_param
- when key
+ elsif key.is_a?(Array)
+ if key.size > 1
+ key.collect { |element| expand_cache_key(element) }.to_param
+ else
+ key.first.to_param
+ end
+ elsif key
key.to_param
end.to_s
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 11846f265c..c53cf3f530 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -159,6 +159,7 @@ class Array
raise "Not all elements respond to to_xml" unless all? { |e| e.respond_to? :to_xml }
require 'builder' unless defined?(Builder)
+ options = options.dup
options[:root] ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? ActiveSupport::Inflector.pluralize(ActiveSupport::Inflector.underscore(first.class.name)) : "records"
options[:children] ||= options[:root].singularize
options[:indent] ||= 2
diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
index 74ce85a1c2..1602a609eb 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
@@ -46,11 +46,12 @@ class Class
end # end
" unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false
EOS
+ self.send("#{sym}=", yield) if block_given?
end
end
- def cattr_accessor(*syms)
+ def cattr_accessor(*syms, &blk)
cattr_reader(*syms)
- cattr_writer(*syms)
+ cattr_writer(*syms, &blk)
end
end
diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
index fd029544c3..6c67df7f50 100644
--- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
@@ -26,13 +26,14 @@ class Class
end
end
- def superclass_delegating_writer(*names)
+ def superclass_delegating_writer(*names, &block)
names.each do |name|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def self.#{name}=(value) # def self.property=(value)
@#{name} = value # @property = value
end # end
EOS
+ self.send("#{name}=", yield) if block_given?
end
end
@@ -42,8 +43,8 @@ class Class
# delegate to their superclass unless they have been given a
# specific value. This stops the strange situation where values
# set after class definition don't get applied to subclasses.
- def superclass_delegating_accessor(*names)
+ def superclass_delegating_accessor(*names, &block)
superclass_delegating_reader(*names)
- superclass_delegating_writer(*names)
+ superclass_delegating_writer(*names, &block)
end
end
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 1fe4ffb8e1..ce3bebc25a 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -82,6 +82,7 @@ class Date
# Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with
# any of these keys: <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>.
def advance(options)
+ options = options.dup
d = self
d = d >> options.delete(:years) * 12 if options[:years]
d = d >> options.delete(:months) if options[:months]
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 2a34874d08..bd9419e1a2 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -86,6 +86,7 @@ class Hash
def to_xml(options = {})
require 'builder' unless defined?(Builder)
+ options = options.dup
options[:indent] ||= 2
options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]),
:root => "hash" })
diff --git a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
index ffde34a741..24d0a2a481 100644
--- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
@@ -1,11 +1,12 @@
class Hash
# Returns a new hash with +self+ and +other_hash+ merged recursively.
def deep_merge(other_hash)
- merge(other_hash) do |key, oldval, newval|
- oldval = oldval.to_hash if oldval.respond_to?(:to_hash)
- newval = newval.to_hash if newval.respond_to?(:to_hash)
- oldval.is_a?( Hash ) && newval.is_a?( Hash ) ? oldval.deep_merge(newval) : newval
+ target = dup
+ other_hash.each_pair do |k,v|
+ tv = target[k]
+ target[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v
end
+ target
end
# Returns a new hash with +self+ and +other_hash+ merged recursively.
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 11e01437aa..df8aefea5a 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -120,10 +120,15 @@ class Module
end
module_eval(<<-EOS, file, line)
- def #{prefix}#{method}(*args, &block) # def customer_name(*args, &block)
- #{on_nil} if #{to}.nil?
- #{to}.__send__(#{method.inspect}, *args, &block) # client && client.__send__(:name, *args, &block)
- end # end
+ def #{prefix}#{method}(*args, &block) # def customer_name(*args, &block)
+ #{to}.__send__(#{method.inspect}, *args, &block) # client.__send__(:name, *args, &block)
+ rescue NoMethodError # rescue NoMethodError
+ if #{to}.nil? # if client.nil?
+ #{on_nil}
+ else # else
+ raise # raise
+ end # end
+ end # end
EOS
end
end
diff --git a/activesupport/lib/active_support/core_ext/regexp.rb b/activesupport/lib/active_support/core_ext/regexp.rb
index 1a04c70d87..95d06ee6ee 100644
--- a/activesupport/lib/active_support/core_ext/regexp.rb
+++ b/activesupport/lib/active_support/core_ext/regexp.rb
@@ -9,6 +9,8 @@ class Regexp #:nodoc:
class << self
def optionalize(pattern)
+ return pattern if pattern == ""
+
case unoptionalize(pattern)
when /\A(.|\(.*\))\Z/ then "#{pattern}?"
else "(?:#{pattern})?"
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 4f3b869f50..4f4492f0fd 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -172,7 +172,8 @@ class Time
# Returns a new Time representing the start of the day (0:00)
def beginning_of_day
- (self - seconds_since_midnight).change(:usec => 0)
+ #(self - seconds_since_midnight).change(:usec => 0)
+ change(:hour => 0, :min => 0, :sec => 0, :usec => 0)
end
alias :midnight :beginning_of_day
alias :at_midnight :beginning_of_day
diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb
index 4ee96b13b4..67aea2782f 100644
--- a/activesupport/lib/active_support/inflector.rb
+++ b/activesupport/lib/active_support/inflector.rb
@@ -69,10 +69,13 @@ module ActiveSupport
@uncountables.delete(plural)
if singular[0,1].upcase == plural[0,1].upcase
plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
+ plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1])
singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
else
plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
+ plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
+ plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1])
singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1])
end
diff --git a/activesupport/lib/active_support/json/backends/yaml.rb b/activesupport/lib/active_support/json/backends/yaml.rb
index 92dd31cfbc..59d2c37e40 100644
--- a/activesupport/lib/active_support/json/backends/yaml.rb
+++ b/activesupport/lib/active_support/json/backends/yaml.rb
@@ -20,7 +20,7 @@ module ActiveSupport
rescue ArgumentError => e
raise ParseError, "Invalid JSON string"
end
-
+
protected
# Ensure that ":" and "," are always followed by a space
def convert_json_to_yaml(json) #:nodoc:
@@ -42,6 +42,8 @@ module ActiveSupport
end
when ":",","
marks << scanner.pos - 1 unless quoting
+ when "\\"
+ scanner.skip(/\\/)
end
end
@@ -89,3 +91,4 @@ module ActiveSupport
end
end
end
+
diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb
index fa6db683d4..7724b9d88b 100644
--- a/activesupport/lib/active_support/memoizable.rb
+++ b/activesupport/lib/active_support/memoizable.rb
@@ -59,7 +59,7 @@ module ActiveSupport
def flush_cache(*syms, &block)
syms.each do |sym|
- methods.each do |m|
+ (methods + private_methods + protected_methods).each do |m|
if m.to_s =~ /^_unmemoized_(#{sym})/
ivar = ActiveSupport::Memoizable.memoized_ivar_for($1)
instance_variable_get(ivar).clear if instance_variable_defined?(ivar)
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 96ed35f0e0..64a35dca40 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -206,7 +206,22 @@ module ActiveSupport #:nodoc:
# 'Café périferôl'.mb_chars.index('ô') #=> 12
# 'Café périferôl'.mb_chars.index(/\w/u) #=> 0
def index(needle, offset=0)
- index = @wrapped_string.index(needle, offset)
+ wrapped_offset = self.first(offset).wrapped_string.length
+ index = @wrapped_string.index(needle, wrapped_offset)
+ index ? (self.class.u_unpack(@wrapped_string.slice(0...index)).size) : nil
+ end
+
+ # Returns the position _needle_ in the string, counting in
+ # codepoints, searching backward from _offset_ or the end of the
+ # string. Returns +nil+ if _needle_ isn't found.
+ #
+ # Example:
+ # 'Café périferôl'.mb_chars.rindex('é') #=> 6
+ # 'Café périferôl'.mb_chars.rindex(/\w/u) #=> 13
+ def rindex(needle, offset=nil)
+ offset ||= length
+ wrapped_offset = self.first(offset).wrapped_string.length
+ index = @wrapped_string.rindex(needle, wrapped_offset)
index ? (self.class.u_unpack(@wrapped_string.slice(0...index)).size) : nil
end
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index 24d33896ce..8198b9bd2c 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -302,6 +302,13 @@ class ArrayToXmlTests < Test::Unit::TestCase
xml = [].to_xml
assert_match(/type="array"\/>/, xml)
end
+
+ def test_to_xml_dups_options
+ options = {:skip_instruct => true}
+ [].to_xml(options)
+ # :builder, etc, shouldn't be added to options
+ assert_equal({:skip_instruct => true}, options)
+ end
end
class ArrayExtractOptionsTests < Test::Unit::TestCase
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index 8a7bae5fc6..18422d68bc 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -251,6 +251,12 @@ class DateExtCalculationsTest < Test::Unit::TestCase
Time.zone_default = nil
end
+ def test_date_advance_should_not_change_passed_options_hash
+ options = { :years => 3, :months => 11, :days => 2 }
+ Date.new(2005,2,28).advance(options)
+ assert_equal({ :years => 3, :months => 11, :days => 2 }, options)
+ end
+
protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index 66507d4652..4170de3dce 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -64,9 +64,9 @@ class EnumerableTests < Test::Unit::TestCase
def test_enumerable_sums
assert_equal 20, (1..4).sum { |i| i * 2 }
assert_equal 10, (1..4).sum
+ assert_equal 10, (1..4.5).sum
assert_equal 6, (1...4).sum
assert_equal 'abc', ('a'..'c').sum
- assert_raises(NoMethodError) { 1..2.5.sum }
end
def test_each_with_object
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index ece5466abb..eb4c37aaf0 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -265,6 +265,18 @@ class HashExtTest < Test::Unit::TestCase
assert_equal expected, hash_1
end
+ def test_deep_merge_on_indifferent_access
+ hash_1 = HashWithIndifferentAccess.new({ :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } })
+ hash_2 = HashWithIndifferentAccess.new({ :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } })
+ hash_3 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }
+ expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } }
+ assert_equal expected, hash_1.deep_merge(hash_2)
+ assert_equal expected, hash_1.deep_merge(hash_3)
+
+ hash_1.deep_merge!(hash_2)
+ assert_equal expected, hash_1
+ end
+
def test_reverse_merge
defaults = { :a => "x", :b => "y", :c => 10 }.freeze
options = { :a => 1, :b => 2 }
@@ -880,6 +892,13 @@ class HashToXmlTest < Test::Unit::TestCase
assert_equal 30, alert_at.min
assert_equal 45, alert_at.sec
end
+
+ def test_to_xml_dups_options
+ options = {:skip_instruct => true}
+ {}.to_xml(options)
+ # :builder, etc, shouldn't be added to options
+ assert_equal({:skip_instruct => true}, options)
+ end
end
class QueryTest < Test::Unit::TestCase
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index f8387ae4ab..87f056ea85 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -32,7 +32,7 @@ end
Somewhere = Struct.new(:street, :city)
Someone = Struct.new(:name, :place) do
- delegate :street, :city, :to => :place
+ delegate :street, :city, :to_f, :to => :place
delegate :state, :to => :@place
delegate :upcase, :to => "place.city"
end
@@ -44,6 +44,7 @@ end
Project = Struct.new(:description, :person) do
delegate :name, :to => :person, :allow_nil => true
+ delegate :to_f, :to => :description, :allow_nil => true
end
class Name
@@ -145,6 +146,16 @@ class ModuleTest < Test::Unit::TestCase
assert_raise(RuntimeError) { david.street }
end
+ def test_delegation_to_method_that_exists_on_nil
+ nil_person = Someone.new(nil)
+ assert_equal 0.0, nil_person.to_f
+ end
+
+ def test_delegation_to_method_that_exists_on_nil_when_allowing_nil
+ nil_project = Project.new(nil)
+ assert_equal 0.0, nil_project.to_f
+ end
+
def test_parent
assert_equal Yz::Zy, Yz::Zy::Cd.parent
assert_equal Yz, Yz::Zy.parent
diff --git a/activesupport/test/core_ext/regexp_ext_test.rb b/activesupport/test/core_ext/regexp_ext_test.rb
index f71099856d..e2d9140bca 100644
--- a/activesupport/test/core_ext/regexp_ext_test.rb
+++ b/activesupport/test/core_ext/regexp_ext_test.rb
@@ -7,20 +7,20 @@ class RegexpExtAccessTests < Test::Unit::TestCase
end
def test_multiline
- assert //m.multiline?
- assert ! //.multiline?
- assert ! /(?m:)/.multiline?
+ assert_equal true, //m.multiline?
+ assert_equal false, //.multiline?
+ assert_equal false, /(?m:)/.multiline?
end
def test_optionalize
- assert "a?", Regexp.optionalize("a")
- assert "(?:foo)?", Regexp.optionalize("foo")
- assert "", Regexp.optionalize("")
+ assert_equal "a?", Regexp.optionalize("a")
+ assert_equal "(?:foo)?", Regexp.optionalize("foo")
+ assert_equal "", Regexp.optionalize("")
end
def test_unoptionalize
- assert "a", Regexp.unoptionalize("a?")
- assert "foo", Regexp.unoptionalize("(?:foo)")
- assert "", Regexp.unoptionalize("")
+ assert_equal "a", Regexp.unoptionalize("a?")
+ assert_equal "foo", Regexp.unoptionalize("(?:foo)?")
+ assert_equal "", Regexp.unoptionalize("")
end
end \ No newline at end of file
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 99c53924c2..97d70cf8c4 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -3,6 +3,7 @@ require 'pp'
require 'active_support/dependencies'
require 'active_support/core_ext/module/loading'
require 'active_support/core_ext/kernel/reporting'
+require 'active_support/core_ext/symbol/to_proc'
module ModuleWithMissing
mattr_accessor :missing_count
@@ -23,9 +24,11 @@ class DependenciesTest < Test::Unit::TestCase
def with_loading(*from)
old_mechanism, ActiveSupport::Dependencies.mechanism = ActiveSupport::Dependencies.mechanism, :load
- dir = File.dirname(__FILE__)
+ this_dir = File.dirname(__FILE__)
+ parent_dir = File.dirname(this_dir)
+ $LOAD_PATH.unshift(parent_dir) unless $LOAD_PATH.include?(parent_dir)
prior_load_paths = ActiveSupport::Dependencies.load_paths
- ActiveSupport::Dependencies.load_paths = from.collect { |f| "#{dir}/#{f}" }
+ ActiveSupport::Dependencies.load_paths = from.collect { |f| "#{this_dir}/#{f}" }
yield
ensure
ActiveSupport::Dependencies.load_paths = prior_load_paths
@@ -33,6 +36,10 @@ class DependenciesTest < Test::Unit::TestCase
ActiveSupport::Dependencies.explicitly_unloadable_constants = []
end
+ def with_autoloading_fixtures(&block)
+ with_loading 'autoloading_fixtures', &block
+ end
+
def test_tracking_loaded_files
require_dependency 'dependencies/service_one'
require_dependency 'dependencies/service_two'
@@ -129,7 +136,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_module_loading
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert_kind_of Module, A
assert_kind_of Class, A::B
assert_kind_of Class, A::C::D
@@ -138,7 +145,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_non_existing_const_raises_name_error
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert_raise(NameError) { DoesNotExist }
assert_raise(NameError) { NoModule::DoesNotExist }
assert_raise(NameError) { A::DoesNotExist }
@@ -147,49 +154,49 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_directories_manifest_as_modules_unless_const_defined
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert_kind_of Module, ModuleFolder
Object.__send__ :remove_const, :ModuleFolder
end
end
def test_module_with_nested_class
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert_kind_of Class, ModuleFolder::NestedClass
Object.__send__ :remove_const, :ModuleFolder
end
end
def test_module_with_nested_inline_class
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert_kind_of Class, ModuleFolder::InlineClass
Object.__send__ :remove_const, :ModuleFolder
end
end
def test_directories_may_manifest_as_nested_classes
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert_kind_of Class, ClassFolder
Object.__send__ :remove_const, :ClassFolder
end
end
def test_class_with_nested_class
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert_kind_of Class, ClassFolder::NestedClass
Object.__send__ :remove_const, :ClassFolder
end
end
def test_class_with_nested_inline_class
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert_kind_of Class, ClassFolder::InlineClass
Object.__send__ :remove_const, :ClassFolder
end
end
def test_class_with_nested_inline_subclass_of_parent
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert_kind_of Class, ClassFolder::ClassFolderSubclass
assert_kind_of Class, ClassFolder
assert_equal 'indeed', ClassFolder::ClassFolderSubclass::ConstantInClassFolder
@@ -198,7 +205,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_nested_class_can_access_sibling
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
sibling = ModuleFolder::NestedClass.class_eval "NestedSibling"
assert defined?(ModuleFolder::NestedSibling)
assert_equal ModuleFolder::NestedSibling, sibling
@@ -207,7 +214,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def failing_test_access_thru_and_upwards_fails
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert ! defined?(ModuleFolder)
assert_raise(NameError) { ModuleFolder::Object }
assert_raise(NameError) { ModuleFolder::NestedClass::Object }
@@ -216,7 +223,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_non_existing_const_raises_name_error_with_fully_qualified_name
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
begin
A::DoesNotExist.nil?
flunk "No raise!!"
@@ -294,7 +301,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_autoloaded?
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder")
assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass")
@@ -373,7 +380,7 @@ class DependenciesTest < Test::Unit::TestCase
end
end_eval
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert_kind_of Integer, ::ModuleWithCustomConstMissing::B
assert_kind_of Module, ::ModuleWithCustomConstMissing::A
assert_kind_of String, ::ModuleWithCustomConstMissing::A::B
@@ -382,7 +389,7 @@ class DependenciesTest < Test::Unit::TestCase
def test_const_missing_should_not_double_load
$counting_loaded_times = 0
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
require_dependency '././counting_loader'
assert_equal 1, $counting_loaded_times
assert_raise(ArgumentError) { ActiveSupport::Dependencies.load_missing_constant Object, :CountingLoader }
@@ -396,7 +403,7 @@ class DependenciesTest < Test::Unit::TestCase
m.module_eval "def a() CountingLoader; end"
extend m
kls = nil
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
kls = nil
assert_nothing_raised { kls = a }
assert_equal "CountingLoader", kls.name
@@ -431,7 +438,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_load_once_paths_do_not_add_to_autoloaded_constants
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
ActiveSupport::Dependencies.load_once_paths = ActiveSupport::Dependencies.load_paths.dup
assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder")
@@ -447,7 +454,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_application_should_special_case_application_controller
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
require_dependency 'application'
assert_equal 10, ApplicationController
assert ActiveSupport::Dependencies.autoloaded?(:ApplicationController)
@@ -455,7 +462,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_const_missing_on_kernel_should_fallback_to_object
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
kls = Kernel::E
assert_equal "E", kls.name
assert_equal kls.object_id, Kernel::E.object_id
@@ -463,14 +470,14 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_preexisting_constants_are_not_marked_as_autoloaded
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
require_dependency 'e'
assert ActiveSupport::Dependencies.autoloaded?(:E)
ActiveSupport::Dependencies.clear
end
Object.const_set :E, Class.new
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
require_dependency 'e'
assert ! ActiveSupport::Dependencies.autoloaded?(:E), "E shouldn't be marked autoloaded!"
ActiveSupport::Dependencies.clear
@@ -481,7 +488,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_unloadable
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
Object.const_set :M, Module.new
M.unloadable
@@ -495,14 +502,14 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_unloadable_should_fail_with_anonymous_modules
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
m = Module.new
assert_raise(ArgumentError) { m.unloadable }
end
end
def test_unloadable_should_return_change_flag
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
Object.const_set :M, Module.new
assert_equal true, M.unloadable
assert_equal false, M.unloadable
@@ -593,7 +600,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_file_with_multiple_constants_and_require_dependency
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert ! defined?(MultipleConstantFile)
assert ! defined?(SiblingConstant)
@@ -611,7 +618,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_file_with_multiple_constants_and_auto_loading
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert ! defined?(MultipleConstantFile)
assert ! defined?(SiblingConstant)
@@ -630,7 +637,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_nested_file_with_multiple_constants_and_require_dependency
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert ! defined?(ClassFolder::NestedClass)
assert ! defined?(ClassFolder::SiblingClass)
@@ -649,7 +656,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_nested_file_with_multiple_constants_and_auto_loading
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert ! defined?(ClassFolder::NestedClass)
assert ! defined?(ClassFolder::SiblingClass)
@@ -668,7 +675,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_autoload_doesnt_shadow_no_method_error_with_relative_constant
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it hasn't been referenced yet!"
2.times do
assert_raise(NoMethodError) { RaisesNoMethodError }
@@ -681,7 +688,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_autoload_doesnt_shadow_no_method_error_with_absolute_constant
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it hasn't been referenced yet!"
2.times do
assert_raise(NoMethodError) { ::RaisesNoMethodError }
@@ -694,7 +701,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_autoload_doesnt_shadow_error_when_mechanism_not_set_to_load
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
ActiveSupport::Dependencies.mechanism = :require
2.times do
assert_raise(NameError) { assert_equal 123, ::RaisesNameError::FooBarBaz }
@@ -703,7 +710,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_autoload_doesnt_shadow_name_error
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
Object.send(:remove_const, :RaisesNameError) if defined?(::RaisesNameError)
2.times do
begin
@@ -737,7 +744,7 @@ class DependenciesTest < Test::Unit::TestCase
end
def test_load_once_constants_should_not_be_unloaded
- with_loading 'autoloading_fixtures' do
+ with_autoloading_fixtures do
ActiveSupport::Dependencies.load_once_paths = ActiveSupport::Dependencies.load_paths
::A.to_s
assert defined?(A)
diff --git a/activesupport/test/flush_cache_on_private_memoization_test.rb b/activesupport/test/flush_cache_on_private_memoization_test.rb
new file mode 100644
index 0000000000..ddbd05b0e0
--- /dev/null
+++ b/activesupport/test/flush_cache_on_private_memoization_test.rb
@@ -0,0 +1,44 @@
+require 'rubygems'
+require 'activesupport'
+require 'test/unit'
+
+class FlashCacheOnPrivateMemoizationTest < Test::Unit::TestCase
+ extend ActiveSupport::Memoizable
+
+ def test_public
+ assert_method_unmemoizable :pub
+ end
+
+ def test_protected
+ assert_method_unmemoizable :prot
+ end
+
+ def test_private
+ assert_method_unmemoizable :priv
+ end
+
+ def pub; rand end
+ memoize :pub
+
+ protected
+
+ def prot; rand end
+ memoize :prot
+
+ private
+
+ def priv; rand end
+ memoize :priv
+
+ def assert_method_unmemoizable(meth, message=nil)
+ full_message = build_message(message, "<?> not unmemoizable.\n", meth)
+ assert_block(full_message) do
+ a = send meth
+ b = send meth
+ unmemoize_all
+ c = send meth
+ a == b && a != c
+ end
+ end
+
+end \ No newline at end of file
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 7d1554910e..76bdc0e973 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -256,6 +256,16 @@ class InflectorTest < Test::Unit::TestCase
end
end
+ Irregularities.each do |irregularity|
+ singular, plural = *irregularity
+ ActiveSupport::Inflector.inflections do |inflect|
+ define_method("test_pluralize_of_irregularity_#{plural}_should_be_the_same") do
+ inflect.irregular(singular, plural)
+ assert_equal plural, ActiveSupport::Inflector.pluralize(plural)
+ end
+ end
+ end
+
[ :all, [] ].each do |scope|
ActiveSupport::Inflector.inflections do |inflect|
define_method("test_clear_inflections_with_#{scope.kind_of?(Array) ? "no_arguments" : scope}") do
diff --git a/activesupport/test/isolation_test.rb b/activesupport/test/isolation_test.rb
index 5a1f285476..7aecdb8009 100644
--- a/activesupport/test/isolation_test.rb
+++ b/activesupport/test/isolation_test.rb
@@ -1,7 +1,9 @@
require 'abstract_unit'
# Does awesome
-if ENV['CHILD']
+if defined?(MiniTest)
+ $stderr.puts "Umm, MiniTest not supported yet, mmkay?"
+elsif ENV['CHILD']
class ChildIsolationTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
@@ -73,7 +75,7 @@ else
File.open(File.join(File.dirname(__FILE__), "fixtures", "isolation_test"), "w") {}
ENV["CHILD"] = "1"
- OUTPUT = `#{Gem.ruby} -I#{File.dirname(__FILE__)} #{File.expand_path(__FILE__)} -v`
+ OUTPUT = `#{Gem.ruby} -I#{File.dirname(__FILE__)} "#{File.expand_path(__FILE__)}" -v`
ENV.delete("CHILD")
def setup
@@ -153,4 +155,4 @@ else
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index 4129a4fab6..05e420ae36 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -14,10 +14,10 @@ class TestJSONDecoding < ActiveSupport::TestCase
%({"a": "a's, b's and c's", "b": "5,000"}) => {"a" => "a's, b's and c's", "b" => "5,000"},
# multibyte
%({"matzue": "松江", "asakusa": "浅草"}) => {"matzue" => "松江", "asakusa" => "浅草"},
- %({"a": "2007-01-01"}) => {'a' => Date.new(2007, 1, 1)},
- %({"a": "2007-01-01 01:12:34 Z"}) => {'a' => Time.utc(2007, 1, 1, 1, 12, 34)},
+ %({"a": "2007-01-01"}) => {'a' => Date.new(2007, 1, 1)},
+ %({"a": "2007-01-01 01:12:34 Z"}) => {'a' => Time.utc(2007, 1, 1, 1, 12, 34)},
# no time zone
- %({"a": "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"},
+ %({"a": "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"},
# needs to be *exact*
%({"a": " 2007-01-01 01:12:34 Z "}) => {'a' => " 2007-01-01 01:12:34 Z "},
%({"a": "2007-01-01 : it's your birthday"}) => {'a' => "2007-01-01 : it's your birthday"},
@@ -29,6 +29,7 @@ class TestJSONDecoding < ActiveSupport::TestCase
%({"a": null}) => {"a" => nil},
%({"a": true}) => {"a" => true},
%({"a": false}) => {"a" => false},
+ %q({"bad":"\\\\","trailing":""}) => {"bad" => "\\", "trailing" => ""},
%q({"a": "http:\/\/test.host\/posts\/1"}) => {"a" => "http://test.host/posts/1"},
%q({"a": "\u003cunicode\u0020escape\u003e"}) => {"a" => "<unicode escape>"},
%q({"a": "\\\\u0020skip double backslashes"}) => {"a" => "\\u0020skip double backslashes"},
@@ -83,3 +84,4 @@ class TestJSONDecoding < ActiveSupport::TestCase
assert_raise(ActiveSupport::JSON::ParseError) { ActiveSupport::JSON.decode(%({: 1})) }
end
end
+
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 661b33cc57..ed37a1a0da 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -1,5 +1,4 @@
# encoding: utf-8
-
require 'abstract_unit'
require 'multibyte_test_helpers'
@@ -184,7 +183,7 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
end
def test_sortability
- words = %w(builder armor zebra).map(&:mb_chars).sort
+ words = %w(builder armor zebra).sort_by { |s| s.mb_chars }
assert_equal %w(armor builder zebra), words
end
@@ -231,7 +230,19 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
assert_nil @chars.index('u')
assert_equal 0, @chars.index('こに')
assert_equal 2, @chars.index('ち')
+ assert_equal 2, @chars.index('ち', -2)
+ assert_equal nil, @chars.index('ち', -1)
assert_equal 3, @chars.index('わ')
+ assert_equal 5, 'ééxééx'.mb_chars.index('x', 4)
+ end
+
+ def test_rindex_should_return_character_offset
+ assert_nil @chars.rindex('u')
+ assert_equal 1, @chars.rindex('に')
+ assert_equal 2, @chars.rindex('ち', -2)
+ assert_nil @chars.rindex('ち', -3)
+ assert_equal 6, 'Café périferôl'.mb_chars.rindex('é')
+ assert_equal 13, 'Café périferôl'.mb_chars.rindex(/\w/u)
end
def test_indexed_insert_should_take_character_offsets
diff --git a/ci/ci_build.rb b/ci/ci_build.rb
index 0b9bd3d278..06e513f907 100755
--- a/ci/ci_build.rb
+++ b/ci/ci_build.rb
@@ -28,21 +28,14 @@ cd "#{root_dir}/activerecord" do
puts
puts "[CruiseControl] Building ActiveRecord with MySQL"
puts
- build_results[:activerecord_mysql] = system 'rake test_mysql'
+ build_results[:activerecord_mysql] = system 'rake mysql:rebuild_databases && rake test_mysql'
end
cd "#{root_dir}/activerecord" do
puts
puts "[CruiseControl] Building ActiveRecord with PostgreSQL"
puts
- build_results[:activerecord_postgresql8] = system 'rake test_postgresql'
-end
-
-cd "#{root_dir}/activerecord" do
- puts
- puts "[CruiseControl] Building ActiveRecord with SQLite 2"
- puts
- build_results[:activerecord_sqlite] = system 'rake test_sqlite'
+ build_results[:activerecord_postgresql8] = system 'rake postgresql:rebuild_databases && rake test_postgresql'
end
cd "#{root_dir}/activerecord" do
@@ -96,7 +89,6 @@ puts "[CruiseControl] #{`uname -a`}"
puts "[CruiseControl] #{`ruby -v`}"
puts "[CruiseControl] #{`mysql --version`}"
puts "[CruiseControl] #{`pg_config --version`}"
-puts "[CruiseControl] SQLite2: #{`sqlite -version`}"
puts "[CruiseControl] SQLite3: #{`sqlite3 -version`}"
`gem env`.each_line {|line| print "[CruiseControl] #{line}"}
puts "[CruiseControl] Local gems:"
diff --git a/ci/ci_setup_notes.txt b/ci/ci_setup_notes.txt
index 780277c939..7581a24672 100644
--- a/ci/ci_setup_notes.txt
+++ b/ci/ci_setup_notes.txt
@@ -17,9 +17,9 @@ root:*:14001:0:99999:7:::
* Change Hostname:
$ sudo vi /etc/hostname
-change to 'ci'
+change to correct hostname
$ sudo vi /etc/hosts
-replace old hostname with 'ci'
+replace old hostname with the correct hostname
# reboot to use new hostname (and test reboot)
$ sudo shutdown -r now
diff --git a/ci/cruise_config.rb b/ci/cruise_config.rb
index 2247a1b6ea..9c7fa98a70 100644
--- a/ci/cruise_config.rb
+++ b/ci/cruise_config.rb
@@ -1,6 +1,9 @@
Project.configure do |project|
- project.build_command = 'sudo update_rubygems && ruby ci/ci_build.rb'
- project.email_notifier.emails = ['thewoolleyman@gmail.com']
-# project.email_notifier.emails = ['thewoolleyman@gmail.com','michael@koziarski.com', 'david@loudthinking.com', 'jeremy@bitsweat.net', 'josh@joshpeek.com', 'pratiknaik@gmail.com', 'wycats@gmail.com']
- project.email_notifier.from = 'thewoolleyman+railsci@gmail.com'
+ # Send email notifications about broken and fixed builds to core mailing list
+ if Socket.gethostname =~ /ci.rubyonrails.org/ && ENV['ENABLE_RAILS_CI_EMAILS'] == 'true'
+ project.email_notifier.emails = ['rubyonrails-core@googlegroups.com']
+ end
+
+ project.build_command = 'sudo gem update --system && ruby ci/ci_build.rb'
+ project.email_notifier.from = 'thewoolleyman@gmail.com'
end
diff --git a/ci/geminstaller.yml b/ci/geminstaller.yml
index 59bdcebc26..3a4079ddbb 100644
--- a/ci/geminstaller.yml
+++ b/ci/geminstaller.yml
@@ -12,9 +12,11 @@ gems:
#version: >= 2.7
version: = 2.7
- name: pg
- version: >= 0.7.9.2008.10.13
+ version: >= 0.8.0
- name: rack
version: '~> 1.0.0'
+- name: josh-rack-test
+ version: >= 0.4.1
- name: rake
version: >= 0.8.1
- name: sqlite-ruby
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 782afd5aa4..d6311f77a0 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*
+* I18n support for plugins. #2325 [Antonio Tapiador, Sven Fuchs]
+
* Ruby 1.9: use UTF-8 for default internal and external encodings. [Jeremy Kemper]
* Added db/seeds.rb as a default file for storing seed data for the database. Can be loaded with rake db:seed (or created alongside the db with db:setup). (This is also known as the "Stop Putting Gawd Damn Seed Data In Your Migrations" feature) [DHH]
diff --git a/railties/lib/commands/dbconsole.rb b/railties/lib/commands/dbconsole.rb
index 8002264f7e..e6f11a45db 100644
--- a/railties/lib/commands/dbconsole.rb
+++ b/railties/lib/commands/dbconsole.rb
@@ -33,11 +33,15 @@ end
def find_cmd(*commands)
dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
commands += commands.map{|cmd| "#{cmd}.exe"} if RUBY_PLATFORM =~ /win32/
- commands.detect do |cmd|
- dirs_on_path.detect do |path|
- File.executable? File.join(path, cmd)
+
+ full_path_command = nil
+ found = commands.detect do |cmd|
+ dir = dirs_on_path.detect do |path|
+ full_path_command = File.join(path, cmd)
+ File.executable? full_path_command
end
- end || abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
+ end
+ found ? full_path_command : abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
end
case config["adapter"]
diff --git a/railties/lib/generators.rb b/railties/lib/generators.rb
index c97c61507a..879abb1c41 100644
--- a/railties/lib/generators.rb
+++ b/railties/lib/generators.rb
@@ -11,7 +11,7 @@ end
$:.unshift(File.dirname(__FILE__))
-require 'vendor/thor-0.11.5/lib/thor'
+require 'vendor/thor-0.11.6/lib/thor'
require 'generators/base'
require 'generators/named_base'
@@ -45,7 +45,6 @@ module Rails
},
:erb => {
- :form => false,
:layout => true
},
@@ -76,39 +75,37 @@ module Rails
}
def self.aliases #:nodoc:
- @@aliases ||= DEFAULT_ALIASES.dup
+ @aliases ||= DEFAULT_ALIASES.dup
end
def self.options #:nodoc:
- @@options ||= DEFAULT_OPTIONS.dup
+ @options ||= DEFAULT_OPTIONS.dup
end
- # Get paths only from loaded rubygems. In other words, to use rspec
- # generators, you first have to ensure that rspec gem was already loaded.
+ # We have two scenarios here: when rubygems is loaded and when bundler is
+ # being used. If rubygems is loaded, we get all generators paths from loaded
+ # specs. Otherwise we just have to look into vendor/gems/gems.
#
- def self.rubygems_generators_paths
+ def self.gems_generators_paths
paths = []
- return paths unless defined?(Gem)
- Gem.loaded_specs.each do |name, spec|
- generator_path = File.join(spec.full_gem_path, "lib/generators")
- paths << generator_path if File.exist?(generator_path)
+ if defined?(Gem) && Gem.respond_to?(:loaded_specs)
+ Gem.loaded_specs.each do |name, spec|
+ generator_path = File.join(spec.full_gem_path, "lib/generators")
+ paths << generator_path if File.exist?(generator_path)
+ end
+ elsif defined?(RAILS_ROOT)
+ paths += Dir[File.join(RAILS_ROOT, "vendor", "gems", "gems", "*", "lib", "generators")]
end
paths
end
- # If RAILS_ROOT is defined, add vendor/gems, vendor/plugins and lib/generators
- # paths.
+ # Load paths from plugin.
#
- def self.rails_root_generators_paths
- paths = []
- if defined?(RAILS_ROOT)
- paths += Dir[File.join(RAILS_ROOT, "vendor", "gems", "gems", "*", "lib", "generators")]
- paths += Dir[File.join(RAILS_ROOT, "vendor", "plugins", "*", "lib", "generators")]
- paths << File.join(RAILS_ROOT, "lib", "generators")
- end
- paths
+ def self.plugins_generators_paths
+ return [] unless defined?(RAILS_ROOT)
+ Dir[File.join(RAILS_ROOT, "vendor", "plugins", "*", "lib", "generators")]
end
# Hold configured generators fallbacks. If a plugin developer wants a
@@ -125,7 +122,7 @@ module Rails
# Rails::Generators.fallbacks[:shoulda] = :test_unit
#
def self.fallbacks
- @@fallbacks ||= {}
+ @fallbacks ||= {}
end
# Remove the color from output.
@@ -143,14 +140,17 @@ module Rails
# 5) rubygems generators
# 6) builtin generators
#
- # TODO Remove hardcoded paths for all, except (1).
+ # TODO Remove hardcoded paths for all, except (6).
#
def self.load_paths
- @@load_paths ||= begin
- paths = self.rails_root_generators_paths
+ @load_paths ||= begin
+ paths = []
+ paths << File.join(RAILS_ROOT, "lib", "generators") if defined?(RAILS_ROOT)
paths << File.join(Thor::Util.user_home, ".rails", "generators")
- paths += self.rubygems_generators_paths
+ paths += self.plugins_generators_paths
+ paths += self.gems_generators_paths
paths << File.expand_path(File.join(File.dirname(__FILE__), "generators"))
+ paths.uniq!
paths
end
end
@@ -279,39 +279,18 @@ module Rails
end
# Receives namespaces in an array and tries to find matching generators
- # in the load path. Each path is traversed into directory lookups. For
- # example:
- #
- # rails:generators:model
- #
- # Becomes:
- #
- # generators/rails/model/model_generator.rb
- # generators/rails/model_generator.rb
- # generators/model_generator.rb
+ # in the load path.
#
def self.lookup(attempts) #:nodoc:
- attempts.each do |attempt|
- generators_path = ['.']
-
- paths = attempt.gsub(':generators:', ':').split(':')
- name = "#{paths.last}_generator.rb"
-
- until paths.empty?
- generators_path.unshift File.join(*paths)
- paths.pop
- end
-
- generators_path.uniq!
- generators_path = "{#{generators_path.join(',')}}"
-
- self.load_paths.each do |path|
- Dir[File.join(path, generators_path, name)].each do |file|
- begin
- require file
- rescue Exception => e
- warn "[WARNING] Could not load generator at #{file.inspect}. Error: #{e.message}"
- end
+ attempts = attempts.map { |a| "#{a.split(":").last}_generator" }.uniq
+ attempts = "{#{attempts.join(',')}}.rb"
+
+ self.load_paths.each do |path|
+ Dir[File.join(path, '**', attempts)].each do |file|
+ begin
+ require file
+ rescue Exception => e
+ warn "[WARNING] Could not load generator at #{file.inspect}. Error: #{e.message}"
end
end
end
diff --git a/railties/lib/generators/actions.rb b/railties/lib/generators/actions.rb
index 03d0d11a07..c4552dd399 100644
--- a/railties/lib/generators/actions.rb
+++ b/railties/lib/generators/actions.rb
@@ -89,13 +89,11 @@ module Rails
# git :add => "onefile.rb", :rm => "badfile.cxx"
#
def git(command={})
- in_root do
- if command.is_a?(Symbol)
- run "git #{command}"
- else
- command.each do |command, options|
- run "git #{command} #{options}"
- end
+ if command.is_a?(Symbol)
+ run "git #{command}"
+ else
+ command.each do |command, options|
+ run "git #{command} #{options}"
end
end
end
diff --git a/railties/lib/generators/active_record.rb b/railties/lib/generators/active_record.rb
index 924b70881a..ff3093f356 100644
--- a/railties/lib/generators/active_record.rb
+++ b/railties/lib/generators/active_record.rb
@@ -1,7 +1,6 @@
require 'generators/named_base'
require 'generators/migration'
require 'generators/active_model'
-require 'active_record'
module ActiveRecord
module Generators
diff --git a/railties/lib/generators/base.rb b/railties/lib/generators/base.rb
index cbe9c0a49d..c5d769b6ed 100644
--- a/railties/lib/generators/base.rb
+++ b/railties/lib/generators/base.rb
@@ -9,6 +9,8 @@ module Rails
include Thor::Actions
include Rails::Generators::Actions
+ add_runtime_options!
+
# Automatically sets the source root based on the class name.
#
def self.source_root
@@ -45,8 +47,10 @@ module Rails
#
# ==== Examples
#
- # class ControllerGenerator < Rails::Generators::Base
- # hook_for :test_framework, :aliases => "-t"
+ # module Rails::Generators
+ # class ControllerGenerator < Base
+ # hook_for :test_framework, :aliases => "-t"
+ # end
# end
#
# The example above will create a test framework option and will invoke
@@ -64,7 +68,49 @@ module Rails
# invoked. This allows any test framework to hook into Rails as long as it
# provides any of the hooks above.
#
- # Finally, if the user don't want to use any test framework, he can do:
+ # ==== Options
+ #
+ # This lookup can be customized with two options: :base and :as. The first
+ # is the root module value and in the example above defaults to "rails".
+ # The later defaults to the generator name, without the "Generator" ending.
+ #
+ # Let's suppose you are creating a generator that needs to invoke the
+ # controller generator from test unit. Your first attempt is:
+ #
+ # class AwesomeGenerator < Rails::Generators::Base
+ # hook_for :test_framework
+ # end
+ #
+ # The lookup in this case for test_unit as input is:
+ #
+ # "test_unit:generators:awesome", "test_unit"
+ #
+ # Which is not the desired the lookup. You can change it by providing the
+ # :as option:
+ #
+ # class AwesomeGenerator < Rails::Generators::Base
+ # hook_for :test_framework, :as => :controller
+ # end
+ #
+ # And now it will lookup at:
+ #
+ # "test_unit:generators:awesome", "test_unit"
+ #
+ # Similarly, if you want it to also lookup in the rails namespace, you just
+ # need to provide the :base value:
+ #
+ # class AwesomeGenerator < Rails::Generators::Base
+ # hook_for :test_framework, :base => :rails, :as => :controller
+ # end
+ #
+ # And the lookup is exactly the same as previously:
+ #
+ # "rails:generators:test_unit", "test_unit:generators:controller", "test_unit"
+ #
+ # ==== Switches
+ #
+ # All hooks come with switches for user interface. If the user don't want
+ # to use any test framework, he can do:
#
# ruby script/generate controller Account --skip-test-framework
#
diff --git a/railties/lib/generators/erb/scaffold/scaffold_generator.rb b/railties/lib/generators/erb/scaffold/scaffold_generator.rb
index 955f22192a..d51dc7d725 100644
--- a/railties/lib/generators/erb/scaffold/scaffold_generator.rb
+++ b/railties/lib/generators/erb/scaffold/scaffold_generator.rb
@@ -1,13 +1,13 @@
require 'generators/erb'
+require 'generators/resource_helpers'
module Erb
module Generators
class ScaffoldGenerator < Base
- include Rails::Generators::ScaffoldBase
+ include Rails::Generators::ResourceHelpers
argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
- class_option :form, :type => :boolean
class_option :layout, :type => :boolean
class_option :singleton, :type => :boolean, :desc => "Supply to skip index view"
@@ -33,7 +33,6 @@ module Erb
end
def copy_form_file
- return unless options[:form]
copy_view :_form
end
diff --git a/railties/lib/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/generators/erb/scaffold/templates/_form.html.erb
new file mode 100644
index 0000000000..d02028d983
--- /dev/null
+++ b/railties/lib/generators/erb/scaffold/templates/_form.html.erb
@@ -0,0 +1,17 @@
+<%% form_for(@<%= singular_name %>) do |f| %>
+ <%%= f.error_messages %>
+
+<% for attribute in attributes -%>
+ <div class="field">
+ <%%= f.label :<%= attribute.name %> %><br />
+ <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %>
+ </div>
+<% end -%>
+ <div class="actions">
+ <%% if @<%= singular_name %>.new_record? %>
+ <%%= f.submit 'Create' %>
+ <%% else %>
+ <%%= f.submit 'Update' %>
+ <%% end %>
+ </div>
+<%% end %>
diff --git a/railties/lib/generators/erb/scaffold/templates/edit.html.erb b/railties/lib/generators/erb/scaffold/templates/edit.html.erb
index cca1d61c68..5bc507ffc8 100644
--- a/railties/lib/generators/erb/scaffold/templates/edit.html.erb
+++ b/railties/lib/generators/erb/scaffold/templates/edit.html.erb
@@ -1,18 +1,6 @@
<h1>Editing <%= singular_name %></h1>
-<%% form_for(@<%= singular_name %>) do |f| %>
- <%%= f.error_messages %>
-
-<% for attribute in attributes -%>
- <p>
- <%%= f.label :<%= attribute.name %> %><br />
- <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %>
- </p>
-<% end -%>
- <p>
- <%%= f.submit 'Update' %>
- </p>
-<%% end %>
+<%%= render 'form' %>
<%%= link_to 'Show', @<%= singular_name %> %> |
-<%%= link_to 'Back', <%= plural_name %>_path %> \ No newline at end of file
+<%%= link_to 'Back', <%= plural_name %>_path %>
diff --git a/railties/lib/generators/erb/scaffold/templates/layout.html.erb b/railties/lib/generators/erb/scaffold/templates/layout.html.erb
index aacfbe4a8f..6460e5b599 100644
--- a/railties/lib/generators/erb/scaffold/templates/layout.html.erb
+++ b/railties/lib/generators/erb/scaffold/templates/layout.html.erb
@@ -7,7 +7,7 @@
</head>
<body>
-<p style="color: green"><%%= flash[:notice] %></p>
+<p class="notice"><%%= flash[:notice] %></p>
<%%= yield %>
diff --git a/railties/lib/generators/erb/scaffold/templates/new.html.erb b/railties/lib/generators/erb/scaffold/templates/new.html.erb
index 96c89fc50e..9a1c489331 100644
--- a/railties/lib/generators/erb/scaffold/templates/new.html.erb
+++ b/railties/lib/generators/erb/scaffold/templates/new.html.erb
@@ -1,17 +1,5 @@
<h1>New <%= singular_name %></h1>
-<%% form_for(@<%= singular_name %>) do |f| %>
- <%%= f.error_messages %>
+<%%= render 'form' %>
-<% for attribute in attributes -%>
- <p>
- <%%= f.label :<%= attribute.name %> %><br />
- <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %>
- </p>
-<% end -%>
- <p>
- <%%= f.submit 'Create' %>
- </p>
-<%% end %>
-
-<%%= link_to 'Back', <%= plural_name %>_path %> \ No newline at end of file
+<%%= link_to 'Back', <%= plural_name %>_path %>
diff --git a/railties/lib/generators/named_base.rb b/railties/lib/generators/named_base.rb
index cd7aa61b50..b6ac05f482 100644
--- a/railties/lib/generators/named_base.rb
+++ b/railties/lib/generators/named_base.rb
@@ -97,67 +97,5 @@ module Rails
end
end
end
-
- # Deal with controller names on scaffold. Also provide helpers to deal with
- # ActionORM.
- #
- module ScaffoldBase
- def self.included(base) #:nodoc:
- base.send :attr_reader, :controller_name, :controller_class_name, :controller_file_name,
- :controller_class_path, :controller_file_path
- end
-
- # Set controller variables on initialization.
- #
- def initialize(*args) #:nodoc:
- super
- @controller_name = name.pluralize
-
- base_name, @controller_class_path, @controller_file_path, class_nesting, class_nesting_depth = extract_modules(@controller_name)
- class_name_without_nesting, @controller_file_name, controller_plural_name = inflect_names(base_name)
-
- @controller_class_name = if class_nesting.empty?
- class_name_without_nesting
- else
- "#{class_nesting}::#{class_name_without_nesting}"
- end
- end
-
- protected
-
- # Loads the ORM::Generators::ActiveModel class. This class is responsable
- # to tell scaffold entities how to generate an specific method for the
- # ORM. Check Rails::Generators::ActiveModel for more information.
- #
- def orm_class
- @orm_class ||= begin
- # Raise an error if the class_option :orm was not defined.
- unless self.class.class_options[:orm]
- raise "You need to have :orm as class option to invoke orm_class and orm_instance"
- end
-
- action_orm = "#{options[:orm].to_s.classify}::Generators::ActiveModel"
-
- # If the orm was not loaded, try to load it at "generators/orm",
- # for example "generators/active_record" or "generators/sequel".
- begin
- klass = action_orm.constantize
- rescue NameError
- require "generators/#{options[:orm]}"
- end
-
- # Try once again after loading the file with success.
- klass ||= action_orm.constantize
- rescue Exception => e
- raise Error, "Could not load #{action_orm}, skipping controller. Error: #{e.message}."
- end
- end
-
- # Initialize ORM::Generators::ActiveModel to access instance methods.
- #
- def orm_instance(name=file_name)
- @orm_instance ||= @orm_class.new(name)
- end
- end
end
end
diff --git a/railties/lib/generators/rails/app/app_generator.rb b/railties/lib/generators/rails/app/app_generator.rb
index c80a344e0d..24c9a969f9 100644
--- a/railties/lib/generators/rails/app/app_generator.rb
+++ b/railties/lib/generators/rails/app/app_generator.rb
@@ -4,7 +4,7 @@ require 'rails/version' unless defined?(Rails::VERSION)
module Rails::Generators
class AppGenerator < Base
- DATABASES = %w( mysql oracle postgresql sqlite2 sqlite3 frontbase ibm_db )
+ DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db )
add_shebang_option!
argument :app_path, :type => :string
diff --git a/railties/lib/generators/rails/app/templates/config/databases/sqlite2.yml b/railties/lib/generators/rails/app/templates/config/databases/sqlite2.yml
deleted file mode 100644
index 46f01cb42c..0000000000
--- a/railties/lib/generators/rails/app/templates/config/databases/sqlite2.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-# SQLite version 2.x
-# gem install sqlite-ruby
-development:
- adapter: sqlite
- database: db/development.sqlite2
- pool: 5
-
-# Warning: The database defined as "test" will be erased and
-# re-generated from your development database when you run "rake".
-# Do not set this db to the same as development or production.
-test:
- adapter: sqlite
- database: db/test.sqlite2
- pool: 5
-
-production:
- adapter: sqlite
- database: db/production.sqlite2
- pool: 5
diff --git a/railties/lib/generators/rails/generator/generator_generator.rb b/railties/lib/generators/rails/generator/generator_generator.rb
index 2fc97b20d3..5b5d1884bc 100644
--- a/railties/lib/generators/rails/generator/generator_generator.rb
+++ b/railties/lib/generators/rails/generator/generator_generator.rb
@@ -6,7 +6,7 @@ module Rails
class_option :namespace, :type => :boolean, :default => true,
:desc => "Namespace generator under lib/generators/name"
- def craete_generator_files
+ def create_generator_files
directory '.', generator_dir
end
diff --git a/railties/lib/generators/rails/resource/resource_generator.rb b/railties/lib/generators/rails/resource/resource_generator.rb
index 70babc0550..9abb8bbeaf 100644
--- a/railties/lib/generators/rails/resource/resource_generator.rb
+++ b/railties/lib/generators/rails/resource/resource_generator.rb
@@ -1,25 +1,19 @@
require 'generators/rails/model/model_generator'
+require 'generators/resource_helpers'
module Rails
module Generators
class ResourceGenerator < ModelGenerator #metagenerator
+ include ResourceHelpers
+
hook_for :resource_controller, :required => true do |base, controller|
- base.invoke controller, [ base.name.pluralize, base.options[:actions] ]
+ base.invoke controller, [ base.controller_name, base.options[:actions] ]
end
class_option :actions, :type => :array, :banner => "ACTION ACTION", :default => [],
:desc => "Actions for the resource controller"
- class_option :singleton, :type => :boolean, :desc => "Supply to create a singleton controller"
- class_option :force_plural, :type => :boolean, :desc => "Forces the use of a plural ModelName"
-
- def initialize(*args)
- super
- if name == name.pluralize && !options[:force_plural]
- say "Plural version of the model detected, using singularized version. Override with --force-plural."
- name.replace name.singularize
- end
- end
+ class_option :singleton, :type => :boolean, :desc => "Supply to create a singleton controller"
def add_resource_route
route "map.resource#{:s unless options[:singleton]} :#{pluralize?(file_name)}"
diff --git a/railties/lib/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/generators/rails/scaffold/scaffold_generator.rb
index af44c8ba65..fdea5bf52b 100644
--- a/railties/lib/generators/rails/scaffold/scaffold_generator.rb
+++ b/railties/lib/generators/rails/scaffold/scaffold_generator.rb
@@ -3,7 +3,8 @@ require 'generators/rails/resource/resource_generator'
module Rails
module Generators
class ScaffoldGenerator < ResourceGenerator #metagenerator
- remove_hook_for :actions, :resource_controller
+ remove_hook_for :resource_controller
+ remove_class_option :actions
hook_for :scaffold_controller, :required => true
hook_for :stylesheets
diff --git a/railties/lib/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/generators/rails/scaffold_controller/scaffold_controller_generator.rb
index 972be5a33b..228cdecb14 100644
--- a/railties/lib/generators/rails/scaffold_controller/scaffold_controller_generator.rb
+++ b/railties/lib/generators/rails/scaffold_controller/scaffold_controller_generator.rb
@@ -1,8 +1,9 @@
+require 'generators/resource_helpers'
+
module Rails
module Generators
class ScaffoldControllerGenerator < NamedBase
- # Add controller methods and ActionORM settings.
- include ScaffoldBase
+ include ResourceHelpers
check_class_collision :suffix => "Controller"
diff --git a/railties/lib/generators/rails/stylesheets/templates/scaffold.css b/railties/lib/generators/rails/stylesheets/templates/scaffold.css
index 093c20994d..d9fa2cf2dc 100644
--- a/railties/lib/generators/rails/stylesheets/templates/scaffold.css
+++ b/railties/lib/generators/rails/stylesheets/templates/scaffold.css
@@ -16,6 +16,14 @@ a { color: #000; }
a:visited { color: #666; }
a:hover { color: #fff; background-color:#000; }
+div.field, div.actions {
+ margin-bottom: 10px;
+}
+
+.notice {
+ color: green;
+}
+
.fieldWithErrors {
padding: 2px;
background-color: red;
diff --git a/railties/lib/generators/resource_helpers.rb b/railties/lib/generators/resource_helpers.rb
new file mode 100644
index 0000000000..ba1444652d
--- /dev/null
+++ b/railties/lib/generators/resource_helpers.rb
@@ -0,0 +1,74 @@
+module Rails
+ module Generators
+ # Deal with controller names on scaffold and add some helpers to deal with
+ # ActiveModel.
+ #
+ module ResourceHelpers
+ def self.included(base) #:nodoc:
+ base.send :attr_reader, :controller_name, :controller_class_name, :controller_file_name,
+ :controller_class_path, :controller_file_path
+
+ base.send :class_option, :force_plural, :type => :boolean, :desc => "Forces the use of a plural ModelName"
+ end
+
+ # Set controller variables on initialization.
+ #
+ def initialize(*args) #:nodoc:
+ super
+
+ if name == name.pluralize && !options[:force_plural]
+ say "Plural version of the model detected, using singularized version. Override with --force-plural."
+ name.replace name.singularize
+ assign_names!(self.name)
+ end
+
+ @controller_name = name.pluralize
+
+ base_name, @controller_class_path, @controller_file_path, class_nesting, class_nesting_depth = extract_modules(@controller_name)
+ class_name_without_nesting, @controller_file_name, controller_plural_name = inflect_names(base_name)
+
+ @controller_class_name = if class_nesting.empty?
+ class_name_without_nesting
+ else
+ "#{class_nesting}::#{class_name_without_nesting}"
+ end
+ end
+
+ protected
+
+ # Loads the ORM::Generators::ActiveModel class. This class is responsable
+ # to tell scaffold entities how to generate an specific method for the
+ # ORM. Check Rails::Generators::ActiveModel for more information.
+ #
+ def orm_class
+ @orm_class ||= begin
+ # Raise an error if the class_option :orm was not defined.
+ unless self.class.class_options[:orm]
+ raise "You need to have :orm as class option to invoke orm_class and orm_instance"
+ end
+
+ active_model = "#{options[:orm].to_s.classify}::Generators::ActiveModel"
+
+ # If the orm was not loaded, try to load it at "generators/orm",
+ # for example "generators/active_record" or "generators/sequel".
+ begin
+ klass = active_model.constantize
+ rescue NameError
+ require "generators/#{options[:orm]}"
+ end
+
+ # Try once again after loading the file with success.
+ klass ||= active_model.constantize
+ rescue Exception => e
+ raise Error, "Could not load #{active_model}, skipping controller. Error: #{e.message}."
+ end
+ end
+
+ # Initialize ORM::Generators::ActiveModel to access instance methods.
+ #
+ def orm_instance(name=file_name)
+ @orm_instance ||= @orm_class.new(name)
+ end
+ end
+ end
+end
diff --git a/railties/lib/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/generators/test_unit/scaffold/scaffold_generator.rb
index 78fcea1e9c..a8f9c8bd79 100644
--- a/railties/lib/generators/test_unit/scaffold/scaffold_generator.rb
+++ b/railties/lib/generators/test_unit/scaffold/scaffold_generator.rb
@@ -1,9 +1,10 @@
require 'generators/test_unit'
+require 'generators/resource_helpers'
module TestUnit
module Generators
class ScaffoldGenerator < Base
- include Rails::Generators::ScaffoldBase
+ include Rails::Generators::ResourceHelpers
class_option :singleton, :type => :boolean, :desc => "Supply to create a singleton controller"
check_class_collision :suffix => "ControllerTest"
diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb
index 49ec5c7fba..1c0af6411a 100644
--- a/railties/lib/rails/plugin.rb
+++ b/railties/lib/rails/plugin.rb
@@ -71,6 +71,10 @@ module Rails
File.exist?(routing_file)
end
+ # Returns true if there is any localization file in locale_path
+ def localized?
+ locale_files.any?
+ end
def view_path
File.join(directory, 'app', 'views')
@@ -87,6 +91,14 @@ module Rails
def routing_file
File.join(directory, 'config', 'routes.rb')
end
+
+ def locale_path
+ File.join(directory, 'config', 'locales')
+ end
+
+ def locale_files
+ Dir[ File.join(locale_path, '*.{rb,yml}') ]
+ end
private
diff --git a/railties/lib/rails/plugin/loader.rb b/railties/lib/rails/plugin/loader.rb
index 7ea9c7c0f3..0d16cbd7c3 100644
--- a/railties/lib/rails/plugin/loader.rb
+++ b/railties/lib/rails/plugin/loader.rb
@@ -73,6 +73,7 @@ module Rails
def configure_engines
if engines.any?
add_engine_routing_configurations
+ add_engine_locales
add_engine_controller_paths
add_engine_view_paths
end
@@ -84,6 +85,14 @@ module Rails
end
end
+ def add_engine_locales
+ localized_engines = engines.select { |engine| engine.localized? }
+
+ # reverse it such that the last engine can overwrite translations from the first, like with routes
+ locale_files = localized_engines.collect { |engine| engine.locale_files }.reverse.flatten
+ I18n.load_path += locale_files - I18n.load_path
+ end
+
def add_engine_controller_paths
ActionController::Routing.controller_paths += engines.collect {|engine| engine.controller_path }
end
diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake
index b82341ba94..8401cf0590 100644
--- a/railties/lib/tasks/databases.rake
+++ b/railties/lib/tasks/databases.rake
@@ -55,7 +55,7 @@ namespace :db do
case config['adapter']
when 'mysql'
@charset = ENV['CHARSET'] || 'utf8'
- @collation = ENV['COLLATION'] || 'utf8_general_ci'
+ @collation = ENV['COLLATION'] || 'utf8_unicode_ci'
creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)}
begin
ActiveRecord::Base.establish_connection(config.merge('database' => nil))
@@ -292,7 +292,11 @@ namespace :db do
desc "Load a schema.rb file into the database"
task :load => :environment do
file = ENV['SCHEMA'] || "#{RAILS_ROOT}/db/schema.rb"
- load(file)
+ if File.exists?(file)
+ load(file)
+ else
+ abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{RAILS_ROOT}/config/environment.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]}
+ end
end
end
diff --git a/railties/lib/vendor/thor-0.11.5/CHANGELOG.rdoc b/railties/lib/vendor/thor-0.11.6/CHANGELOG.rdoc
index dba25b7205..dba25b7205 100644
--- a/railties/lib/vendor/thor-0.11.5/CHANGELOG.rdoc
+++ b/railties/lib/vendor/thor-0.11.6/CHANGELOG.rdoc
diff --git a/railties/lib/vendor/thor-0.11.5/LICENSE b/railties/lib/vendor/thor-0.11.6/LICENSE
index 98722da459..98722da459 100644
--- a/railties/lib/vendor/thor-0.11.5/LICENSE
+++ b/railties/lib/vendor/thor-0.11.6/LICENSE
diff --git a/railties/lib/vendor/thor-0.11.5/README.rdoc b/railties/lib/vendor/thor-0.11.6/README.rdoc
index f1106f02b6..f1106f02b6 100644
--- a/railties/lib/vendor/thor-0.11.5/README.rdoc
+++ b/railties/lib/vendor/thor-0.11.6/README.rdoc
diff --git a/railties/lib/vendor/thor-0.11.5/bin/rake2thor b/railties/lib/vendor/thor-0.11.6/bin/rake2thor
index 50c7410d80..50c7410d80 100755
--- a/railties/lib/vendor/thor-0.11.5/bin/rake2thor
+++ b/railties/lib/vendor/thor-0.11.6/bin/rake2thor
diff --git a/railties/lib/vendor/thor-0.11.5/bin/thor b/railties/lib/vendor/thor-0.11.6/bin/thor
index eaf849fb4a..eaf849fb4a 100755
--- a/railties/lib/vendor/thor-0.11.5/bin/thor
+++ b/railties/lib/vendor/thor-0.11.6/bin/thor
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor.rb b/railties/lib/vendor/thor-0.11.6/lib/thor.rb
index 8dfcfd4c5b..3b45c4e9b7 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor.rb
@@ -135,7 +135,7 @@ class Thor
args, opts = given_args, {}
end
- task ||= Task.dynamic(meth)
+ task ||= Thor::Task::Dynamic.new(meth)
trailing = args[Range.new(arguments.size, -1)]
new(args, opts, config).invoke(task, trailing || [])
end
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/actions.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/actions.rb
index 1d09dc38ae..d561ccb2aa 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/actions.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/actions.rb
@@ -8,23 +8,8 @@ class Thor
module Actions
attr_accessor :behavior
- # On inclusion, add some options to base.
- #
def self.included(base) #:nodoc:
base.extend ClassMethods
- return unless base.respond_to?(:class_option)
-
- base.class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime,
- :desc => "Run but do not make any changes"
-
- base.class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime,
- :desc => "Overwrite files that already exist"
-
- base.class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime,
- :desc => "Skip files that already exist"
-
- base.class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime,
- :desc => "Supress status output"
end
module ClassMethods
@@ -49,6 +34,22 @@ class Thor
paths += from_superclass(:source_paths, [])
paths
end
+
+ # Add runtime options that help actions execution.
+ #
+ def add_runtime_options!
+ class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime,
+ :desc => "Run but do not make any changes"
+
+ class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime,
+ :desc => "Overwrite files that already exist"
+
+ class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime,
+ :desc => "Skip files that already exist"
+
+ class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime,
+ :desc => "Supress status output"
+ end
end
# Extends initializer to add more configuration options.
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/actions/create_file.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/actions/create_file.rb
index 8f6badee27..8f6badee27 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/actions/create_file.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/actions/create_file.rb
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/actions/directory.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/actions/directory.rb
index be5eb822ac..be5eb822ac 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/actions/directory.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/actions/directory.rb
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/actions/empty_directory.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/actions/empty_directory.rb
index 03c1fe4af1..03c1fe4af1 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/actions/empty_directory.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/actions/empty_directory.rb
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/actions/file_manipulation.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/actions/file_manipulation.rb
index 74c157ba8c..d77d90d448 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/actions/file_manipulation.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/actions/file_manipulation.rb
@@ -100,7 +100,7 @@ class Thor
FileUtils.chmod_R(mode, path) unless options[:pretend]
end
- # Prepend text to a file.
+ # Prepend text to a file. Since it depends on inject_into_file, it's reversible.
#
# ==== Parameters
# path<String>:: path of the file to be changed
@@ -111,19 +111,17 @@ class Thor
#
# prepend_file 'config/environments/test.rb', 'config.gem "rspec"'
#
- def prepend_file(path, data=nil, config={}, &block)
- return unless behavior == :invoke
- path = File.expand_path(path, destination_root)
- say_status :prepend, relative_to_original_destination_root(path), config.fetch(:verbose, true)
-
- unless options[:pretend]
- content = data || block.call
- content << File.read(path)
- File.open(path, 'wb') { |file| file.write(content) }
- end
+ # prepend_file 'config/environments/test.rb' do
+ # 'config.gem "rspec"'
+ # end
+ #
+ def prepend_file(path, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ config.merge!(:after => /\A/)
+ inject_into_file(path, *(args << config), &block)
end
- # Append text to a file.
+ # Append text to a file. Since it depends on inject_into_file, it's reversible.
#
# ==== Parameters
# path<String>:: path of the file to be changed
@@ -134,11 +132,37 @@ class Thor
#
# append_file 'config/environments/test.rb', 'config.gem "rspec"'
#
- def append_file(path, data=nil, config={}, &block)
- return unless behavior == :invoke
- path = File.expand_path(path, destination_root)
- say_status :append, relative_to_original_destination_root(path), config.fetch(:verbose, true)
- File.open(path, 'ab') { |file| file.write(data || block.call) } unless options[:pretend]
+ # append_file 'config/environments/test.rb' do
+ # 'config.gem "rspec"'
+ # end
+ #
+ def append_file(path, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ config.merge!(:before => /\z/)
+ inject_into_file(path, *(args << config), &block)
+ end
+
+ # Injects text right after the class definition. Since it depends on
+ # inject_into_file, it's reversible.
+ #
+ # ==== Parameters
+ # path<String>:: path of the file to be changed
+ # klass<String|Class>:: the class to be manipulated
+ # data<String>:: the data to append to the class, can be also given as a block.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Examples
+ #
+ # inject_into_class "app/controllers/application_controller.rb", " filter_parameter :password\n"
+ #
+ # inject_into_class "app/controllers/application_controller.rb", ApplicationController do
+ # " filter_parameter :password\n"
+ # end
+ #
+ def inject_into_class(path, klass, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ config.merge!(:after => /class #{klass}\n|class #{klass} .*\n/)
+ inject_into_file(path, *(args << config), &block)
end
# Run a regular expression replacement on a file.
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/actions/inject_into_file.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/actions/inject_into_file.rb
index 66dd1f5fc1..0636ec6591 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/actions/inject_into_file.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/actions/inject_into_file.rb
@@ -3,10 +3,8 @@ require 'thor/actions/empty_directory'
class Thor
module Actions
- # Injects the given content into a file. Different from append_file,
- # prepend_file and gsub_file, this method is reversible. By this reason,
- # the flag can only be strings. gsub_file is your friend if you need to
- # deal with more complex cases.
+ # Injects the given content into a file. Different from gsub_file, this
+ # method is reversible.
#
# ==== Parameters
# destination<String>:: Relative path to the destination root
@@ -16,11 +14,11 @@ class Thor
#
# ==== Examples
#
- # inject_into_file "config/environment.rb", "config.gem thor", :after => "Rails::Initializer.run do |config|\n"
+ # inject_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n"
#
# inject_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do
# gems = ask "Which gems would you like to add?"
- # gems.split(" ").map{ |gem| " config.gem #{gem}" }.join("\n")
+ # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
# end
#
def inject_into_file(destination, *args, &block)
@@ -29,40 +27,65 @@ class Thor
else
data, config = args.shift, args.shift
end
-
- log_status = args.empty? || args.pop
action InjectIntoFile.new(self, destination, data, config)
end
class InjectIntoFile < EmptyDirectory #:nodoc:
- attr_reader :flag, :replacement
+ attr_reader :replacement, :flag, :behavior
def initialize(base, destination, data, config)
super(base, destination, { :verbose => true }.merge(config))
- data = data.call if data.is_a?(Proc)
-
- @replacement = if @config.key?(:after)
- @flag = @config.delete(:after)
- @flag + data
+ @behavior, @flag = if @config.key?(:after)
+ [:after, @config.delete(:after)]
else
- @flag = @config.delete(:before)
- data + @flag
+ [:before, @config.delete(:before)]
end
+
+ @replacement = data.is_a?(Proc) ? data.call : data
+ @flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp)
end
def invoke!
- say_status :inject, config[:verbose]
- replace!(flag, replacement)
+ say_status :invoke
+
+ content = if @behavior == :after
+ '\0' + replacement
+ else
+ replacement + '\0'
+ end
+
+ replace!(/#{flag}/, content)
end
def revoke!
- say_status :deinject, config[:verbose]
- replace!(replacement, flag)
+ say_status :revoke
+
+ regexp = if @behavior == :after
+ content = '\1\2'
+ /(#{flag})(.*)(#{Regexp.escape(replacement)})/m
+ else
+ content = '\2\3'
+ /(#{Regexp.escape(replacement)})(.*)(#{flag})/m
+ end
+
+ replace!(regexp, content)
end
protected
+ def say_status(behavior)
+ status = if flag == /\A/
+ behavior == :invoke ? :prepend : :unprepend
+ elsif flag == /\z/
+ behavior == :invoke ? :append : :unappend
+ else
+ behavior == :invoke ? :inject : :deinject
+ end
+
+ super(status, config[:verbose])
+ end
+
# Adds the content to the file.
#
def replace!(regexp, string)
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/base.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/base.rb
index 0fa87f8162..700d794123 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/base.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/base.rb
@@ -355,6 +355,7 @@ class Thor
else
config[:shell].error e.message
end
+ exit(1) if exit_on_failure?
end
protected
@@ -491,6 +492,12 @@ class Thor
end
end
+ # A flag that makes the process exit with status 1 if any error happens.
+ #
+ def exit_on_failure?
+ false
+ end
+
# SIGNATURE: Sets the baseclass. This is where the superclass lookup
# finishes.
def baseclass #:nodoc:
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/hash_with_indifferent_access.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/core_ext/hash_with_indifferent_access.rb
index 78bc5cf4bf..78bc5cf4bf 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/hash_with_indifferent_access.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/core_ext/hash_with_indifferent_access.rb
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/ordered_hash.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/core_ext/ordered_hash.rb
index 27fea5bb35..27fea5bb35 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/ordered_hash.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/core_ext/ordered_hash.rb
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/error.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/error.rb
index f9b31a35d1..f9b31a35d1 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/error.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/error.rb
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/group.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/group.rb
index 1e59df2313..1e59df2313 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/group.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/group.rb
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/invocation.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/invocation.rb
index c0388dd863..32e6a72454 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/invocation.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/invocation.rb
@@ -153,7 +153,7 @@ class Thor
raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
task ||= klass.default_task if klass <= Thor
- task = klass.all_tasks[task.to_s] || Task.dynamic(task) if task && !task.is_a?(Thor::Task)
+ task = klass.all_tasks[task.to_s] || Thor::Task::Dynamic.new(task) if task && !task.is_a?(Thor::Task)
task
end
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/parser.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/parser.rb
index 57a3f6e1a5..57a3f6e1a5 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/parser.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/parser.rb
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/parser/argument.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/parser/argument.rb
index aa8ace4719..aa8ace4719 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/parser/argument.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/parser/argument.rb
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/parser/arguments.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/parser/arguments.rb
index fb5d965e06..fb5d965e06 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/parser/arguments.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/parser/arguments.rb
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/parser/option.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/parser/option.rb
index 9e40ec73fa..9e40ec73fa 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/parser/option.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/parser/option.rb
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/parser/options.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/parser/options.rb
index 75092308b5..75092308b5 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/parser/options.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/parser/options.rb
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/rake_compat.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/rake_compat.rb
index 3ab6bb21f5..3ab6bb21f5 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/rake_compat.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/rake_compat.rb
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/runner.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/runner.rb
index 3639ac0aa9..43da09b336 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/runner.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/runner.rb
@@ -175,6 +175,10 @@ class Thor::Runner < Thor #:nodoc:
File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml }
end
+ def self.exit_on_failure?
+ true
+ end
+
# Load the thorfiles. If relevant_to is supplied, looks for specific files
# in the thor_root instead of loading them all.
#
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/shell.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/shell.rb
index 0d3f4d5951..0d3f4d5951 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/shell.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/shell.rb
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/shell/basic.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/shell/basic.rb
index 3c02e47c33..ea9665380b 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/shell/basic.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/shell/basic.rb
@@ -34,12 +34,11 @@ class Thor
# ==== Example
# say("I know you knew that.")
#
- def say(message="", color=nil, force_new_line=false)
+ def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)$/))
message = message.to_s
- new_line = force_new_line || !(message[-1, 1] == " " || message[-1, 1] == "\t")
message = set_color(message, color) if color
- if new_line
+ if force_new_line
$stdout.puts(message)
else
$stdout.print(message)
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/shell/color.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/shell/color.rb
index 24704f7885..24704f7885 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/shell/color.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/shell/color.rb
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/task.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/task.rb
index 23d35b883c..91c7564d3f 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/task.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/task.rb
@@ -1,11 +1,19 @@
class Thor
class Task < Struct.new(:name, :description, :usage, :options)
- # Creates a dynamic task. Dynamic tasks are created on demand to allow method
- # missing calls (since a method missing does not have a task object for it).
+ # A dynamic task that handles method missing scenarios.
#
- def self.dynamic(name)
- new(name, "A dynamically-generated task", name.to_s)
+ class Dynamic < Task
+ def initialize(name)
+ super(name.to_s, "A dynamically-generated task", name.to_s)
+ end
+
+ def run(instance, args=[])
+ unless (instance.methods & [name.to_s, name.to_sym]).empty?
+ raise Error, "could not find Thor class or task '#{name}'"
+ end
+ super
+ end
end
def initialize(name, description, usage, options=nil)
@@ -37,8 +45,6 @@ class Thor
# injected in the usage.
#
def formatted_usage(klass=nil, namespace=false, show_options=true)
- formatted = ''
-
formatted = if namespace.is_a?(String)
"#{namespace}:"
elsif klass && namespace
@@ -77,7 +83,7 @@ class Thor
#
def public_method?(instance) #:nodoc:
collection = instance.private_methods + instance.protected_methods
- !(collection).include?(name.to_s) && !(collection).include?(name.to_sym) # For Ruby 1.9
+ (collection & [name.to_s, name.to_sym]).empty?
end
# Clean everything that comes from the Thor gempath and remove the caller.
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/util.rb b/railties/lib/vendor/thor-0.11.6/lib/thor/util.rb
index 4938dc4aca..fd820d7462 100644
--- a/railties/lib/vendor/thor-0.11.5/lib/thor/util.rb
+++ b/railties/lib/vendor/thor-0.11.6/lib/thor/util.rb
@@ -22,7 +22,7 @@ class Thor
# namespace<String>:: The namespace to search for.
#
def self.find_by_namespace(namespace)
- namespace = 'default' if namespace.empty?
+ namespace = "default#{namespace}" if namespace.empty? || namespace =~ /^:/
Thor::Base.subclasses.find do |klass|
klass.namespace == namespace
@@ -137,17 +137,18 @@ class Thor
# inherit from Thor or Thor::Group.
#
def self.namespace_to_thor_class_and_task(namespace, raise_if_nil=true)
- klass, task_name = Thor::Util.find_by_namespace(namespace), nil
+ if namespace.include?(?:)
+ pieces = namespace.split(":")
+ task = pieces.pop
+ klass = Thor::Util.find_by_namespace(pieces.join(":"))
+ end
- if klass.nil? && namespace.include?(?:)
- namespace = namespace.split(":")
- task_name = namespace.pop
- klass = Thor::Util.find_by_namespace(namespace.join(":"))
+ unless klass
+ klass, task = Thor::Util.find_by_namespace(namespace), nil
end
raise Error, "could not find Thor class or task '#{namespace}'" if raise_if_nil && klass.nil?
-
- return klass, task_name
+ return klass, task
end
# Receives a path and load the thor file in the path. The file is evaluated
diff --git a/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml b/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml
new file mode 100644
index 0000000000..641a7e035c
--- /dev/null
+++ b/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml
@@ -0,0 +1,2 @@
+en:
+ hello: "Hello from Engine"
diff --git a/railties/test/fixtures/vendor/another_gem_path/xspec/lib/generators/xspec_generator.rb b/railties/test/fixtures/vendor/another_gem_path/xspec/lib/generators/xspec_generator.rb
new file mode 100644
index 0000000000..cd477eb4c9
--- /dev/null
+++ b/railties/test/fixtures/vendor/another_gem_path/xspec/lib/generators/xspec_generator.rb
@@ -0,0 +1,2 @@
+class XspecGenerator < Rails::Generators::NamedBase
+end
diff --git a/railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb b/railties/test/fixtures/vendor/plugins/mspec/lib/generators/mspec_generator.rb
index 191bdbf2fc..191bdbf2fc 100644
--- a/railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb
+++ b/railties/test/fixtures/vendor/plugins/mspec/lib/generators/mspec_generator.rb
diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb
index 9444a9ed4b..a258574dce 100644
--- a/railties/test/generators/generators_test_helper.rb
+++ b/railties/test/generators/generators_test_helper.rb
@@ -8,9 +8,12 @@ else
RAILS_ROOT = fixtures
end
-$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../activerecord/lib"
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../lib"
+$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../activerecord/lib"
+$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../actionpack/lib"
require 'generators'
+require 'activerecord'
+require 'action_dispatch'
CURRENT_PATH = File.expand_path(Dir.pwd)
Rails::Generators.no_color!
@@ -19,7 +22,7 @@ class GeneratorsTestCase < Test::Unit::TestCase
include FileUtils
def destination_root
- @destination_root ||= File.expand_path(File.join(File.dirname(__FILE__),
+ @destination_root ||= File.expand_path(File.join(File.dirname(__FILE__),
'..', 'fixtures', 'tmp'))
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 05eadd3460..63559a8a01 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -75,6 +75,7 @@ class ScaffoldGeneratorTest < GeneratorsTestCase
edit
new
show
+ _form
).each { |view| assert_file "app/views/product_lines/#{view}.html.erb" }
assert_file "app/views/layouts/product_lines.html.erb"
diff --git a/railties/test/generators/session_migration_generator_test.rb b/railties/test/generators/session_migration_generator_test.rb
index 57bd755a9a..293b903b87 100644
--- a/railties/test/generators/session_migration_generator_test.rb
+++ b/railties/test/generators/session_migration_generator_test.rb
@@ -2,16 +2,6 @@ require 'abstract_unit'
require 'generators/generators_test_helper'
require 'generators/rails/session_migration/session_migration_generator'
-module ActiveRecord
- module SessionStore
- class Session
- class << self
- attr_accessor :table_name
- end
- end
- end
-end
-
class SessionMigrationGeneratorTest < GeneratorsTestCase
def test_session_migration_with_default_name
@@ -31,7 +21,10 @@ class SessionMigrationGeneratorTest < GeneratorsTestCase
assert_match /class AddSessionsTable < ActiveRecord::Migration/, migration
assert_match /create_table :custom_table_name/, migration
end
+ ensure
+ ActiveRecord::SessionStore::Session.table_name = "sessions"
end
+
protected
def run_generator(args=[])
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 89d52dd170..4cc0b33521 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -4,6 +4,11 @@ require 'generators/test_unit/model/model_generator'
require 'mocha'
class GeneratorsTest < GeneratorsTestCase
+ def setup
+ Rails::Generators.instance_variable_set(:@load_paths, nil)
+ Gem.stubs(:respond_to?).with(:loaded_specs).returns(false)
+ end
+
def test_invoke_when_generator_is_not_found
output = capture(:stdout){ Rails::Generators.invoke :unknown }
assert_equal "Could not find generator unknown.\n", output
@@ -70,6 +75,20 @@ class GeneratorsTest < GeneratorsTestCase
assert_equal "mspec", klass.namespace
end
+ def test_find_by_namespace_lookup_with_gem_specification
+ assert_nil Rails::Generators.find_by_namespace(:xspec)
+ Rails::Generators.instance_variable_set(:@load_paths, nil)
+
+ spec = Gem::Specification.new
+ spec.expects(:full_gem_path).returns(File.join(RAILS_ROOT, 'vendor', 'another_gem_path', 'xspec'))
+ Gem.expects(:respond_to?).with(:loaded_specs).returns(true)
+ Gem.expects(:loaded_specs).returns(:spec => spec)
+
+ klass = Rails::Generators.find_by_namespace(:xspec)
+ assert klass
+ assert_equal "xspec", klass.namespace
+ end
+
def test_builtin_generators
assert Rails::Generators.builtin.include? %w(rails model)
end
diff --git a/railties/test/initializer_test.rb b/railties/test/initializer_test.rb
index 1fecd62995..5bbd060962 100644
--- a/railties/test/initializer_test.rb
+++ b/railties/test/initializer_test.rb
@@ -406,6 +406,7 @@ class InitializerSetupI18nTests < Test::Unit::TestCase
File.expand_path(File.dirname(__FILE__) + "/../../actionpack/lib/action_view/locale/en.yml"),
File.expand_path(File.dirname(__FILE__) + "/../../activemodel/lib/active_model/locale/en.yml"),
File.expand_path(File.dirname(__FILE__) + "/../../activerecord/lib/active_record/locale/en.yml"),
+ File.expand_path(File.dirname(__FILE__) + "/../../railties/test/fixtures/plugins/engines/engine/config/locales/en.yml"),
"my/test/locale.yml",
"my/other/locale.yml" ], I18n.load_path.collect { |path| path =~ /\.\./ ? File.expand_path(path) : path }
end
diff --git a/railties/test/plugin_loader_test.rb b/railties/test/plugin_loader_test.rb
index 873e000222..99301347b6 100644
--- a/railties/test/plugin_loader_test.rb
+++ b/railties/test/plugin_loader_test.rb
@@ -156,6 +156,14 @@ class TestPluginLoader < Test::Unit::TestCase
plugin_load_paths.each { |path| assert $LOAD_PATH.include?(path) }
end
+ def test_should_add_locale_files_to_I18n_load_path
+ only_load_the_following_plugins! [:engine]
+
+ @loader.send :add_engine_locales
+
+ assert I18n.load_path.include?(File.join(plugin_fixture_path('engines/engine'), 'config', 'locales', 'en.yml'))
+ end
+
private
def reset_load_path!