aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile6
-rw-r--r--README.rdoc1
-rw-r--r--actionmailer/CHANGELOG.md54
-rw-r--r--actionmailer/README.rdoc2
-rwxr-xr-xactionmailer/Rakefile1
-rw-r--r--actionmailer/actionmailer.gemspec2
-rw-r--r--actionmailer/lib/action_mailer/base.rb11
-rw-r--r--actionmailer/lib/action_mailer/collector.rb4
-rw-r--r--actionmailer/lib/action_mailer/mail_helper.rb9
-rw-r--r--actionmailer/lib/action_mailer/test_helper.rb2
-rw-r--r--actionmailer/test/mail_helper_test.rb44
-rw-r--r--actionpack/CHANGELOG.md264
-rw-r--r--actionpack/README.rdoc2
-rwxr-xr-xactionpack/Rakefile16
-rw-r--r--actionpack/actionpack.gemspec6
-rw-r--r--actionpack/lib/abstract_controller/base.rb6
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb28
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb2
-rw-r--r--actionpack/lib/action_controller.rb1
-rw-r--r--actionpack/lib/action_controller/base.rb13
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb13
-rw-r--r--actionpack/lib/action_controller/deprecated.rb6
-rw-r--r--actionpack/lib/action_controller/deprecated/integration_test.rb3
-rw-r--r--actionpack/lib/action_controller/deprecated/performance_test.rb2
-rw-r--r--actionpack/lib/action_controller/metal.rb16
-rw-r--r--actionpack/lib/action_controller/metal/compatibility.rb58
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb11
-rw-r--r--actionpack/lib/action_controller/metal/exceptions.rb2
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb20
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb6
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb2
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb44
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb10
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb18
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb2
-rw-r--r--actionpack/lib/action_controller/railtie.rb7
-rw-r--r--actionpack/lib/action_controller/test_case.rb16
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb14
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/mime_types.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/rack_cache.rb5
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb17
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb5
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb13
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb3
-rw-r--r--actionpack/lib/action_dispatch/routing.rb19
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb157
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb41
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb10
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb35
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb3
-rw-r--r--actionpack/lib/action_view/asset_paths.rb4
-rw-r--r--actionpack/lib/action_view/base.rb8
-rw-r--r--actionpack/lib/action_view/flows.rb5
-rw-r--r--actionpack/lib/action_view/helpers/active_model_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb98
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb7
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb7
-rw-r--r--actionpack/lib/action_view/helpers/csrf_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb120
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb379
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb290
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb26
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb1
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb131
-rw-r--r--actionpack/lib/action_view/helpers/output_safety_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/record_tag_helper.rb25
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb51
-rw-r--r--actionpack/lib/action_view/helpers/tags.rb33
-rw-r--r--actionpack/lib/action_view/helpers/tags/base.rb148
-rw-r--r--actionpack/lib/action_view/helpers/tags/check_box.rb58
-rw-r--r--actionpack/lib/action_view/helpers/tags/checkable.rb16
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb37
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_helpers.rb82
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb30
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_select.rb28
-rw-r--r--actionpack/lib/action_view/helpers/tags/date_field.rb15
-rw-r--r--actionpack/lib/action_view/helpers/tags/date_select.rb70
-rw-r--r--actionpack/lib/action_view/helpers/tags/datetime_select.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/email_field.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/file_field.rb12
-rw-r--r--actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb29
-rw-r--r--actionpack/lib/action_view/helpers/tags/hidden_field.rb12
-rw-r--r--actionpack/lib/action_view/helpers/tags/label.rb65
-rw-r--r--actionpack/lib/action_view/helpers/tags/number_field.rb19
-rw-r--r--actionpack/lib/action_view/helpers/tags/password_field.rb12
-rw-r--r--actionpack/lib/action_view/helpers/tags/radio_button.rb31
-rw-r--r--actionpack/lib/action_view/helpers/tags/range_field.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/search_field.rb24
-rw-r--r--actionpack/lib/action_view/helpers/tags/select.rb41
-rw-r--r--actionpack/lib/action_view/helpers/tags/tel_field.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/text_area.rb20
-rw-r--r--actionpack/lib/action_view/helpers/tags/text_field.rb30
-rw-r--r--actionpack/lib/action_view/helpers/tags/time_select.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/time_zone_select.rb20
-rw-r--r--actionpack/lib/action_view/helpers/tags/url_field.rb8
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb8
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb90
-rw-r--r--actionpack/lib/action_view/locale/en.yml10
-rw-r--r--actionpack/lib/action_view/log_subscriber.rb3
-rw-r--r--actionpack/lib/action_view/lookup_context.rb15
-rw-r--r--actionpack/lib/action_view/railtie.rb4
-rw-r--r--actionpack/lib/action_view/renderer/abstract_renderer.rb2
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb3
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb3
-rw-r--r--actionpack/lib/action_view/template.rb5
-rw-r--r--actionpack/lib/action_view/template/error.rb4
-rw-r--r--actionpack/lib/action_view/template/handlers.rb14
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb4
-rw-r--r--actionpack/lib/action_view/test_case.rb6
-rw-r--r--actionpack/lib/sprockets/assets.rake18
-rw-r--r--actionpack/lib/sprockets/compressors.rb42
-rw-r--r--actionpack/lib/sprockets/helpers/rails_helper.rb12
-rw-r--r--actionpack/lib/sprockets/railtie.rb5
-rw-r--r--actionpack/lib/sprockets/static_compiler.rb4
-rw-r--r--actionpack/test/abstract_unit.rb15
-rw-r--r--actionpack/test/activerecord/active_record_store_test.rb10
-rw-r--r--actionpack/test/controller/addresses_render_test.rb35
-rw-r--r--actionpack/test/controller/assert_select_test.rb4
-rw-r--r--actionpack/test/controller/base_test.rb71
-rw-r--r--actionpack/test/controller/caching_test.rb20
-rw-r--r--actionpack/test/controller/capture_test.rb2
-rw-r--r--actionpack/test/controller/content_type_test.rb10
-rw-r--r--actionpack/test/controller/filters_test.rb16
-rw-r--r--actionpack/test/controller/flash_test.rb4
-rw-r--r--actionpack/test/controller/force_ssl_test.rb45
-rw-r--r--actionpack/test/controller/helper_test.rb7
-rw-r--r--actionpack/test/controller/integration_test.rb73
-rw-r--r--actionpack/test/controller/mime_responds_test.rb66
-rw-r--r--actionpack/test/controller/redirect_test.rb2
-rw-r--r--actionpack/test/controller/render_test.rb52
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb24
-rw-r--r--actionpack/test/controller/rescue_test.rb4
-rw-r--r--actionpack/test/controller/resources_test.rb26
-rw-r--r--actionpack/test/controller/routing_test.rb179
-rw-r--r--actionpack/test/controller/test_case_test.rb50
-rw-r--r--actionpack/test/controller/view_paths_test.rb16
-rw-r--r--actionpack/test/controller/webservice_test.rb2
-rw-r--r--actionpack/test/dispatch/callbacks_test.rb11
-rw-r--r--actionpack/test/dispatch/cookies_test.rb11
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb6
-rw-r--r--actionpack/test/dispatch/middleware_stack_test.rb6
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb6
-rw-r--r--actionpack/test/dispatch/reloader_test.rb13
-rw-r--r--actionpack/test/dispatch/request_test.rb38
-rw-r--r--actionpack/test/dispatch/routing_test.rb2229
-rw-r--r--actionpack/test/dispatch/session/cache_store_test.rb2
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb2
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb2
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb4
-rw-r--r--actionpack/test/dispatch/static_test.rb97
-rw-r--r--actionpack/test/fixtures/addresses/list.erb1
-rw-r--r--actionpack/test/fixtures/developers.yml2
-rw-r--r--actionpack/test/fixtures/public/foo/こんにちは.html1
-rw-r--r--actionpack/test/fixtures/test/one.html.erb1
-rw-r--r--actionpack/test/fixtures/with_format.json.erb1
-rw-r--r--actionpack/test/lib/controller/fake_controllers.rb31
-rw-r--r--actionpack/test/lib/controller/fake_models.rb10
-rw-r--r--actionpack/test/routing/helper_test.rb31
-rw-r--r--actionpack/test/template/active_model_helper_test.rb2
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb185
-rw-r--r--actionpack/test/template/atom_feed_helper_test.rb7
-rw-r--r--actionpack/test/template/date_helper_i18n_test.rb12
-rw-r--r--actionpack/test/template/date_helper_test.rb36
-rw-r--r--actionpack/test/template/erb_util_test.rb14
-rw-r--r--actionpack/test/template/form_collections_helper_test.rb327
-rw-r--r--actionpack/test/template/form_helper_test.rb372
-rw-r--r--actionpack/test/template/form_options_helper_test.rb118
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb17
-rw-r--r--actionpack/test/template/html-scanner/sanitizer_test.rb11
-rw-r--r--actionpack/test/template/javascript_helper_test.rb1
-rw-r--r--actionpack/test/template/log_subscriber_test.rb4
-rw-r--r--actionpack/test/template/lookup_context_test.rb4
-rw-r--r--actionpack/test/template/number_helper_test.rb26
-rw-r--r--actionpack/test/template/record_tag_helper_test.rb81
-rw-r--r--actionpack/test/template/render_test.rb18
-rw-r--r--actionpack/test/template/template_error_test.rb4
-rw-r--r--actionpack/test/template/template_test.rb2
-rw-r--r--actionpack/test/template/text_helper_test.rb24
-rw-r--r--actionpack/test/template/url_helper_test.rb54
-rw-r--r--activemodel/CHANGELOG.md54
-rw-r--r--activemodel/README.rdoc17
-rwxr-xr-xactivemodel/Rakefile1
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb8
-rw-r--r--activemodel/lib/active_model/callbacks.rb3
-rw-r--r--activemodel/lib/active_model/errors.rb40
-rw-r--r--activemodel/lib/active_model/lint.rb46
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb6
-rw-r--r--activemodel/lib/active_model/observing.rb2
-rw-r--r--activemodel/lib/active_model/secure_password.rb2
-rw-r--r--activemodel/lib/active_model/serialization.rb13
-rw-r--r--activemodel/lib/active_model/validations.rb2
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb3
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb2
-rw-r--r--activemodel/lib/active_model/validations/length.rb6
-rw-r--r--activemodel/lib/active_model/validations/validates.rb21
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb8
-rw-r--r--activemodel/test/cases/errors_test.rb29
-rw-r--r--activemodel/test/cases/lint_test.rb6
-rw-r--r--activemodel/test/cases/mass_assignment_security_test.rb7
-rw-r--r--activemodel/test/cases/serialization_test.rb89
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb4
-rw-r--r--activemodel/test/cases/validations/callbacks_test.rb7
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb18
-rw-r--r--activemodel/test/cases/validations_test.rb22
-rw-r--r--activerecord/CHANGELOG.md225
-rw-r--r--activerecord/RUNNING_UNIT_TESTS2
-rwxr-xr-xactiverecord/Rakefile18
-rw-r--r--activerecord/activerecord.gemspec3
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/aggregations.rb2
-rw-r--r--activerecord/lib/active_record/associations.rb8
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb9
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb35
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb33
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb8
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb12
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb3
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/join_helper.rb10
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb15
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb1
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb25
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb12
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb20
-rw-r--r--activerecord/lib/active_record/attribute_methods/query.rb7
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb118
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb22
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb85
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb36
-rw-r--r--activerecord/lib/active_record/autosave_association.rb7
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb99
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb93
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb49
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb243
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb464
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb36
-rw-r--r--activerecord/lib/active_record/connection_handling.rb8
-rw-r--r--activerecord/lib/active_record/core.rb71
-rw-r--r--activerecord/lib/active_record/counter_cache.rb9
-rw-r--r--activerecord/lib/active_record/dynamic_finder_match.rb82
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb2
-rw-r--r--activerecord/lib/active_record/dynamic_scope_match.rb4
-rw-r--r--activerecord/lib/active_record/explain.rb2
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb5
-rw-r--r--activerecord/lib/active_record/fixtures.rb3
-rw-r--r--activerecord/lib/active_record/identity_map.rb28
-rw-r--r--activerecord/lib/active_record/inheritance.rb6
-rw-r--r--activerecord/lib/active_record/locale/en.yml3
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb10
-rw-r--r--activerecord/lib/active_record/locking/pessimistic.rb22
-rw-r--r--activerecord/lib/active_record/migration.rb244
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb12
-rw-r--r--activerecord/lib/active_record/migration/join_table.rb17
-rw-r--r--activerecord/lib/active_record/model_schema.rb45
-rw-r--r--activerecord/lib/active_record/null_relation.rb10
-rw-r--r--activerecord/lib/active_record/persistence.rb1
-rw-r--r--activerecord/lib/active_record/query_cache.rb45
-rw-r--r--activerecord/lib/active_record/querying.rb14
-rw-r--r--activerecord/lib/active_record/railtie.rb3
-rw-r--r--activerecord/lib/active_record/railties/databases.rake12
-rw-r--r--activerecord/lib/active_record/railties/jdbcmysql_error.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb6
-rw-r--r--activerecord/lib/active_record/relation.rb39
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb26
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb6
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb12
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb83
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb14
-rw-r--r--activerecord/lib/active_record/result.rb34
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/schema_migration.rb34
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb3
-rw-r--r--activerecord/lib/active_record/store.rb12
-rw-r--r--activerecord/lib/active_record/test_case.rb57
-rw-r--r--activerecord/lib/active_record/validations/associated.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb18
-rw-r--r--activerecord/lib/rails/generators/active_record.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/migration.rb15
-rw-r--r--activerecord/test/active_record/connection_adapters/fake_adapter.rb2
-rw-r--r--activerecord/test/assets/test.txt1
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb76
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb11
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb10
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb4
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb32
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb2
-rw-r--r--activerecord/test/cases/associations/eager_test.rb161
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb16
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb78
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb67
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb6
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb2
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb4
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations_test.rb7
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb8
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb55
-rw-r--r--activerecord/test/cases/base_test.rb120
-rw-r--r--activerecord/test/cases/binary_test.rb2
-rw-r--r--activerecord/test/cases/calculations_test.rb20
-rw-r--r--activerecord/test/cases/column_definition_test.rb9
-rw-r--r--activerecord/test/cases/column_test.rb29
-rw-r--r--activerecord/test/cases/connection_management_test.rb29
-rw-r--r--activerecord/test/cases/counter_cache_test.rb20
-rw-r--r--activerecord/test/cases/dirty_test.rb14
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb48
-rw-r--r--activerecord/test/cases/explain_test.rb16
-rw-r--r--activerecord/test/cases/finder_test.rb22
-rw-r--r--activerecord/test/cases/fixtures_test.rb4
-rw-r--r--activerecord/test/cases/helper.rb36
-rw-r--r--activerecord/test/cases/identity_map/middleware_test.rb3
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb12
-rw-r--r--activerecord/test/cases/locking_test.rb20
-rw-r--r--activerecord/test/cases/method_scoping_test.rb54
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb328
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb188
-rw-r--r--activerecord/test/cases/migration/column_positioning_test.rb60
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb12
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb70
-rw-r--r--activerecord/test/cases/migration/helper.rb64
-rw-r--r--activerecord/test/cases/migration/index_test.rb185
-rw-r--r--activerecord/test/cases/migration/logger_test.rb37
-rw-r--r--activerecord/test/cases/migration/rename_column_test.rb194
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb72
-rw-r--r--activerecord/test/cases/migration/table_and_index_test.rb24
-rw-r--r--activerecord/test/cases/migration_test.rb2731
-rw-r--r--activerecord/test/cases/migrator_test.rb377
-rw-r--r--activerecord/test/cases/modules_test.rb2
-rw-r--r--activerecord/test/cases/multiple_db_test.rb12
-rw-r--r--activerecord/test/cases/primary_keys_test.rb16
-rw-r--r--activerecord/test/cases/query_cache_test.rb19
-rw-r--r--activerecord/test/cases/reflection_test.rb4
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb6
-rw-r--r--activerecord/test/cases/relation_test.rb21
-rw-r--r--activerecord/test/cases/relations_test.rb76
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb31
-rw-r--r--activerecord/test/cases/serialization_test.rb9
-rw-r--r--activerecord/test/cases/store_test.rb13
-rw-r--r--activerecord/test/cases/test_case.rb10
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb17
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb24
-rw-r--r--activerecord/test/fixtures/colleges.yml3
-rw-r--r--activerecord/test/fixtures/courses.yml1
-rw-r--r--activerecord/test/fixtures/developers.yml2
-rw-r--r--activerecord/test/fixtures/dog_lovers.yml4
-rw-r--r--activerecord/test/fixtures/dogs.yml3
-rw-r--r--activerecord/test/migrations/broken/100_migration_that_raises_exception.rb10
-rw-r--r--activerecord/test/migrations/duplicate/1_people_have_last_names.rb9
-rw-r--r--activerecord/test/migrations/duplicate/2_we_need_reminders.rb12
-rw-r--r--activerecord/test/migrations/duplicate/3_foo.rb7
-rw-r--r--activerecord/test/migrations/duplicate/3_innocent_jointable.rb12
-rw-r--r--activerecord/test/migrations/duplicate_names/20080507052938_chunky.rb7
-rw-r--r--activerecord/test/migrations/duplicate_names/20080507053028_chunky.rb7
-rw-r--r--activerecord/test/migrations/interleaved/pass_1/3_interleaved_innocent_jointable.rb12
-rw-r--r--activerecord/test/migrations/interleaved/pass_2/1_interleaved_people_have_last_names.rb9
-rw-r--r--activerecord/test/migrations/interleaved/pass_2/3_interleaved_innocent_jointable.rb12
-rw-r--r--activerecord/test/migrations/interleaved/pass_3/1_interleaved_people_have_last_names.rb9
-rw-r--r--activerecord/test/migrations/interleaved/pass_3/2_interleaved_i_raise_on_down.rb8
-rw-r--r--activerecord/test/migrations/interleaved/pass_3/3_interleaved_innocent_jointable.rb12
-rw-r--r--activerecord/test/models/admin/user.rb1
-rw-r--r--activerecord/test/models/arunit2_model.rb3
-rw-r--r--activerecord/test/models/author.rb6
-rw-r--r--activerecord/test/models/category.rb2
-rw-r--r--activerecord/test/models/college.rb5
-rw-r--r--activerecord/test/models/company.rb3
-rw-r--r--activerecord/test/models/course.rb5
-rw-r--r--activerecord/test/models/dog.rb4
-rw-r--r--activerecord/test/models/dog_lover.rb4
-rw-r--r--activerecord/test/models/person.rb5
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb4
-rw-r--r--activerecord/test/schema/mysql_specific_schema.rb2
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb11
-rw-r--r--activerecord/test/schema/schema.rb21
-rw-r--r--activerecord/test/support/connection.rb3
-rw-r--r--activeresource/CHANGELOG.md55
-rwxr-xr-xactiveresource/Rakefile3
-rw-r--r--activeresource/lib/active_resource/base.rb158
-rw-r--r--activeresource/lib/active_resource/connection.rb53
-rw-r--r--activeresource/lib/active_resource/custom_methods.rb20
-rw-r--r--activeresource/lib/active_resource/http_mock.rb23
-rw-r--r--activeresource/lib/active_resource/validations.rb44
-rw-r--r--activeresource/test/cases/authorization_test.rb161
-rw-r--r--activeresource/test/cases/base_errors_test.rb34
-rw-r--r--activeresource/test/cases/base_test.rb39
-rw-r--r--activeresource/test/cases/connection_test.rb1
-rw-r--r--activeresource/test/cases/format_test.rb18
-rw-r--r--activeresource/test/cases/http_mock_test.rb4
-rw-r--r--activeresource/test/fixtures/street_address.rb2
-rw-r--r--activesupport/CHANGELOG.md93
-rw-r--r--activesupport/activesupport.gemspec3
-rwxr-xr-x[-rw-r--r--]activesupport/bin/generate_tables0
-rw-r--r--activesupport/lib/active_support/buffered_logger.rb3
-rw-r--r--activesupport/lib/active_support/cache.rb11
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb2
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb1
-rw-r--r--activesupport/lib/active_support/callbacks.rb164
-rw-r--r--activesupport/lib/active_support/configurable.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb27
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/integer/inflections.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb38
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/range/overlaps.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb28
-rw-r--r--activesupport/lib/active_support/core_ext/uri.rb2
-rw-r--r--activesupport/lib/active_support/dependencies.rb4
-rw-r--r--activesupport/lib/active_support/gzip.rb1
-rw-r--r--activesupport/lib/active_support/inflections.rb16
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb15
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb63
-rw-r--r--activesupport/lib/active_support/json/encoding.rb8
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb6
-rw-r--r--activesupport/lib/active_support/logger.rb35
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb14
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb5
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb2
-rw-r--r--activesupport/lib/active_support/notifications.rb4
-rw-r--r--activesupport/lib/active_support/ordered_options.rb9
-rw-r--r--activesupport/lib/active_support/ruby/shim.rb1
-rw-r--r--activesupport/lib/active_support/tagged_logging.rb81
-rw-r--r--activesupport/lib/active_support/test_case.rb2
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb32
-rw-r--r--activesupport/lib/active_support/testing/pending.rb20
-rw-r--r--activesupport/lib/active_support/testing/performance.rb47
-rw-r--r--activesupport/lib/active_support/testing/performance/ruby.rb34
-rw-r--r--activesupport/lib/active_support/testing/performance/ruby/yarv.rb48
-rw-r--r--activesupport/lib/active_support/values/unicode_tables.datbin877274 -> 904408 bytes
-rw-r--r--activesupport/lib/active_support/xml_mini/jdom.rb8
-rw-r--r--activesupport/test/broadcast_logger_test.rb82
-rw-r--r--activesupport/test/caching_test.rb22
-rw-r--r--activesupport/test/callback_inheritance_test.rb16
-rw-r--r--activesupport/test/callbacks_test.rb95
-rw-r--r--activesupport/test/constantize_test_cases.rb5
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb13
-rw-r--r--activesupport/test/core_ext/integer_ext_test.rb6
-rw-r--r--activesupport/test/core_ext/module_test.rb6
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb12
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb9
-rw-r--r--activesupport/test/core_ext/uri_ext_test.rb8
-rw-r--r--activesupport/test/inflector_test.rb149
-rw-r--r--activesupport/test/inflector_test_cases.rb1
-rw-r--r--activesupport/test/log_subscriber_test.rb4
-rw-r--r--activesupport/test/multibyte_chars_test.rb3
-rw-r--r--activesupport/test/safe_buffer_test.rb4
-rw-r--r--install.rb2
-rw-r--r--load_paths.rb2
-rw-r--r--rails.gemspec2
-rw-r--r--railties/CHANGELOG.md82
-rw-r--r--railties/guides/assets/stylesheets/fixes.css2
-rw-r--r--railties/guides/assets/stylesheets/main.css1
-rw-r--r--railties/guides/code/getting_started/Gemfile23
-rw-r--r--railties/guides/code/getting_started/app/assets/javascripts/application.js12
-rw-r--r--railties/guides/code/getting_started/app/assets/stylesheets/application.css16
-rw-r--r--railties/guides/code/getting_started/app/views/layouts/application.html.erb2
-rw-r--r--railties/guides/code/getting_started/config/application.rb14
-rw-r--r--railties/guides/code/getting_started/config/database.yml12
-rw-r--r--railties/guides/code/getting_started/config/environments/development.rb18
-rw-r--r--railties/guides/code/getting_started/config/environments/production.rb40
-rw-r--r--railties/guides/code/getting_started/config/environments/test.rb20
-rw-r--r--railties/guides/code/getting_started/config/initializers/inflections.rb5
-rw-r--r--railties/guides/code/getting_started/config/routes.rb8
-rw-r--r--railties/guides/code/getting_started/public/500.html1
-rw-r--r--railties/guides/rails_guides/generator.rb2
-rw-r--r--railties/guides/rails_guides/indexer.rb3
-rw-r--r--railties/guides/source/3_1_release_notes.textile109
-rw-r--r--railties/guides/source/3_2_release_notes.textile124
-rw-r--r--railties/guides/source/_welcome.html.erb2
-rw-r--r--railties/guides/source/action_controller_overview.textile2
-rw-r--r--railties/guides/source/action_view_overview.textile101
-rw-r--r--railties/guides/source/active_model_basics.textile2
-rw-r--r--railties/guides/source/active_record_querying.textile58
-rw-r--r--railties/guides/source/active_record_validations_callbacks.textile35
-rw-r--r--railties/guides/source/active_resource_basics.textile2
-rw-r--r--railties/guides/source/active_support_core_extensions.textile45
-rw-r--r--railties/guides/source/ajax_on_rails.textile5
-rw-r--r--railties/guides/source/asset_pipeline.textile60
-rw-r--r--railties/guides/source/association_basics.textile116
-rw-r--r--railties/guides/source/command_line.textile4
-rw-r--r--railties/guides/source/configuring.textile45
-rw-r--r--railties/guides/source/contributing_to_ruby_on_rails.textile8
-rw-r--r--railties/guides/source/documents.yaml9
-rw-r--r--railties/guides/source/engines.textile206
-rw-r--r--railties/guides/source/form_helpers.textile26
-rw-r--r--railties/guides/source/getting_started.textile48
-rw-r--r--railties/guides/source/i18n.textile6
-rw-r--r--railties/guides/source/layout.html.erb1
-rw-r--r--railties/guides/source/layouts_and_rendering.textile8
-rw-r--r--railties/guides/source/migrations.textile30
-rw-r--r--railties/guides/source/plugins.textile2
-rw-r--r--railties/guides/source/routing.textile30
-rw-r--r--railties/guides/source/ruby_on_rails_guides_guidelines.textile16
-rw-r--r--railties/guides/source/security.textile2
-rw-r--r--railties/guides/source/testing.textile21
-rw-r--r--railties/guides/source/upgrading_ruby_on_rails.textile164
-rw-r--r--railties/lib/rails.rb7
-rw-r--r--railties/lib/rails/application.rb8
-rw-r--r--railties/lib/rails/application/bootstrap.rb10
-rw-r--r--railties/lib/rails/application/configuration.rb18
-rw-r--r--railties/lib/rails/application/finisher.rb3
-rw-r--r--railties/lib/rails/application/route_inspector.rb2
-rw-r--r--railties/lib/rails/backtrace_cleaner.rb13
-rw-r--r--railties/lib/rails/code_statistics.rb30
-rw-r--r--railties/lib/rails/commands/benchmarker.rb2
-rw-r--r--railties/lib/rails/commands/console.rb75
-rw-r--r--railties/lib/rails/commands/profiler.rb2
-rw-r--r--railties/lib/rails/commands/server.rb9
-rw-r--r--railties/lib/rails/deprecation.rb18
-rw-r--r--railties/lib/rails/engine.rb6
-rw-r--r--railties/lib/rails/generators/actions.rb2
-rw-r--r--railties/lib/rails/generators/active_model.rb6
-rw-r--r--railties/lib/rails/generators/app_base.rb22
-rw-r--r--railties/lib/rails/generators/base.rb14
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb6
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb13
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb15
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml17
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml12
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml12
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt18
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt43
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt16
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb4
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/Gemfile2
-rwxr-xr-xrailties/lib/rails/generators/rails/plugin_new/templates/Rakefile9
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb4
-rw-r--r--railties/lib/rails/rack/log_tailer.rb10
-rw-r--r--railties/lib/rails/rubyprof_ext.rb2
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb4
-rw-r--r--railties/lib/rails/tasks/documentation.rake8
-rw-r--r--railties/lib/rails/tasks/statistics.rake1
-rw-r--r--railties/lib/rails/test_unit/sub_test_task.rb32
-rw-r--r--railties/lib/rails/test_unit/testing.rake16
-rw-r--r--railties/test/application/assets_test.rb26
-rw-r--r--railties/test/application/configuration_test.rb62
-rw-r--r--railties/test/application/loading_test.rb8
-rw-r--r--railties/test/application/middleware/session_test.rb30
-rw-r--r--railties/test/application/middleware_test.rb4
-rw-r--r--railties/test/application/paths_test.rb1
-rw-r--r--railties/test/application/rake/migrations_test.rb50
-rw-r--r--railties/test/application/rake/notes_test.rb22
-rw-r--r--railties/test/application/route_inspect_test.rb15
-rw-r--r--railties/test/commands/console_test.rb87
-rw-r--r--railties/test/generators/app_generator_test.rb17
-rw-r--r--railties/test/generators/migration_generator_test.rb6
-rw-r--r--railties/test/generators/named_base_test.rb9
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb15
-rw-r--r--railties/test/generators_test.rb8
-rw-r--r--railties/test/isolation/abstract_unit.rb2
580 files changed, 13720 insertions, 7737 deletions
diff --git a/Gemfile b/Gemfile
index 76be75e872..5bd9d4a215 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,6 +8,7 @@ else
gem 'arel'
end
+gem 'rack-test', :git => "https://github.com/brynary/rack-test.git"
gem 'bcrypt-ruby', '~> 3.0.0'
gem 'jquery-rails'
@@ -37,11 +38,6 @@ end
# AS
gem 'memcache-client', '>= 1.8.5'
-platforms :mri_18 do
- gem 'system_timer'
- gem 'json'
-end
-
# Add your own local bundler stuff
local_gemfile = File.dirname(__FILE__) + "/.Gemfile"
instance_eval File.read local_gemfile if File.exists? local_gemfile
diff --git a/README.rdoc b/README.rdoc
index dc8805245b..78640b39aa 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -76,4 +76,3 @@ to proceed. {Join us}[http://contributors.rubyonrails.org]!
Ruby on Rails is released under the MIT license:
* http://www.opensource.org/licenses/MIT
-
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 57af4ee6a4..da5d5c4086 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,14 +1,66 @@
+## Rails 4.0.0 (unreleased) ##
+
+* No changes
+
+
+## Rails 3.2.1 (January 26, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.0 (January 20, 2012) ##
+
+* Upgrade mail version to 2.4.0 *ML*
+
+* Remove Old ActionMailer API *Josh Kalderimis*
+
+
+## Rails 3.1.3 (November 20, 2011) ##
+
+* No changes
+
+
+## Rails 3.1.2 (November 18, 2011) ##
+
+* No changes
+
+
+## Rails 3.1.1 (October 7, 2011) ##
+
+* No changes
+
+
## Rails 3.1.0 (August 30, 2011) ##
* No changes
+## Rails 3.0.11 (November 18, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.10 (August 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.9 (June 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.8 (June 7, 2011) ##
+
+* Mail dependency increased to 2.2.19
+
+
## Rails 3.0.7 (April 18, 2011) ##
* remove AM delegating register_observer and register_interceptor to Mail *Josh Kalderimis*
-* Rails 3.0.6 (April 5, 2011)
+## Rails 3.0.6 (April 5, 2011) ##
* Don't allow i18n to change the minor version, version now set to ~> 0.5.0 *Santiago Pastorino*
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index 4c2516db59..755717cfba 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -82,7 +82,7 @@ Note that every value you set with this method will get over written if you use
Example:
- class Authenticationmailer < ActionMailer::Base
+ class AuthenticationMailer < ActionMailer::Base
default :from => "awesome@application.com", :subject => Proc.new { "E-mail was generated at #{Time.now}" }
.....
end
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index e7d8ee299d..8f5aeb9603 100755
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -11,6 +11,7 @@ Rake::TestTask.new { |t|
t.libs << "test"
t.pattern = 'test/**/*_test.rb'
t.warning = true
+ t.verbose = true
}
namespace :test do
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index b506f8804c..c605f1ff04 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -17,5 +17,5 @@ Gem::Specification.new do |s|
s.requirements << 'none'
s.add_dependency('actionpack', version)
- s.add_dependency('mail', '~> 2.3.0')
+ s.add_dependency('mail', '~> 2.4.1')
end
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 7d8852f961..1800ff5839 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -1,7 +1,6 @@
require 'mail'
require 'action_mailer/collector'
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/proc'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/hash/except'
require 'action_mailer/log_subscriber'
@@ -409,7 +408,7 @@ module ActionMailer #:nodoc:
# and passing a Mail::Message will do nothing except tell the logger you sent the email.
def deliver_mail(mail) #:nodoc:
ActiveSupport::Notifications.instrument("deliver.action_mailer") do |payload|
- self.set_payload_for_mail(payload, mail)
+ set_payload_for_mail(payload, mail)
yield # Let Mail do the delivery actions
end
end
@@ -602,9 +601,6 @@ module ActionMailer #:nodoc:
# end
#
def mail(headers={}, &block)
- # Guard flag to prevent both the old and the new API from firing
- # Should be removed when old API is removed
- @mail_was_called = true
m = @_message
# At the beginning, do not consider class default for parts order neither content_type
@@ -612,8 +608,9 @@ module ActionMailer #:nodoc:
parts_order = headers[:parts_order]
# Call all the procs (if any)
- default_values = self.class.default.merge(self.class.default) do |k,v|
- v.respond_to?(:call) ? v.bind(self).call : v
+ class_default = self.class.default
+ default_values = class_default.merge(class_default) do |k,v|
+ v.respond_to?(:to_proc) ? instance_eval(&v) : v
end
# Handle defaults
diff --git a/actionmailer/lib/action_mailer/collector.rb b/actionmailer/lib/action_mailer/collector.rb
index d03e085e83..17b22aea2a 100644
--- a/actionmailer/lib/action_mailer/collector.rb
+++ b/actionmailer/lib/action_mailer/collector.rb
@@ -22,9 +22,9 @@ module ActionMailer #:nodoc:
def custom(mime, options={})
options.reverse_merge!(:content_type => mime.to_s)
- @context.freeze_formats([mime.to_sym])
+ @context.formats = [mime.to_sym]
options[:body] = block_given? ? yield : @default_render.call
@responses << options
end
end
-end \ No newline at end of file
+end
diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb
index 6f22adc479..2036883b22 100644
--- a/actionmailer/lib/action_mailer/mail_helper.rb
+++ b/actionmailer/lib/action_mailer/mail_helper.rb
@@ -1,11 +1,10 @@
module ActionMailer
module MailHelper
- # Uses Text::Format to take the text and format it, indented two spaces for
- # each line, and wrapped at 72 columns.
+ # Take the text and format it, indented two spaces for each line, and wrapped at 72 columns.
def block_format(text)
- formatted = text.split(/\n\r\n/).collect { |paragraph|
+ formatted = text.split(/\n\r?\n/).collect { |paragraph|
format_paragraph(paragraph)
- }.join("\n")
+ }.join("\n\n")
# Make list points stand on their own line
formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" }
@@ -41,7 +40,7 @@ module ActionMailer
sentences = [[]]
text.split.each do |word|
- if (sentences.last + [word]).join(' ').length > len
+ if sentences.first.present? && (sentences.last + [word]).join(' ').length > len
sentences << [word]
else
sentences.last << word
diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb
index 5beab87ad2..7204822395 100644
--- a/actionmailer/lib/action_mailer/test_helper.rb
+++ b/actionmailer/lib/action_mailer/test_helper.rb
@@ -1,7 +1,5 @@
module ActionMailer
module TestHelper
- extend ActiveSupport::Concern
-
# Asserts that the number of emails sent matches the given number.
#
# def test_emails
diff --git a/actionmailer/test/mail_helper_test.rb b/actionmailer/test/mail_helper_test.rb
index 17e9c82045..d8a73e6c46 100644
--- a/actionmailer/test/mail_helper_test.rb
+++ b/actionmailer/test/mail_helper_test.rb
@@ -22,6 +22,14 @@ class HelperMailer < ActionMailer::Base
end
end
+ def use_format_paragraph_with_long_first_word
+ @text = "Antidisestablishmentarianism is very long."
+
+ mail_with_defaults do |format|
+ format.html { render(:inline => "<%= format_paragraph @text, 10, 1 %>") }
+ end
+ end
+
def use_mailer
mail_with_defaults do |format|
format.html { render(:inline => "<%= mailer.message.subject %>") }
@@ -34,6 +42,23 @@ class HelperMailer < ActionMailer::Base
end
end
+ def use_block_format
+ @text = <<-TEXT
+This is the
+first paragraph.
+
+The second
+ paragraph.
+
+* item1 * item2
+ * item3
+ TEXT
+
+ mail_with_defaults do |format|
+ format.html { render(:inline => "<%= block_format @text %>") }
+ end
+ end
+
protected
def mail_with_defaults(&block)
@@ -63,5 +88,24 @@ class MailerHelperTest < ActionMailer::TestCase
mail = HelperMailer.use_format_paragraph
assert_match " But soft! What\r\n light through\r\n yonder window\r\n breaks?", mail.body.encoded
end
+
+ def test_use_format_paragraph_with_long_first_word
+ mail = HelperMailer.use_format_paragraph_with_long_first_word
+ assert_equal " Antidisestablishmentarianism\r\n is very\r\n long.", mail.body.encoded
+ end
+
+ def test_use_block_format
+ mail = HelperMailer.use_block_format
+ expected = <<-TEXT
+ This is the first paragraph.
+
+ The second paragraph.
+
+ * item1
+ * item2
+ * item3
+ TEXT
+ assert_equal expected.gsub("\n", "\r\n"), mail.body.encoded
+ end
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 583d984989..74a8d9f78a 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,8 +1,158 @@
+## Rails 4.0.0 (unreleased) ##
+
+* Forms of persisted records use always PATCH (via the `_method` hack). *fxn*
+
+* For resources, both PATCH and PUT are routed to the `update` action. *fxn*
+
+* Don't ignore `force_ssl` in development. This is a change of behavior - use a `:if` condition to recreate the old behavior.
+
+ class AccountsController < ApplicationController
+ force_ssl :if => :ssl_configured?
+
+ def ssl_configured?
+ !Rails.env.development?
+ end
+ end
+
+ *Pat Allan*
+
+* Adds support for the PATCH verb:
+ * Request objects respond to `patch?`.
+ * Routes have a new `patch` method, and understand `:patch` in the
+ existing places where a verb is configured, like `:via`.
+ * New method `patch` available in functional tests.
+ * If `:patch` is the default verb for updates, edits are
+ tunneled as PATCH rather than as PUT, and routing acts accordingly.
+ * New method `patch_via_redirect` available in integration tests.
+
+ *dlee*
+
+* Integration tests support the `OPTIONS` method. *Jeremy Kemper*
+
+* `expires_in` accepts a `must_revalidate` flag. If true, "must-revalidate"
+ is added to the Cache-Control header. *fxn*
+
+* Add `date_field` and `date_field_tag` helpers which render an `input[type="date"]` tag *Olek Janiszewski*
+
+* Adds `image_url`, `javascript_url`, `stylesheet_url`, `audio_url`, `video_url`, and `font_url`
+ to assets tag helper. These URL helpers will return the full path to your assets. This is useful
+ when you are going to reference this asset from external host. *Prem Sichanugrist*
+
+* Default responder will now always use your overridden block in `respond_with` to render your response. *Prem Sichanugrist*
+
+* Allow `value_method` and `text_method` arguments from `collection_select` and
+ `options_from_collection_for_select` to receive an object that responds to `:call`,
+ such as a `proc`, to evaluate the option in the current element context. This works
+ the same way with `collection_radio_buttons` and `collection_check_boxes`.
+
+ *Carlos Antonio da Silva + Rafael Mendonça França*
+
+* Add `collection_check_boxes` form helper, similar to `collection_select`:
+ Example:
+
+ collection_check_boxes :post, :author_ids, Author.all, :id, :name
+ # Outputs something like:
+ <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" />
+ <label for="post_author_ids_1">D. Heinemeier Hansson</label>
+ <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
+ <label for="post_author_ids_2">D. Thomas</label>
+ <input name="post[author_ids][]" type="hidden" value="" />
+
+ The label/check_box pairs can be customized with a block.
+
+ *Carlos Antonio da Silva + Rafael Mendonça França*
+
+* Add `collection_radio_buttons` form helper, similar to `collection_select`:
+ Example:
+
+ collection_radio_buttons :post, :author_id, Author.all, :id, :name
+ # Outputs something like:
+ <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" />
+ <label for="post_author_id_1">D. Heinemeier Hansson</label>
+ <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
+ <label for="post_author_id_2">D. Thomas</label>
+
+ The label/radio_button pairs can be customized with a block.
+
+ *Carlos Antonio da Silva + Rafael Mendonça França*
+
+* check_box with `:form` html5 attribute will now replicate the `:form`
+ attribute to the hidden field as well. *Carlos Antonio da Silva*
+
+* `label` form helper accepts :for => nil to not generate the attribute. *Carlos Antonio da Silva*
+
+* Add `:format` option to number_to_percentage *Rodrigo Flores*
+
+* Add `config.action_view.logger` to configure logger for ActionView. *Rafael Mendonça França*
+
+* Deprecated ActionController::Integration in favour of ActionDispatch::Integration
+
+* Deprecated ActionController::IntegrationTest in favour of ActionDispatch::IntegrationTest
+
+* Deprecated ActionController::PerformanceTest in favour of ActionDispatch::PerformanceTest
+
+* Deprecated ActionController::AbstractRequest in favour of ActionDispatch::Request
+
+* Deprecated ActionController::Request in favour of ActionDispatch::Request
+
+* Deprecated ActionController::AbstractResponse in favour of ActionDispatch::Response
+
+* Deprecated ActionController::Response in favour of ActionDispatch::Response
+
+* Deprecated ActionController::Routing in favour of ActionDispatch::Routing
+
* check_box helper with :disabled => true will generate a disabled hidden field to conform with the HTML convention where disabled fields are not submitted with the form.
This is a behavior change, previously the hidden tag had a value of the disabled checkbox.
*Tadas Tamosauskas*
-## Rails 3.2.0 (unreleased) ##
+* `favicon_link_tag` helper will now use the favicon in app/assets by default. *Lucas Caton*
+
+* `ActionView::Helpers::TextHelper#highlight` now defaults to the
+ HTML5 `mark` element. *Brian Cardarella*
+
+
+## Rails 3.2.2 (unreleased) ##
+
+* Format lookup for partials is derived from the format in which the template is being rendered. Closes #5025 part 2 *Santiago Pastorino*
+
+* Use the right format when a partial is missing. Closes #5025. *Santiago Pastorino*
+
+* Default responder will now always use your overridden block in `respond_with` to render your response. *Prem Sichanugrist*
+
+* check_box helper with :disabled => true will generate a disabled hidden field to conform with the HTML convention where disabled fields are not submitted with the form.
+ This is a behavior change, previously the hidden tag had a value of the disabled checkbox.
+ *Tadas Tamosauskas*
+
+
+## Rails 3.2.1 (January 26, 2012) ##
+
+* Documentation improvements.
+
+* Allow `form.select` to accept ranges (regression). *Jeremy Walker*
+
+* `datetime_select` works with -/+ infinity dates. *Joe Van Dyk*
+
+
+## Rails 3.2.0 (January 20, 2012) ##
+
+* Add `config.action_dispatch.default_charset` to configure default charset for ActionDispatch::Response. *Carlos Antonio da Silva*
+
+* Deprecate setting default charset at controller level, use the new `config.action_dispatch.default_charset` instead. *Carlos Antonio da Silva*
+
+* Deprecate ActionController::UnknownAction in favour of AbstractController::ActionNotFound. *Carlos Antonio da Silva*
+
+* Deprecate ActionController::DoubleRenderError in favour of AbstractController::DoubleRenderError. *Carlos Antonio da Silva*
+
+* Deprecate method_missing handling for not found actions, use action_missing instead. *Carlos Antonio da Silva*
+
+* Deprecate ActionController#rescue_action, ActionController#initialize_template_class, and ActionController#assign_shortcuts.
+ These methods were not being used internally anymore and are going to be removed in Rails 4. *Carlos Antonio da Silva*
+
+* Use a BodyProxy instead of including a Module that responds to
+ close. Closes #4441 if Active Record is disabled assets are delivered
+ correctly *Santiago Pastorino*
+
+* Rails initialization with initialize_on_precompile = false should set assets_dir *Santiago Pastorino*
* Add font_path helper method *Santiago Pastorino*
@@ -22,7 +172,7 @@
<%= f.button %>
<% end %>
-* Date helpers accept a new option, `:use_two_digit_numbers = true`, that renders select boxes for months and days with a leading zero without changing the respective values.
+* Date helpers accept a new option, `:use_two_digit_numbers = true`, that renders select boxes for months and days with a leading zero without changing the respective values.
For example, this is useful for displaying ISO8601-style dates such as '2011-08-01'. *Lennart Fridén and Kim Persson*
* Make ActiveSupport::Benchmarkable a default module for ActionController::Base, so the #benchmark method is once again available in the controller context like it used to be *DHH*
@@ -153,20 +303,30 @@
## Rails 3.1.4 (unreleased) ##
+* Skip assets group in Gemfile and all assets configurations options
+ when the application is generated with --skip-sprockets option.
+
+ *Guillermo Iguaran*
+
+* Use ProcessedAsset#pathname in Sprockets helpers when debugging is on. Closes #3333 #3348 #3361.
+
+ *Guillermo Iguaran*
+
* Allow to use asset_path on named_routes aliasing RailsHelper's
asset_path to path_to_asset *Adrian Pike*
-* Assets should use the request protocol by default or default to
- relative if no request is available *Jonathan del Strother*
+* Assets should use the request protocol by default or default to relative if no request is available *Jonathan del Strother*
+
+## Rails 3.1.3 (November 20, 2011) ##
-## Rails 3.1.3 (unreleased) ##
+* Downgrade sprockets to ~> 2.0.3. Using 2.1.0 caused regressions.
* Fix using `translate` helper with a html translation which uses the `:count` option for
pluralization.
*Jon Leighton*
-## Rails 3.1.2 (unreleased) ##
+## Rails 3.1.2 (November 18, 2011) ##
* Fix XSS security vulnerability in the `translate` helper method. When using interpolation
in combination with HTML-safe translations, the interpolated input would not get HTML
@@ -207,7 +367,7 @@
* Ensure users upgrading from 3.0.x to 3.1.x will properly upgrade their flash object in session (issues #3298 and #2509)
-## Rails 3.1.1 (unreleased) ##
+## Rails 3.1.1 (October 07, 2011) ##
* javascript_path and stylesheet_path now refer to /assets if asset pipelining
is on. *Santiago Pastorino*
@@ -443,6 +603,96 @@
* Add Rack::Cache to the default stack. Create a Rails store that delegates to the Rails cache, so by default, whatever caching layer you are using will be used for HTTP caching. Note that Rack::Cache will be used if you use #expires_in, #fresh_when or #stale with :public => true. Otherwise, the caching rules will apply to the browser only. *Yehuda Katz, Carl Lerche*
+## Rails 3.0.12 (unreleased) ##
+
+* Fix using `tranlate` helper with a html translation which uses the `:count` option for
+ pluralization.
+
+ *Jon Leighton*
+
+
+## Rails 3.0.11 (November 18, 2011) ##
+
+* Fix XSS security vulnerability in the `translate` helper method. When using interpolation
+ in combination with HTML-safe translations, the interpolated input would not get HTML
+ escaped. *GH 3664*
+
+ Before:
+
+ translate('foo_html', :something => '<script>') # => "...<script>..."
+
+ After:
+
+ translate('foo_html', :something => '<script>') # => "...&lt;script&gt;..."
+
+ *Sergey Nartimov*
+
+* Implement a workaround for a bug in ruby-1.9.3p0 where an error would be
+ raised while attempting to convert a template from one encoding to another.
+
+ Please see http://redmine.ruby-lang.org/issues/5564 for details of the bug.
+
+ The workaround is to load all conversions into memory ahead of time, and will
+ only happen if the ruby version is exactly 1.9.3p0. The hope is obviously
+ that the underlying problem will be resolved in the next patchlevel release
+ of 1.9.3.
+
+* Fix assert_select_email to work on multipart and non-multipart emails as the method stopped working correctly in Rails 3.x due to changes in the new mail gem.
+
+* Fix url_for when passed a hash to prevent additional options (eg. :host, :protocol) from being added to the hash after calling it.
+
+
+## Rails 3.0.10 (August 16, 2011) ##
+
+* Fixes an issue where cache sweepers with only after filters would have no
+ controller object, it would raise undefined method controller_name for nil [jeroenj]
+
+* Ensure status codes are logged when exceptions are raised.
+
+* Subclasses of OutputBuffer are respected.
+
+* Fixed ActionView::FormOptionsHelper#select with :multiple => false
+
+* Avoid extra call to Cache#read in case of a fragment cache hit
+
+
+## Rails 3.0.9 (June 16, 2011) ##
+
+* json_escape will now return a SafeBuffer string if it receives SafeBuffer string [tenderlove]
+
+* Make sure escape_js returns SafeBuffer string if it receives SafeBuffer string [Prem Sichanugrist]
+
+* Fix text helpers to work correctly with the new SafeBuffer restriction [Paul Gallagher, Arun Agrawal, Prem Sichanugrist]
+
+
+## Rails 3.0.8 (June 7, 2011) ##
+
+* It is prohibited to perform a in-place SafeBuffer mutation [tenderlove]
+
+ The old behavior of SafeBuffer allowed you to mutate string in place via
+ method like `sub!`. These methods can add unsafe strings to a safe buffer,
+ and the safe buffer will continue to be marked as safe.
+
+ An example problem would be something like this:
+
+ <%= link_to('hello world', @user).sub!(/hello/, params[:xss]) %>
+
+ In the above example, an untrusted string (`params[:xss]`) is added to the
+ safe buffer returned by `link_to`, and the untrusted content is successfully
+ sent to the client without being escaped. To prevent this from happening
+ `sub!` and other similar methods will now raise an exception when they are called on a safe buffer.
+
+ In addition to the in-place versions, some of the versions of these methods which return a copy of the string will incorrectly mark strings as safe. For example:
+
+ <%= link_to('hello world', @user).sub(/hello/, params[:xss]) %>
+
+ The new versions will now ensure that *all* strings returned by these methods on safe buffers are marked unsafe.
+
+ You can read more about this change in http://groups.google.com/group/rubyonrails-security/browse_thread/thread/2e516e7acc96c4fb
+
+* Fixed github issue #342 with asset paths and relative roots.
+
+
## Rails 3.0.7 (April 18, 2011) ##
* No changes.
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 076a93bbcd..1fdc57e14d 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -218,7 +218,7 @@ A short rundown of some of the major features:
def show
# the output of the method will be cached as
- # ActionController::Base.page_cache_directory + "/weblog/show/n.html"
+ # ActionController::Base.page_cache_directory + "/weblog/show.html"
# and the web server will pick it up without even hitting Rails
end
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index effb6badfc..bb1e704767 100755
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -55,15 +55,15 @@ end
task :lines do
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
- for file_name in FileList["lib/**/*.rb"]
+ FileList["lib/**/*.rb"].each do |file_name|
next if file_name =~ /vendor/
- f = File.open(file_name)
-
- while line = f.gets
- lines += 1
- next if line =~ /^\s*$/
- next if line =~ /^\s*#/
- codelines += 1
+ File.open(file_name, 'r') do |f|
+ while line = f.gets
+ lines += 1
+ next if line =~ /^\s*$/
+ next if line =~ /^\s*#/
+ codelines += 1
+ end
end
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index dbf78a6d94..b205acdb79 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -20,10 +20,10 @@ Gem::Specification.new do |s|
s.add_dependency('activemodel', version)
s.add_dependency('rack-cache', '~> 1.1')
s.add_dependency('builder', '~> 3.0.0')
- s.add_dependency('rack', '~> 1.4.0')
+ s.add_dependency('rack', '~> 1.4.1')
s.add_dependency('rack-test', '~> 0.6.1')
- s.add_dependency('journey', '~> 1.0.0.rc1')
- s.add_dependency('sprockets', '~> 2.1.2')
+ s.add_dependency('journey', '~> 1.0.1')
+ s.add_dependency('sprockets', '~> 2.2.0')
s.add_dependency('erubis', '~> 2.7.0')
s.add_development_dependency('tzinfo', '~> 0.3.29')
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index fd6a46fbec..43cea3b79e 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -42,8 +42,8 @@ module AbstractController
controller.public_instance_methods(true)
end
- # The list of hidden actions to an empty array. Defaults to an
- # empty array. This can be modified by other modules or subclasses
+ # The list of hidden actions. Defaults to an empty array.
+ # This can be modified by other modules or subclasses
# to specify particular actions as hidden.
#
# ==== Returns
@@ -85,7 +85,7 @@ module AbstractController
# Returns the full controller name, underscored, without the ending Controller.
# For instance, MyApp::MyPostsController would return "my_app/my_posts" for
- # controller_name.
+ # controller_path.
#
# ==== Returns
# * <tt>string</tt>
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index fffe3edac2..c0fa28cae9 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -8,7 +8,7 @@ module AbstractController
include ActiveSupport::Callbacks
included do
- define_callbacks :process_action, :terminator => "response_body"
+ define_callbacks :process_action, :terminator => "response_body", :skip_after_callbacks_if_terminated => true
end
# Override AbstractController::Base's process_action to run the
@@ -21,11 +21,9 @@ module AbstractController
module ClassMethods
# If :only or :except are used, convert the options into the
- # primitive form (:per_key) used by ActiveSupport::Callbacks.
+ # :unless and :if options of ActiveSupport::Callbacks.
# The basic idea is that :only => :index gets converted to
- # :if => proc {|c| c.action_name == "index" }, but that the
- # proc is only evaluated once per action for the lifetime of
- # a Rails process.
+ # :if => proc {|c| c.action_name == "index" }.
#
# ==== Options
# * <tt>only</tt> - The callback should be run only for this action
@@ -33,11 +31,11 @@ module AbstractController
def _normalize_callback_options(options)
if only = options[:only]
only = Array(only).map {|o| "action_name == '#{o}'"}.join(" || ")
- options[:per_key] = {:if => only}
+ options[:if] = Array(options[:if]) << only
end
if except = options[:except]
except = Array(except).map {|e| "action_name == '#{e}'"}.join(" || ")
- options[:per_key] = {:unless => except}
+ options[:unless] = Array(options[:unless]) << except
end
end
@@ -48,7 +46,7 @@ module AbstractController
# callbacks. Note that skipping uses Ruby equality, so it's
# impossible to skip a callback defined using an anonymous proc
# using #skip_filter
- def skip_filter(*names, &blk)
+ def skip_filter(*names)
skip_before_filter(*names)
skip_after_filter(*names)
skip_around_filter(*names)
@@ -66,7 +64,7 @@ module AbstractController
# ==== Block Parameters
# * <tt>name</tt> - The callback to be added
# * <tt>options</tt> - A hash of options to be used when adding the callback
- def _insert_callbacks(callbacks, block)
+ def _insert_callbacks(callbacks, block = nil)
options = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
_normalize_callback_options(options)
callbacks.push(block) if block
@@ -92,7 +90,7 @@ module AbstractController
##
# :method: skip_before_filter
#
- # :call-seq: skip_before_filter(names, block)
+ # :call-seq: skip_before_filter(names)
#
# Skip a before filter. See _insert_callbacks for parameter details.
@@ -120,7 +118,7 @@ module AbstractController
##
# :method: skip_after_filter
#
- # :call-seq: skip_after_filter(names, block)
+ # :call-seq: skip_after_filter(names)
#
# Skip an after filter. See _insert_callbacks for parameter details.
@@ -148,7 +146,7 @@ module AbstractController
##
# :method: skip_around_filter
#
- # :call-seq: skip_around_filter(names, block)
+ # :call-seq: skip_around_filter(names)
#
# Skip an around filter. See _insert_callbacks for parameter details.
@@ -167,7 +165,6 @@ module AbstractController
# for details on the allowed parameters.
def #{filter}_filter(*names, &blk) # def before_filter(*names, &blk)
_insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- options[:if] = (Array(options[:if]) << "!halted") if #{filter == :after} # options[:if] = (Array(options[:if]) << "!halted") if false
set_callback(:process_action, :#{filter}, name, options) # set_callback(:process_action, :before, name, options)
end # end
end # end
@@ -176,15 +173,14 @@ module AbstractController
# for details on the allowed parameters.
def prepend_#{filter}_filter(*names, &blk) # def prepend_before_filter(*names, &blk)
_insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- options[:if] = (Array(options[:if]) << "!halted") if #{filter == :after} # options[:if] = (Array(options[:if]) << "!halted") if false
set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true))
end # end
end # end
# Skip a before, after or around filter. See _insert_callbacks
# for details on the allowed parameters.
- def skip_#{filter}_filter(*names, &blk) # def skip_before_filter(*names, &blk)
- _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
+ def skip_#{filter}_filter(*names) # def skip_before_filter(*names)
+ _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options|
skip_callback(:process_action, :#{filter}, name, options) # skip_callback(:process_action, :before, name, options)
end # end
end # end
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 6a6387632c..aa39e21ba9 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -322,7 +322,7 @@ module AbstractController
super
if _include_layout?(options)
- layout = options.key?(:layout) ? options.delete(:layout) : :default
+ layout = options.delete(:layout) { :default }
options[:layout] = _layout_for_option(layout)
end
end
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index f4eaa2fd1b..a0c54dbd84 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -40,7 +40,6 @@ module ActionController
autoload :Integration, 'action_controller/deprecated/integration_test'
autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
- autoload :UrlWriter, 'action_controller/deprecated'
autoload :Routing, 'action_controller/deprecated'
autoload :TestCase, 'action_controller/test_case'
autoload :TemplateAssertions, 'action_controller/test_case'
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 98bfe72fef..3b82231b15 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -116,12 +116,12 @@ module ActionController
#
# Title: <%= @post.title %>
#
- # You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates
+ # You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates
# will use the manual rendering methods:
#
# def search
# @results = Search.find(params[:query])
- # case @results
+ # case @results.count
# when 0 then render :action => "no_results"
# when 1 then render :action => "show"
# when 2..10 then render :action => "show_many"
@@ -133,7 +133,7 @@ module ActionController
# == Redirects
#
# Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to the
- # database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're
+ # database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're
# going to reuse (and redirect to) a <tt>show</tt> action that we'll assume has already been created. The code might look like this:
#
# def create
@@ -228,8 +228,11 @@ module ActionController
include mod
end
- # Rails 2.x compatibility
- include ActionController::Compatibility
+ # Define some internal variables that should not be propagated to the view.
+ self.protected_instance_variables = [
+ :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request,
+ :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout
+ ]
ActiveSupport.run_load_hooks(:action_controller, self)
end
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index 3b86a9a93a..ba96735e56 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -47,7 +47,8 @@ module ActionController #:nodoc:
# And you can also use <tt>:if</tt> (or <tt>:unless</tt>) to pass a
# proc that specifies when the action should be cached.
#
- # Finally, if you are using memcached, you can also pass <tt>:expires_in</tt>.
+ # As of Rails 3.0, you can also pass <tt>:expires_in</tt> with a time
+ # interval (in seconds) to schedule expiration of the cached item.
#
# The following example depicts some of the points made above:
#
@@ -56,14 +57,14 @@ module ActionController #:nodoc:
#
# caches_page :public
#
- # caches_action :index, :if => proc do
+ # caches_action :index, :if => Proc.new do
# !request.format.json? # cache if is not a JSON request
# end
#
# caches_action :show, :cache_path => { :project => 1 },
# :expires_in => 1.hour
#
- # caches_action :feed, :cache_path => proc do
+ # caches_action :feed, :cache_path => Proc.new do
# if params[:user_id]
# user_list_url(params[:user_id, params[:id])
# else
@@ -102,8 +103,10 @@ module ActionController #:nodoc:
end
def _save_fragment(name, options)
- content = response_body
- content = content.join if content.is_a?(Array)
+ content = ""
+ response_body.each do |parts|
+ content << parts
+ end
if caching_allowed?
write_fragment(name, content, options)
diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb
index aa0cfc9395..2405bebb97 100644
--- a/actionpack/lib/action_controller/deprecated.rb
+++ b/actionpack/lib/action_controller/deprecated.rb
@@ -1,3 +1,7 @@
ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request
ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
-ActionController::Routing = ActionDispatch::Routing \ No newline at end of file
+ActionController::Routing = ActionDispatch::Routing
+
+ActiveSupport::Deprecation.warn 'ActionController::AbstractRequest and ActionController::Request are deprecated and will be removed, use ActionDispatch::Request instead.'
+ActiveSupport::Deprecation.warn 'ActionController::AbstractResponse and ActionController::Response are deprecated and will be removed, use ActionDispatch::Response instead.'
+ActiveSupport::Deprecation.warn 'ActionController::Routing is deprecated and will be removed, use ActionDispatch::Routing instead.' \ No newline at end of file
diff --git a/actionpack/lib/action_controller/deprecated/integration_test.rb b/actionpack/lib/action_controller/deprecated/integration_test.rb
index 86336b6bc4..54eae48f47 100644
--- a/actionpack/lib/action_controller/deprecated/integration_test.rb
+++ b/actionpack/lib/action_controller/deprecated/integration_test.rb
@@ -1,2 +1,5 @@
ActionController::Integration = ActionDispatch::Integration
ActionController::IntegrationTest = ActionDispatch::IntegrationTest
+
+ActiveSupport::Deprecation.warn 'ActionController::Integration is deprecated and will be removed, use ActionDispatch::Integration instead.'
+ActiveSupport::Deprecation.warn 'ActionController::IntegrationTest is deprecated and will be removed, use ActionDispatch::IntegrationTest instead.'
diff --git a/actionpack/lib/action_controller/deprecated/performance_test.rb b/actionpack/lib/action_controller/deprecated/performance_test.rb
index fcf47d31a7..c7ba5a2fe7 100644
--- a/actionpack/lib/action_controller/deprecated/performance_test.rb
+++ b/actionpack/lib/action_controller/deprecated/performance_test.rb
@@ -1 +1,3 @@
ActionController::PerformanceTest = ActionDispatch::PerformanceTest
+
+ActiveSupport::Deprecation.warn 'ActionController::PerformanceTest is deprecated and will be removed, use ActionDispatch::PerformanceTest instead.'
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 125dbf6bb5..92433ab462 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -181,15 +181,13 @@ module ActionController
@_status = Rack::Utils.status_code(status)
end
- def response_body=(val)
- body = if val.is_a?(String)
- [val]
- elsif val.nil? || val.respond_to?(:each)
- val
- else
- [val]
- end
- super body
+ def response_body=(body)
+ body = [body] unless body.nil? || body.respond_to?(:each)
+ super
+ end
+
+ def performed?
+ !!response_body
end
def dispatch(name, request) #:nodoc:
diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb
deleted file mode 100644
index 43719d5808..0000000000
--- a/actionpack/lib/action_controller/metal/compatibility.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-module ActionController
- module Compatibility
- extend ActiveSupport::Concern
-
- class ::ActionController::ActionControllerError < StandardError #:nodoc:
- end
-
- # Temporary hax
- included do
- ::ActionController::UnknownAction = ::AbstractController::ActionNotFound
- ::ActionController::DoubleRenderError = ::AbstractController::DoubleRenderError
-
- # ROUTES TODO: This should be handled by a middleware and route generation
- # should be able to handle SCRIPT_NAME
- self.config.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT']
-
- class << self
- delegate :default_charset=, :to => "ActionDispatch::Response"
- end
-
- self.protected_instance_variables = [
- :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request,
- :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout
- ]
-
- def rescue_action(env)
- raise env["action_dispatch.rescue.exception"]
- end
- end
-
- # For old tests
- def initialize_template_class(*) end
- def assign_shortcuts(*) end
-
- def _normalize_options(options)
- options[:text] = nil if options.delete(:nothing) == true
- options[:text] = " " if options.key?(:text) && options[:text].nil?
- super
- end
-
- def render_to_body(options)
- options[:template].sub!(/^\//, '') if options.key?(:template)
- super || " "
- end
-
- def _handle_method_missing
- method_missing(@_action_name.to_sym)
- end
-
- def method_for_action(action_name)
- super || (respond_to?(:method_missing) && "_handle_method_missing")
- end
-
- def performed?
- response_body
- end
- end
-end
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index 1645400693..2a8e4c575e 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -110,16 +110,23 @@ module ActionController
#
# Examples:
# expires_in 20.minutes
- # expires_in 3.hours, :public => true
+ # expires_in 3.hours, :public => true, :must_revalidate => true
# expires_in 3.hours, 'max-stale' => 5.hours, :public => true
#
# This method will overwrite an existing Cache-Control header.
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
+ #
+ # The method will also ensure a HTTP Date header for client compatibility.
def expires_in(seconds, options = {}) #:doc:
- response.cache_control.merge!(:max_age => seconds, :public => options.delete(:public))
+ response.cache_control.merge!(
+ :max_age => seconds,
+ :public => options.delete(:public),
+ :must_revalidate => options.delete(:must_revalidate)
+ )
options.delete(:private)
response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
+ response.date = Time.now unless response.date?
end
# Sets a HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should occur by the browser or
diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb
index 07024d0a9a..ece9ba3725 100644
--- a/actionpack/lib/action_controller/metal/exceptions.rb
+++ b/actionpack/lib/action_controller/metal/exceptions.rb
@@ -43,4 +43,4 @@ module ActionController
class UnknownHttpMethod < ActionControllerError #:nodoc:
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index 0fd42f9d8a..a1e40fc4e0 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -18,17 +18,33 @@ module ActionController
# Force the request to this particular controller or specified actions to be
# under HTTPS protocol.
#
- # Note that this method will not be effective on development environment.
+ # If you need to disable this for any reason (e.g. development) then you can use
+ # an +:if+ or +:unless+ condition.
+ #
+ # class AccountsController < ApplicationController
+ # force_ssl :if => :ssl_configured?
+ #
+ # def ssl_configured?
+ # !Rails.env.development?
+ # end
+ # end
#
# ==== Options
+ # * <tt>host</tt> - Redirect to a different host name
# * <tt>only</tt> - The callback should be run only for this action
# * <tt>except<tt> - The callback should be run for all actions except this action
+ # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback
+ # will be called only when it returns a true value.
+ # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
+ # will be called only when it returns a false value.
def force_ssl(options = {})
host = options.delete(:host)
before_filter(options) do
- if !request.ssl? && !Rails.env.development?
+ unless request.ssl?
redirect_options = {:protocol => 'https://', :status => :moved_permanently}
redirect_options.merge!(:host => host) if host
+ redirect_options.merge!(:params => request.query_parameters)
+ flash.keep
redirect_to redirect_options
end
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 4972c6bede..b8b43ea9ef 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -67,7 +67,7 @@ module ActionController
# class PostsController < ApplicationController
# REALM = "SuperSecret"
# USERS = {"dhh" => "secret", #plain text password
- # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":")) #ha1 digest password
+ # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
#
# before_filter :authenticate, :except => [:index]
#
@@ -279,7 +279,7 @@ module ActionController
#
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
- # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
+ # POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
# of this document.
#
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
@@ -293,7 +293,7 @@ module ActionController
end
# Might want a shorter timeout depending on whether the request
- # is a PUT or POST, and if client is browser or web service.
+ # is a PATCH, PUT, or POST, and if client is browser or web service.
# Can be much shorter if the Stale directive is implemented. This would
# allow a user to use new nonce without prompting user again for their
# username and password.
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index e8e465d3ba..ae04b53825 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -2,7 +2,7 @@ module ActionController
module ImplicitRender
def send_action(method, *args)
ret = super
- default_render unless response_body
+ default_render unless performed?
ret
end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index ca383be76b..55de7e7d8e 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -191,8 +191,9 @@ 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?
- if response = retrieve_response_from_mimes(mimes, &block)
- response.call(nil)
+ if collector = retrieve_collector_from_mimes(mimes, &block)
+ response = collector.response
+ response ? response.call : default_render({})
end
end
@@ -232,10 +233,18 @@ module ActionController #:nodoc:
raise "In order to use respond_with, first you need to declare the formats your " <<
"controller responds to in the class level" if self.class.mimes_for_respond_to.empty?
- if response = retrieve_response_from_mimes(&block)
+ if collector = retrieve_collector_from_mimes(&block)
options = resources.size == 1 ? {} : resources.extract_options!
- options.merge!(:default_response => response)
- (options.delete(:responder) || self.class.responder).call(self, resources, options)
+
+ if defined_response = collector.response
+ if action = options.delete(:action)
+ render :action => action
+ else
+ defined_response.call
+ end
+ else
+ (options.delete(:responder) || self.class.responder).call(self, resources, options)
+ end
end
end
@@ -263,15 +272,16 @@ module ActionController #:nodoc:
# 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=nil, &block) #:nodoc:
+ def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc:
mimes ||= collect_mimes_from_class_level
- collector = Collector.new(mimes) { |options| default_render(options || {}) }
+ collector = Collector.new(mimes)
block.call(collector) if block_given?
+ format = collector.negotiate_format(request)
- if format = request.negotiate_mime(collector.order)
+ if format
self.content_type ||= format.to_s
- lookup_context.freeze_formats([format.to_sym])
- collector.response_for(format)
+ lookup_context.formats = [format.to_sym]
+ collector
else
head :not_acceptable
nil
@@ -280,10 +290,10 @@ module ActionController #:nodoc:
class Collector #:nodoc:
include AbstractController::Collector
- attr_accessor :order
+ attr_accessor :order, :format
- def initialize(mimes, &block)
- @order, @responses, @default_response = [], {}, block
+ def initialize(mimes)
+ @order, @responses = [], {}
mimes.each { |mime| send(mime) }
end
@@ -302,8 +312,12 @@ module ActionController #:nodoc:
@responses[mime_type] ||= block
end
- def response_for(mime)
- @responses[mime] || @responses[Mime::ALL] || @default_response
+ def response
+ @responses[format] || @responses[Mime::ALL]
+ end
+
+ def negotiate_format(request)
+ @format = request.negotiate_mime(order)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 70fd79bb8b..c5e7d4e357 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -14,7 +14,7 @@ module ActionController
def render(*args) #:nodoc:
raise ::AbstractController::DoubleRenderError if response_body
super
- self.content_type ||= Mime[formats.first].to_s
+ self.content_type ||= Mime[lookup_context.rendered_format].to_s
response_body
end
@@ -29,6 +29,10 @@ module ActionController
self.response_body = nil
end
+ def render_to_body(*)
+ super || " "
+ end
+
private
# Normalize arguments by catching blocks and setting them on :update.
@@ -44,6 +48,10 @@ module ActionController
options[:text] = options[:text].to_text
end
+ if options.delete(:nothing) || (options.key?(:text) && options[:text].nil?)
+ options[:text] = " "
+ end
+
if options[:status]
options[:status] = Rack::Utils.status_code(options[:status])
end
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index 9500a349cb..ccda01ed44 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -53,7 +53,7 @@ module ActionController #:nodoc:
# end
# end
#
- # The same happens for PUT and DELETE requests.
+ # The same happens for PATCH/PUT and DELETE requests.
#
# === Nested resources
#
@@ -116,8 +116,9 @@ module ActionController #:nodoc:
class Responder
attr_reader :controller, :request, :format, :resource, :resources, :options
- ACTIONS_FOR_VERBS = {
+ DEFAULT_ACTIONS_FOR_VERBS = {
:post => :new,
+ :patch => :edit,
:put => :edit
}
@@ -129,11 +130,10 @@ module ActionController #:nodoc:
@resources = resources
@options = options
@action = options.delete(:action)
- @default_response = options.delete(:default_response)
end
delegate :head, :render, :redirect_to, :to => :controller
- delegate :get?, :post?, :put?, :delete?, :to => :request
+ delegate :get?, :post?, :patch?, :put?, :delete?, :to => :request
# Undefine :to_json and :to_yaml since it's defined on Object
undef_method(:to_json) if method_defined?(:to_json)
@@ -226,7 +226,7 @@ module ActionController #:nodoc:
# controller.
#
def default_render
- @default_response.call(options)
+ controller.default_render(options)
end
# Display is just a shortcut to render a resource with the current format.
@@ -260,15 +260,15 @@ module ActionController #:nodoc:
resource.respond_to?(:errors) && !resource.errors.empty?
end
- # By default, render the <code>:edit</code> action for HTML requests with failure, unless
- # the verb is POST.
+ # By default, render the <code>:edit</code> action for HTML requests with errors, unless
+ # the verb was POST.
#
def default_action
- @action ||= ACTIONS_FOR_VERBS[request.request_method_symbol]
+ @action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol]
end
def resource_errors
- respond_to?("#{format}_resource_errors") ? send("#{format}_resource_errors") : resource.errors
+ respond_to?("#{format}_resource_errors", true) ? send("#{format}_resource_errors") : resource.errors
end
def json_resource_errors
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 0e46402962..e9783e6919 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -216,7 +216,7 @@ module ActionController #:nodoc:
end
end
- # Call render_to_body if we are streaming instead of usual +render+.
+ # Call render_body if we are streaming instead of usual +render+.
def _render_template(options) #:nodoc:
if options.delete(:stream)
Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index 7af256fd99..a288e69649 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -14,14 +14,17 @@ module ActionController
end
initializer "action_controller.initialize_framework_caches" do
- ActiveSupport.on_load(:action_controller) { self.cache_store ||= RAILS_CACHE }
+ ActiveSupport.on_load(:action_controller) { self.cache_store ||= Rails.cache }
+ end
+
+ initializer "action_controller.assets_config", :group => :all do |app|
+ app.config.action_controller.assets_dir ||= app.config.paths["public"].first
end
initializer "action_controller.set_configs" do |app|
paths = app.config.paths
options = app.config.action_controller
- options.assets_dir ||= paths["public"].first
options.javascripts_dir ||= paths["public/javascripts"].first
options.stylesheets_dir ||= paths["public/stylesheets"].first
options.page_cache_directory ||= paths["public"].first
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index ed402f5f4d..3509e74d5e 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -225,7 +225,7 @@ module ActionController
# == Basic example
#
# Functional tests are written as follows:
- # 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
+ # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
# an HTTP request.
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
# the controller's HTTP response, the database contents, etc.
@@ -251,6 +251,7 @@ module ActionController
# def test_create
# json = {:book => { :title => "Love Hina" }}.to_json
# post :create, json
+ # end
#
# == Special instance variables
#
@@ -350,7 +351,7 @@ module ActionController
def tests(controller_class)
case controller_class
when String, Symbol
- self.controller_class = "#{controller_class.to_s.underscore}_controller".camelize.constantize
+ self.controller_class = "#{controller_class.to_s.camelize}Controller".constantize
when Class
self.controller_class = controller_class
else
@@ -391,6 +392,11 @@ module ActionController
process(action, "POST", *args)
end
+ # Executes a request simulating PATCH HTTP method and set/volley the response
+ def patch(action, *args)
+ process(action, "PATCH", *args)
+ end
+
# Executes a request simulating PUT HTTP method and set/volley the response
def put(action, *args)
process(action, "PUT", *args)
@@ -403,7 +409,7 @@ module ActionController
# Executes a request simulating HEAD HTTP method and set/volley the response
def head(action, parameters = nil, session = nil, flash = nil)
- process(action, parameters, session, flash, "HEAD")
+ process(action, "HEAD", parameters, session, flash)
end
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@@ -431,9 +437,9 @@ module ActionController
def process(action, http_method = 'GET', *args)
check_required_ivars
- http_method, args = handle_old_process_api(http_method, args)
+ http_method, args = handle_old_process_api(http_method, args)
- if args.first.is_a?(String)
+ if args.first.is_a?(String) && http_method != 'HEAD'
@request.env['RAW_POST_DATA'] = args.shift
end
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
index af06bffa16..24ffc28710 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
@@ -171,7 +171,7 @@ module HTML
def contains_bad_protocols?(attr_name, value)
uri_attributes.include?(attr_name) &&
- (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase))
+ (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip))
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index bea62b94d2..5ee4c044ea 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -60,6 +60,20 @@ module ActionDispatch
headers[LAST_MODIFIED] = utc_time.httpdate
end
+ def date
+ if date_header = headers['Date']
+ Time.httpdate(date_header)
+ end
+ end
+
+ def date?
+ headers.include?('Date')
+ end
+
+ def date=(utc_time)
+ headers['Date'] = utc_time.httpdate
+ end
+
def etag=(etag)
key = ActiveSupport::Cache.expand_cache_key(etag)
@etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}")
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 02a15ad599..132b0c82bc 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -50,7 +50,7 @@ module ActionDispatch
end
def env_filter
- parameter_filter_for(Array(@env["action_dispatch.parameter_filter"]) << /RAW_POST_DATA/)
+ parameter_filter_for(Array(@env["action_dispatch.parameter_filter"]) + [/RAW_POST_DATA/, "rack.request.form_vars"])
end
def parameter_filter_for(filters)
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 25affb9f50..2152351703 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -82,6 +82,7 @@ module Mime
class << self
TRAILING_STAR_REGEXP = /(text|application)\/\*/
+ Q_SEPARATOR_REGEXP = /;\s*q=/
def lookup(string)
LOOKUP[string]
@@ -108,6 +109,7 @@ module Mime
def parse(accept_header)
if accept_header !~ /,/
+ accept_header = accept_header.split(Q_SEPARATOR_REGEXP).first
if accept_header =~ TRAILING_STAR_REGEXP
parse_data_with_trailing_star($1)
else
@@ -117,7 +119,7 @@ module Mime
# keep track of creation order to keep the subsequent sort stable
list, index = [], 0
accept_header.split(/,/).each do |header|
- params, q = header.split(/;\s*q=/)
+ params, q = header.split(Q_SEPARATOR_REGEXP)
if params.present?
params.strip!
diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb
index 3da4f91051..a6b3aee5e7 100644
--- a/actionpack/lib/action_dispatch/http/mime_types.rb
+++ b/actionpack/lib/action_dispatch/http/mime_types.rb
@@ -9,7 +9,7 @@ Mime::Type.register "text/calendar", :ics
Mime::Type.register "text/csv", :csv
Mime::Type.register "image/png", :png, [], %w(png)
-Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe)
+Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
Mime::Type.register "image/gif", :gif, [], %w(gif)
Mime::Type.register "image/bmp", :bmp, [], %w(bmp)
Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff)
diff --git a/actionpack/lib/action_dispatch/http/rack_cache.rb b/actionpack/lib/action_dispatch/http/rack_cache.rb
index cc8edee300..003ae4029d 100644
--- a/actionpack/lib/action_dispatch/http/rack_cache.rb
+++ b/actionpack/lib/action_dispatch/http/rack_cache.rb
@@ -8,8 +8,7 @@ module ActionDispatch
new
end
- # TODO: Finally deal with the RAILS_CACHE global
- def initialize(store = RAILS_CACHE)
+ def initialize(store = Rails.cache)
@store = store
end
@@ -33,7 +32,7 @@ module ActionDispatch
new
end
- def initialize(store = RAILS_CACHE)
+ def initialize(store = Rails.cache)
@store = store
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 0a0ebe7fad..de014a9c00 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -97,6 +97,12 @@ module ActionDispatch
HTTP_METHOD_LOOKUP[request_method] == :post
end
+ # Is this a PATCH request?
+ # Equivalent to <tt>request.request_method == :patch</tt>.
+ def patch?
+ HTTP_METHOD_LOOKUP[request_method] == :patch
+ end
+
# Is this a PUT request?
# Equivalent to <tt>request.request_method_symbol == :put</tt>.
def put?
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 64459836b5..80ffbe575b 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -1,6 +1,8 @@
module ActionDispatch
module Http
module URL
+ IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
+
mattr_accessor :tld_length
self.tld_length = 1
@@ -21,7 +23,7 @@ module ActionDispatch
end
def url_for(options = {})
- unless options[:host].present? || options[:only_path].present?
+ if options[:host].blank? && options[:only_path].blank?
raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
end
@@ -52,7 +54,7 @@ module ActionDispatch
private
def named_host?(host)
- !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
+ host && IP_HOST_REGEXP !~ host
end
def rewrite_authentication(options)
@@ -167,7 +169,7 @@ module ActionDispatch
# such as 2 to catch <tt>"www"</tt> instead of <tt>"www.rubyonrails"</tt>
# in "www.rubyonrails.co.uk".
def subdomain(tld_length = @@tld_length)
- subdomains(tld_length).join(".")
+ ActionDispatch::Http::URL.extract_subdomain(host, tld_length)
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index 8c0f4052ec..338b116940 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -5,7 +5,7 @@ module ActionDispatch
class Callbacks
include ActiveSupport::Callbacks
- define_callbacks :call, :rescuable => true
+ define_callbacks :call
class << self
delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader"
@@ -24,9 +24,15 @@ module ActionDispatch
end
def call(env)
- run_callbacks :call do
- @app.call(env)
+ error = nil
+ result = run_callbacks :call do
+ begin
+ @app.call(env)
+ rescue => error
+ end
end
+ raise error if error
+ result
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 39ff58a447..25f1db8228 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -191,6 +191,15 @@ module ActionDispatch
value
end
+ # Whether the given cookie is to be deleted by this CookieJar.
+ # Like <tt>[]=</tt>, you can pass in an options hash to test if a
+ # deletion applies to a specific <tt>:path</tt>, <tt>:domain</tt> etc.
+ def deleted?(key, options = {})
+ options.symbolize_keys!
+ handle_options(options)
+ @delete_cookies[key.to_s] == options
+ end
+
# Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
def clear(options = {})
@cookies.each_key{ |k| delete(k, options) }
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index 4f48f1c974..a0388e0e13 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -60,8 +60,10 @@ module ActionDispatch
def call(env)
@validated = @condition.call
prepare!
+
response = @app.call(env)
- response[2].extend(module_hook)
+ response[2] = ::Rack::BodyProxy.new(response[2]) { cleanup! }
+
response
rescue Exception
cleanup!
@@ -83,18 +85,5 @@ module ActionDispatch
def validated? #:nodoc:
@validated
end
-
- def module_hook #:nodoc:
- middleware = self
- Module.new do
- define_method :close do
- begin
- super() if defined?(super)
- ensure
- middleware.cleanup!
- end
- end
- end
- end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 030ccb2017..d924f21fad 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -18,11 +18,13 @@ module ActionDispatch
def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
@app = app
@check_ip = check_ip_spoofing
- if custom_proxies
- custom_regexp = Regexp.new(custom_proxies)
- @proxies = Regexp.union(TRUSTED_PROXIES, custom_regexp)
+ @proxies = case custom_proxies
+ when Regexp
+ custom_proxies
+ when nil
+ TRUSTED_PROXIES
else
- @proxies = TRUSTED_PROXIES
+ Regexp.union(TRUSTED_PROXIES, custom_proxies)
end
end
@@ -57,7 +59,7 @@ module ActionDispatch
"HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
end
- not_proxy = client_ip || forwarded_ips.last || remote_addrs.first
+ not_proxy = client_ip || forwarded_ips.first || remote_addrs.first
# Return first REMOTE_ADDR if there are no other options
not_proxy || ips_from('REMOTE_ADDR', :allow_proxies).first
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index a4308f528c..28e8fbdab8 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -93,8 +93,9 @@ module ActionDispatch
end
def swap(target, *args, &block)
- insert_before(target, *args, &block)
- delete(target)
+ index = assert_index(target, :before)
+ insert(index, *args, &block)
+ middlewares.delete_at(index + 1)
end
def delete(target)
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 404943d720..63b7422287 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -1,4 +1,5 @@
require 'rack/utils'
+require 'active_support/core_ext/uri'
module ActionDispatch
class FileHandler
@@ -11,14 +12,14 @@ module ActionDispatch
def match?(path)
path = path.dup
- full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path))
+ full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(unescape_path(path)))
paths = "#{full_path}#{ext}"
matches = Dir[paths]
match = matches.detect { |m| File.file?(m) }
if match
match.sub!(@compiled_root, '')
- match
+ ::Rack::Utils.escape(match)
end
end
@@ -32,6 +33,14 @@ module ActionDispatch
"{,#{ext},/index#{ext}}"
end
end
+
+ def unescape_path(path)
+ URI.parser.unescape(path)
+ end
+
+ def escape_glob_chars(path)
+ path.gsub(/[*?{}\[\]]/, "\\\\\\&")
+ end
end
class Static
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 46c06386d8..35f901c575 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -11,6 +11,7 @@ module ActionDispatch
config.action_dispatch.ignore_accept_header = false
config.action_dispatch.rescue_templates = { }
config.action_dispatch.rescue_responses = { }
+ config.action_dispatch.default_charset = nil
config.action_dispatch.rack_cache = {
:metastore => "rails:/",
@@ -21,7 +22,7 @@ module ActionDispatch
initializer "action_dispatch.configure" do |app|
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
- ActionDispatch::Response.default_charset = app.config.encoding
+ ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 2f6b9d266d..38a0270151 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -182,15 +182,18 @@ module ActionDispatch
#
# == HTTP Methods
#
- # Using the <tt>:via</tt> option when specifying a route allows you to restrict it to a specific HTTP method.
- # Possible values are <tt>:post</tt>, <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>.
- # If your route needs to respond to more than one method you can use an array, e.g. <tt>[ :get, :post ]</tt>.
- # The default value is <tt>:any</tt> which means that the route will respond to any of the HTTP methods.
+ # Using the <tt>:via</tt> option when specifying a route allows you to
+ # restrict it to a specific HTTP method. Possible values are <tt>:post</tt>,
+ # <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and
+ # <tt>:any</tt>. If your route needs to respond to more than one method you
+ # can use an array, e.g. <tt>[ :get, :post ]</tt>. The default value is
+ # <tt>:any</tt> which means that the route will respond to any of the HTTP
+ # methods.
#
# Examples:
#
# match 'post/:id' => 'posts#show', :via => :get
- # match 'post/:id' => "posts#create_comment', :via => :post
+ # match 'post/:id' => 'posts#create_comment', :via => :post
#
# Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
# URL will route to the <tt>show</tt> action.
@@ -198,12 +201,12 @@ module ActionDispatch
# === HTTP helper methods
#
# An alternative method of specifying which HTTP method a route should respond to is to use the helper
- # methods <tt>get</tt>, <tt>post</tt>, <tt>put</tt> and <tt>delete</tt>.
+ # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
#
# Examples:
#
# get 'post/:id' => 'posts#show'
- # post 'post/:id' => "posts#create_comment'
+ # post 'post/:id' => 'posts#create_comment'
#
# This syntax is less verbose and the intention is more apparent to someone else reading your code,
# however if your route needs to respond to more than one HTTP method (or all methods) then using the
@@ -283,6 +286,6 @@ module ActionDispatch
autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
SEPARATORS = %w( / . ? ) #:nodoc:
- HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc:
+ HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index db1e3198b3..02a27110e4 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -54,6 +54,7 @@ module ActionDispatch
def initialize(set, scope, path, options)
@set, @scope = set, scope
+ @segment_keys = nil
@options = (@scope[:options] || {}).merge(options)
@path = normalize_path(path)
normalize_options!
@@ -213,7 +214,9 @@ module ActionDispatch
end
def segment_keys
- @segment_keys ||= Journey::Path::Pattern.new(
+ return @segment_keys if @segment_keys
+
+ @segment_keys = Journey::Path::Pattern.new(
Journey::Router::Strexp.compile(@path, requirements, SEPARATORS)
).names
end
@@ -325,7 +328,7 @@ module ActionDispatch
# +call+ or a string representing a controller's action.
#
# match 'path', :to => 'controller#action'
- # match 'path', :to => lambda { [200, {}, "Success!"] }
+ # match 'path', :to => lambda { |env| [200, {}, "Success!"] }
# match 'path', :to => RackApp
#
# [:on]
@@ -462,9 +465,9 @@ module ActionDispatch
#
# Example:
#
- # get 'bacon', :to => 'food#bacon'
+ # get 'bacon', :to => 'food#bacon'
def get(*args, &block)
- map_method(:get, *args, &block)
+ map_method(:get, args, &block)
end
# Define a route that only recognizes HTTP POST.
@@ -472,9 +475,19 @@ module ActionDispatch
#
# Example:
#
- # post 'bacon', :to => 'food#bacon'
+ # post 'bacon', :to => 'food#bacon'
def post(*args, &block)
- map_method(:post, *args, &block)
+ map_method(:post, args, &block)
+ end
+
+ # Define a route that only recognizes HTTP PATCH.
+ # For supported arguments, see <tt>Base#match</tt>.
+ #
+ # Example:
+ #
+ # patch 'bacon', :to => 'food#bacon'
+ def patch(*args, &block)
+ map_method(:patch, args, &block)
end
# Define a route that only recognizes HTTP PUT.
@@ -482,27 +495,26 @@ module ActionDispatch
#
# Example:
#
- # put 'bacon', :to => 'food#bacon'
+ # put 'bacon', :to => 'food#bacon'
def put(*args, &block)
- map_method(:put, *args, &block)
+ map_method(:put, args, &block)
end
- # Define a route that only recognizes HTTP PUT.
+ # Define a route that only recognizes HTTP DELETE.
# For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
- # delete 'broccoli', :to => 'food#broccoli'
+ # delete 'broccoli', :to => 'food#broccoli'
def delete(*args, &block)
- map_method(:delete, *args, &block)
+ map_method(:delete, args, &block)
end
private
- def map_method(method, *args, &block)
+ def map_method(method, args, &block)
options = args.extract_options!
options[:via] = method
- args.push(options)
- match(*args, &block)
+ match(*args, options, &block)
self
end
end
@@ -520,13 +532,13 @@ module ActionDispatch
# This will create a number of routes for each of the posts and comments
# controller. For <tt>Admin::PostsController</tt>, Rails will create:
#
- # GET /admin/posts
- # GET /admin/posts/new
- # POST /admin/posts
- # GET /admin/posts/1
- # GET /admin/posts/1/edit
- # PUT /admin/posts/1
- # DELETE /admin/posts/1
+ # GET /admin/posts
+ # GET /admin/posts/new
+ # POST /admin/posts
+ # GET /admin/posts/1
+ # GET /admin/posts/1/edit
+ # PATCH/PUT /admin/posts/1
+ # DELETE /admin/posts/1
#
# If you want to route /posts (without the prefix /admin) to
# <tt>Admin::PostsController</tt>, you could use
@@ -554,13 +566,13 @@ module ActionDispatch
# not use scope. In the last case, the following paths map to
# +PostsController+:
#
- # GET /admin/posts
- # GET /admin/posts/new
- # POST /admin/posts
- # GET /admin/posts/1
- # GET /admin/posts/1/edit
- # PUT /admin/posts/1
- # DELETE /admin/posts/1
+ # GET /admin/posts
+ # GET /admin/posts/new
+ # POST /admin/posts
+ # GET /admin/posts/1
+ # GET /admin/posts/1/edit
+ # PATCH/PUT /admin/posts/1
+ # DELETE /admin/posts/1
module Scoping
# Scopes a set of routes to the given default options.
#
@@ -649,13 +661,13 @@ module ActionDispatch
#
# This generates the following routes:
#
- # admin_posts GET /admin/posts(.:format) admin/posts#index
- # admin_posts POST /admin/posts(.:format) admin/posts#create
- # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
- # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
- # admin_post GET /admin/posts/:id(.:format) admin/posts#show
- # admin_post PUT /admin/posts/:id(.:format) admin/posts#update
- # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
+ # admin_posts GET /admin/posts(.:format) admin/posts#index
+ # admin_posts POST /admin/posts(.:format) admin/posts#create
+ # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
+ # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
+ # admin_post GET /admin/posts/:id(.:format) admin/posts#show
+ # admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update
+ # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
#
# === Options
#
@@ -970,12 +982,12 @@ module ActionDispatch
# the +GeoCoders+ controller (note that the controller is named after
# the plural):
#
- # GET /geocoder/new
- # POST /geocoder
- # GET /geocoder
- # GET /geocoder/edit
- # PUT /geocoder
- # DELETE /geocoder
+ # GET /geocoder/new
+ # POST /geocoder
+ # GET /geocoder
+ # GET /geocoder/edit
+ # PATCH/PUT /geocoder
+ # DELETE /geocoder
#
# === Options
# Takes same options as +resources+.
@@ -998,9 +1010,12 @@ module ActionDispatch
end if parent_resource.actions.include?(:new)
member do
- get :edit if parent_resource.actions.include?(:edit)
- get :show if parent_resource.actions.include?(:show)
- put :update if parent_resource.actions.include?(:update)
+ get :edit if parent_resource.actions.include?(:edit)
+ get :show if parent_resource.actions.include?(:show)
+ if parent_resource.actions.include?(:update)
+ patch :update
+ put :update
+ end
delete :destroy if parent_resource.actions.include?(:destroy)
end
end
@@ -1018,13 +1033,13 @@ module ActionDispatch
# creates seven different routes in your application, all mapping to
# the +Photos+ controller:
#
- # GET /photos
- # GET /photos/new
- # POST /photos
- # GET /photos/:id
- # GET /photos/:id/edit
- # PUT /photos/:id
- # DELETE /photos/:id
+ # GET /photos
+ # GET /photos/new
+ # POST /photos
+ # GET /photos/:id
+ # GET /photos/:id/edit
+ # PATCH/PUT /photos/:id
+ # DELETE /photos/:id
#
# Resources can also be nested infinitely by using this block syntax:
#
@@ -1034,13 +1049,13 @@ module ActionDispatch
#
# This generates the following comments routes:
#
- # GET /photos/:photo_id/comments
- # GET /photos/:photo_id/comments/new
- # POST /photos/:photo_id/comments
- # GET /photos/:photo_id/comments/:id
- # GET /photos/:photo_id/comments/:id/edit
- # PUT /photos/:photo_id/comments/:id
- # DELETE /photos/:photo_id/comments/:id
+ # GET /photos/:photo_id/comments
+ # GET /photos/:photo_id/comments/new
+ # POST /photos/:photo_id/comments
+ # GET /photos/:photo_id/comments/:id
+ # GET /photos/:photo_id/comments/:id/edit
+ # PATCH/PUT /photos/:photo_id/comments/:id
+ # DELETE /photos/:photo_id/comments/:id
#
# === Options
# Takes same options as <tt>Base#match</tt> as well as:
@@ -1102,13 +1117,13 @@ module ActionDispatch
#
# The +comments+ resource here will have the following routes generated for it:
#
- # post_comments GET /posts/:post_id/comments(.:format)
- # post_comments POST /posts/:post_id/comments(.:format)
- # new_post_comment GET /posts/:post_id/comments/new(.:format)
- # edit_comment GET /sekret/comments/:id/edit(.:format)
- # comment GET /sekret/comments/:id(.:format)
- # comment PUT /sekret/comments/:id(.:format)
- # comment DELETE /sekret/comments/:id(.:format)
+ # post_comments GET /posts/:post_id/comments(.:format)
+ # post_comments POST /posts/:post_id/comments(.:format)
+ # new_post_comment GET /posts/:post_id/comments/new(.:format)
+ # edit_comment GET /sekret/comments/:id/edit(.:format)
+ # comment GET /sekret/comments/:id(.:format)
+ # comment PATCH/PUT /sekret/comments/:id(.:format)
+ # comment DELETE /sekret/comments/:id(.:format)
#
# === Examples
#
@@ -1137,9 +1152,12 @@ module ActionDispatch
end if parent_resource.actions.include?(:new)
member do
- get :edit if parent_resource.actions.include?(:edit)
- get :show if parent_resource.actions.include?(:show)
- put :update if parent_resource.actions.include?(:update)
+ get :edit if parent_resource.actions.include?(:edit)
+ get :show if parent_resource.actions.include?(:show)
+ if parent_resource.actions.include?(:update)
+ patch :update
+ put :update
+ end
delete :destroy if parent_resource.actions.include?(:destroy)
end
end
@@ -1247,6 +1265,9 @@ module ActionDispatch
parent_resource.instance_of?(Resource) && @scope[:shallow]
end
+ # match 'path' => 'controller#action'
+ # match 'path', to: 'controller#action'
+ # match 'path', 'otherpath', on: :member, via: :get
def match(path, *rest)
if rest.empty? && Hash === path
options = path
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 2c21887220..57c6972078 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -9,6 +9,12 @@ require 'action_controller/metal/exceptions'
module ActionDispatch
module Routing
class RouteSet #:nodoc:
+ # Since the router holds references to many parts of the system
+ # like engines, controllers and the application itself, inspecting
+ # the route set can actually be really slow, therefore we default
+ # alias inspect to to_s.
+ alias inspect to_s
+
PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
class Dispatcher #:nodoc:
@@ -31,6 +37,7 @@ module ActionDispatch
end
def prepare_params!(params)
+ normalize_controller!(params)
merge_default_action!(params)
split_glob_param!(params) if @glob_param
end
@@ -66,6 +73,10 @@ module ActionDispatch
controller.action(action).call(env)
end
+ def normalize_controller!(params)
+ params[:controller] = params[:controller].underscore if params.key?(:controller)
+ end
+
def merge_default_action!(params)
params[:action] ||= 'index'
end
@@ -246,8 +257,7 @@ module ActionDispatch
def eval_block(block)
if block.arity == 1
raise "You are using the old router DSL which has been removed in Rails 3.1. " <<
- "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/ " <<
- "or add the rails_legacy_mapper gem to your Gemfile"
+ "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
end
mapper = Mapper.new(self)
if default_scope
@@ -360,7 +370,26 @@ module ActionDispatch
SEPARATORS,
anchor)
- Journey::Path::Pattern.new(strexp)
+ pattern = Journey::Path::Pattern.new(strexp)
+
+ builder = Journey::GTG::Builder.new pattern.spec
+
+ # Get all the symbol nodes followed by literals that are not the
+ # dummy node.
+ symbols = pattern.spec.grep(Journey::Nodes::Symbol).find_all { |n|
+ builder.followpos(n).first.literal?
+ }
+
+ # Get all the symbol nodes preceded by literals.
+ symbols.concat pattern.spec.find_all(&:literal?).map { |n|
+ builder.followpos(n).first
+ }.find_all(&:symbol?)
+
+ symbols.each { |x|
+ x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
+ }
+
+ pattern
end
private :build_path
@@ -463,7 +492,7 @@ module ActionDispatch
# if the current controller is "foo/bar/baz" and :controller => "baz/bat"
# is specified, the controller becomes "foo/baz/bat"
def use_relative_controller!
- if !named_route && different_controller?
+ if !named_route && different_controller? && !controller.start_with?("/")
old_parts = current_controller.split('/')
size = controller.count("/") + 1
parts = old_parts[0...-size] << controller
@@ -548,6 +577,7 @@ module ActionDispatch
path_addition, params = generate(path_options, path_segments || {})
path << path_addition
+ params.merge!(options[:params] || {})
ActionDispatch::Http::URL.url_for(options.merge!({
:path => path,
@@ -579,7 +609,8 @@ module ActionDispatch
params[key] = URI.parser.unescape(value)
end
end
-
+ old_params = env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] = (old_params || {}).merge(params)
dispatcher = route.app
while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do
dispatcher = dispatcher.app
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index 4d963803e6..8eed85bce2 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -340,8 +340,8 @@ module ActionDispatch
# element +encoded+. It then calls the block with all un-encoded elements.
#
# ==== Examples
- # # Selects all bold tags from within the title of an ATOM feed's entries (perhaps to nab a section name prefix)
- # assert_select_feed :atom, 1.0 do
+ # # Selects all bold tags from within the title of an Atom feed's entries (perhaps to nab a section name prefix)
+ # assert_select "feed[xmlns='http://www.w3.org/2005/Atom']" do
# # Select each entry item and then the title item
# assert_select "entry>title" do
# # Run assertions on the encoded title elements
@@ -353,7 +353,7 @@ module ActionDispatch
#
#
# # Selects all paragraph tags from within the description of an RSS feed
- # assert_select_feed :rss, 2.0 do
+ # assert_select "rss[version=2.0]" do
# # Select description element of each feed item.
# assert_select "channel>item>description" do
# # Run assertions on the encoded elements.
@@ -417,8 +417,8 @@ module ActionDispatch
deliveries = ActionMailer::Base.deliveries
assert !deliveries.empty?, "No e-mail in delivery list"
- for delivery in deliveries
- for part in (delivery.parts.empty? ? [delivery] : delivery.parts)
+ deliveries.each do |delivery|
+ (delivery.parts.empty? ? [delivery] : delivery.parts).each do |part|
if part["Content-Type"].to_s =~ /^text\/html\W/
root = HTML::Document.new(part.body.to_s).root
assert_select root, ":root", &block
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 26db8662a8..69d54f6981 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -4,7 +4,6 @@ require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/object/inclusion'
require 'active_support/core_ext/object/try'
require 'rack/test'
-require 'test/unit/assertions'
module ActionDispatch
module Integration #:nodoc:
@@ -27,8 +26,8 @@ module ActionDispatch
# object's <tt>@response</tt> instance variable will point to the same
# response object.
#
- # You can also perform POST, PUT, DELETE, and HEAD requests with +#post+,
- # +#put+, +#delete+, and +#head+.
+ # You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
+ # +#post+, +#patch+, +#put+, +#delete+, and +#head+.
def get(path, parameters = nil, headers = nil)
process :get, path, parameters, headers
end
@@ -39,6 +38,12 @@ module ActionDispatch
process :post, path, parameters, headers
end
+ # Performs a PATCH request with the given parameters. See +#get+ for more
+ # details.
+ def patch(path, parameters = nil, headers = nil)
+ process :patch, path, parameters, headers
+ end
+
# Performs a PUT request with the given parameters. See +#get+ for more
# details.
def put(path, parameters = nil, headers = nil)
@@ -57,13 +62,19 @@ module ActionDispatch
process :head, path, parameters, headers
end
+ # Performs a OPTIONS request with the given parameters. See +#get+ for
+ # more details.
+ def options(path, parameters = nil, headers = nil)
+ process :options, path, parameters, headers
+ end
+
# Performs an XMLHttpRequest request with the given parameters, mirroring
# a request from the Prototype library.
#
- # The request_method is +:get+, +:post+, +:put+, +:delete+ or +:head+; the
- # parameters are +nil+, a hash, or a url-encoded or multipart string;
- # the headers are a hash. Keys are automatically upcased and prefixed
- # with 'HTTP_' if not already.
+ # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
+ # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
+ # string; the headers are a hash. Keys are automatically upcased and
+ # prefixed with 'HTTP_' if not already.
def xml_http_request(request_method, path, parameters = nil, headers = nil)
headers ||= {}
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@@ -103,6 +114,12 @@ module ActionDispatch
request_via_redirect(:post, path, parameters, headers)
end
+ # Performs a PATCH request, following any subsequent redirect.
+ # See +request_via_redirect+ for more information.
+ def patch_via_redirect(path, parameters = nil, headers = nil)
+ request_via_redirect(:patch, path, parameters, headers)
+ end
+
# Performs a PUT request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def put_via_redirect(path, parameters = nil, headers = nil)
@@ -127,7 +144,7 @@ module ActionDispatch
class Session
DEFAULT_HOST = "www.example.com"
- include Test::Unit::Assertions
+ include MiniTest::Assertions
include TestProcess, RequestHelpers, Assertions
%w( status status_message headers body redirect? ).each do |method|
@@ -313,7 +330,7 @@ module ActionDispatch
@integration_session = Integration::Session.new(app)
end
- %w(get post put head delete cookies assigns
+ %w(get post patch put head delete options cookies assigns
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
reset! unless integration_session
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index b08ff41950..3a6d081721 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -5,7 +5,8 @@ require 'active_support/core_ext/hash/indifferent_access'
module ActionDispatch
module TestProcess
def assigns(key = nil)
- assigns = @controller.view_assigns.with_indifferent_access
+ assigns = {}.with_indifferent_access
+ @controller.view_assigns.each {|k, v| assigns.regular_writer(k, v)}
key.nil? ? assigns : assigns[key]
end
diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb
index aa5db0d7bc..2a28e780bf 100644
--- a/actionpack/lib/action_view/asset_paths.rb
+++ b/actionpack/lib/action_view/asset_paths.rb
@@ -4,6 +4,8 @@ require 'action_controller/metal/exceptions'
module ActionView
class AssetPaths #:nodoc:
+ URI_REGEXP = %r{^[-a-z]+://|^cid:|^//}
+
attr_reader :config, :controller
def initialize(config, controller = nil)
@@ -37,7 +39,7 @@ module ActionView
end
def is_uri?(path)
- path =~ %r{^[-a-z]+://|^cid:|^//}
+ path =~ URI_REGEXP
end
private
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 08149df423..23329d7f35 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -143,10 +143,10 @@ module ActionView #:nodoc:
class_attribute :helpers
class_attribute :_routes
+ class_attribute :logger
class << self
delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB'
- delegate :logger, :to => 'ActionController::Base', :allow_nil => true
def cache_template_loading
ActionView::Resolver.caching?
@@ -156,12 +156,6 @@ module ActionView #:nodoc:
ActionView::Resolver.caching = value
end
- def process_view_paths(value)
- value.is_a?(PathSet) ?
- value.dup : ActionView::PathSet.new(Array(value))
- end
- deprecate :process_view_paths
-
def xss_safe? #:nodoc:
true
end
diff --git a/actionpack/lib/action_view/flows.rb b/actionpack/lib/action_view/flows.rb
index a8f740713f..c0e458cd41 100644
--- a/actionpack/lib/action_view/flows.rb
+++ b/actionpack/lib/action_view/flows.rb
@@ -22,11 +22,8 @@ module ActionView
def append(key, value)
@content[key] << value
end
+ alias_method :append!, :append
- # Called by provide
- def append!(key, value)
- @content[key] << value
- end
end
class StreamingFlow < OutputFlow #:nodoc:
diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb
index 56f15604a6..e27111012d 100644
--- a/actionpack/lib/action_view/helpers/active_model_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_model_helper.rb
@@ -16,8 +16,8 @@ module ActionView
end
end
- %w(content_tag to_date_select_tag to_datetime_select_tag to_time_select_tag).each do |meth|
- module_eval "def #{meth}(*) error_wrapping(super) end", __FILE__, __LINE__
+ def content_tag(*)
+ error_wrapping(super)
end
def tag(type, options, *)
@@ -39,7 +39,7 @@ module ActionView
private
def object_has_errors?
- object.respond_to?(:errors) && object.errors.respond_to?(:full_messages) && error_message.any?
+ object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
end
def tag_generate_errors?(options)
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 5dbba3c4a7..6dd52d8186 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/array/extract_options'
+require 'active_support/core_ext/hash/keys'
require 'action_view/helpers/asset_tag_helpers/javascript_tag_helpers'
require 'action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers'
require 'action_view/helpers/asset_tag_helpers/asset_paths'
@@ -22,9 +24,10 @@ module ActionView
# server by setting ActionController::Base.asset_host in the application
# configuration, typically in <tt>config/environments/production.rb</tt>.
# For example, you'd define <tt>assets.example.com</tt> to be your asset
- # host this way:
+ # host this way, inside the <tt>configure</tt> block of your environment-specific
+ # configuration files or <tt>config/application.rb</tt>:
#
- # ActionController::Base.asset_host = "assets.example.com"
+ # config.action_controller.asset_host = "assets.example.com"
#
# Helpers take that into account:
#
@@ -196,7 +199,7 @@ module ActionView
include JavascriptTagHelpers
include StylesheetTagHelpers
# Returns a link tag that browsers and news readers can use to auto-detect
- # an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
+ # an RSS or Atom feed. The +type+ can either be <tt>:rss</tt> (default) or
# <tt>:atom</tt>. Control the link options in url_for format using the
# +url_options+. You can modify the LINK tag itself in +tag_options+.
#
@@ -232,7 +235,7 @@ module ActionView
#
# generates
#
- # <link href="/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+ # <link href="/assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
#
# You may specify a different file in the first argument:
#
@@ -250,7 +253,7 @@ module ActionView
#
# <%= favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png' %>
#
- def favicon_link_tag(source='/favicon.ico', options={})
+ def favicon_link_tag(source='favicon.ico', options={})
tag('link', {
:rel => 'shortcut icon',
:type => 'image/vnd.microsoft.icon',
@@ -276,6 +279,13 @@ module ActionView
end
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
+ # Computes the full URL to an image asset in the public images directory.
+ # This will use +image_path+ internally, so most of their behaviors will be the same.
+ def image_url(source)
+ URI.join(current_host, path_to_image(source)).to_s
+ end
+ alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route
+
# Computes the path to a video asset in the public videos directory.
# Full paths from the document root will be passed through.
# Used internally by +video_tag+ to build the video path.
@@ -291,6 +301,13 @@ module ActionView
end
alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
+ # Computes the full URL to a video asset in the public videos directory.
+ # This will use +video_path+ internally, so most of their behaviors will be the same.
+ def video_url(source)
+ URI.join(current_host, path_to_video(source)).to_s
+ end
+ alias_method :url_to_video, :video_url # aliased to avoid conflicts with an video_url named route
+
# Computes the path to an audio asset in the public audios directory.
# Full paths from the document root will be passed through.
# Used internally by +audio_tag+ to build the audio path.
@@ -306,6 +323,13 @@ module ActionView
end
alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
+ # Computes the full URL to a audio asset in the public audios directory.
+ # This will use +audio_path+ internally, so most of their behaviors will be the same.
+ def audio_url(source)
+ URI.join(current_host, path_to_audio(source)).to_s
+ end
+ alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route
+
# Computes the path to a font asset in the public fonts directory.
# Full paths from the document root will be passed through.
#
@@ -320,6 +344,13 @@ module ActionView
end
alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route
+ # Computes the full URL to a font asset in the public fonts directory.
+ # This will use +font_path+ internally, so most of their behaviors will be the same.
+ def font_url(source)
+ URI.join(current_host, path_to_font(source)).to_s
+ end
+ alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route
+
# Returns an html image tag for the +source+. The +source+ can be a full
# path or a file that exists in your public images directory.
#
@@ -353,8 +384,8 @@ module ActionView
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
# image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
- def image_tag(source, options = {})
- options.symbolize_keys!
+ def image_tag(source, options={})
+ options = options.dup.symbolize_keys!
src = options[:src] = path_to_image(source)
@@ -407,26 +438,19 @@ module ActionView
# <video src="/trailers/hd.avi" width="16" height="16" />
# video_tag("/trailers/hd.avi", :height => '32', :width => '32') # =>
# <video height="32" src="/trailers/hd.avi" width="32" />
+ # video_tag("trailer.ogg", "trailer.flv") # =>
+ # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
# video_tag(["trailer.ogg", "trailer.flv"]) # =>
- # <video><source src="trailer.ogg" /><source src="trailer.ogg" /><source src="trailer.flv" /></video>
+ # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
# video_tag(["trailer.ogg", "trailer.flv"] :size => "160x120") # =>
- # <video height="120" width="160"><source src="trailer.ogg" /><source src="trailer.flv" /></video>
- def video_tag(sources, options = {})
- options.symbolize_keys!
-
- options[:poster] = path_to_image(options[:poster]) if options[:poster]
-
- if size = options.delete(:size)
- options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
- end
+ # <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
+ def video_tag(*sources)
+ multiple_sources_tag('video', sources) do |options|
+ options[:poster] = path_to_image(options[:poster]) if options[:poster]
- if sources.is_a?(Array)
- content_tag("video", options) do
- sources.map { |source| tag("source", :src => source) }.join.html_safe
+ if size = options.delete(:size)
+ options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
end
- else
- options[:src] = path_to_video(sources)
- tag("video", options)
end
end
@@ -441,10 +465,10 @@ module ActionView
# <audio src="/audios/sound.wav" />
# audio_tag("sound.wav", :autoplay => true, :controls => true) # =>
# <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
- def audio_tag(source, options = {})
- options.symbolize_keys!
- options[:src] = path_to_audio(source)
- tag("audio", options)
+ # audio_tag("sound.wav", "sound.mid") # =>
+ # <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
+ def audio_tag(*sources)
+ multiple_sources_tag('audio', sources)
end
private
@@ -452,6 +476,26 @@ module ActionView
def asset_paths
@asset_paths ||= AssetTagHelper::AssetPaths.new(config, controller)
end
+
+ def multiple_sources_tag(type, sources)
+ options = sources.extract_options!.dup.symbolize_keys!
+ sources.flatten!
+
+ yield options if block_given?
+
+ if sources.size > 1
+ content_tag(type, options) do
+ safe_join sources.map { |source| tag("source", :src => send("path_to_#{type}", source)) }
+ end
+ else
+ options[:src] = send("path_to_#{type}", sources.first)
+ content_tag(type, nil, options)
+ end
+ end
+
+ def current_host
+ url_for(:only_path => false)
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
index d9f1f88ade..c67f81dcf4 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
@@ -87,6 +87,13 @@ module ActionView
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
+ # Computes the full URL to a javascript asset in the public javascripts directory.
+ # This will use +javascript_path+ internally, so most of their behaviors will be the same.
+ def javascript_url(source)
+ URI.join(current_host, path_to_javascript(source)).to_s
+ end
+ alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route
+
# Returns an HTML script tag for each of the +sources+ provided.
#
# Sources may be paths to JavaScript files. Relative paths are assumed to be relative
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
index 41958c6559..2584b67548 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
@@ -65,6 +65,13 @@ module ActionView
end
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
+ # Computes the full URL to a stylesheet asset in the public stylesheets directory.
+ # This will use +stylesheet_path+ internally, so most of their behaviors will be the same.
+ def stylesheet_url(source)
+ URI.join(current_host, path_to_stylesheet(source)).to_s
+ end
+ alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route
+
# Returns a stylesheet link tag for the sources specified as arguments. If
# you don't specify an extension, <tt>.css</tt> will be appended automatically.
# You can modify the link attributes by passing a hash as the last argument.
diff --git a/actionpack/lib/action_view/helpers/csrf_helper.rb b/actionpack/lib/action_view/helpers/csrf_helper.rb
index 1f2bc28cac..eeb0ed94b9 100644
--- a/actionpack/lib/action_view/helpers/csrf_helper.rb
+++ b/actionpack/lib/action_view/helpers/csrf_helper.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/string/strip'
-
module ActionView
# = Action View CSRF Helper
module Helpers
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 2806348337..2d37923825 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -142,6 +142,8 @@ module ActionView
# ==== Options
# * <tt>:use_month_numbers</tt> - Set to true if you want to use month numbers rather than month names (e.g.
# "2" instead of "February").
+ # * <tt>:use_two_digit_numbers</tt> - Set to true if you want to display two digit month and day numbers (e.g.
+ # "02" instead of "February" and "08" instead of "8").
# * <tt>:use_short_month</tt> - Set to true if you want to use abbreviated month names instead of full
# month names (e.g. "Feb" instead of "February").
# * <tt>:add_month_numbers</tt> - Set to true if you want to use both month numbers and month names (e.g.
@@ -189,6 +191,10 @@ module ActionView
# date_select("article", "written_on", :start_year => 1995, :use_month_numbers => true,
# :discard_day => true, :include_blank => true)
#
+ # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
+ # # with two digit numbers used for months and days.
+ # date_select("article", "written_on", :use_two_digit_numbers => true)
+ #
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
# # with the fields ordered as day, month, year rather than month, day, year.
# date_select("article", "written_on", :order => [:day, :month, :year])
@@ -213,7 +219,7 @@ module ActionView
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
# all month choices are valid.
def date_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options)
+ Tags::DateSelect.new(object_name, method, self, options, html_options).render
end
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a
@@ -251,7 +257,7 @@ module ActionView
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
# all month choices are valid.
def time_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_time_select_tag(options, html_options)
+ Tags::TimeSelect.new(object_name, method, self, options, html_options).render
end
# Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a
@@ -287,7 +293,7 @@ module ActionView
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
def datetime_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
+ Tags::DatetimeSelect.new(object_name, method, self, options, html_options).render
end
# Returns a set of html select-tags (one for year, month, day, hour, minute, and second) pre-selected with the
@@ -502,6 +508,7 @@ module ActionView
# Returns a select tag with options for each of the days 1 through 31 with the current day selected.
# The <tt>date</tt> can also be substituted for a day number.
+ # If you want to display days with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true.
# Override the field name using the <tt>:field_name</tt> option, 'day' by default.
#
# ==== Examples
@@ -513,6 +520,9 @@ module ActionView
# # Generates a select field for days that defaults to the number given.
# select_day(5)
#
+ # # Generates a select field for days that defaults to the number given, but displays it with two digits.
+ # select_day(5, :use_two_digit_numbers => true)
+ #
# # Generates a select field for days that defaults to the day for the date in my_date
# # that is named 'due' rather than 'day'.
# select_day(my_time, :field_name => 'due')
@@ -532,6 +542,7 @@ module ActionView
# want both numbers and names, set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer
# to show month names as abbreviations, set the <tt>:use_short_month</tt> key in +options+ to true. If you want
# to use your own month names, set the <tt>:use_month_names</tt> key in +options+ to an array of 12 month names.
+ # If you want to display months with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true.
# Override the field name using the <tt>:field_name</tt> option, 'month' by default.
#
# ==== Examples
@@ -559,6 +570,10 @@ module ActionView
# # will use keys like "Januar", "Marts."
# select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...))
#
+ # # Generates a select field for months that defaults to the current month that
+ # # will use keys with two digit numbers like "01", "03".
+ # select_month(Date.today, :use_two_digit_numbers => true)
+ #
# # Generates a select field for months with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
# select_month(14, :prompt => 'Choose month')
@@ -734,7 +749,7 @@ module ActionView
def select_day
if @options[:use_hidden] || @options[:discard_day]
- build_hidden(:day, day)
+ build_hidden(:day, day || 1)
else
build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false, :use_two_digit_numbers => @options[:use_two_digit_numbers])
end
@@ -742,7 +757,7 @@ module ActionView
def select_month
if @options[:use_hidden] || @options[:discard_month]
- build_hidden(:month, month)
+ build_hidden(:month, month || 1)
else
month_options = []
1.upto(12) do |month_number|
@@ -756,7 +771,7 @@ module ActionView
def select_year
if !@datetime || @datetime == 0
- val = ''
+ val = '1'
middle_year = Date.today.year
else
val = middle_year = year
@@ -817,6 +832,9 @@ module ActionView
# If <tt>:use_month_numbers</tt> option is passed
# month_name(1) => 1
#
+ # If <tt>:use_two_month_numbers</tt> option is passed
+ # month_name(1) => '01'
+ #
# If <tt>:add_month_numbers</tt> option is passed
# month_name(1) => "1 - January"
def month_name(number)
@@ -836,7 +854,15 @@ module ActionView
end
def translated_date_order
- I18n.translate(:'date.order', :locale => @options[:locale]) || []
+ date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => [])
+
+ forbidden_elements = date_order - [:year, :month, :day]
+ if forbidden_elements.any?
+ raise StandardError,
+ "#{@options[:locale]}.date.order only accepts :year, :month and :day"
+ end
+
+ date_order
end
# Build full select tag from date type and options.
@@ -850,6 +876,12 @@ module ActionView
# <option value="2">2</option>
# <option value="3">3</option>..."
#
+ # If <tt>:use_two_digit_numbers => true</tt> option is passed
+ # build_options(15, :start => 1, :end => 31, :use_two_digit_numbers => true)
+ # => "<option value="1">01</option>
+ # <option value="2">02</option>
+ # <option value="3">03</option>..."
+ #
# If <tt>:step</tt> options is passed
# build_options(15, :start => 1, :end => 31, :step => 2)
# => "<option value="1">1</option>
@@ -958,82 +990,16 @@ module ActionView
# Returns the separator for a given datetime component.
def separator(type)
case type
- when :year
- @options[:discard_year] ? "" : @options[:date_separator]
- when :month
- @options[:discard_month] ? "" : @options[:date_separator]
- when :day
- @options[:discard_day] ? "" : @options[:date_separator]
+ when :year, :month, :day
+ @options[:"discard_#{type}"] ? "" : @options[:date_separator]
when :hour
(@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
- when :minute
- @options[:discard_minute] ? "" : @options[:time_separator]
- when :second
- @options[:include_seconds] ? @options[:time_separator] : ""
- end
- end
- end
-
- module DateHelperInstanceTag
- def to_date_select_tag(options = {}, html_options = {})
- datetime_selector(options, html_options).select_date.html_safe
- end
-
- def to_time_select_tag(options = {}, html_options = {})
- datetime_selector(options, html_options).select_time.html_safe
- end
-
- def to_datetime_select_tag(options = {}, html_options = {})
- datetime_selector(options, html_options).select_datetime.html_safe
- end
-
- private
- def datetime_selector(options, html_options)
- datetime = value(object) || default_datetime(options)
- @auto_index ||= nil
-
- options = options.dup
- options[:field_name] = @method_name
- options[:include_position] = true
- options[:prefix] ||= @object_name
- options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
-
- DateTimeSelector.new(datetime, options, html_options)
- end
-
- def default_datetime(options)
- return if options[:include_blank] || options[:prompt]
-
- case options[:default]
- when nil
- Time.current
- when Date, Time
- options[:default]
- else
- default = options[:default].dup
-
- # Rename :minute and :second to :min and :sec
- default[:min] ||= default[:minute]
- default[:sec] ||= default[:second]
-
- time = Time.current
-
- [:year, :month, :day, :hour, :min, :sec].each do |key|
- default[key] ||= time.send(key)
- end
-
- Time.utc_time(
- default[:year], default[:month], default[:day],
- default[:hour], default[:min], default[:sec]
- )
+ when :minute, :second
+ @options[:"discard_#{type}"] ? "" : @options[:time_separator]
end
end
end
- class InstanceTag #:nodoc:
- include DateHelperInstanceTag
- end
-
class FormBuilder
def date_select(method, options = {}, html_options = {})
@template.date_select(@object_name, method, objectify_options(options), html_options)
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index fdddb33c31..53ae8b66da 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -3,6 +3,7 @@ require 'action_view/helpers/date_helper'
require 'action_view/helpers/tag_helper'
require 'action_view/helpers/form_tag_helper'
require 'action_view/helpers/active_model_helper'
+require 'action_view/helpers/tags'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/blank'
@@ -249,7 +250,7 @@ module ActionView
#
# You can force the form to use the full array of HTTP verbs by setting
#
- # :method => (:get|:post|:put|:delete)
+ # :method => (:get|:post|:patch|:put|:delete)
#
# in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the
# form will be set to POST and a hidden input called _method will carry the intended verb for the server
@@ -365,34 +366,33 @@ module ActionView
else
object = record.is_a?(Array) ? record.last : record
object_name = options[:as] || ActiveModel::Naming.param_key(object)
- apply_form_for_options!(record, options)
+ apply_form_for_options!(record, object, options)
end
options[:html][:remote] = options.delete(:remote) if options.has_key?(:remote)
options[:html][:method] = options.delete(:method) if options.has_key?(:method)
options[:html][:authenticity_token] = options.delete(:authenticity_token)
- builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &proc)
+ builder = options[:parent_builder] = instantiate_builder(object_name, object, options)
fields_for = fields_for(object_name, object, options, &proc)
default_options = builder.multipart? ? { :multipart => true } : {}
- output = form_tag(options.delete(:url) || {}, default_options.merge!(options.delete(:html)))
- output << fields_for
- output.safe_concat('</form>')
+ default_options.merge!(options.delete(:html))
+
+ form_tag(options.delete(:url) || {}, default_options) { fields_for }
end
- def apply_form_for_options!(object_or_array, options) #:nodoc:
- object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
+ def apply_form_for_options!(record, object, options) #:nodoc:
object = convert_to_model(object)
as = options[:as]
- action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :put] : [:new, :post]
+ action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post]
options[:html].reverse_merge!(
:class => as ? "#{action}_#{as}" : dom_class(object, action),
:id => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
:method => method
)
- options[:url] ||= polymorphic_path(object_or_array, :format => options.delete(:format))
+ options[:url] ||= polymorphic_path(record, :format => options.delete(:format))
end
private :apply_form_for_options!
@@ -601,7 +601,7 @@ module ActionView
# ...
# <% end %>
def fields_for(record_name, record_object = nil, options = {}, &block)
- builder = instantiate_builder(record_name, record_object, options, &block)
+ builder = instantiate_builder(record_name, record_object, options)
output = capture(builder, &block)
output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id?
output
@@ -655,16 +655,7 @@ module ActionView
# 'Accept <a href="/terms">Terms</a>.'.html_safe
# end
def label(object_name, method, content_or_options = nil, options = nil, &block)
- content_is_options = content_or_options.is_a?(Hash)
- if content_is_options || block_given?
- options = content_or_options if content_is_options
- text = nil
- else
- text = content_or_options
- end
-
- options ||= {}
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options, &block)
+ Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
end
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -686,7 +677,7 @@ module ActionView
# # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
#
def text_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
+ Tags::TextField.new(object_name, method, self, options).render
end
# Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -708,7 +699,7 @@ module ActionView
# # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" />
#
def password_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", { :value => nil }.merge!(options))
+ Tags::PasswordField.new(object_name, method, self, options).render
end
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -726,7 +717,7 @@ module ActionView
# hidden_field(:user, :token)
# # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
def hidden_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
+ Tags::HiddenField.new(object_name, method, self, options).render
end
# Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -747,7 +738,7 @@ module ActionView
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
#
def file_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options.update({:size => nil}))
+ Tags::FileField.new(object_name, method, self, options).render
end
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
@@ -775,7 +766,7 @@ module ActionView
# # #{@entry.body}
# # </textarea>
def text_area(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
+ Tags::TextArea.new(object_name, method, self, options).render
end
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -837,7 +828,7 @@ module ActionView
# # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
#
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
+ Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render
end
# Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
@@ -859,7 +850,7 @@ module ActionView
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
def radio_button(object_name, method, tag_value, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
+ Tags::RadioButton.new(object_name, method, self, tag_value, options).render
end
# Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
@@ -885,20 +876,7 @@ module ActionView
# # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" size="30" type="search" />
#
def search_field(object_name, method, options = {})
- options = options.stringify_keys
-
- if options["autosave"]
- if options["autosave"] == true
- options["autosave"] = request.host.split(".").reverse.join(".")
- end
- options["results"] ||= 10
- end
-
- if options["onsearch"]
- options["incremental"] = true unless options.has_key?("incremental")
- end
-
- InstanceTag.new(object_name, method, self, options.delete("object")).to_input_field_tag("search", options)
+ Tags::SearchField.new(object_name, method, self, options).render
end
# Returns a text_field of type "tel".
@@ -907,17 +885,35 @@ module ActionView
# # => <input id="user_phone" name="user[phone]" size="30" type="tel" />
#
def telephone_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("tel", options)
+ Tags::TelField.new(object_name, method, self, options).render
end
alias phone_field telephone_field
+ # Returns a text_field of type "date".
+ #
+ # date_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="date" />
+ #
+ # The default value is generated by trying to call "to_date"
+ # on the object's value, which makes it behave as expected for instances
+ # of DateTime and ActiveSupport::TimeWithZone. You can still override that
+ # by passing the "value" option explicitly, e.g.
+ #
+ # @user.born_on = Date.new(1984, 1, 27)
+ # date_field("user", "born_on", value: "1984-05-12")
+ # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-05-12" />
+ #
+ def date_field(object_name, method, options = {})
+ Tags::DateField.new(object_name, method, self, options).render
+ end
+
# Returns a text_field of type "url".
#
# url_field("user", "homepage")
# # => <input id="user_homepage" size="30" name="user[homepage]" type="url" />
#
def url_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("url", options)
+ Tags::UrlField.new(object_name, method, self, options).render
end
# Returns a text_field of type "email".
@@ -926,7 +922,7 @@ module ActionView
# # => <input id="user_address" size="30" name="user[address]" type="email" />
#
def email_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("email", options)
+ Tags::EmailField.new(object_name, method, self, options).render
end
# Returns an input tag of type "number".
@@ -934,7 +930,7 @@ module ActionView
# ==== Options
# * Accepts same options as number_field_tag
def number_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("number", options)
+ Tags::NumberField.new(object_name, method, self, options).render
end
# Returns an input tag of type "range".
@@ -942,12 +938,12 @@ module ActionView
# ==== Options
# * Accepts same options as range_field_tag
def range_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("range", options)
+ Tags::RangeField.new(object_name, method, self, options).render
end
private
- def instantiate_builder(record_name, record_object, options, &block)
+ def instantiate_builder(record_name, record_object, options)
case record_name
when String, Symbol
object = record_object
@@ -958,273 +954,7 @@ module ActionView
end
builder = options[:builder] || ActionView::Base.default_form_builder
- builder.new(object_name, object, self, options, block)
- end
- end
-
- class InstanceTag
- include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
-
- attr_reader :object, :method_name, :object_name
-
- DEFAULT_FIELD_OPTIONS = { "size" => 30 }
- DEFAULT_RADIO_OPTIONS = { }
- DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }
-
- def initialize(object_name, method_name, template_object, object = nil)
- @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
- @template_object = template_object
-
- @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
- @object = retrieve_object(object)
- @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
- end
-
- def to_label_tag(text = nil, options = {}, &block)
- options = options.stringify_keys
- tag_value = options.delete("value")
- name_and_id = options.dup
-
- if name_and_id["for"]
- name_and_id["id"] = name_and_id["for"]
- else
- name_and_id.delete("id")
- end
-
- add_default_name_and_id_for_value(tag_value, name_and_id)
- options.delete("index")
- options.delete("namespace")
- options["for"] ||= name_and_id["id"]
-
- if block_given?
- @template_object.label_tag(name_and_id["id"], options, &block)
- else
- content = if text.blank?
- object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
- method_and_value = tag_value.present? ? "#{method_name}.#{tag_value}" : method_name
-
- if object.respond_to?(:to_model)
- key = object.class.model_name.i18n_key
- i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
- end
-
- i18n_default ||= ""
- I18n.t("#{object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
- else
- text.to_s
- end
-
- content ||= if object && object.class.respond_to?(:human_attribute_name)
- object.class.human_attribute_name(method_name)
- end
-
- content ||= method_name.humanize
-
- label_tag(name_and_id["id"], content, options)
- end
- end
-
- def to_input_field_tag(field_type, options = {})
- options = options.stringify_keys
- options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
- options = DEFAULT_FIELD_OPTIONS.merge(options)
- if field_type == "hidden"
- options.delete("size")
- end
- options["type"] ||= field_type
- options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
- options["value"] &&= ERB::Util.html_escape(options["value"])
- add_default_name_and_id(options)
- tag("input", options)
- end
-
- def to_number_field_tag(field_type, options = {})
- options = options.stringify_keys
- options['size'] ||= nil
-
- if range = options.delete("in") || options.delete("within")
- options.update("min" => range.min, "max" => range.max)
- end
- to_input_field_tag(field_type, options)
- end
-
- def to_radio_button_tag(tag_value, options = {})
- options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
- options["type"] = "radio"
- options["value"] = tag_value
- if options.has_key?("checked")
- cv = options.delete "checked"
- checked = cv == true || cv == "checked"
- else
- checked = self.class.radio_button_checked?(value(object), tag_value)
- end
- options["checked"] = "checked" if checked
- add_default_name_and_id_for_value(tag_value, options)
- tag("input", options)
- end
-
- def to_text_area_tag(options = {})
- options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
- add_default_name_and_id(options)
-
- if size = options.delete("size")
- options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
- end
-
- content_tag("textarea", ERB::Util.html_escape(options.delete('value') || value_before_type_cast(object)), options)
- end
-
- def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
- options = options.stringify_keys
- options["type"] = "checkbox"
- options["value"] = checked_value
- if options.has_key?("checked")
- cv = options.delete "checked"
- checked = cv == true || cv == "checked"
- else
- checked = self.class.check_box_checked?(value(object), checked_value)
- end
- options["checked"] = "checked" if checked
- if options["multiple"]
- add_default_name_and_id_for_value(checked_value, options)
- options.delete("multiple")
- else
- add_default_name_and_id(options)
- end
- hidden = unchecked_value ? tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value, "disabled" => options["disabled"]) : ""
- checkbox = tag("input", options)
- hidden + checkbox
- end
-
- def to_boolean_select_tag(options = {})
- options = options.stringify_keys
- add_default_name_and_id(options)
- value = value(object)
- tag_text = "<select"
- tag_text << tag_options(options)
- tag_text << "><option value=\"false\""
- tag_text << " selected" if value == false
- tag_text << ">False</option><option value=\"true\""
- tag_text << " selected" if value
- tag_text << ">True</option></select>"
- end
-
- def to_content_tag(tag_name, options = {})
- content_tag(tag_name, value(object), options)
- end
-
- def retrieve_object(object)
- if object
- object
- elsif @template_object.instance_variable_defined?("@#{@object_name}")
- @template_object.instance_variable_get("@#{@object_name}")
- end
- rescue NameError
- # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
- nil
- end
-
- def retrieve_autoindex(pre_match)
- object = self.object || @template_object.instance_variable_get("@#{pre_match}")
- if object && object.respond_to?(:to_param)
- object.to_param
- else
- raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
- end
- end
-
- def value(object)
- self.class.value(object, @method_name)
- end
-
- def value_before_type_cast(object)
- self.class.value_before_type_cast(object, @method_name)
- end
-
- class << self
- def value(object, method_name)
- object.send method_name if object
- end
-
- def value_before_type_cast(object, method_name)
- unless object.nil?
- object.respond_to?(method_name + "_before_type_cast") ?
- object.send(method_name + "_before_type_cast") :
- object.send(method_name)
- end
- end
-
- def check_box_checked?(value, checked_value)
- case value
- when TrueClass, FalseClass
- value
- when NilClass
- false
- when Integer
- value != 0
- when String
- value == checked_value
- when Array
- value.include?(checked_value)
- else
- value.to_i != 0
- end
- end
-
- def radio_button_checked?(value, checked_value)
- value.to_s == checked_value.to_s
- end
- end
-
- private
- def add_default_name_and_id_for_value(tag_value, options)
- unless tag_value.nil?
- pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
- specified_id = options["id"]
- add_default_name_and_id(options)
- options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present?
- else
- add_default_name_and_id(options)
- end
- end
-
- def add_default_name_and_id(options)
- if options.has_key?("index")
- options["name"] ||= tag_name_with_index(options["index"])
- options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
- options.delete("index")
- elsif defined?(@auto_index)
- options["name"] ||= tag_name_with_index(@auto_index)
- options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
- else
- options["name"] ||= tag_name + (options['multiple'] ? '[]' : '')
- options["id"] = options.fetch("id"){ tag_id }
- end
- options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
- end
-
- def tag_name
- "#{@object_name}[#{sanitized_method_name}]"
- end
-
- def tag_name_with_index(index)
- "#{@object_name}[#{index}][#{sanitized_method_name}]"
- end
-
- def tag_id
- "#{sanitized_object_name}_#{sanitized_method_name}"
- end
-
- def tag_id_with_index(index)
- "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
- end
-
- def sanitized_object_name
- @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
- end
-
- def sanitized_method_name
- @sanitized_method_name ||= @method_name.sub(/\?$/,"")
+ builder.new(object_name, object, self, options)
end
end
@@ -1255,9 +985,9 @@ module ActionView
self
end
- def initialize(object_name, object, template, options, proc)
+ def initialize(object_name, object, template, options)
@nested_child_index = {}
- @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
+ @object_name, @object, @template, @options = object_name, object, template, options
@parent_builder = options[:parent_builder]
@default_options = @options ? @options.slice(:index, :namespace) : {}
if @object_name.to_s.match(/\[\]$/)
@@ -1372,14 +1102,14 @@ module ActionView
# <% end %>
#
# In the example above, if @post is a new record, it will use "Create Post" as
- # submit button label, otherwise, it uses "Update Post".
+ # button label, otherwise, it uses "Update Post".
#
- # Those labels can be customized using I18n, under the helpers.submit key and accept
- # the %{model} as translation interpolation:
+ # Those labels can be customized using I18n, under the helpers.submit key
+ # (the same as submit helper) and accept the %{model} as translation interpolation:
#
# en:
# helpers:
- # button:
+ # submit:
# create: "Create a %{model}"
# update: "Confirm changes to %{model}"
#
@@ -1387,7 +1117,7 @@ module ActionView
#
# en:
# helpers:
- # button:
+ # submit:
# post:
# create: "Add %{model}"
#
@@ -1471,9 +1201,6 @@ module ActionView
end
ActiveSupport.on_load(:action_view) do
- class ActionView::Base
- cattr_accessor :default_form_builder
- @@default_form_builder = ::ActionView::Helpers::FormBuilder
- end
+ cattr_accessor(:default_form_builder) { ::ActionView::Helpers::FormBuilder }
end
end
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index ba9ff1d5aa..5be3da9b94 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -154,7 +154,7 @@ module ActionView
# key in the query string, that works for ordinary forms.
#
def select(object, method, choices, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
+ Tags::Select.new(object, method, self, choices, options, html_options).render
end
# Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
@@ -164,7 +164,9 @@ module ActionView
#
# The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are methods to be called on each member
# of +collection+. The return values are used as the +value+ attribute and contents of each
- # <tt><option></tt> tag, respectively.
+ # <tt><option></tt> tag, respectively. They can also be any object that responds to +call+, such
+ # as a +proc+, that will be called for each member of the +collection+ to
+ # retrieve the value/text.
#
# Example object structure for use with this method:
# class Post < ActiveRecord::Base
@@ -188,10 +190,9 @@ module ActionView
# <option value="3">M. Clark</option>
# </select>
def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
+ Tags::CollectionSelect.new(object, method, self, collection, value_method, text_method, options, html_options).render
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>
@@ -240,7 +241,7 @@ module ActionView
# </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)
+ Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render
end
# Return select and option tags for the given object and method, using
@@ -274,7 +275,7 @@ module ActionView
#
# time_zone_select( "user", "time_zone", ActiveSupport::TimeZone.all.sort, :model => ActiveSupport::TimeZone)
def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
+ Tags::TimeZoneSelect.new(object, method, self, priority_zones, options, html_options).render
end
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
@@ -329,13 +330,16 @@ module ActionView
container.map do |element|
html_attributes = option_html_attributes(element)
text, value = option_text_and_value(element).map { |item| item.to_s }
- selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
- disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled)
- %(<option value="#{ERB::Util.html_escape(value)}"#{selected_attribute}#{disabled_attribute}#{html_attributes}>#{ERB::Util.html_escape(text)}</option>)
+
+ html_attributes[:selected] = 'selected' if option_value_selected?(value, selected)
+ html_attributes[:disabled] = 'disabled' if disabled && option_value_selected?(value, disabled)
+ html_attributes[:value] = value
+
+ content_tag(:option, text, html_attributes)
end.join("\n").html_safe
end
- # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the
+ # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning
# the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
# Example:
# options_from_collection_for_select(@people, 'id', 'name')
@@ -361,12 +365,13 @@ module ActionView
# should produce the desired results.
def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
options = collection.map do |element|
- [element.send(text_method), element.send(value_method)]
+ [value_for_collection(element, text_method), value_for_collection(element, value_method)]
end
selected, disabled = extract_selected_and_disabled(selected)
- select_deselect = {}
- select_deselect[:selected] = extract_values_from_collection(collection, value_method, selected)
- select_deselect[:disabled] = extract_values_from_collection(collection, value_method, disabled)
+ select_deselect = {
+ :selected => extract_values_from_collection(collection, value_method, selected),
+ :disabled => extract_values_from_collection(collection, value_method, disabled)
+ }
options_for_select(options, select_deselect)
end
@@ -419,10 +424,10 @@ module ActionView
# wrap the output in an appropriate <tt><select></tt> tag.
def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
collection.map do |group|
- group_label_string = eval("group.#{group_label_method}")
- "<optgroup label=\"#{ERB::Util.html_escape(group_label_string)}\">" +
- options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key) +
- '</optgroup>'
+ option_tags = options_from_collection_for_select(
+ group.send(group_method), option_key_method, option_value_method, selected_key)
+
+ content_tag(:optgroup, option_tags, :label => group.send(group_label_method))
end.join.html_safe
end
@@ -470,16 +475,16 @@ module ActionView
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
# wrap the output in an appropriate <tt><select></tt> tag.
def grouped_options_for_select(grouped_options, selected_key = nil, prompt = nil)
- body = ''
- body << content_tag(:option, prompt, { :value => "" }, true) if prompt
+ body = "".html_safe
+ body.safe_concat content_tag(:option, prompt, :value => "") if prompt
grouped_options = grouped_options.sort if grouped_options.is_a?(Hash)
- grouped_options.each do |group|
- body << content_tag(:optgroup, options_for_select(group[1], selected_key), :label => group[0])
+ grouped_options.each do |label, container|
+ body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), :label => label)
end
- body.html_safe
+ body
end
# Returns a string of option tags for pretty much any time zone in the
@@ -501,42 +506,162 @@ module ActionView
# NOTE: Only the option tags are returned, you have to wrap this call in
# a regular HTML select tag.
def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone)
- zone_options = ""
+ zone_options = "".html_safe
zones = model.all
convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
if priority_zones
if priority_zones.is_a?(Regexp)
- priority_zones = model.all.find_all {|z| z =~ priority_zones}
+ priority_zones = zones.select { |z| z =~ priority_zones }
end
- zone_options += options_for_select(convert_zones[priority_zones], selected)
- zone_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
- zones = zones.reject { |z| priority_zones.include?( z ) }
+ zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
+ zone_options.safe_concat content_tag(:option, '-------------', :value => '', :disabled => 'disabled')
+ zone_options.safe_concat "\n"
+
+ zones.reject! { |z| priority_zones.include?(z) }
end
- zone_options += options_for_select(convert_zones[zones], selected)
- zone_options.html_safe
+ zone_options.safe_concat options_for_select(convert_zones[zones], selected)
+ end
+
+ # Returns radio button 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.
+ #
+ # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are
+ # methods to be called on each member of +collection+. The return values
+ # are used as the +value+ attribute and contents of each radio button tag,
+ # respectively. They can also be any object that responds to +call+, such
+ # as a +proc+, that will be called for each member of the +collection+ to
+ # retrieve the value/text.
+ #
+ # Example object structure for use with this method:
+ # class Post < ActiveRecord::Base
+ # belongs_to :author
+ # end
+ # class Author < ActiveRecord::Base
+ # has_many :posts
+ # def name_with_initial
+ # "#{first_name.first}. #{last_name}"
+ # end
+ # end
+ #
+ # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial)
+ #
+ # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
+ # <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" checked="checked" />
+ # <label for="post_author_id_1">D. Heinemeier Hansson</label>
+ # <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
+ # <label for="post_author_id_2">D. Thomas</label>
+ # <input id="post_author_id_3" name="post[author_id]" type="radio" value="3" />
+ # <label for="post_author_id_3">M. Clark</label>
+ #
+ # It is also possible to customize the way the elements will be shown by
+ # giving a block to the method:
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
+ # b.label { b.radio_button }
+ # end
+ #
+ # The argument passed to the block is a special kind of builder for this
+ # collection, which has the ability to generate the label and radio button
+ # for the current item in the collection, with proper text and value.
+ # Using it, you can change the label and radio button display order or
+ # even use the label as wrapper, as in the example above.
+ #
+ # The builder methods <tt>label</tt> and <tt>radio_button</tt> also accept
+ # extra html options:
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
+ # b.label(:class => "radio_button") { b.radio_button(:class => "radio_button") }
+ # end
+ #
+ # There are also three special methods available: <tt>object</tt>, <tt>text</tt> and
+ # <tt>value</tt>, which are the current item being rendered, its text and value methods,
+ # respectively. You can use them like this:
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
+ # b.label(:"data-value" => b.value) { b.radio_button + b.text }
+ # end
+ def collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
+ Tags::CollectionRadioButtons.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
+ end
+
+ # Returns check box 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.
+ #
+ # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are
+ # methods to be called on each member of +collection+. The return values
+ # are used as the +value+ attribute and contents of each check box tag,
+ # respectively. They can also be any object that responds to +call+, such
+ # as a +proc+, that will be called for each member of the +collection+ to
+ # retrieve the value/text.
+ #
+ # Example object structure for use with this method:
+ # class Post < ActiveRecord::Base
+ # has_and_belongs_to_many :author
+ # end
+ # class Author < ActiveRecord::Base
+ # has_and_belongs_to_many :posts
+ # def name_with_initial
+ # "#{first_name.first}. #{last_name}"
+ # end
+ # end
+ #
+ # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial)
+ #
+ # If <tt>@post.author_ids</tt> is already <tt>[1]</tt>, this would return:
+ # <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" checked="checked" />
+ # <label for="post_author_ids_1">D. Heinemeier Hansson</label>
+ # <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
+ # <label for="post_author_ids_2">D. Thomas</label>
+ # <input id="post_author_ids_3" name="post[author_ids][]" type="checkbox" value="3" />
+ # <label for="post_author_ids_3">M. Clark</label>
+ # <input name="post[author_ids][]" type="hidden" value="" />
+ #
+ # It is also possible to customize the way the elements will be shown by
+ # giving a block to the method:
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
+ # b.label { b.check_box }
+ # end
+ #
+ # The argument passed to the block is a special kind of builder for this
+ # collection, which has the ability to generate the label and check box
+ # for the current item in the collection, with proper text and value.
+ # Using it, you can change the label and check box display order or even
+ # use the label as wrapper, as in the example above.
+ #
+ # The builder methods <tt>label</tt> and <tt>check_box</tt> also accept
+ # extra html options:
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
+ # b.label(:class => "check_box") { b.check_box(:class => "check_box") }
+ # end
+ #
+ # There are also three special methods available: <tt>object</tt>, <tt>text</tt> and
+ # <tt>value</tt>, which are the current item being rendered, its text and value methods,
+ # respectively. You can use them like this:
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
+ # b.label(:"data-value" => b.value) { b.check_box + b.text }
+ # end
+ def collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
+ Tags::CollectionCheckBoxes.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
end
private
def option_html_attributes(element)
- return "" unless Array === element
- html_attributes = []
- element.select { |e| Hash === e }.reduce({}, :merge).each do |k, v|
- html_attributes << " #{k}=\"#{ERB::Util.html_escape(v.to_s)}\""
- end
- html_attributes.join
+ return {} unless Array === element
+
+ Hash[element.select { |e| Hash === e }.reduce({}, :merge).map { |k, v| [k, ERB::Util.html_escape(v.to_s)] }]
end
def option_text_and_value(option)
# Options are [text, value] pairs or strings used for both.
- case
- when Array === option
- option = option.reject { |e| Hash === e }
- [option.first, option.last]
- when !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
+ if !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
+ option = option.reject { |e| Hash === e } if Array === option
[option.first, option.last]
else
[option, option]
@@ -544,21 +669,17 @@ module ActionView
end
def option_value_selected?(value, selected)
- if selected.respond_to?(:include?) && !selected.is_a?(String)
- selected.include? value
- else
- value == selected
- end
+ Array(selected).include? value
end
def extract_selected_and_disabled(selected)
if selected.is_a?(Proc)
- [ selected, nil ]
+ [selected, nil]
else
selected = Array.wrap(selected)
options = selected.extract_options!.symbolize_keys
- selected_items = options.include?(:selected) ? options[:selected] : selected
- [ selected_items, options[:disabled] ]
+ selected_items = options.fetch(:selected, selected)
+ [selected_items, options[:disabled]]
end
end
@@ -571,68 +692,9 @@ module ActionView
selected
end
end
- end
-
- class InstanceTag #:nodoc:
- include FormOptionsHelper
-
- def to_select_tag(choices, options, html_options)
- selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
-
- # Grouped choices look like this:
- #
- # [nil, []]
- # { nil => [] }
- #
- if !choices.empty? && choices.first.respond_to?(:last) && Array === choices.first.last
- option_tags = grouped_options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
- else
- option_tags = options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
- end
-
- select_content_tag(option_tags, options, html_options)
- end
-
- def to_collection_select_tag(collection, value_method, text_method, options, html_options)
- selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
- select_content_tag(
- options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => options[:disabled]), options, html_options
- )
- end
-
- def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
- select_content_tag(
- option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value(object)), options, html_options
- )
- end
-
- def to_time_zone_select_tag(priority_zones, options, html_options)
- select_content_tag(
- time_zone_options_for_select(value(object) || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), options, html_options
- )
- end
-
- private
- def add_options(option_tags, options, value = nil)
- if options[:include_blank]
- option_tags = "<option value=\"\">#{ERB::Util.html_escape(options[:include_blank]) if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
- end
- if value.blank? && options[:prompt]
- prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
- option_tags = "<option value=\"\">#{ERB::Util.html_escape(prompt)}</option>\n" + option_tags
- end
- option_tags.html_safe
- end
- def select_content_tag(option_tags, options, html_options)
- html_options = html_options.stringify_keys
- add_default_name_and_id(html_options)
- select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
- if html_options["multiple"]
- tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
- else
- select
- end
+ def value_for_collection(item, value)
+ value.respond_to?(:call) ? value.call(item) : item.send(value)
end
end
@@ -652,6 +714,14 @@ module ActionView
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
+
+ def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {})
+ @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
+ end
+
+ def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {})
+ @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index e3ad96ec1b..2cf1c9055c 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -313,7 +313,7 @@ module ActionView
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
end
- escape = options.key?("escape") ? options.delete("escape") : true
+ escape = options.delete("escape") { true }
content = ERB::Util.html_escape(content) if escape
content_tag :textarea, content.to_s.html_safe, { "name" => name, "id" => sanitize_to_id(name) }.update(options)
@@ -549,6 +549,14 @@ module ActionView
end
alias phone_field_tag telephone_field_tag
+ # Creates a text field of type "date".
+ #
+ # ==== Options
+ # * Accepts the same options as text_field_tag.
+ def date_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "date"))
+ end
+
# Creates a text field of type "url".
#
# ==== Options
@@ -627,7 +635,7 @@ module ActionView
token_tag(authenticity_token)
else
html_options["method"] = "post"
- tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag(authenticity_token)
+ method_tag(method) + token_tag(authenticity_token)
end
tags = utf8_enforcer_tag << method_tag
@@ -636,26 +644,16 @@ module ActionView
def form_tag_html(html_options)
extra_tags = extra_tags_for_form(html_options)
- (tag(:form, html_options, true) + extra_tags).html_safe
+ tag(:form, html_options, true) + extra_tags
end
def form_tag_in_block(html_options, &block)
content = capture(&block)
- output = ActiveSupport::SafeBuffer.new
- output.safe_concat(form_tag_html(html_options))
+ output = form_tag_html(html_options)
output << content
output.safe_concat("</form>")
end
- def token_tag(token)
- if token == false || !protect_against_forgery?
- ''
- else
- token ||= form_authenticity_token
- tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token)
- end
- end
-
# see http://www.w3.org/TR/html4/types.html#type-name
def sanitize_to_id(name)
name.to_s.gsub(']','').gsub(/[^-a-zA-Z0-9:.]/, "_")
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index 309923490c..ac9e530f01 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -1,5 +1,4 @@
require 'action_view/helpers/tag_helper'
-require 'active_support/core_ext/string/encoding'
module ActionView
module Helpers
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 43122ef2ba..b0860f87c4 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -57,15 +57,11 @@ module ActionView
# # => +1.123.555.1234 x 1343
def number_to_phone(number, options = {})
return unless number
+ options = options.symbolize_keys
- begin
- Float(number)
- rescue ArgumentError, TypeError
- raise InvalidNumberError, number
- end if options[:raise]
+ parse_float(number, true) if options[:raise]
number = number.to_s.strip
- options = options.symbolize_keys
area_code = options[:area_code]
delimiter = options[:delimiter] || "-"
extension = options[:extension]
@@ -75,7 +71,7 @@ module ActionView
number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
else
number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
- number.slice!(0, 1) if number.starts_with?(delimiter) && !delimiter.blank?
+ number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
end
str = []
@@ -122,14 +118,12 @@ module ActionView
# # => 1234567890,50 &pound;
def number_to_currency(number, options = {})
return unless number
+ options = options.symbolize_keys
- options.symbolize_keys!
-
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :default => {})
+ currency = translations_for('currency', options[:locale])
currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
- defaults = DEFAULT_CURRENCY_VALUES.merge(defaults).merge!(currency)
+ defaults = DEFAULT_CURRENCY_VALUES.merge(defaults_translations(options[:locale])).merge!(currency)
defaults[:negative_format] = "-" + options[:format] if options[:format]
options = defaults.merge!(options)
@@ -152,7 +146,6 @@ module ActionView
e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
end
end
-
end
# Formats a +number+ as a percentage string (e.g., 65%). You can customize the format in the +options+ hash.
@@ -169,6 +162,8 @@ module ActionView
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator
# (defaults to +false+).
+ # * <tt>:format</tt> - Specifies the format of the percentage string
+ # The number field is <tt>%n</tt> (defaults to "%n%").
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid.
#
# ==== Examples
@@ -180,26 +175,27 @@ module ActionView
# number_to_percentage(302.24398923423, :precision => 5) # => 302.24399%
# number_to_percentage(1000, :locale => :fr) # => 1 000,000%
# number_to_percentage("98a") # => 98a%
+ # number_to_percentage(100, :format => "%n %") # => 100 %
#
# number_to_percentage("98a", :raise => true) # => InvalidNumberError
def number_to_percentage(number, options = {})
return unless number
+ options = options.symbolize_keys
- options.symbolize_keys!
+ defaults = format_translations('percentage', options[:locale])
+ options = defaults.merge!(options)
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :default => {})
- defaults = defaults.merge(percentage)
-
- options = options.reverse_merge(defaults)
+ format = options[:format] || "%n%"
begin
- "#{number_with_precision(number, options.merge(:raise => true))}%".html_safe
+ value = number_with_precision(number, options.merge(:raise => true))
+ format.gsub(/%n/, value).html_safe
rescue InvalidNumberError => e
if options[:raise]
raise
else
- e.number.to_s.html_safe? ? "#{e.number}%".html_safe : "#{e.number}%"
+ formatted_number = format.gsub(/%n/, e.number)
+ e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
end
end
end
@@ -229,25 +225,15 @@ module ActionView
#
# number_with_delimiter("112a", :raise => true) # => raise InvalidNumberError
def number_with_delimiter(number, options = {})
- options.symbolize_keys!
+ options = options.symbolize_keys
- begin
- Float(number)
- rescue ArgumentError, TypeError
- if options[:raise]
- raise InvalidNumberError, number
- else
- return number
- end
- end
+ parse_float(number, options[:raise]) or return number
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- options = options.reverse_merge(defaults)
+ options = defaults_translations(options[:locale]).merge(options)
parts = number.to_s.to_str.split('.')
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
parts.join(options[:separator]).html_safe
-
end
# Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision
@@ -264,6 +250,7 @@ module ActionView
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator
# (defaults to +false+).
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid.
#
# ==== Examples
# number_with_precision(111.2345) # => 111.235
@@ -282,23 +269,13 @@ module ActionView
# number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
# # => 1.111,23
def number_with_precision(number, options = {})
- options.symbolize_keys!
+ options = options.symbolize_keys
- number = begin
- Float(number)
- rescue ArgumentError, TypeError
- if options[:raise]
- raise InvalidNumberError, number
- else
- return number
- end
- end
+ number = (parse_float(number, options[:raise]) or return number)
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale], :default => {})
- defaults = defaults.merge(precision_defaults)
+ defaults = format_translations('precision', options[:locale])
+ options = defaults.merge!(options)
- options = options.reverse_merge(defaults) # Allow the user to unset default values: Eg.: :significant => false
precision = options.delete :precision
significant = options.delete :significant
strip_insignificant_zeros = options.delete :strip_insignificant_zeros
@@ -323,7 +300,6 @@ module ActionView
else
formatted_number
end
-
end
STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
@@ -343,6 +319,7 @@ module ActionView
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+)
# * <tt>:prefix</tt> - If +:si+ formats the number using the SI prefix (defaults to :binary)
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid.
# ==== Examples
# number_to_human_size(123) # => 123 Bytes
# number_to_human_size(1234) # => 1.21 KB
@@ -359,23 +336,13 @@ module ActionView
# number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
# number_to_human_size(524288000, :precision => 5) # => "500 MB"
def number_to_human_size(number, options = {})
- options.symbolize_keys!
+ options = options.symbolize_keys
- number = begin
- Float(number)
- rescue ArgumentError, TypeError
- if options[:raise]
- raise InvalidNumberError, number
- else
- return number
- end
- end
+ number = (parse_float(number, options[:raise]) or return number)
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {})
- defaults = defaults.merge(human)
+ defaults = format_translations('human', options[:locale])
+ options = defaults.merge!(options)
- options = options.reverse_merge(defaults)
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
@@ -424,6 +391,7 @@ module ActionView
# * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, <tt>:billion</tt>, <tt>:trillion</tt>, <tt>:quadrillion</tt>
# * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, <tt>:pico</tt>, <tt>:femto</tt>
# * <tt>:format</tt> - Sets the format of the output string (defaults to "%n %u"). The field types are:
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid.
#
# %u The quantifier (ex.: 'thousand')
# %n The number
@@ -478,23 +446,13 @@ module ActionView
# number_to_human(0.34, :units => :distance) # => "34 centimeters"
#
def number_to_human(number, options = {})
- options.symbolize_keys!
+ options = options.symbolize_keys
- number = begin
- Float(number)
- rescue ArgumentError, TypeError
- if options[:raise]
- raise InvalidNumberError, number
- else
- return number
- end
- end
+ number = (parse_float(number, options[:raise]) or return number)
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {})
- defaults = defaults.merge(human)
+ defaults = format_translations('human', options[:locale])
+ options = defaults.merge!(options)
- options = options.reverse_merge(defaults)
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
@@ -530,6 +488,25 @@ module ActionView
decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip.html_safe
end
+ private
+
+ def format_translations(namespace, locale)
+ defaults_translations(locale).merge(translations_for(namespace, locale))
+ end
+
+ def defaults_translations(locale)
+ I18n.translate(:'number.format', :locale => locale, :default => {})
+ end
+
+ def translations_for(namespace, locale)
+ I18n.translate(:"number.#{namespace}.format", :locale => locale, :default => {})
+ end
+
+ def parse_float(number, raise_error)
+ Float(number)
+ rescue ArgumentError, TypeError
+ raise InvalidNumberError, number if raise_error
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/output_safety_helper.rb b/actionpack/lib/action_view/helpers/output_safety_helper.rb
index a035dd70ad..2e7e9dc50c 100644
--- a/actionpack/lib/action_view/helpers/output_safety_helper.rb
+++ b/actionpack/lib/action_view/helpers/output_safety_helper.rb
@@ -28,11 +28,10 @@ module ActionView #:nodoc:
# # => "<p>foo</p><br /><p>bar</p>"
#
def safe_join(array, sep=$,)
- sep ||= "".html_safe
sep = ERB::Util.html_escape(sep)
array.map { |i| ERB::Util.html_escape(i) }.join(sep).html_safe
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb
index b351302d01..1a15459406 100644
--- a/actionpack/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb
@@ -81,13 +81,11 @@ module ActionView
# <li id="person_123" class="person bar">...
#
def content_tag_for(tag_name, single_or_multiple_records, prefix = nil, options = nil, &block)
- if single_or_multiple_records.respond_to?(:to_ary)
- single_or_multiple_records.to_ary.map do |single_record|
- capture { content_tag_for_single_record(tag_name, single_record, prefix, options, &block) }
- end.join("\n").html_safe
- else
- content_tag_for_single_record(tag_name, single_or_multiple_records, prefix, options, &block)
- end
+ options, prefix = prefix, nil if prefix.is_a?(Hash)
+
+ Array(single_or_multiple_records).map do |single_record|
+ content_tag_for_single_record(tag_name, single_record, prefix, options, &block)
+ end.join("\n").html_safe
end
private
@@ -95,14 +93,11 @@ module ActionView
# Called by <tt>content_tag_for</tt> internally to render a content tag
# for each record.
def content_tag_for_single_record(tag_name, record, prefix, options, &block)
- options, prefix = prefix, nil if prefix.is_a?(Hash)
- options ||= {}
- options.merge!({ :class => "#{dom_class(record, prefix)} #{options[:class]}".strip, :id => dom_id(record, prefix) })
- if block.arity == 0
- content_tag(tag_name, capture(&block), options)
- else
- content_tag(tag_name, capture(record, &block), options)
- end
+ options = options ? options.dup : {}
+ options.merge!(:class => "#{dom_class(record, prefix)} #{options[:class]}".rstrip, :id => dom_id(record, prefix))
+
+ content = block.arity == 0 ? capture(&block) : capture(record, &block)
+ content_tag(tag_name, content, options)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index 93a3c40683..ecd26891d6 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -118,38 +118,49 @@ module ActionView
# escape_once("&lt;&lt; Accept & Checkout")
# # => "&lt;&lt; Accept &amp; Checkout"
def escape_once(html)
- html.to_s.gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] }
+ ERB::Util.html_escape_once(html)
end
private
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
- "<#{name}#{tag_options}>#{escape ? ERB::Util.h(content) : content}</#{name}>".html_safe
+ content = ERB::Util.h(content) if escape
+ "<#{name}#{tag_options}>#{content}</#{name}>".html_safe
end
def tag_options(options, escape = true)
- unless options.blank?
- attrs = []
- options.each_pair do |key, value|
- if key.to_s == 'data' && value.is_a?(Hash)
- value.each do |k, v|
- if !v.is_a?(String) && !v.is_a?(Symbol)
- v = v.to_json
- end
- v = ERB::Util.html_escape(v) if escape
- attrs << %(data-#{k.to_s.dasherize}="#{v}")
- end
- elsif BOOLEAN_ATTRIBUTES.include?(key)
- attrs << %(#{key}="#{key}") if value
- elsif !value.nil?
- final_value = value.is_a?(Array) ? value.join(" ") : value
- final_value = ERB::Util.html_escape(final_value) if escape
- attrs << %(#{key}="#{final_value}")
+ return if options.blank?
+ attrs = []
+ options.each_pair do |key, value|
+ if key.to_s == 'data' && value.is_a?(Hash)
+ value.each_pair do |k, v|
+ attrs << data_tag_option(k, v, escape)
end
+ elsif BOOLEAN_ATTRIBUTES.include?(key)
+ attrs << boolean_tag_option(key) if value
+ elsif !value.nil?
+ attrs << tag_option(key, value, escape)
end
- " #{attrs.sort * ' '}".html_safe unless attrs.empty?
end
+ " #{attrs.sort * ' '}".html_safe unless attrs.empty?
+ end
+
+ def data_tag_option(key, value, escape)
+ key = "data-#{key.to_s.dasherize}"
+ value = value.to_json if !value.is_a?(String) && !value.is_a?(Symbol)
+
+ tag_option(key, value, escape)
+ end
+
+ def boolean_tag_option(key)
+ %(#{key}="#{key}")
+ end
+
+ def tag_option(key, value, escape)
+ value = value.join(" ") if value.is_a?(Array)
+ value = ERB::Util.h(value) if escape
+ %(#{key}="#{value}")
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags.rb b/actionpack/lib/action_view/helpers/tags.rb
new file mode 100644
index 0000000000..3cf762877f
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags.rb
@@ -0,0 +1,33 @@
+module ActionView
+ module Helpers
+ module Tags #:nodoc:
+ extend ActiveSupport::Autoload
+
+ autoload :Base
+ autoload :CheckBox
+ autoload :CollectionCheckBoxes
+ autoload :CollectionRadioButtons
+ autoload :CollectionSelect
+ autoload :DateField
+ autoload :DateSelect
+ autoload :DatetimeSelect
+ autoload :EmailField
+ autoload :FileField
+ autoload :GroupedCollectionSelect
+ autoload :HiddenField
+ autoload :Label
+ autoload :NumberField
+ autoload :PasswordField
+ autoload :RadioButton
+ autoload :RangeField
+ autoload :SearchField
+ autoload :Select
+ autoload :TelField
+ autoload :TextArea
+ autoload :TextField
+ autoload :TimeSelect
+ autoload :TimeZoneSelect
+ autoload :UrlField
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb
new file mode 100644
index 0000000000..c9c891daa1
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/base.rb
@@ -0,0 +1,148 @@
+module ActionView
+ module Helpers
+ module Tags
+ class Base #:nodoc:
+ include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
+ include FormOptionsHelper
+
+ DEFAULT_FIELD_OPTIONS = { "size" => 30 }
+
+ attr_reader :object
+
+ def initialize(object_name, method_name, template_object, options = {})
+ @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
+ @template_object = template_object
+
+ @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
+ @object = retrieve_object(options.delete(:object))
+ @options = options
+ @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
+ end
+
+ # This is what child classes implement.
+ def render
+ raise NotImplementedError, "Subclasses must implement a render method"
+ end
+
+ private
+
+ def value(object)
+ object.send @method_name if object
+ end
+
+ def value_before_type_cast(object)
+ unless object.nil?
+ method_before_type_cast = @method_name + "_before_type_cast"
+
+ object.respond_to?(method_before_type_cast) ?
+ object.send(method_before_type_cast) :
+ value(object)
+ end
+ end
+
+ def retrieve_object(object)
+ if object
+ object
+ elsif @template_object.instance_variable_defined?("@#{@object_name}")
+ @template_object.instance_variable_get("@#{@object_name}")
+ end
+ rescue NameError
+ # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
+ nil
+ end
+
+ def retrieve_autoindex(pre_match)
+ object = self.object || @template_object.instance_variable_get("@#{pre_match}")
+ if object && object.respond_to?(:to_param)
+ object.to_param
+ else
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
+ end
+ end
+
+ def add_default_name_and_id_for_value(tag_value, options)
+ if tag_value.nil?
+ add_default_name_and_id(options)
+ else
+ specified_id = options["id"]
+ add_default_name_and_id(options)
+
+ if specified_id.blank? && options["id"].present?
+ options["id"] += "_#{sanitized_value(tag_value)}"
+ end
+ end
+ end
+
+ def add_default_name_and_id(options)
+ if options.has_key?("index")
+ options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"]) }
+ options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
+ options.delete("index")
+ elsif defined?(@auto_index)
+ options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index) }
+ options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
+ else
+ options["name"] ||= options.fetch("name"){ options['multiple'] ? tag_name_multiple : tag_name }
+ options["id"] = options.fetch("id"){ tag_id }
+ end
+ options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
+ end
+
+ def tag_name
+ "#{@object_name}[#{sanitized_method_name}]"
+ end
+
+ def tag_name_multiple
+ "#{tag_name}[]"
+ end
+
+ def tag_name_with_index(index)
+ "#{@object_name}[#{index}][#{sanitized_method_name}]"
+ end
+
+ def tag_id
+ "#{sanitized_object_name}_#{sanitized_method_name}"
+ end
+
+ def tag_id_with_index(index)
+ "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
+ end
+
+ def sanitized_object_name
+ @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
+ end
+
+ def sanitized_method_name
+ @sanitized_method_name ||= @method_name.sub(/\?$/,"")
+ end
+
+ def sanitized_value(value)
+ value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
+ end
+
+ def select_content_tag(option_tags, options, html_options)
+ html_options = html_options.stringify_keys
+ add_default_name_and_id(html_options)
+ select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
+ if html_options["multiple"]
+ tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
+ else
+ select
+ end
+ end
+
+ def add_options(option_tags, options, value = nil)
+ if options[:include_blank]
+ include_blank = options[:include_blank] if options[:include_blank].kind_of?(String)
+ option_tags = content_tag(:option, include_blank, :value => '').safe_concat("\n").safe_concat(option_tags)
+ end
+ if value.blank? && options[:prompt]
+ prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
+ option_tags = content_tag(:option, prompt, :value => '').safe_concat("\n").safe_concat(option_tags)
+ end
+ option_tags
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/check_box.rb b/actionpack/lib/action_view/helpers/tags/check_box.rb
new file mode 100644
index 0000000000..579cdb9fc9
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/check_box.rb
@@ -0,0 +1,58 @@
+require 'action_view/helpers/tags/checkable'
+
+module ActionView
+ module Helpers
+ module Tags
+ class CheckBox < Base #:nodoc:
+ include Checkable
+
+ def initialize(object_name, method_name, template_object, checked_value, unchecked_value, options)
+ @checked_value = checked_value
+ @unchecked_value = unchecked_value
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ options = @options.stringify_keys
+ options["type"] = "checkbox"
+ options["value"] = @checked_value
+ options["checked"] = "checked" if input_checked?(object, options)
+
+ if options["multiple"]
+ add_default_name_and_id_for_value(@checked_value, options)
+ options.delete("multiple")
+ else
+ add_default_name_and_id(options)
+ end
+
+ hidden = hidden_field_for_checkbox(options)
+ checkbox = tag("input", options)
+ hidden + checkbox
+ end
+
+ private
+
+ def checked?(value)
+ case value
+ when TrueClass, FalseClass
+ value
+ when NilClass
+ false
+ when Integer
+ value != 0
+ when String
+ value == @checked_value
+ when Array
+ value.include?(@checked_value)
+ else
+ value.to_i != 0
+ end
+ end
+
+ def hidden_field_for_checkbox(options)
+ @unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)) : "".html_safe
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/checkable.rb b/actionpack/lib/action_view/helpers/tags/checkable.rb
new file mode 100644
index 0000000000..b97c0c68d7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/checkable.rb
@@ -0,0 +1,16 @@
+module ActionView
+ module Helpers
+ module Tags
+ module Checkable
+ def input_checked?(object, options)
+ if options.has_key?("checked")
+ checked = options.delete "checked"
+ checked == true || checked == "checked"
+ else
+ checked?(value(object))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
new file mode 100644
index 0000000000..e23f5113fb
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -0,0 +1,37 @@
+require 'action_view/helpers/tags/collection_helpers'
+
+module ActionView
+ module Helpers
+ module Tags
+ class CollectionCheckBoxes < Base
+ include CollectionHelpers
+
+ class CheckBoxBuilder < Builder
+ def check_box(extra_html_options={})
+ html_options = extra_html_options.merge(@input_html_options)
+ @template_object.check_box(@object_name, @method_name, html_options, @value, nil)
+ end
+ end
+
+ def render
+ rendered_collection = render_collection do |item, value, text, default_html_options|
+ default_html_options[:multiple] = true
+ builder = instantiate_builder(CheckBoxBuilder, item, value, text, default_html_options)
+
+ if block_given?
+ yield builder
+ else
+ builder.check_box + builder.label
+ end
+ end
+
+ # Append a hidden field to make sure something will be sent back to the
+ # server if all check boxes are unchecked.
+ hidden = @template_object.hidden_field_tag(tag_name_multiple, "", :id => nil)
+
+ rendered_collection + hidden
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
new file mode 100644
index 0000000000..6a1479069f
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
@@ -0,0 +1,82 @@
+module ActionView
+ module Helpers
+ module Tags
+ module CollectionHelpers
+ class Builder
+ attr_reader :object, :text, :value
+
+ def initialize(template_object, object_name, method_name, object,
+ sanitized_attribute_name, text, value, input_html_options)
+ @template_object = template_object
+ @object_name = object_name
+ @method_name = method_name
+ @object = object
+ @sanitized_attribute_name = sanitized_attribute_name
+ @text = text
+ @value = value
+ @input_html_options = input_html_options
+ end
+
+ def label(label_html_options={}, &block)
+ @template_object.label(@object_name, @sanitized_attribute_name, @text, label_html_options, &block)
+ end
+ end
+
+ def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
+ @collection = collection
+ @value_method = value_method
+ @text_method = text_method
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ private
+
+ def instantiate_builder(builder_class, item, value, text, html_options)
+ builder_class.new(@template_object, @object_name, @method_name, item,
+ sanitize_attribute_name(value), text, value, html_options)
+ end
+
+ # Generate default options for collection helpers, such as :checked and
+ # :disabled.
+ def default_html_options_for_collection(item, value) #:nodoc:
+ html_options = @html_options.dup
+
+ [:checked, :selected, :disabled].each do |option|
+ next unless current_value = @options[option]
+
+ accept = if current_value.respond_to?(:call)
+ current_value.call(item)
+ else
+ Array(current_value).include?(value)
+ end
+
+ if accept
+ html_options[option] = true
+ elsif option == :checked
+ html_options[option] = false
+ end
+ end
+
+ html_options[:object] = @object
+ html_options
+ end
+
+ def sanitize_attribute_name(value) #:nodoc:
+ "#{sanitized_method_name}_#{sanitized_value(value)}"
+ end
+
+ def render_collection #:nodoc:
+ @collection.map do |item|
+ value = value_for_collection(item, @value_method)
+ text = value_for_collection(item, @text_method)
+ default_html_options = default_html_options_for_collection(item, value)
+
+ yield item, value, text, default_html_options
+ end.join.html_safe
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb
new file mode 100644
index 0000000000..ba2035f074
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb
@@ -0,0 +1,30 @@
+require 'action_view/helpers/tags/collection_helpers'
+
+module ActionView
+ module Helpers
+ module Tags
+ class CollectionRadioButtons < Base
+ include CollectionHelpers
+
+ class RadioButtonBuilder < Builder
+ def radio_button(extra_html_options={})
+ html_options = extra_html_options.merge(@input_html_options)
+ @template_object.radio_button(@object_name, @method_name, @value, html_options)
+ end
+ end
+
+ def render
+ render_collection do |item, value, text, default_html_options|
+ builder = instantiate_builder(RadioButtonBuilder, item, value, text, default_html_options)
+
+ if block_given?
+ yield builder
+ else
+ builder.radio_button + builder.label
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_select.rb b/actionpack/lib/action_view/helpers/tags/collection_select.rb
new file mode 100644
index 0000000000..ec78e6e5f9
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/collection_select.rb
@@ -0,0 +1,28 @@
+module ActionView
+ module Helpers
+ module Tags
+ class CollectionSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
+ @collection = collection
+ @value_method = value_method
+ @text_method = text_method
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ option_tags_options = {
+ :selected => @options.fetch(:selected) { value(@object) },
+ :disabled => @options[:disabled]
+ }
+
+ select_content_tag(
+ options_from_collection_for_select(@collection, @value_method, @text_method, option_tags_options),
+ @options, @html_options
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/date_field.rb b/actionpack/lib/action_view/helpers/tags/date_field.rb
new file mode 100644
index 0000000000..bb968e9f39
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/date_field.rb
@@ -0,0 +1,15 @@
+module ActionView
+ module Helpers
+ module Tags
+ class DateField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options["value"] = @options.fetch("value") { value(object).try(:to_date) }
+ options["size"] = nil
+ @options = options
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/date_select.rb b/actionpack/lib/action_view/helpers/tags/date_select.rb
new file mode 100644
index 0000000000..5d706087b0
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/date_select.rb
@@ -0,0 +1,70 @@
+module ActionView
+ module Helpers
+ module Tags
+ class DateSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, options, html_options)
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ error_wrapping(datetime_selector(@options, @html_options).send("select_#{select_type}").html_safe)
+ end
+
+ class << self
+ def select_type
+ @select_type ||= self.name.split("::").last.sub("Select", "").downcase
+ end
+ end
+
+ private
+
+ def select_type
+ self.class.select_type
+ end
+
+ def datetime_selector(options, html_options)
+ datetime = value(object) || default_datetime(options)
+ @auto_index ||= nil
+
+ options = options.dup
+ options[:field_name] = @method_name
+ options[:include_position] = true
+ options[:prefix] ||= @object_name
+ options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
+
+ DateTimeSelector.new(datetime, options, html_options)
+ end
+
+ def default_datetime(options)
+ return if options[:include_blank] || options[:prompt]
+
+ case options[:default]
+ when nil
+ Time.current
+ when Date, Time
+ options[:default]
+ else
+ default = options[:default].dup
+
+ # Rename :minute and :second to :min and :sec
+ default[:min] ||= default[:minute]
+ default[:sec] ||= default[:second]
+
+ time = Time.current
+
+ [:year, :month, :day, :hour, :min, :sec].each do |key|
+ default[key] ||= time.send(key)
+ end
+
+ Time.utc_time(
+ default[:year], default[:month], default[:day],
+ default[:hour], default[:min], default[:sec]
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/datetime_select.rb b/actionpack/lib/action_view/helpers/tags/datetime_select.rb
new file mode 100644
index 0000000000..a32c840bce
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/datetime_select.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class DatetimeSelect < DateSelect #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/email_field.rb b/actionpack/lib/action_view/helpers/tags/email_field.rb
new file mode 100644
index 0000000000..45cde507d7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/email_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class EmailField < TextField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/file_field.rb b/actionpack/lib/action_view/helpers/tags/file_field.rb
new file mode 100644
index 0000000000..56442e1c14
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/file_field.rb
@@ -0,0 +1,12 @@
+module ActionView
+ module Helpers
+ module Tags
+ class FileField < TextField #:nodoc:
+ def render
+ @options.update(:size => nil)
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb b/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb
new file mode 100644
index 0000000000..507ba8835f
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb
@@ -0,0 +1,29 @@
+module ActionView
+ module Helpers
+ module Tags
+ class GroupedCollectionSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
+ @collection = collection
+ @group_method = group_method
+ @group_label_method = group_label_method
+ @option_key_method = option_key_method
+ @option_value_method = option_value_method
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ option_tags_options = {
+ :selected => @options.fetch(:selected) { value(@object) },
+ :disabled => @options[:disabled]
+ }
+
+ select_content_tag(
+ option_groups_from_collection_for_select(@collection, @group_method, @group_label_method, @option_key_method, @option_value_method, option_tags_options), @options, @html_options
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/hidden_field.rb b/actionpack/lib/action_view/helpers/tags/hidden_field.rb
new file mode 100644
index 0000000000..ea86596e0b
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/hidden_field.rb
@@ -0,0 +1,12 @@
+module ActionView
+ module Helpers
+ module Tags
+ class HiddenField < TextField #:nodoc:
+ def render
+ @options.update(:size => nil)
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/label.rb b/actionpack/lib/action_view/helpers/tags/label.rb
new file mode 100644
index 0000000000..1bd71c2778
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/label.rb
@@ -0,0 +1,65 @@
+module ActionView
+ module Helpers
+ module Tags
+ class Label < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
+ content_is_options = content_or_options.is_a?(Hash)
+ if content_is_options
+ options = content_or_options
+ @content = nil
+ else
+ @content = content_or_options
+ end
+
+ options ||= {}
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render(&block)
+ options = @options.stringify_keys
+ tag_value = options.delete("value")
+ name_and_id = options.dup
+
+ if name_and_id["for"]
+ name_and_id["id"] = name_and_id["for"]
+ else
+ name_and_id.delete("id")
+ end
+
+ add_default_name_and_id_for_value(tag_value, name_and_id)
+ options.delete("index")
+ options.delete("namespace")
+ options["for"] = name_and_id["id"] unless options.key?("for")
+
+ if block_given?
+ @template_object.label_tag(name_and_id["id"], options, &block)
+ else
+ content = if @content.blank?
+ @object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
+ method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name
+
+ if object.respond_to?(:to_model)
+ key = object.class.model_name.i18n_key
+ i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
+ end
+
+ i18n_default ||= ""
+ I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
+ else
+ @content.to_s
+ end
+
+ content ||= if object && object.class.respond_to?(:human_attribute_name)
+ object.class.human_attribute_name(@method_name)
+ end
+
+ content ||= @method_name.humanize
+
+ label_tag(name_and_id["id"], content, options)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/number_field.rb b/actionpack/lib/action_view/helpers/tags/number_field.rb
new file mode 100644
index 0000000000..e89fdbec46
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/number_field.rb
@@ -0,0 +1,19 @@
+module ActionView
+ module Helpers
+ module Tags
+ class NumberField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options['size'] ||= nil
+
+ if range = options.delete("in") || options.delete("within")
+ options.update("min" => range.min, "max" => range.max)
+ end
+
+ @options = options
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/password_field.rb b/actionpack/lib/action_view/helpers/tags/password_field.rb
new file mode 100644
index 0000000000..6e7a4d3c36
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/password_field.rb
@@ -0,0 +1,12 @@
+module ActionView
+ module Helpers
+ module Tags
+ class PasswordField < TextField #:nodoc:
+ def render
+ @options = {:value => nil}.merge!(@options)
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/radio_button.rb b/actionpack/lib/action_view/helpers/tags/radio_button.rb
new file mode 100644
index 0000000000..8a0421f061
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/radio_button.rb
@@ -0,0 +1,31 @@
+require 'action_view/helpers/tags/checkable'
+
+module ActionView
+ module Helpers
+ module Tags
+ class RadioButton < Base #:nodoc:
+ include Checkable
+
+ def initialize(object_name, method_name, template_object, tag_value, options)
+ @tag_value = tag_value
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ options = @options.stringify_keys
+ options["type"] = "radio"
+ options["value"] = @tag_value
+ options["checked"] = "checked" if input_checked?(object, options)
+ add_default_name_and_id_for_value(@tag_value, options)
+ tag("input", options)
+ end
+
+ private
+
+ def checked?(value)
+ value.to_s == @tag_value.to_s
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/range_field.rb b/actionpack/lib/action_view/helpers/tags/range_field.rb
new file mode 100644
index 0000000000..47db4680e7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/range_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class RangeField < NumberField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/search_field.rb b/actionpack/lib/action_view/helpers/tags/search_field.rb
new file mode 100644
index 0000000000..818fd4b887
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/search_field.rb
@@ -0,0 +1,24 @@
+module ActionView
+ module Helpers
+ module Tags
+ class SearchField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+
+ if options["autosave"]
+ if options["autosave"] == true
+ options["autosave"] = request.host.split(".").reverse.join(".")
+ end
+ options["results"] ||= 10
+ end
+
+ if options["onsearch"]
+ options["incremental"] = true unless options.has_key?("incremental")
+ end
+
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/select.rb b/actionpack/lib/action_view/helpers/tags/select.rb
new file mode 100644
index 0000000000..53a108b7e6
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/select.rb
@@ -0,0 +1,41 @@
+module ActionView
+ module Helpers
+ module Tags
+ class Select < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, choices, options, html_options)
+ @choices = choices
+ @choices = @choices.to_a if @choices.is_a?(Range)
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ option_tags_options = {
+ :selected => @options.fetch(:selected) { value(@object) },
+ :disabled => @options[:disabled]
+ }
+
+ option_tags = if grouped_choices?
+ grouped_options_for_select(@choices, option_tags_options)
+ else
+ options_for_select(@choices, option_tags_options)
+ end
+
+ select_content_tag(option_tags, @options, @html_options)
+ end
+
+ private
+
+ # Grouped choices look like this:
+ #
+ # [nil, []]
+ # { nil => [] }
+ #
+ def grouped_choices?
+ !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/tel_field.rb b/actionpack/lib/action_view/helpers/tags/tel_field.rb
new file mode 100644
index 0000000000..87c1f6b6b6
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/tel_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TelField < TextField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/text_area.rb b/actionpack/lib/action_view/helpers/tags/text_area.rb
new file mode 100644
index 0000000000..461a049fc2
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/text_area.rb
@@ -0,0 +1,20 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TextArea < Base #:nodoc:
+ DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }
+
+ def render
+ options = DEFAULT_TEXT_AREA_OPTIONS.merge(@options.stringify_keys)
+ add_default_name_and_id(options)
+
+ if size = options.delete("size")
+ options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
+ end
+
+ content_tag("textarea", "\n#{options.delete('value') || value_before_type_cast(object)}", options)
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/text_field.rb b/actionpack/lib/action_view/helpers/tags/text_field.rb
new file mode 100644
index 0000000000..ce5182d20f
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/text_field.rb
@@ -0,0 +1,30 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TextField < Base #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
+ options = DEFAULT_FIELD_OPTIONS.merge(options)
+ options["type"] ||= field_type
+ options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
+ options["value"] &&= ERB::Util.html_escape(options["value"])
+ add_default_name_and_id(options)
+ tag("input", options)
+ end
+
+ class << self
+ def field_type
+ @field_type ||= self.name.split("::").last.sub("Field", "").downcase
+ end
+ end
+
+ private
+
+ def field_type
+ self.class.field_type
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/time_select.rb b/actionpack/lib/action_view/helpers/tags/time_select.rb
new file mode 100644
index 0000000000..9e97deb706
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/time_select.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TimeSelect < DateSelect #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/time_zone_select.rb b/actionpack/lib/action_view/helpers/tags/time_zone_select.rb
new file mode 100644
index 0000000000..0a176157c3
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/time_zone_select.rb
@@ -0,0 +1,20 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TimeZoneSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, priority_zones, options, html_options)
+ @priority_zones = priority_zones
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ select_content_tag(
+ time_zone_options_for_select(value(@object) || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/url_field.rb b/actionpack/lib/action_view/helpers/tags/url_field.rb
new file mode 100644
index 0000000000..1ffdfe0b3c
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/url_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class UrlField < TextField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index ce79a3da48..3dc651501e 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -90,11 +90,11 @@ module ActionView
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
# a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
# as a single-quoted string with \1 where the phrase is to be inserted (defaults to
- # '<strong class="highlight">\1</strong>')
+ # '<mark>\1</mark>')
#
# ==== Examples
# highlight('You searched for: rails', 'rails')
- # # => You searched for: <strong class="highlight">rails</strong>
+ # # => You searched for: <mark>rails</mark>
#
# highlight('You searched for: ruby, rails, dhh', 'actionpack')
# # => You searched for: ruby, rails, dhh
@@ -111,9 +111,9 @@ module ActionView
def highlight(text, phrases, *args)
options = args.extract_options!
unless args.empty?
- options[:highlighter] = args[0] || '<strong class="highlight">\1</strong>'
+ options[:highlighter] = args[0] || '<mark>\1</mark>'
end
- options.reverse_merge!(:highlighter => '<strong class="highlight">\1</strong>')
+ options.reverse_merge!(:highlighter => '<mark>\1</mark>')
text = sanitize(text) unless options[:sanitize] == false
if text.blank? || phrases.blank?
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 0c2e1aa3a9..8d7417809b 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -97,12 +97,12 @@ module ActionView
# <%= url_for(:back) %>
# # if request.env["HTTP_REFERER"] is not set or is blank
# # => javascript:history.back()
- def url_for(options = {})
- options ||= {}
+ def url_for(options = nil)
case options
when String
options
- when Hash
+ when nil, Hash
+ options ||= {}
options = options.symbolize_keys.reverse_merge!(:only_path => options[:host].nil?)
super
when :back
@@ -146,12 +146,12 @@ module ActionView
# create an HTML form and immediately submit the form for processing using
# the HTTP verb specified. Useful for having links perform a POST operation
# in dangerous actions like deleting a record (which search bots can follow
- # while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt> and <tt>:put</tt>.
+ # while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>.
# Note that if the user has JavaScript disabled, the request will fall back
# to using GET. If <tt>:href => '#'</tt> is used and the user has JavaScript
# disabled clicking the link will have no effect. If you are relying on the
# POST behavior, you should check for it in your controller's action by using
- # the request object's methods for <tt>post?</tt>, <tt>delete?</tt> or <tt>put?</tt>.
+ # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>:patch</tt>, or <tt>put?</tt>.
# * <tt>:remote => true</tt> - This will allow the unobtrusive JavaScript
# driver to make an Ajax request to the URL in question instead of following
# the link. The drivers each provide mechanisms for listening for the
@@ -272,7 +272,7 @@ module ActionView
#
# There are a few special +html_options+:
# * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
- # <tt>:delete</tt> and <tt>:put</tt>. By default it will be <tt>:post</tt>.
+ # <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>.
# * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
# * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to
# prompt with the question specified. If the user accepts, the link is
@@ -301,7 +301,7 @@ module ActionView
# # <div><input value="Create" type="submit" /></div>
# # </form>"
#
- #
+ #
# <%= button_to "Delete Image", { :action => "delete", :id => @image.id },
# :confirm => "Are you sure?", :method => :delete %>
# # => "<form method="post" action="/images/delete/1" class="button_to">
@@ -323,34 +323,25 @@ module ActionView
# #
def button_to(name, options = {}, html_options = {})
html_options = html_options.stringify_keys
- convert_boolean_attributes!(html_options, %w( disabled ))
+ convert_boolean_attributes!(html_options, %w(disabled))
- method_tag = ''
- if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s)
- method_tag = tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
- end
+ url = options.is_a?(String) ? options : url_for(options)
+ remote = html_options.delete('remote')
+
+ method = html_options.delete('method').to_s
+ method_tag = %w{patch put delete}.include?(method) ? method_tag(method) : ""
- form_method = method.to_s == 'get' ? 'get' : 'post'
+ form_method = method == 'get' ? 'get' : 'post'
form_options = html_options.delete('form') || {}
form_options[:class] ||= html_options.delete('form_class') || 'button_to'
-
- remote = html_options.delete('remote')
-
- request_token_tag = ''
- if form_method == 'post' && protect_against_forgery?
- request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
- end
+ form_options.merge!(:method => form_method, :action => url)
+ form_options.merge!("data-remote" => "true") if remote
- url = options.is_a?(String) ? options : self.url_for(options)
- name ||= url
+ request_token_tag = form_method == 'post' ? token_tag : ''
html_options = convert_options_to_data_attributes(options, html_options)
+ html_options.merge!("type" => "submit", "value" => name || url)
- html_options.merge!("type" => "submit", "value" => name)
-
- form_options.merge!(:method => form_method, :action => url)
- form_options.merge!("data-remote" => "true") if remote
-
"#{tag(:form, form_options, true)}<div>#{method_tag}#{tag("input", html_options)}#{request_token_tag}</div></form>".html_safe
end
@@ -476,7 +467,7 @@ module ActionView
# string given as the value.
# * <tt>:subject</tt> - Preset the subject line of the email.
# * <tt>:body</tt> - Preset the body of the email.
- # * <tt>:cc</tt> - Carbon Copy addition recipients on the email.
+ # * <tt>:cc</tt> - Carbon Copy additional recipients on the email.
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
#
# ==== Examples
@@ -503,7 +494,7 @@ module ActionView
extras = %w{ cc bcc body subject }.map { |item|
option = html_options.delete(item) || next
- "#{item}=#{Rack::Utils.escape(option).gsub("+", "%20")}"
+ "#{item}=#{Rack::Utils.escape_path(option)}"
}.compact
extras = extras.empty? ? '' : '?' + ERB::Util.html_escape(extras.join('&'))
@@ -599,11 +590,7 @@ module ActionView
# We ignore any extra parameters in the request_uri if the
# submitted url doesn't have any either. This lets the function
# work with things like ?order=asc
- if url_string.index("?")
- request_uri = request.fullpath
- else
- request_uri = request.path
- end
+ request_uri = url_string.index("?") ? request.fullpath : request.path
if url_string =~ /^\w+:\/\//
url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
@@ -624,7 +611,7 @@ module ActionView
html_options["data-disable-with"] = disable_with if disable_with
html_options["data-confirm"] = confirm if confirm
- add_method_to_attributes!(html_options, method) if method
+ add_method_to_attributes!(html_options, method) if method
html_options
else
@@ -633,32 +620,16 @@ module ActionView
end
def link_to_remote_options?(options)
- options.is_a?(Hash) && options.key?('remote') && options.delete('remote')
+ options.is_a?(Hash) && options.delete('remote')
end
def add_method_to_attributes!(html_options, method)
if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/
- html_options["rel"] = "#{html_options["rel"]} nofollow".strip
+ html_options["rel"] = "#{html_options["rel"]} nofollow".lstrip
end
html_options["data-method"] = method
end
- def options_for_javascript(options)
- if options.empty?
- '{}'
- else
- "{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}"
- end
- end
-
- def array_or_string_for_javascript(option)
- if option.kind_of?(Array)
- "['#{option.join('\',\'')}']"
- elsif !option.nil?
- "'#{option}'"
- end
- end
-
# Processes the +html_options+ hash, converting the boolean
# attributes from true/false form into the form required by
# HTML/XHTML. (An attribute is considered to be boolean if
@@ -686,6 +657,19 @@ module ActionView
bool_attrs.each { |x| html_options[x] = x if html_options.delete(x) }
html_options
end
+
+ def token_tag(token=nil)
+ if token == false || !protect_against_forgery?
+ ''
+ else
+ token ||= form_authenticity_token
+ tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token)
+ end
+ end
+
+ def method_tag(method)
+ tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
+ end
end
end
end
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index f2a83b92a9..8e9db634fb 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -37,6 +37,7 @@
# precision:
# significant: false
# strip_insignificant_zeros: false
+ format: "%n%"
# Used in number_to_precision()
precision:
@@ -146,15 +147,8 @@
# Default value for :prompt => true in FormOptionsHelper
prompt: "Please select"
- # Default translation keys for submit FormHelper
+ # Default translation keys for submit and button FormHelper
submit:
create: 'Create %{model}'
update: 'Update %{model}'
submit: 'Save %{model}'
-
- # Default translation keys for button FormHelper
- button:
- create: 'Create %{model}'
- update: 'Update %{model}'
- submit: 'Save %{model}'
-
diff --git a/actionpack/lib/action_view/log_subscriber.rb b/actionpack/lib/action_view/log_subscriber.rb
index bf90d012bf..cc3a871576 100644
--- a/actionpack/lib/action_view/log_subscriber.rb
+++ b/actionpack/lib/action_view/log_subscriber.rb
@@ -12,9 +12,8 @@ module ActionView
alias :render_partial :render_template
alias :render_collection :render_template
- # TODO: Ideally, ActionView should have its own logger so it does not depend on AC.logger
def logger
- ActionController::Base.logger if defined?(ActionController::Base)
+ ActionView::Base.logger
end
protected
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index 90d88ca967..b7945a23be 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -9,7 +9,7 @@ module ActionView
# generate a key, given to view paths, used in the resolver cache lookup. Since
# this key is generated just once during the request, it speeds up all cache accesses.
class LookupContext #:nodoc:
- attr_accessor :prefixes
+ attr_accessor :prefixes, :rendered_format
mattr_accessor :fallbacks
@@fallbacks = FallbackFileSystemResolver.instances
@@ -152,6 +152,7 @@ module ActionView
def normalize_name(name, prefixes) #:nodoc:
prefixes = nil if prefixes.blank?
parts = name.to_s.split('/')
+ parts.shift if parts.first.empty?
name = parts.pop
return name, prefixes || [""] if parts.empty?
@@ -169,23 +170,15 @@ module ActionView
def initialize(view_paths, details = {}, prefixes = [])
@details, @details_key = {}, nil
- @frozen_formats, @skip_default_locale = false, false
+ @skip_default_locale = false
@cache = true
@prefixes = prefixes
+ @rendered_format = nil
self.view_paths = view_paths
initialize_details(details)
end
- # Freeze the current formats in the lookup context. By freezing them, you
- # that next template lookups are not going to modify the formats. The con
- # use this, to ensure that formats won't be further modified (as it does
- def freeze_formats(formats, unless_frozen=false) #:nodoc:
- return if unless_frozen && @frozen_formats
- self.formats = formats
- @frozen_formats = true
- end
-
# Override formats= to expand ["*/*"] values and automatically
# add :html as fallback to :js.
def formats=(values)
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index 80391d72cc..43371a1c49 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -8,6 +8,10 @@ module ActionView
config.action_view.stylesheet_expansions = {}
config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) }
+ initializer "action_view.logger" do
+ ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
+ end
+
initializer "action_view.cache_asset_ids" do |app|
unless app.config.cache_classes
ActiveSupport.on_load(:action_view) do
diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb
index a588abcee3..52473cd222 100644
--- a/actionpack/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb
@@ -1,7 +1,7 @@
module ActionView
class AbstractRenderer #:nodoc:
delegate :find_template, :template_exists?, :with_fallbacks, :update_details,
- :with_layout_format, :formats, :freeze_formats, :to => :@lookup_context
+ :with_layout_format, :formats, :to => :@lookup_context
def initialize(lookup_context)
@lookup_context = lookup_context
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index e231aade01..3033294883 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -85,8 +85,7 @@ module ActionView
# == Rendering objects that respond to `to_partial_path`
#
# Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
- # and pick the proper path by checking `to_proper_path` method. If the object passed to render is a collection,
- # all objects must return the same path.
+ # and pick the proper path by checking `to_partial_path` method.
#
# # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
# # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb
index f3abc6d533..20f75fba45 100644
--- a/actionpack/lib/action_view/renderer/template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/template_renderer.rb
@@ -6,7 +6,8 @@ module ActionView
@view = context
@details = extract_details(options)
template = determine_template(options)
- freeze_formats(template.formats, true)
+ @lookup_context.rendered_format ||= template.formats.first
+ @lookup_context.formats = template.formats
render_template(template, options[:layout], options[:locals])
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 593eaa2abf..edb3d427d5 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -288,7 +288,7 @@ module ActionView
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
end
- raise ActionView::Template::Error.new(self, {}, e)
+ raise ActionView::Template::Error.new(self, e)
end
end
@@ -297,13 +297,12 @@ module ActionView
e.sub_template_of(self)
raise e
else
- assigns = view.respond_to?(:assigns) ? view.assigns : {}
template = self
unless template.source
template = refresh(view)
template.encode!
end
- raise Template::Error.new(template, assigns, e)
+ raise Template::Error.new(template, e)
end
end
diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb
index 83df2604bb..d8258f7b11 100644
--- a/actionpack/lib/action_view/template/error.rb
+++ b/actionpack/lib/action_view/template/error.rb
@@ -55,9 +55,9 @@ module ActionView
attr_reader :original_exception, :backtrace
- def initialize(template, assigns, original_exception)
+ def initialize(template, original_exception)
super(original_exception.message)
- @template, @assigns, @original_exception = template, assigns.dup, original_exception
+ @template, @original_exception = template, original_exception
@sub_templates = nil
@backtrace = original_exception.backtrace
end
diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb
index aa693335e3..4e22bec6cc 100644
--- a/actionpack/lib/action_view/template/handlers.rb
+++ b/actionpack/lib/action_view/template/handlers.rb
@@ -17,15 +17,13 @@ module ActionView #:nodoc:
@@template_extensions ||= @@template_handlers.keys
end
- # Register a class that knows how to handle template files with the given
+ # Register an object that knows how to handle template files with the given
# extension. This can be used to implement new template types.
- # The constructor for the class must take the ActiveView::Base instance
- # as a parameter, and the class must implement a +render+ method that
- # takes the contents of the template to render as well as the Hash of
- # local assigns available to the template. The +render+ method ought to
- # return the rendered template as a string.
- def register_template_handler(extension, klass)
- @@template_handlers[extension.to_sym] = klass
+ # The handler must respond to `:call`, which will be passed the template
+ # and should return the rendered template as a String.
+ def register_template_handler(extension, handler)
+ @@template_handlers[extension.to_sym] = handler
+ @@template_extensions = nil
end
def template_handler_extensions
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index 323df67c97..19b9112afd 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -44,10 +44,6 @@ module ActionView
class_attribute :erb_trim_mode
self.erb_trim_mode = '-'
- # Default format used by ERB.
- class_attribute :default_format
- self.default_format = Mime::HTML
-
# Default implementation used.
class_attribute :erb_implementation
self.erb_implementation = Erubis
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index c734c914db..fece499c94 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -59,8 +59,10 @@ module ActionView
end
def determine_default_helper_class(name)
- mod = name.sub(/Test$/, '').safe_constantize
+ mod = name.sub(/Test$/, '').constantize
mod.is_a?(Class) ? nil : mod
+ rescue NameError
+ nil
end
def helper_method(*methods)
@@ -233,10 +235,8 @@ module ActionView
super
end
end
-
end
include Behavior
-
end
end
diff --git a/actionpack/lib/sprockets/assets.rake b/actionpack/lib/sprockets/assets.rake
index 43405f3db9..2e92fe416b 100644
--- a/actionpack/lib/sprockets/assets.rake
+++ b/actionpack/lib/sprockets/assets.rake
@@ -6,7 +6,11 @@ namespace :assets do
groups = ENV['RAILS_GROUPS'] || 'assets'
args = [$0, task,"RAILS_ENV=#{env}","RAILS_GROUPS=#{groups}"]
args << "--trace" if Rake.application.options.trace
- fork ? ruby(*args) : Kernel.exec(FileUtils::RUBY, *args)
+ if $0 =~ /rake\.bat\Z/i
+ Kernel.exec $0, *args
+ else
+ fork ? ruby(*args) : Kernel.exec(FileUtils::RUBY, *args)
+ end
end
# We are currently running with no explicit bundler group
@@ -62,11 +66,11 @@ namespace :assets do
ruby_rake_task("assets:precompile:nondigest", false) if Rails.application.config.assets.digest
end
- task :primary => ["assets:environment", "tmp:cache:clear"] do
+ task :primary => ["assets:cache:clean"] do
internal_precompile
end
- task :nondigest => ["assets:environment", "tmp:cache:clear"] do
+ task :nondigest => ["assets:cache:clean"] do
internal_precompile(false)
end
end
@@ -77,13 +81,19 @@ namespace :assets do
end
namespace :clean do
- task :all => ["assets:environment", "tmp:cache:clear"] do
+ task :all => ["assets:cache:clean"] do
config = Rails.application.config
public_asset_path = File.join(Rails.public_path, config.assets.prefix)
rm_rf public_asset_path, :secure => true
end
end
+ namespace :cache do
+ task :clean => ["assets:environment"] do
+ Rails.application.assets.cache.clear
+ end
+ end
+
task :environment do
if Rails.application.config.assets.initialize_on_precompile
Rake::Task["environment"].invoke
diff --git a/actionpack/lib/sprockets/compressors.rb b/actionpack/lib/sprockets/compressors.rb
index cb3e13314b..8b728d6570 100644
--- a/actionpack/lib/sprockets/compressors.rb
+++ b/actionpack/lib/sprockets/compressors.rb
@@ -1,38 +1,28 @@
module Sprockets
module Compressors
+ extend self
+
@@css_compressors = {}
@@js_compressors = {}
@@default_css_compressor = nil
@@default_js_compressor = nil
- def self.register_css_compressor(name, klass, options = {})
+ def register_css_compressor(name, klass, options = {})
@@default_css_compressor = name.to_sym if options[:default] || @@default_css_compressor.nil?
- @@css_compressors[name.to_sym] = {:klass => klass.to_s, :require => options[:require]}
+ @@css_compressors[name.to_sym] = { :klass => klass.to_s, :require => options[:require] }
end
- def self.register_js_compressor(name, klass, options = {})
+ def register_js_compressor(name, klass, options = {})
@@default_js_compressor = name.to_sym if options[:default] || @@default_js_compressor.nil?
- @@js_compressors[name.to_sym] = {:klass => klass.to_s, :require => options[:require]}
+ @@js_compressors[name.to_sym] = { :klass => klass.to_s, :require => options[:require] }
end
- def self.registered_css_compressor(name)
- if name.respond_to?(:to_sym)
- compressor = @@css_compressors[name.to_sym] || @@css_compressors[@@default_css_compressor]
- require compressor[:require] if compressor[:require]
- compressor[:klass].constantize.new
- else
- name
- end
+ def registered_css_compressor(name)
+ find_registered_compressor name, @@css_compressors, @@default_css_compressor
end
- def self.registered_js_compressor(name)
- if name.respond_to?(:to_sym)
- compressor = @@js_compressors[name.to_sym] || @@js_compressors[@@default_js_compressor]
- require compressor[:require] if compressor[:require]
- compressor[:klass].constantize.new
- else
- name
- end
+ def registered_js_compressor(name)
+ find_registered_compressor name, @@js_compressors, @@default_js_compressor
end
# The default compressors must be registered in default plugins (ex. Sass-Rails)
@@ -43,6 +33,18 @@ module Sprockets
register_css_compressor(:yui, 'YUI::CssCompressor', :require => 'yui/compressor')
register_js_compressor(:closure, 'Closure::Compiler', :require => 'closure-compiler')
register_js_compressor(:yui, 'YUI::JavaScriptCompressor', :require => 'yui/compressor')
+
+ private
+
+ def find_registered_compressor(name, compressors_hash, default_compressor_name)
+ if name.respond_to?(:to_sym)
+ compressor = compressors_hash[name.to_sym] || compressors_hash[default_compressor_name]
+ require compressor[:require] if compressor[:require]
+ compressor[:klass].constantize.new
+ else
+ name
+ end
+ end
end
# An asset compressor which does nothing.
diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb
index 976ae5a76d..839ec7635f 100644
--- a/actionpack/lib/sprockets/helpers/rails_helper.rb
+++ b/actionpack/lib/sprockets/helpers/rails_helper.rb
@@ -19,9 +19,9 @@ module Sprockets
def javascript_include_tag(*sources)
options = sources.extract_options!
- debug = options.key?(:debug) ? options.delete(:debug) : debug_assets?
- body = options.key?(:body) ? options.delete(:body) : false
- digest = options.key?(:digest) ? options.delete(:digest) : digest_assets?
+ debug = options.delete(:debug) { debug_assets? }
+ body = options.delete(:body) { false }
+ digest = options.delete(:digest) { digest_assets? }
sources.collect do |source|
if debug && asset = asset_paths.asset_for(source, 'js')
@@ -36,9 +36,9 @@ module Sprockets
def stylesheet_link_tag(*sources)
options = sources.extract_options!
- debug = options.key?(:debug) ? options.delete(:debug) : debug_assets?
- body = options.key?(:body) ? options.delete(:body) : false
- digest = options.key?(:digest) ? options.delete(:digest) : digest_assets?
+ debug = options.delete(:debug) { debug_assets? }
+ body = options.delete(:body) { false }
+ digest = options.delete(:digest) { digest_assets? }
sources.collect do |source|
if debug && asset = asset_paths.asset_for(source, 'css')
diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb
index a7eb03acaf..2bc482a39d 100644
--- a/actionpack/lib/sprockets/railtie.rb
+++ b/actionpack/lib/sprockets/railtie.rb
@@ -21,9 +21,12 @@ module Sprockets
require 'sprockets'
app.assets = Sprockets::Environment.new(app.root.to_s) do |env|
- env.logger = ::Rails.logger
env.version = ::Rails.env + "-#{config.assets.version}"
+ if config.assets.logger != false
+ env.logger = config.assets.logger || ::Rails.logger
+ end
+
if config.assets.cache_store != false
env.cache = ActiveSupport::Cache.lookup_store(config.assets.cache_store) || ::Rails.cache
end
diff --git a/actionpack/lib/sprockets/static_compiler.rb b/actionpack/lib/sprockets/static_compiler.rb
index 719df0bd51..9bbb464474 100644
--- a/actionpack/lib/sprockets/static_compiler.rb
+++ b/actionpack/lib/sprockets/static_compiler.rb
@@ -8,8 +8,8 @@ module Sprockets
@env = env
@target = target
@paths = paths
- @digest = options.key?(:digest) ? options.delete(:digest) : true
- @manifest = options.key?(:manifest) ? options.delete(:manifest) : true
+ @digest = options.fetch(:digest, true)
+ @manifest = options.fetch(:manifest, true)
@manifest_path = options.delete(:manifest_path) || target
@zip_files = options.delete(:zip_files) || /\.(?:css|html|js|svg|txt|xml)$/
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 0a25d7ba47..b1a5356ddd 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -344,6 +344,21 @@ module ActionDispatch
end
end
+module ActionDispatch
+ module RoutingVerbs
+ def get(uri_or_host, path = nil, port = nil)
+ host = uri_or_host.host unless path
+ path ||= uri_or_host.path
+
+ params = {'PATH_INFO' => path,
+ 'REQUEST_METHOD' => 'GET',
+ 'HTTP_HOST' => host}
+
+ routes.call(params)[2].join
+ end
+ end
+end
+
module RoutingTestHelpers
def url_for(set, options, recall = nil)
set.send(:url_for, options.merge(:only_path => true, :_path_segments => recall))
diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb
index 768ac713ca..2fe7959f5a 100644
--- a/actionpack/test/activerecord/active_record_store_test.rb
+++ b/actionpack/test/activerecord/active_record_store_test.rb
@@ -33,8 +33,6 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest
session[:foo] = "baz"
head :ok
end
-
- def rescue_action(e) raise end
end
def setup
@@ -225,16 +223,16 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest
assert_equal session_id, cookies['_session_id']
end
end
-
+
def test_incoming_invalid_session_id_via_cookie_should_be_ignored
with_test_route_set do
open_session do |sess|
sess.cookies['_session_id'] = 'INVALID'
-
+
sess.get '/set_session_value'
new_session_id = sess.cookies['_session_id']
assert_not_equal 'INVALID', new_session_id
-
+
sess.get '/get_session_value'
new_session_id_2 = sess.cookies['_session_id']
assert_equal new_session_id, new_session_id_2
@@ -248,7 +246,7 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest
sess.get '/set_session_value', :_session_id => 'INVALID'
new_session_id = sess.cookies['_session_id']
assert_not_equal 'INVALID', new_session_id
-
+
sess.get '/get_session_value'
new_session_id_2 = sess.cookies['_session_id']
assert_equal new_session_id, new_session_id_2
diff --git a/actionpack/test/controller/addresses_render_test.rb b/actionpack/test/controller/addresses_render_test.rb
deleted file mode 100644
index 0b5f2d7679..0000000000
--- a/actionpack/test/controller/addresses_render_test.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'abstract_unit'
-require 'active_support/logger'
-require 'controller/fake_controllers'
-
-class Address
- def Address.count(conditions = nil, join = nil)
- nil
- end
-
- def Address.find_all(arg1, arg2, arg3, arg4)
- []
- end
-
- def self.find(*args)
- []
- end
-end
-
-class AddressesTest < ActionController::TestCase
- tests AddressesController
-
- def setup
- super
- # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
- # a more accurate simulation of what happens in "real life".
- @controller.logger = ActiveSupport::Logger.new(nil)
-
- @request.host = "www.nextangle.com"
- end
-
- def test_list
- get :list
- assert_equal "We only need to get this far!", @response.body.chomp
- end
-end
diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb
index 5eef8a32d7..d3359e79a6 100644
--- a/actionpack/test/controller/assert_select_test.rb
+++ b/actionpack/test/controller/assert_select_test.rb
@@ -47,10 +47,6 @@ class AssertSelectTest < ActionController::TestCase
render :text=>@content, :layout=>false, :content_type=>Mime::XML
@content = nil
end
-
- def rescue_action(e)
- raise e
- end
end
tests AssertSelectController
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index c5c48bc559..791edb9069 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -40,29 +40,6 @@ class NonEmptyController < ActionController::Base
end
end
-class MethodMissingController < ActionController::Base
- hide_action :shouldnt_be_called
- def shouldnt_be_called
- raise "NO WAY!"
- end
-
-protected
-
- def method_missing(selector)
- render :text => selector.to_s
- end
-end
-
-class AnotherMethodMissingController < ActionController::Base
- cattr_accessor :_exception
- rescue_from Exception, :with => :_exception=
-
- protected
- def method_missing(*attrs, &block)
- super
- end
-end
-
class DefaultUrlOptionsController < ActionController::Base
def from_view
render :inline => "<%= #{params[:route]} %>"
@@ -116,6 +93,12 @@ class ControllerInstanceTests < ActiveSupport::TestCase
Submodule::ContainedNonEmptyController.new]
end
+ def test_performed?
+ assert !@empty.performed?
+ @empty.response_body = ["sweet"]
+ assert @empty.performed?
+ end
+
def test_action_methods
@empty_controllers.each do |c|
assert_equal Set.new, c.class.action_methods, "#{c.controller_path} should be empty!"
@@ -159,32 +142,10 @@ class PerformActionTest < ActionController::TestCase
assert_equal exception.message, "The action 'non_existent' could not be found for EmptyController"
end
- def test_get_on_priv_should_show_selector
- use_controller MethodMissingController
- get :shouldnt_be_called
- assert_response :success
- assert_equal 'shouldnt_be_called', @response.body
- end
-
- def test_method_missing_is_not_an_action_name
- use_controller MethodMissingController
- assert !@controller.__send__(:action_method?, 'method_missing')
-
- get :method_missing
- assert_response :success
- assert_equal 'method_missing', @response.body
- end
-
- def test_method_missing_should_recieve_symbol
- use_controller AnotherMethodMissingController
- get :some_action
- assert_kind_of NameError, @controller._exception
- end
-
def test_get_on_hidden_should_fail
use_controller NonEmptyController
- assert_raise(ActionController::UnknownAction) { get :hidden_action }
- assert_raise(ActionController::UnknownAction) { get :another_hidden_action }
+ assert_raise(AbstractController::ActionNotFound) { get :hidden_action }
+ assert_raise(AbstractController::ActionNotFound) { get :another_hidden_action }
end
end
@@ -197,6 +158,22 @@ class UrlOptionsTest < ActionController::TestCase
rescue_action_in_public!
end
+ def test_url_for_query_params_included
+ rs = ActionDispatch::Routing::RouteSet.new
+ rs.draw do
+ match 'home' => 'pages#home'
+ end
+
+ options = {
+ :action => "home",
+ :controller => "pages",
+ :only_path => true,
+ :params => { "token" => "secret" }
+ }
+
+ assert_equal '/home?token=secret', rs.url_for(options)
+ end
+
def test_url_options_override
with_routing do |set|
set.draw do
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 34a38a5567..a42c68a628 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -180,7 +180,7 @@ class PageCachingTest < ActionController::TestCase
end
[:ok, :no_content, :found, :not_found].each do |status|
- [:get, :post, :put, :delete].each do |method|
+ [:get, :post, :patch, :put, :delete].each do |method|
unless method == :get && status == :ok
define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
send(method, status)
@@ -237,6 +237,7 @@ class ActionCachingTestController < CachingController
caches_action :with_format_and_http_param, :cache_path => Proc.new { |c| { :key => 'value' } }
caches_action :layout_false, :layout => false
caches_action :record_not_found, :four_oh_four, :simple_runtime_error
+ caches_action :streaming
layout 'talk_from_action'
@@ -296,6 +297,10 @@ class ActionCachingTestController < CachingController
expire_action url_for(:controller => 'action_caching_test', :action => 'index')
render :nothing => true
end
+
+ def streaming
+ render :text => "streaming", :stream => true
+ end
end
class MockTime < Time
@@ -647,6 +652,13 @@ class ActionCacheTest < ActionController::TestCase
assert_response 500
end
+ def test_action_caching_plus_streaming
+ get :streaming
+ assert_response :success
+ assert_match(/streaming/, @response.body)
+ assert fragment_exist?('hostname.com/action_caching_test/streaming')
+ end
+
private
def content_to_cache
assigns(:cache_this)
@@ -686,8 +698,6 @@ class FragmentCachingTest < ActionController::TestCase
@controller.params = @params
@controller.request = @request
@controller.response = @response
- @controller.send(:initialize_template_class, @response)
- @controller.send(:assign_shortcuts, @request, @response)
end
def test_fragment_cache_key
@@ -795,10 +805,6 @@ class FunctionalCachingController < CachingController
format.xml
end
end
-
- def rescue_action(e)
- raise e
- end
end
class FunctionalFragmentCachingTest < ActionController::TestCase
diff --git a/actionpack/test/controller/capture_test.rb b/actionpack/test/controller/capture_test.rb
index a217510434..72263156d9 100644
--- a/actionpack/test/controller/capture_test.rb
+++ b/actionpack/test/controller/capture_test.rb
@@ -28,8 +28,6 @@ class CaptureController < ActionController::Base
def proper_block_detection
@todo = "some todo"
end
-
- def rescue_action(e) raise end
end
class CaptureTest < ActionController::TestCase
diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb
index a312b7c32a..03d5d27cf4 100644
--- a/actionpack/test/controller/content_type_test.rb
+++ b/actionpack/test/controller/content_type_test.rb
@@ -48,8 +48,6 @@ class OldContentTypeController < ActionController::Base
format.rss { render :text => "hello world!", :content_type => Mime::XML }
end
end
-
- def rescue_action(e) raise end
end
class ContentTypeTest < ActionController::TestCase
@@ -70,12 +68,12 @@ class ContentTypeTest < ActionController::TestCase
end
def test_render_changed_charset_default
- OldContentTypeController.default_charset = "utf-16"
+ ActionDispatch::Response.default_charset = "utf-16"
get :render_defaults
assert_equal "utf-16", @response.charset
assert_equal Mime::HTML, @response.content_type
ensure
- OldContentTypeController.default_charset = "utf-8"
+ ActionDispatch::Response.default_charset = "utf-8"
end
# :ported:
@@ -107,12 +105,12 @@ class ContentTypeTest < ActionController::TestCase
end
def test_nil_default_for_erb
- OldContentTypeController.default_charset = nil
+ ActionDispatch::Response.default_charset = nil
get :render_default_for_erb
assert_equal Mime::HTML, @response.content_type
assert_nil @response.charset, @response.headers.inspect
ensure
- OldContentTypeController.default_charset = "utf-8"
+ ActionDispatch::Response.default_charset = "utf-8"
end
def test_default_for_erb
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index 9ad0dc75f5..65c853f6eb 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -165,8 +165,6 @@ class FilterTest < ActionController::TestCase
@ran_filter ||= []
@ran_filter << "clean_up_tmp"
end
-
- def rescue_action(e) raise(e) end
end
class ConditionalCollectionFilterController < ConditionalFilterController
@@ -454,11 +452,6 @@ class FilterTest < ActionController::TestCase
def show
raise ErrorToRescue.new("Something made the bad noise.")
end
-
- private
- def rescue_action(exception)
- raise exception
- end
end
class NonYieldingAroundFilterController < ActionController::Base
@@ -472,9 +465,6 @@ class FilterTest < ActionController::TestCase
render :inline => "index"
end
- #make sure the controller complains
- def rescue_action(e); raise e; end
-
private
def filter_one
@@ -825,11 +815,7 @@ class FilterTest < ActionController::TestCase
end
end
-
-
class PostsController < ActionController::Base
- def rescue_action(e); raise e; end
-
module AroundExceptions
class Error < StandardError ; end
class Before < Error ; end
@@ -951,9 +937,7 @@ class ControllerWithAllTypesOfFilters < PostsController
end
class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters
- $vbf = true
skip_filter :around_again
- $vbf = false
skip_filter :after
end
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index d497913dc4..37ccc7a4a5 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -51,10 +51,6 @@ class FlashTest < ActionController::TestCase
render :inline => "hello"
end
- def rescue_action(e)
- raise unless ActionView::MissingTemplate === e
- end
-
# methods for test_sweep_after_halted_filter_chain
before_filter :halt_and_redir, :only => "filter_halting_action"
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
index 43b20fdead..7feeda25b3 100644
--- a/actionpack/test/controller/force_ssl_test.rb
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -26,6 +26,21 @@ class ForceSSLExceptAction < ForceSSLController
force_ssl :except => :banana
end
+class ForceSSLFlash < ForceSSLController
+ force_ssl :except => [:banana, :set_flash, :use_flash]
+
+ def set_flash
+ flash["that"] = "hello"
+ redirect_to '/force_ssl_flash/cheeseburger'
+ end
+
+ def use_flash
+ @flash_copy = {}.update flash
+ @flashy = flash["that"]
+ render :inline => "hello"
+ end
+end
+
class ForceSSLControllerLevelTest < ActionController::TestCase
tests ForceSSLControllerLevel
@@ -35,6 +50,12 @@ class ForceSSLControllerLevelTest < ActionController::TestCase
assert_equal "https://test.host/force_ssl_controller_level/banana", redirect_to_url
end
+ def test_banana_redirects_to_https_with_extra_params
+ get :banana, :token => "secret"
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_controller_level/banana?token=secret", redirect_to_url
+ end
+
def test_cheeseburger_redirects_to_https
get :cheeseburger
assert_response 301
@@ -50,7 +71,7 @@ class ForceSSLCustomDomainTest < ActionController::TestCase
assert_response 301
assert_equal "https://secure.test.host/force_ssl_custom_domain/banana", redirect_to_url
end
-
+
def test_cheeseburger_redirects_to_https_with_custom_host
get :cheeseburger
assert_response 301
@@ -88,16 +109,20 @@ class ForceSSLExceptActionTest < ActionController::TestCase
end
end
-class ForceSSLExcludeDevelopmentTest < ActionController::TestCase
- tests ForceSSLControllerLevel
+class ForceSSLFlashTest < ActionController::TestCase
+ tests ForceSSLFlash
- def setup
- Rails.env.stubs(:development?).returns(false)
- end
+ def test_cheeseburger_redirects_to_https
+ get :set_flash
+ assert_response 302
+ assert_equal "http://test.host/force_ssl_flash/cheeseburger", redirect_to_url
- def test_development_environment_not_redirects_to_https
- Rails.env.stubs(:development?).returns(true)
- get :banana
- assert_response 200
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_flash/cheeseburger", redirect_to_url
+
+ get :use_flash
+ assert_equal "hello", assigns["flash_copy"]["that"]
+ assert_equal "hello", assigns["flashy"]
end
end
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 35a87c1aae..757661d8d0 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -7,16 +7,12 @@ module Fun
def render_hello_world
render :inline => "hello: <%= stratego %>"
end
-
- def rescue_action(e) raise end
end
class PdfController < ActionController::Base
def test
render :inline => "test: <%= foobar %>"
end
-
- def rescue_action(e) raise end
end
end
@@ -60,7 +56,6 @@ class HelperTest < ActiveSupport::TestCase
class TestController < ActionController::Base
attr_accessor :delegate_attr
def delegate_method() end
- def rescue_action(e) raise end
end
def setup
@@ -201,8 +196,6 @@ class IsolatedHelpersTest < ActiveSupport::TestCase
def index
render :inline => '<%= shout %>'
end
-
- def rescue_action(e) raise end
end
class B < A
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index a328372cff..44f033119d 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -63,6 +63,12 @@ class SessionTest < ActiveSupport::TestCase
@session.post_via_redirect(path, args, headers)
end
+ def test_patch_via_redirect
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ @session.expects(:request_via_redirect).with(:patch, path, args, headers)
+ @session.patch_via_redirect(path, args, headers)
+ end
+
def test_put_via_redirect
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:put, path, args, headers)
@@ -87,6 +93,12 @@ class SessionTest < ActiveSupport::TestCase
@session.post(path,params,headers)
end
+ def test_patch
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:patch,path,params,headers)
+ @session.patch(path,params,headers)
+ end
+
def test_put
path = "/index"; params = "blah"; headers = {:location => 'blah'}
@session.expects(:process).with(:put,path,params,headers)
@@ -105,6 +117,12 @@ class SessionTest < ActiveSupport::TestCase
@session.head(path,params,headers)
end
+ def test_options
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:options,path,params,headers)
+ @session.options(path,params,headers)
+ end
+
def test_xml_http_request_get
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
@@ -125,6 +143,16 @@ class SessionTest < ActiveSupport::TestCase
@session.xml_http_request(:post,path,params,headers)
end
+ def test_xml_http_request_patch
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:patch,path,params,headers_after_xhr)
+ @session.xml_http_request(:patch,path,params,headers)
+ end
+
def test_xml_http_request_put
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
@@ -155,6 +183,16 @@ class SessionTest < ActiveSupport::TestCase
@session.xml_http_request(:head,path,params,headers)
end
+ def test_xml_http_request_options
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:options,path,params,headers_after_xhr)
+ @session.xml_http_request(:options,path,params,headers)
+ end
+
def test_xml_http_request_override_accept
path = "/index"; params = "blah"; headers = {:location => 'blah', "HTTP_ACCEPT" => "application/xml"}
headers_after_xhr = headers.merge(
@@ -212,7 +250,7 @@ class IntegrationTestUsesCorrectClass < ActionDispatch::IntegrationTest
@integration_session.stubs(:generic_url_rewriter)
@integration_session.stubs(:process)
- %w( get post head put delete ).each do |verb|
+ %w( get post head patch put delete options ).each do |verb|
assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') }
end
end
@@ -535,3 +573,36 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
assert_equal old_env, env
end
end
+
+class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ def post
+ render :text => "Created", :status => 201
+ end
+ end
+
+ def self.call(env)
+ env["action_dispatch.parameter_filter"] = [:password]
+ routes.call(env)
+ end
+
+ def self.routes
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+
+ routes.draw do
+ match '/post', :to => 'environment_filter_integration_test/test#post', :via => :post
+ end
+
+ def app
+ self.class
+ end
+
+ test "filters rack request form vars" do
+ post "/post", :username => 'cjolly', :password => 'secret'
+
+ assert_equal 'cjolly', request.filtered_parameters['username']
+ assert_equal '[FILTERED]', request.filtered_parameters['password']
+ assert_equal '[FILTERED]', request.filtered_env['rack.request.form_vars']
+ end
+end
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index 76a8c89e60..ae368842b5 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -151,10 +151,6 @@ class RespondToController < ActionController::Base
end
end
- def rescue_action(e)
- raise
- end
-
protected
def set_layout
if action_name.in?(["all_types_with_layout", "iphone_with_html_response_type"])
@@ -498,7 +494,7 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
assert_equal "text/html", @response.content_type
end
-
+
def test_invalid_format
get :using_defaults, :format => "invalidformat"
assert_equal " ", @response.body
@@ -597,6 +593,19 @@ class RenderJsonRespondWithController < RespondWithController
format.json { render :json => RenderJsonTestException.new('boom') }
end
end
+
+ def create
+ resource = ValidatedCustomer.new(params[:name], 1)
+ respond_with(resource) do |format|
+ format.json do
+ if resource.errors.empty?
+ render :json => { :valid => true }
+ else
+ render :json => { :valid => false }
+ end
+ end
+ end
+ end
end
class EmptyRespondWithController < ActionController::Base
@@ -761,6 +770,41 @@ class RespondWithControllerTest < ActionController::TestCase
end
end
+ def test_using_resource_for_patch_with_html_redirects_on_success
+ with_test_route_set do
+ patch :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 302, @response.status
+ assert_equal "http://www.example.com/customers/13", @response.location
+ assert @response.redirect?
+ end
+ end
+
+ def test_using_resource_for_patch_with_html_rerender_on_failure
+ with_test_route_set do
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ patch :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "Edit world!\n", @response.body
+ assert_nil @response.location
+ end
+ end
+
+ def test_using_resource_for_patch_with_html_rerender_on_failure_even_on_method_override
+ with_test_route_set do
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ @request.env["rack.methodoverride.original_method"] = "POST"
+ patch :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "Edit world!\n", @response.body
+ assert_nil @response.location
+ end
+ end
+
def test_using_resource_for_put_with_html_redirects_on_success
with_test_route_set do
put :using_resource
@@ -968,6 +1012,18 @@ class RespondWithControllerTest < ActionController::TestCase
assert_match(/"error":"RenderJsonTestException"/, @response.body)
end
+ def test_api_response_with_valid_resource_respect_override_block
+ @controller = RenderJsonRespondWithController.new
+ post :create, :name => "sikachu", :format => :json
+ assert_equal '{"valid":true}', @response.body
+ end
+
+ def test_api_response_with_invalid_resource_respect_override_block
+ @controller = RenderJsonRespondWithController.new
+ post :create, :name => "david", :format => :json
+ assert_equal '{"valid":false}', @response.body
+ end
+
def test_no_double_render_is_raised
@request.accept = "text/html"
assert_raise ActionView::MissingTemplate do
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 5b739e49ac..b1d76150f8 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -105,8 +105,6 @@ class RedirectController < ActionController::Base
def rescue_errors(e) raise e end
- def rescue_action(e) raise end
-
protected
def dashbord_url(id, message)
url_for :action => "dashboard", :params => { "id" => id, "message" => message }
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index ec26315dc7..e040878b26 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -9,7 +9,7 @@ module Fun
end
def nested_partial_with_form_builder
- render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}, Proc.new {})
+ render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {})
end
end
end
@@ -54,7 +54,7 @@ class TestController < ActionController::Base
def conditional_hello_with_record
record = Struct.new(:updated_at, :cache_key).new(Time.now.utc.beginning_of_day, "foo/123")
-
+
if stale?(record)
render :action => 'hello_world'
end
@@ -91,6 +91,16 @@ class TestController < ActionController::Base
render :action => 'hello_world'
end
+ def conditional_hello_with_expires_in_with_must_revalidate
+ expires_in 1.minute, :must_revalidate => true
+ render :action => 'hello_world'
+ end
+
+ def conditional_hello_with_expires_in_with_public_and_must_revalidate
+ expires_in 1.minute, :public => true, :must_revalidate => true
+ render :action => 'hello_world'
+ end
+
def conditional_hello_with_expires_in_with_public_with_more_keys
expires_in 1.minute, :public => true, 'max-stale' => 5.hours
render :action => 'hello_world'
@@ -558,11 +568,11 @@ class TestController < ActionController::Base
end
def partial_with_form_builder
- render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}, Proc.new {})
+ render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {})
end
def partial_with_form_builder_subclass
- render :partial => LabellingFormBuilder.new(:post, nil, view_context, {}, Proc.new {})
+ render :partial => LabellingFormBuilder.new(:post, nil, view_context, {})
end
def partial_collection
@@ -649,10 +659,6 @@ class TestController < ActionController::Base
render :action => "calling_partial_with_layout", :layout => "layouts/partial_with_layout"
end
- def rescue_action(e)
- raise
- end
-
before_filter :only => :render_with_filters do
request.format = :xml
end
@@ -696,7 +702,8 @@ class RenderTest < ActionController::TestCase
# enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
# a more accurate simulation of what happens in "real life".
super
- @controller.logger = ActiveSupport::Logger.new(nil)
+ @controller.logger = ActiveSupport::Logger.new(nil)
+ ActionView::Base.logger = ActiveSupport::Logger.new(nil)
@request.host = "www.nextangle.com"
end
@@ -891,12 +898,12 @@ class RenderTest < ActionController::TestCase
# :ported:
def test_attempt_to_access_object_method
- assert_raise(ActionController::UnknownAction, "No action responded to [clone]") { get :clone }
+ assert_raise(AbstractController::ActionNotFound, "No action responded to [clone]") { get :clone }
end
# :ported:
def test_private_methods
- assert_raise(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout }
+ assert_raise(AbstractController::ActionNotFound, "No action responded to [determine_layout]") { get :determine_layout }
end
# :ported:
@@ -1096,15 +1103,15 @@ class RenderTest < ActionController::TestCase
# :ported:
def test_double_render
- assert_raise(ActionController::DoubleRenderError) { get :double_render }
+ assert_raise(AbstractController::DoubleRenderError) { get :double_render }
end
def test_double_redirect
- assert_raise(ActionController::DoubleRenderError) { get :double_redirect }
+ assert_raise(AbstractController::DoubleRenderError) { get :double_redirect }
end
def test_render_and_redirect
- assert_raise(ActionController::DoubleRenderError) { get :render_and_redirect }
+ assert_raise(AbstractController::DoubleRenderError) { get :render_and_redirect }
end
# specify the one exception to double render rule - render_to_string followed by render
@@ -1402,6 +1409,16 @@ class ExpiresInRenderTest < ActionController::TestCase
assert_equal "max-age=60, public", @response.headers["Cache-Control"]
end
+ def test_expires_in_header_with_must_revalidate
+ get :conditional_hello_with_expires_in_with_must_revalidate
+ assert_equal "max-age=60, private, must-revalidate", @response.headers["Cache-Control"]
+ end
+
+ def test_expires_in_header_with_public_and_must_revalidate
+ get :conditional_hello_with_expires_in_with_public_and_must_revalidate
+ assert_equal "max-age=60, public, must-revalidate", @response.headers["Cache-Control"]
+ end
+
def test_expires_in_header_with_additional_headers
get :conditional_hello_with_expires_in_with_public_with_more_keys
assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"]
@@ -1416,6 +1433,13 @@ class ExpiresInRenderTest < ActionController::TestCase
get :conditional_hello_with_expires_now
assert_equal "no-cache", @response.headers["Cache-Control"]
end
+
+ def test_date_header_when_expires_in
+ time = Time.mktime(2011,10,30)
+ Time.stubs(:now).returns(time)
+ get :conditional_hello_with_expires_in
+ assert_equal Time.now.httpdate, @response.headers["Date"]
+ end
end
class LastModifiedRenderTest < ActionController::TestCase
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index fd5a41a0bb..64ed7f667f 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -1,6 +1,5 @@
require 'abstract_unit'
require 'digest/sha1'
-require 'active_support/core_ext/string/strip'
require "active_support/log_subscriber/test_helper"
# common controller actions
@@ -36,8 +35,6 @@ module RequestForgeryProtectionActions
def form_for_without_protection
render :inline => "<%= form_for(:some_resource, :authenticity_token => false ) {} %>"
end
-
- def rescue_action(e) raise e end
end
# sample controllers
@@ -74,9 +71,7 @@ class CustomAuthenticityParamController < RequestForgeryProtectionController
end
end
-
# common test methods
-
module RequestForgeryProtectionTests
def setup
@token = "cf50faa3fe97702ca1ae"
@@ -119,6 +114,10 @@ module RequestForgeryProtectionTests
assert_blocked { post :index, :format=>'xml' }
end
+ def test_should_not_allow_patch_without_token
+ assert_blocked { patch :index }
+ end
+
def test_should_not_allow_put_without_token
assert_blocked { put :index }
end
@@ -135,6 +134,10 @@ module RequestForgeryProtectionTests
assert_not_blocked { post :index, :custom_authenticity_token => @token }
end
+ def test_should_allow_patch_with_token
+ assert_not_blocked { patch :index, :custom_authenticity_token => @token }
+ end
+
def test_should_allow_put_with_token
assert_not_blocked { put :index, :custom_authenticity_token => @token }
end
@@ -153,6 +156,11 @@ module RequestForgeryProtectionTests
assert_not_blocked { delete :index }
end
+ def test_should_allow_patch_with_token_in_header
+ @request.env['HTTP_X_CSRF_TOKEN'] = @token
+ assert_not_blocked { patch :index }
+ end
+
def test_should_allow_put_with_token_in_header
@request.env['HTTP_X_CSRF_TOKEN'] = @token
assert_not_blocked { put :index }
@@ -237,7 +245,7 @@ class FreeCookieControllerTest < ActionController::TestCase
end
def test_should_allow_all_methods_without_token
- [:post, :put, :delete].each do |method|
+ [:post, :patch, :put, :delete].each do |method|
assert_nothing_raised { send(method, :index)}
end
end
@@ -248,10 +256,6 @@ class FreeCookieControllerTest < ActionController::TestCase
end
end
-
-
-
-
class CustomAuthenticityParamControllerTest < ActionController::TestCase
def setup
ActionController::Base.request_forgery_protection_token = :custom_token_name
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index 86d6737cbb..9c51db135b 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -137,11 +137,11 @@ class RescueController < ActionController::Base
end
def io_error_in_view
- raise ActionView::TemplateError.new(nil, {}, IOError.new('this is io error'))
+ raise ActionView::TemplateError.new(nil, IOError.new('this is io error'))
end
def zero_division_error_in_view
- raise ActionView::TemplateError.new(nil, {}, ZeroDivisionError.new('this is zero division error'))
+ raise ActionView::TemplateError.new(nil, ZeroDivisionError.new('this is zero division error'))
end
protected
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 6b8a8f6161..3c0a5d36ca 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -6,7 +6,6 @@ require 'active_support/core_ext/object/inclusion'
class ResourcesController < ActionController::Base
def index() render :nothing => true end
alias_method :show, :index
- def rescue_action(e) raise e end
end
class ThreadsController < ResourcesController; end
@@ -159,7 +158,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_collection_actions
- actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
with_routing do |set|
set.draw do
@@ -168,6 +167,7 @@ class ResourcesTest < ActionController::TestCase
put :b, :on => :collection
post :c, :on => :collection
delete :d, :on => :collection
+ patch :e, :on => :collection
end
end
@@ -186,7 +186,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_collection_actions_and_name_prefix
- actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
with_routing do |set|
set.draw do
@@ -196,6 +196,7 @@ class ResourcesTest < ActionController::TestCase
put :b, :on => :collection
post :c, :on => :collection
delete :d, :on => :collection
+ patch :e, :on => :collection
end
end
end
@@ -242,7 +243,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_collection_action_and_name_prefix_and_formatted
- actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
with_routing do |set|
set.draw do
@@ -252,6 +253,7 @@ class ResourcesTest < ActionController::TestCase
put :b, :on => :collection
post :c, :on => :collection
delete :d, :on => :collection
+ patch :e, :on => :collection
end
end
end
@@ -271,7 +273,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_member_action
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_restful_routing :messages, :member => { :mark => method } do
mark_options = {:action => 'mark', :id => '1'}
mark_path = "/messages/1/mark"
@@ -295,7 +297,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_member_when_override_paths_for_default_restful_actions_with
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_restful_routing :messages, :member => { :mark => method }, :path_names => {:new => 'nuevo'} do
mark_options = {:action => 'mark', :id => '1', :controller => "messages"}
mark_path = "/messages/1/mark"
@@ -312,7 +314,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_two_member_actions_with_same_method
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_routing do |set|
set.draw do
resources :messages do
@@ -565,7 +567,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_singleton_resource_with_member_action
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_routing do |set|
set.draw do
resource :account do
@@ -587,7 +589,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_singleton_resource_with_two_member_actions_with_same_method
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_routing do |set|
set.draw do
resource :account do
@@ -652,13 +654,17 @@ class ResourcesTest < ActionController::TestCase
end
end
- def test_should_not_allow_delete_or_put_on_collection_path
+ def test_should_not_allow_delete_or_patch_or_put_on_collection_path
controller_name = :messages
with_restful_routing controller_name do
options = { :controller => controller_name.to_s }
collection_path = "/#{controller_name}"
assert_raise(ActionController::RoutingError) do
+ assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :patch)
+ end
+
+ assert_raise(ActionController::RoutingError) do
assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put)
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index bf33b8cdd7..843ae1a813 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -6,7 +6,6 @@ require 'active_support/core_ext/object/with_options'
class MilestonesController < ActionController::Base
def index() head :ok end
alias_method :show, :index
- def rescue_action(e) raise e end
end
ROUTING = ActionDispatch::Routing
@@ -74,23 +73,127 @@ end
class LegacyRouteSetTests < ActiveSupport::TestCase
include RoutingTestHelpers
+ include ActionDispatch::RoutingVerbs
attr_reader :rs
+ alias :routes :rs
def setup
@rs = ::ActionDispatch::Routing::RouteSet.new
@response = nil
end
- def get(uri_or_host, path = nil, port = nil)
- host = uri_or_host.host unless path
- path ||= uri_or_host.path
+ def test_symbols_with_dashes
+ rs.draw do
+ match '/:artist/:song-omg', :to => lambda { |env|
+ resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ [200, {}, [resp]]
+ }
+ end
+
+ hash = JSON.load get(URI('http://example.org/journey/faithfully-omg'))
+ assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash)
+ end
+
+ def test_id_with_dash
+ rs.draw do
+ match '/journey/:id', :to => lambda { |env|
+ resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ [200, {}, [resp]]
+ }
+ end
+
+ hash = JSON.load get(URI('http://example.org/journey/faithfully-omg'))
+ assert_equal({"id"=>"faithfully-omg"}, hash)
+ end
+
+ def test_dash_with_custom_regexp
+ rs.draw do
+ match '/:artist/:song-omg', :constraints => { :song => /\d+/ }, :to => lambda { |env|
+ resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ [200, {}, [resp]]
+ }
+ end
+
+ hash = JSON.load get(URI('http://example.org/journey/123-omg'))
+ assert_equal({"artist"=>"journey", "song"=>"123"}, hash)
+ assert_equal 'Not Found', get(URI('http://example.org/journey/faithfully-omg'))
+ end
+
+ def test_pre_dash
+ rs.draw do
+ match '/:artist/omg-:song', :to => lambda { |env|
+ resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ [200, {}, [resp]]
+ }
+ end
+
+ hash = JSON.load get(URI('http://example.org/journey/omg-faithfully'))
+ assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash)
+ end
+
+ def test_pre_dash_with_custom_regexp
+ rs.draw do
+ match '/:artist/omg-:song', :constraints => { :song => /\d+/ }, :to => lambda { |env|
+ resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ [200, {}, [resp]]
+ }
+ end
+
+ hash = JSON.load get(URI('http://example.org/journey/omg-123'))
+ assert_equal({"artist"=>"journey", "song"=>"123"}, hash)
+ assert_equal 'Not Found', get(URI('http://example.org/journey/omg-faithfully'))
+ end
+
+ def test_star_paths_are_greedy
+ rs.draw do
+ match "/*path", :to => lambda { |env|
+ x = env["action_dispatch.request.path_parameters"][:path]
+ [200, {}, [x]]
+ }, :format => false
+ end
+
+ u = URI('http://example.org/foo/bar.html')
+ assert_equal u.path.sub(/^\//, ''), get(u)
+ end
- params = {'PATH_INFO' => path,
- 'REQUEST_METHOD' => 'GET',
- 'HTTP_HOST' => host}
+ def test_star_paths_are_greedy_but_not_too_much
+ rs.draw do
+ match "/*path", :to => lambda { |env|
+ x = JSON.dump env["action_dispatch.request.path_parameters"]
+ [200, {}, [x]]
+ }
+ end
- @rs.call(params)[2].join
+ expected = { "path" => "foo/bar", "format" => "html" }
+ u = URI('http://example.org/foo/bar.html')
+ assert_equal expected, JSON.parse(get(u))
+ end
+
+ def test_optional_star_paths_are_greedy
+ rs.draw do
+ match "/(*filters)", :to => lambda { |env|
+ x = env["action_dispatch.request.path_parameters"][:filters]
+ [200, {}, [x]]
+ }, :format => false
+ end
+
+ u = URI('http://example.org/ne_27.065938,-80.6092/sw_25.489856,-82.542794')
+ assert_equal u.path.sub(/^\//, ''), get(u)
+ end
+
+ def test_optional_star_paths_are_greedy_but_not_too_much
+ rs.draw do
+ match "/(*filters)", :to => lambda { |env|
+ x = JSON.dump env["action_dispatch.request.path_parameters"]
+ [200, {}, [x]]
+ }
+ end
+
+ expected = { "filters" => "ne_27.065938,-80.6092/sw_25.489856,-82",
+ "format" => "542794" }
+ u = URI('http://example.org/ne_27.065938,-80.6092/sw_25.489856,-82.542794')
+ assert_equal expected, JSON.parse(get(u))
end
def test_regexp_precidence
@@ -190,36 +293,6 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
test_default_setup
end
- def test_time_recognition
- # We create many routes to make situation more realistic
- @rs = ::ActionDispatch::Routing::RouteSet.new
- @rs.draw {
- root :to => "search#new", :as => "frontpage"
- resources :videos do
- resources :comments
- resource :file, :controller => 'video_file'
- resource :share, :controller => 'video_shares'
- resource :abuse, :controller => 'video_abuses'
- end
- resources :abuses, :controller => 'video_abuses'
- resources :video_uploads
- resources :video_visits
-
- resources :users do
- resource :settings
- resources :videos
- end
- resources :channels do
- resources :videos, :controller => 'channel_videos'
- end
- resource :session
- resource :lost_password
- match 'search' => 'search#index', :as => 'search'
- resources :pages
- match ':controller/:action/:id'
- }
- end
-
def test_route_with_colon_first
rs.draw do
match '/:controller/:action/:id', :action => 'index', :id => nil
@@ -600,11 +673,12 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
match '/match' => 'books#get', :via => :get
match '/match' => 'books#post', :via => :post
match '/match' => 'books#put', :via => :put
+ match '/match' => 'books#patch', :via => :patch
match '/match' => 'books#delete', :via => :delete
end
end
- %w(GET POST PUT DELETE).each do |request_method|
+ %w(GET PATCH POST PUT DELETE).each do |request_method|
define_method("test_request_method_recognized_with_#{request_method}") do
setup_request_method_routes_for(request_method)
params = rs.recognize_path("/match", :method => request_method)
@@ -987,6 +1061,7 @@ class RouteSetTest < ActiveSupport::TestCase
post "/people" => "people#create"
get "/people/:id" => "people#show", :as => "person"
put "/people/:id" => "people#update"
+ patch "/people/:id" => "people#update"
delete "/people/:id" => "people#destroy"
end
@@ -999,6 +1074,9 @@ class RouteSetTest < ActiveSupport::TestCase
params = set.recognize_path("/people/5", :method => :put)
assert_equal("update", params[:action])
+ params = set.recognize_path("/people/5", :method => :patch)
+ assert_equal("update", params[:action])
+
assert_raise(ActionController::UnknownHttpMethod) {
set.recognize_path("/people", :method => :bacon)
}
@@ -1011,6 +1089,10 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal("update", params[:action])
assert_equal("5", params[:id])
+ params = set.recognize_path("/people/5", :method => :patch)
+ assert_equal("update", params[:action])
+ assert_equal("5", params[:id])
+
params = set.recognize_path("/people/5", :method => :delete)
assert_equal("destroy", params[:action])
assert_equal("5", params[:id])
@@ -1064,6 +1146,7 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw do
get "people/:id" => "people#show", :as => "person"
put "people/:id" => "people#update"
+ patch "people/:id" => "people#update"
get "people/:id(.:format)" => "people#show"
end
@@ -1074,6 +1157,9 @@ class RouteSetTest < ActiveSupport::TestCase
params = set.recognize_path("/people/5", :method => :put)
assert_equal("update", params[:action])
+ params = set.recognize_path("/people/5", :method => :patch)
+ assert_equal("update", params[:action])
+
params = set.recognize_path("/people/5.png", :method => :get)
assert_equal("show", params[:action])
assert_equal("5", params[:id])
@@ -1328,7 +1414,20 @@ class RouteSetTest < ActiveSupport::TestCase
end
end
end
-
+
+ def test_route_with_subdomain_and_constraints_must_receive_params
+ name_param = nil
+ set.draw do
+ match 'page/:name' => 'pages#show', :constraints => lambda {|request|
+ name_param = request.params[:name]
+ return true
+ }
+ end
+ assert_equal({:controller => 'pages', :action => 'show', :name => 'mypage'},
+ set.recognize_path('http://subdomain.example.org/page/mypage'))
+ assert_equal(name_param, 'mypage')
+ end
+
def test_route_requirement_recognize_with_ignore_case
set.draw do
match 'page/:name' => 'pages#show',
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 73b860ac18..ecba9fed22 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -1,8 +1,7 @@
require 'abstract_unit'
require 'controller/fake_controllers'
-require 'active_support/ordered_hash'
-class TestTest < ActionController::TestCase
+class TestCaseTest < ActionController::TestCase
class TestController < ActionController::Base
def no_op
render :text => 'dummy'
@@ -120,13 +119,11 @@ XML
def test_assigns
@foo = "foo"
+ @foo_hash = {:foo => :bar}
render :nothing => true
end
private
- def rescue_action(e)
- raise e
- end
def generate_url(opts)
url_for(opts.merge(:action => "test_uri"))
@@ -158,35 +155,44 @@ XML
end
def test_raw_post_handling
- params = ActiveSupport::OrderedHash[:page, {:name => 'page name'}, 'some key', 123]
+ params = Hash[:page, {:name => 'page name'}, 'some key', 123]
post :render_raw_post, params.dup
assert_equal params.to_query, @response.body
end
def test_body_stream
- params = ActiveSupport::OrderedHash[:page, { :name => 'page name' }, 'some key', 123]
+ params = Hash[:page, { :name => 'page name' }, 'some key', 123]
post :render_body, params.dup
assert_equal params.to_query, @response.body
end
-
+
def test_document_body_and_params_with_post
post :test_params, :id => 1
- assert_equal("{\"id\"=>\"1\", \"controller\"=>\"test_test/test\", \"action\"=>\"test_params\"}", @response.body)
+ assert_equal("{\"id\"=>\"1\", \"controller\"=>\"test_case_test/test\", \"action\"=>\"test_params\"}", @response.body)
end
-
+
def test_document_body_with_post
post :render_body, "document body"
assert_equal "document body", @response.body
end
-
+
def test_document_body_with_put
put :render_body, "document body"
assert_equal "document body", @response.body
end
+ def test_head
+ head :test_params
+ assert_equal 200, @response.status
+ end
+
+ def test_head_params_as_sting
+ assert_raise(NoMethodError) { head :test_params, "document body", :id => 10 }
+ end
+
def test_process_without_flash
process :set_flash
assert_equal '><', flash['test']
@@ -238,18 +244,18 @@ XML
def test_process_with_request_uri_with_no_params
process :test_uri
- assert_equal "/test_test/test/test_uri", @response.body
+ assert_equal "/test_case_test/test/test_uri", @response.body
end
def test_process_with_request_uri_with_params
process :test_uri, "GET", :id => 7
- assert_equal "/test_test/test/test_uri/7", @response.body
+ assert_equal "/test_case_test/test/test_uri/7", @response.body
end
-
+
def test_process_with_old_api
assert_deprecated do
process :test_uri, :id => 7
- assert_equal "/test_test/test/test_uri/7", @response.body
+ assert_equal "/test_case_test/test/test_uri/7", @response.body
end
end
@@ -287,6 +293,10 @@ XML
assert_equal "foo", assigns("foo")
assert_equal "foo", assigns[:foo]
assert_equal "foo", assigns["foo"]
+
+ # but the assigned variable should not have its own keys stringified
+ expected_hash = { :foo => :bar }
+ assert_equal expected_hash, assigns(:foo_hash)
end
def test_view_assigns
@@ -533,7 +543,7 @@ XML
get :test_params, :page => {:name => "Page name", :month => '4', :year => '2004', :day => '6'}
parsed_params = eval(@response.body)
assert_equal(
- {'controller' => 'test_test/test', 'action' => 'test_params',
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}},
parsed_params
)
@@ -543,7 +553,7 @@ XML
get :test_params, :page => {:name => "Page name", :month => 4, :year => 2004, :day => 6}
parsed_params = eval(@response.body)
assert_equal(
- {'controller' => 'test_test/test', 'action' => 'test_params',
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}},
parsed_params
)
@@ -555,7 +565,7 @@ XML
end
parsed_params = eval(@response.body)
assert_equal(
- {'controller' => 'test_test/test', 'action' => 'test_params',
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
'frozen' => 'icy', 'frozens' => ['icy']},
parsed_params
)
@@ -575,7 +585,7 @@ XML
def test_array_path_parameter_handled_properly
with_routing do |set|
set.draw do
- match 'file/*path', :to => 'test_test/test#test_params'
+ match 'file/*path', :to => 'test_case_test/test#test_params'
match ':controller/:action'
end
@@ -710,7 +720,7 @@ XML
end
def test_fixture_path_is_accessed_from_self_instead_of_active_support_test_case
- TestTest.stubs(:fixture_path).returns(FILES_DIR)
+ TestCaseTest.stubs(:fixture_path).returns(FILES_DIR)
uploaded_file = fixture_file_upload('/mona_lisa.jpg', 'image/png')
assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read
diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb
index f5ac886c20..40f6dc6f0f 100644
--- a/actionpack/test/controller/view_paths_test.rb
+++ b/actionpack/test/controller/view_paths_test.rb
@@ -3,7 +3,6 @@ require 'abstract_unit'
class ViewLoadPathsTest < ActionController::TestCase
class TestController < ActionController::Base
def self.controller_path() "test" end
- def rescue_action(e) raise end
before_filter :add_view_path, :only => :hello_world_at_request_time
@@ -16,22 +15,17 @@ class ViewLoadPathsTest < ActionController::TestCase
end
end
- class Test::SubController < ActionController::Base
- layout 'test/sub'
- def hello_world; render(:template => 'test/hello_world'); end
+ module Test
+ class SubController < ActionController::Base
+ layout 'test/sub'
+ def hello_world; render(:template => 'test/hello_world'); end
+ end
end
def setup
- # TestController.view_paths = nil
-
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
-
@controller = TestController.new
- # Following is needed in order to setup @controller.template object properly
- @controller.send :assign_shortcuts, @request, @response
- @controller.send :initialize_template_class, @response
-
@paths = TestController.view_paths
end
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index ae8588cbb0..351e61eeae 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -18,8 +18,6 @@ class WebServiceTest < ActionDispatch::IntegrationTest
s << "#{k}#{value}"
end
end
-
- def rescue_action(e) raise end
end
def setup
diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb
index eed2eca2ab..f767b07e75 100644
--- a/actionpack/test/dispatch/callbacks_test.rb
+++ b/actionpack/test/dispatch/callbacks_test.rb
@@ -27,6 +27,12 @@ class DispatcherTest < ActiveSupport::TestCase
dispatch
assert_equal 4, Foo.a
assert_equal 4, Foo.b
+
+ dispatch do |env|
+ raise "error"
+ end rescue nil
+ assert_equal 6, Foo.a
+ assert_equal 6, Foo.b
end
def test_to_prepare_and_cleanup_delegation
@@ -44,8 +50,9 @@ class DispatcherTest < ActiveSupport::TestCase
private
def dispatch(&block)
- @dispatcher ||= ActionDispatch::Callbacks.new(block || DummyApp.new)
- @dispatcher.call({'rack.input' => StringIO.new('')})
+ ActionDispatch::Callbacks.new(block || DummyApp.new).call(
+ {'rack.input' => StringIO.new('')}
+ )
end
end
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 6ebd02e85c..3e48d97e67 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -245,6 +245,17 @@ class CookiesTest < ActionController::TestCase
assert_cookie_header "user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT"
end
+ def test_deleted_cookie_predicate
+ cookies.delete("user_name")
+ assert cookies.deleted?("user_name")
+ assert_equal false, cookies.deleted?("another")
+ end
+
+ def test_deleted_cookie_predicate_with_mismatching_options
+ cookies.delete("user_name", :path => "/path")
+ assert_equal false, cookies.deleted?("user_name", :path => "/different")
+ end
+
def test_cookies_persist_throughout_request
response = get :authenticate
assert response.headers["Set-Cookie"] =~ /user_name=david/
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index f3dc160d7d..11c292d61a 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -24,7 +24,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
when "/pass"
[404, { "X-Cascade" => "pass" }, self]
when "/not_found"
- raise ActionController::UnknownAction
+ raise AbstractController::ActionNotFound
when "/runtime_error"
raise RuntimeError
when "/method_not_allowed"
@@ -34,7 +34,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
when "/unprocessable_entity"
raise ActionController::InvalidAuthenticityToken
when "/not_found_original_exception"
- raise ActionView::Template::Error.new('template', {}, AbstractController::ActionNotFound.new)
+ raise ActionView::Template::Error.new('template', AbstractController::ActionNotFound.new)
else
raise "puke!"
end
@@ -83,7 +83,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
assert_response 404
- assert_match(/#{ActionController::UnknownAction.name}/, body)
+ assert_match(/#{AbstractController::ActionNotFound.name}/, body)
get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
assert_response 405
diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb
index 831f3db3e2..4191ed1ff4 100644
--- a/actionpack/test/dispatch/middleware_stack_test.rb
+++ b/actionpack/test/dispatch/middleware_stack_test.rb
@@ -81,6 +81,12 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal BazMiddleware, @stack[0].klass
end
+ test "swaps one middleware out for same middleware class" do
+ assert_equal FooMiddleware, @stack[0].klass
+ @stack.swap(FooMiddleware, FooMiddleware, Proc.new { |env| [500, {}, ['error!']] })
+ assert_equal FooMiddleware, @stack[0].klass
+ end
+
test "raise an error on invalid index" do
assert_raise RuntimeError do
@stack.insert("HiyaMiddleware", BazMiddleware)
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index db21080c42..0372c42920 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -69,6 +69,12 @@ class MimeTypeTest < ActiveSupport::TestCase
assert_equal expect, Mime::Type.parse(accept)
end
+ test "parse single media range with q" do
+ accept = "text/html;q=0.9"
+ expect = [Mime::HTML]
+ assert_equal expect, Mime::Type.parse(accept)
+ end
+
# Accept header send with user HTTP_USER_AGENT: Sunrise/0.42j (Windows XP)
test "parse broken acceptlines" do
accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/*,,*/*;q=0.5"
diff --git a/actionpack/test/dispatch/reloader_test.rb b/actionpack/test/dispatch/reloader_test.rb
index e371c3b0c1..ce9ccfcee8 100644
--- a/actionpack/test/dispatch/reloader_test.rb
+++ b/actionpack/test/dispatch/reloader_test.rb
@@ -43,6 +43,16 @@ class ReloaderTest < ActiveSupport::TestCase
assert_respond_to body, :close
end
+ def test_returned_body_object_always_responds_to_close_even_if_called_twice
+ body = call_and_return_body
+ assert_respond_to body, :close
+ body.close
+
+ body = call_and_return_body
+ assert_respond_to body, :close
+ body.close
+ end
+
def test_condition_specifies_when_to_reload
i, j = 0, 0, 0, 0
Reloader.to_prepare { |*args| i += 1 }
@@ -154,7 +164,8 @@ class ReloaderTest < ActiveSupport::TestCase
private
def call_and_return_body(&block)
- @reloader ||= Reloader.new(block || proc {[200, {}, 'response']})
+ @response ||= 'response'
+ @reloader ||= Reloader.new(block || proc {[200, {}, @response]})
@reloader.call({'rack.input' => StringIO.new('')})[2]
end
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 5b3d38c48c..6c8b22c47f 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -42,7 +42,7 @@ class RequestTest < ActiveSupport::TestCase
'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
assert_equal '3.4.5.6', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,3.4.5.6'
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,unknown'
assert_equal '3.4.5.6', request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '172.16.0.1,3.4.5.6'
@@ -63,7 +63,7 @@ class RequestTest < ActiveSupport::TestCase
request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1'
assert_equal 'unknown', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4'
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 172.31.4.4'
assert_equal '3.4.5.6', request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1',
@@ -85,7 +85,7 @@ class RequestTest < ActiveSupport::TestCase
:ip_spoofing_check => false
assert_equal '2.2.2.2', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 9.9.9.9'
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 8.8.8.8'
assert_equal '9.9.9.9', request.remote_ip
end
@@ -94,8 +94,8 @@ class RequestTest < ActiveSupport::TestCase
assert_equal '127.0.0.1', request.remote_ip
end
- test "remote ip with user specified trusted proxies" do
- @trusted_proxies = /^67\.205\.106\.73$/i
+ test "remote ip with user specified trusted proxies String" do
+ @trusted_proxies = "67.205.106.73"
request = stub_request 'REMOTE_ADDR' => '67.205.106.73',
'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
@@ -116,8 +116,19 @@ class RequestTest < ActiveSupport::TestCase
request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,67.205.106.73'
assert_equal 'unknown', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 67.205.106.73'
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 67.205.106.73'
+ assert_equal '3.4.5.6', request.remote_ip
+ end
+
+ test "remote ip with user specified trusted proxies Regexp" do
+ @trusted_proxies = /^67\.205\.106\.73$/i
+
+ request = stub_request 'REMOTE_ADDR' => '67.205.106.73',
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '67.205.106.73, 10.0.0.1, 9.9.9.9, 3.4.5.6'
+ assert_equal '10.0.0.1', request.remote_ip
end
test "domains" do
@@ -314,14 +325,14 @@ class RequestTest < ActiveSupport::TestCase
end
test "String request methods" do
- [:get, :post, :put, :delete].each do |method|
+ [:get, :post, :patch, :put, :delete].each do |method|
request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
assert_equal method.to_s.upcase, request.method
end
end
test "Symbol forms of request methods via method_symbol" do
- [:get, :post, :put, :delete].each do |method|
+ [:get, :post, :patch, :put, :delete].each do |method|
request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
assert_equal method, request.method_symbol
end
@@ -335,7 +346,7 @@ class RequestTest < ActiveSupport::TestCase
end
test "allow method hacking on post" do
- %w(GET OPTIONS PUT POST DELETE).each do |method|
+ %w(GET OPTIONS PATCH PUT POST DELETE).each do |method|
request = stub_request "REQUEST_METHOD" => method.to_s.upcase
assert_equal(method == "HEAD" ? "GET" : method, request.method)
end
@@ -349,7 +360,7 @@ class RequestTest < ActiveSupport::TestCase
end
test "restrict method hacking" do
- [:get, :put, :delete].each do |method|
+ [:get, :patch, :put, :delete].each do |method|
request = stub_request 'REQUEST_METHOD' => method.to_s.upcase,
'action_dispatch.request.request_parameters' => { :_method => 'put' }
assert_equal method.to_s.upcase, request.method
@@ -364,6 +375,13 @@ class RequestTest < ActiveSupport::TestCase
assert request.head?
end
+ test "post masquerading as patch" do
+ request = stub_request 'REQUEST_METHOD' => 'PATCH', "rack.methodoverride.original_method" => "POST"
+ assert_equal "POST", request.method
+ assert_equal "PATCH", request.request_method
+ assert request.patch?
+ end
+
test "post masquerading as put" do
request = stub_request 'REQUEST_METHOD' => 'PUT', "rack.methodoverride.original_method" => "POST"
assert_equal "POST", request.method
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index e5ed11d1ea..700666600b 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -1,3 +1,4 @@
+# encoding: UTF-8
require 'erb'
require 'abstract_unit'
require 'controller/fake_controllers'
@@ -524,6 +525,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
"GET"
end
+ def ip
+ "127.0.0.1"
+ end
+
def x_header
@env["HTTP_X_HEADER"] || ""
end
@@ -574,52 +579,42 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
include Routes.url_helpers
def test_logout
- with_test_routes do
- delete '/logout'
- assert_equal 'sessions#destroy', @response.body
+ delete '/logout'
+ assert_equal 'sessions#destroy', @response.body
- assert_equal '/logout', logout_path
- assert_equal '/logout', url_for(:controller => 'sessions', :action => 'destroy', :only_path => true)
- end
+ assert_equal '/logout', logout_path
+ assert_equal '/logout', url_for(:controller => 'sessions', :action => 'destroy', :only_path => true)
end
def test_login
- with_test_routes do
- get '/login'
- assert_equal 'sessions#new', @response.body
- assert_equal '/login', login_path
+ get '/login'
+ assert_equal 'sessions#new', @response.body
+ assert_equal '/login', login_path
- post '/login'
- assert_equal 'sessions#create', @response.body
+ post '/login'
+ assert_equal 'sessions#create', @response.body
- assert_equal '/login', url_for(:controller => 'sessions', :action => 'create', :only_path => true)
- assert_equal '/login', url_for(:controller => 'sessions', :action => 'new', :only_path => true)
+ assert_equal '/login', url_for(:controller => 'sessions', :action => 'create', :only_path => true)
+ assert_equal '/login', url_for(:controller => 'sessions', :action => 'new', :only_path => true)
- assert_equal 'http://rubyonrails.org/login', Routes.url_for(:controller => 'sessions', :action => 'create')
- assert_equal 'http://rubyonrails.org/login', Routes.url_helpers.login_url
- end
+ assert_equal 'http://rubyonrails.org/login', Routes.url_for(:controller => 'sessions', :action => 'create')
+ assert_equal 'http://rubyonrails.org/login', Routes.url_helpers.login_url
end
def test_login_redirect
- with_test_routes do
- get '/account/login'
- verify_redirect 'http://www.example.com/login'
- end
+ get '/account/login'
+ verify_redirect 'http://www.example.com/login'
end
def test_logout_redirect_without_to
- with_test_routes do
- assert_equal '/account/logout', logout_redirect_path
- get '/account/logout'
- verify_redirect 'http://www.example.com/logout'
- end
+ assert_equal '/account/logout', logout_redirect_path
+ get '/account/logout'
+ verify_redirect 'http://www.example.com/logout'
end
def test_namespace_redirect
- with_test_routes do
- get '/private'
- verify_redirect 'http://www.example.com/private/index'
- end
+ get '/private'
+ verify_redirect 'http://www.example.com/private/index'
end
def test_namespace_with_controller_segment
@@ -635,189 +630,159 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_session_singleton_resource
- with_test_routes do
- get '/session'
- assert_equal 'sessions#create', @response.body
- assert_equal '/session', session_path
+ get '/session'
+ assert_equal 'sessions#create', @response.body
+ assert_equal '/session', session_path
- post '/session'
- assert_equal 'sessions#create', @response.body
+ post '/session'
+ assert_equal 'sessions#create', @response.body
- put '/session'
- assert_equal 'sessions#update', @response.body
+ put '/session'
+ assert_equal 'sessions#update', @response.body
- delete '/session'
- assert_equal 'sessions#destroy', @response.body
+ delete '/session'
+ assert_equal 'sessions#destroy', @response.body
- get '/session/new'
- assert_equal 'sessions#new', @response.body
- assert_equal '/session/new', new_session_path
+ get '/session/new'
+ assert_equal 'sessions#new', @response.body
+ assert_equal '/session/new', new_session_path
- get '/session/edit'
- assert_equal 'sessions#edit', @response.body
- assert_equal '/session/edit', edit_session_path
+ get '/session/edit'
+ assert_equal 'sessions#edit', @response.body
+ assert_equal '/session/edit', edit_session_path
- post '/session/reset'
- assert_equal 'sessions#reset', @response.body
- assert_equal '/session/reset', reset_session_path
- end
+ post '/session/reset'
+ assert_equal 'sessions#reset', @response.body
+ assert_equal '/session/reset', reset_session_path
end
def test_session_info_nested_singleton_resource
- with_test_routes do
- get '/session/info'
- assert_equal 'infos#show', @response.body
- assert_equal '/session/info', session_info_path
- end
+ get '/session/info'
+ assert_equal 'infos#show', @response.body
+ assert_equal '/session/info', session_info_path
end
def test_member_on_resource
- with_test_routes do
- get '/session/crush'
- assert_equal 'sessions#crush', @response.body
- assert_equal '/session/crush', crush_session_path
- end
+ get '/session/crush'
+ assert_equal 'sessions#crush', @response.body
+ assert_equal '/session/crush', crush_session_path
end
def test_redirect_modulo
- with_test_routes do
- get '/account/modulo/name'
- verify_redirect 'http://www.example.com/names'
- end
+ get '/account/modulo/name'
+ verify_redirect 'http://www.example.com/names'
end
def test_redirect_proc
- with_test_routes do
- get '/account/proc/person'
- verify_redirect 'http://www.example.com/people'
- end
+ get '/account/proc/person'
+ verify_redirect 'http://www.example.com/people'
end
def test_redirect_proc_with_request
- with_test_routes do
- get '/account/proc_req'
- verify_redirect 'http://www.example.com/GET'
- end
+ get '/account/proc_req'
+ verify_redirect 'http://www.example.com/GET'
end
def test_redirect_hash_with_subdomain
- with_test_routes do
- get '/mobile'
- verify_redirect 'http://mobile.example.com/mobile'
- end
+ get '/mobile'
+ verify_redirect 'http://mobile.example.com/mobile'
end
def test_redirect_hash_with_host
- with_test_routes do
- get '/super_new_documentation?section=top'
- verify_redirect 'http://super-docs.com/super_new_documentation?section=top'
- end
+ get '/super_new_documentation?section=top'
+ verify_redirect 'http://super-docs.com/super_new_documentation?section=top'
end
def test_redirect_class
- with_test_routes do
- get '/youtube_favorites/oHg5SJYRHA0/rick-rolld'
- verify_redirect 'http://www.youtube.com/watch?v=oHg5SJYRHA0'
- end
+ get '/youtube_favorites/oHg5SJYRHA0/rick-rolld'
+ verify_redirect 'http://www.youtube.com/watch?v=oHg5SJYRHA0'
end
def test_openid
- with_test_routes do
- get '/openid/login'
- assert_equal 'openid#login', @response.body
+ get '/openid/login'
+ assert_equal 'openid#login', @response.body
- post '/openid/login'
- assert_equal 'openid#login', @response.body
- end
+ post '/openid/login'
+ assert_equal 'openid#login', @response.body
end
def test_bookmarks
- with_test_routes do
- get '/bookmark/build'
- assert_equal 'bookmarks#new', @response.body
- assert_equal '/bookmark/build', bookmark_new_path
-
- post '/bookmark/create'
- assert_equal 'bookmarks#create', @response.body
- assert_equal '/bookmark/create', bookmark_path
-
- put '/bookmark/update'
- assert_equal 'bookmarks#update', @response.body
- assert_equal '/bookmark/update', bookmark_update_path
-
- get '/bookmark/remove'
- assert_equal 'bookmarks#destroy', @response.body
- assert_equal '/bookmark/remove', bookmark_remove_path
- end
+ get '/bookmark/build'
+ assert_equal 'bookmarks#new', @response.body
+ assert_equal '/bookmark/build', bookmark_new_path
+
+ post '/bookmark/create'
+ assert_equal 'bookmarks#create', @response.body
+ assert_equal '/bookmark/create', bookmark_path
+
+ put '/bookmark/update'
+ assert_equal 'bookmarks#update', @response.body
+ assert_equal '/bookmark/update', bookmark_update_path
+
+ get '/bookmark/remove'
+ assert_equal 'bookmarks#destroy', @response.body
+ assert_equal '/bookmark/remove', bookmark_remove_path
end
def test_pagemarks
- with_test_routes do
- get '/pagemark/build'
- assert_equal 'pagemarks#new', @response.body
- assert_equal '/pagemark/build', pagemark_new_path
-
- post '/pagemark/create'
- assert_equal 'pagemarks#create', @response.body
- assert_equal '/pagemark/create', pagemark_path
-
- put '/pagemark/update'
- assert_equal 'pagemarks#update', @response.body
- assert_equal '/pagemark/update', pagemark_update_path
-
- get '/pagemark/remove'
- assert_equal 'pagemarks#destroy', @response.body
- assert_equal '/pagemark/remove', pagemark_remove_path
- end
+ get '/pagemark/build'
+ assert_equal 'pagemarks#new', @response.body
+ assert_equal '/pagemark/build', pagemark_new_path
+
+ post '/pagemark/create'
+ assert_equal 'pagemarks#create', @response.body
+ assert_equal '/pagemark/create', pagemark_path
+
+ put '/pagemark/update'
+ assert_equal 'pagemarks#update', @response.body
+ assert_equal '/pagemark/update', pagemark_update_path
+
+ get '/pagemark/remove'
+ assert_equal 'pagemarks#destroy', @response.body
+ assert_equal '/pagemark/remove', pagemark_remove_path
end
def test_admin
- with_test_routes do
- get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
- assert_equal 'queenbee#index', @response.body
+ get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'queenbee#index', @response.body
- get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'}
- assert_equal 'pass', @response.headers['X-Cascade']
+ get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ assert_equal 'pass', @response.headers['X-Cascade']
- get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'}
- assert_equal 'queenbee#accounts', @response.body
+ get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'queenbee#accounts', @response.body
- get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'}
- assert_equal 'pass', @response.headers['X-Cascade']
+ get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ assert_equal 'pass', @response.headers['X-Cascade']
- get '/admin/passwords', {}, {'REMOTE_ADDR' => '192.168.1.100'}
- assert_equal 'queenbee#passwords', @response.body
+ get '/admin/passwords', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'queenbee#passwords', @response.body
- get '/admin/passwords', {}, {'REMOTE_ADDR' => '10.0.0.100'}
- assert_equal 'pass', @response.headers['X-Cascade']
- end
+ get '/admin/passwords', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ assert_equal 'pass', @response.headers['X-Cascade']
end
def test_global
- with_test_routes do
- get '/global/dashboard'
- assert_equal 'global#dashboard', @response.body
+ get '/global/dashboard'
+ assert_equal 'global#dashboard', @response.body
- get '/global/export'
- assert_equal 'global#export', @response.body
+ get '/global/export'
+ assert_equal 'global#export', @response.body
- get '/global/hide_notice'
- assert_equal 'global#hide_notice', @response.body
+ get '/global/hide_notice'
+ assert_equal 'global#hide_notice', @response.body
- get '/export/123/foo.txt'
- assert_equal 'global#export', @response.body
+ get '/export/123/foo.txt'
+ assert_equal 'global#export', @response.body
- assert_equal '/global/export', export_request_path
- assert_equal '/global/hide_notice', global_hide_notice_path
- assert_equal '/export/123/foo.txt', export_download_path(:id => 123, :file => 'foo.txt')
- end
+ assert_equal '/global/export', export_request_path
+ assert_equal '/global/hide_notice', global_hide_notice_path
+ assert_equal '/export/123/foo.txt', export_download_path(:id => 123, :file => 'foo.txt')
end
def test_local
- with_test_routes do
- get '/local/dashboard'
- assert_equal 'local#dashboard', @response.body
- end
+ get '/local/dashboard'
+ assert_equal 'local#dashboard', @response.body
end
# tests the use of dup in url_for
@@ -844,648 +809,561 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_projects_status
- with_test_routes do
- assert_equal '/projects/status', url_for(:controller => 'projects', :action => 'status', :only_path => true)
- assert_equal '/projects/status.json', url_for(:controller => 'projects', :action => 'status', :format => 'json', :only_path => true)
- end
+ assert_equal '/projects/status', url_for(:controller => 'projects', :action => 'status', :only_path => true)
+ assert_equal '/projects/status.json', url_for(:controller => 'projects', :action => 'status', :format => 'json', :only_path => true)
end
def test_projects
- with_test_routes do
- get '/projects'
- assert_equal 'project#index', @response.body
- assert_equal '/projects', projects_path
+ get '/projects'
+ assert_equal 'project#index', @response.body
+ assert_equal '/projects', projects_path
- post '/projects'
- assert_equal 'project#create', @response.body
+ post '/projects'
+ assert_equal 'project#create', @response.body
- get '/projects.xml'
- assert_equal 'project#index', @response.body
- assert_equal '/projects.xml', projects_path(:format => 'xml')
+ get '/projects.xml'
+ assert_equal 'project#index', @response.body
+ assert_equal '/projects.xml', projects_path(:format => 'xml')
- get '/projects/new'
- assert_equal 'project#new', @response.body
- assert_equal '/projects/new', new_project_path
+ get '/projects/new'
+ assert_equal 'project#new', @response.body
+ assert_equal '/projects/new', new_project_path
- get '/projects/new.xml'
- assert_equal 'project#new', @response.body
- assert_equal '/projects/new.xml', new_project_path(:format => 'xml')
+ get '/projects/new.xml'
+ assert_equal 'project#new', @response.body
+ assert_equal '/projects/new.xml', new_project_path(:format => 'xml')
- get '/projects/1'
- assert_equal 'project#show', @response.body
- assert_equal '/projects/1', project_path(:id => '1')
+ get '/projects/1'
+ assert_equal 'project#show', @response.body
+ assert_equal '/projects/1', project_path(:id => '1')
- get '/projects/1.xml'
- assert_equal 'project#show', @response.body
- assert_equal '/projects/1.xml', project_path(:id => '1', :format => 'xml')
+ get '/projects/1.xml'
+ assert_equal 'project#show', @response.body
+ assert_equal '/projects/1.xml', project_path(:id => '1', :format => 'xml')
- get '/projects/1/edit'
- assert_equal 'project#edit', @response.body
- assert_equal '/projects/1/edit', edit_project_path(:id => '1')
- end
+ get '/projects/1/edit'
+ assert_equal 'project#edit', @response.body
+ assert_equal '/projects/1/edit', edit_project_path(:id => '1')
end
def test_projects_involvements
- with_test_routes do
- get '/projects/1/involvements'
- assert_equal 'involvements#index', @response.body
- assert_equal '/projects/1/involvements', project_involvements_path(:project_id => '1')
+ get '/projects/1/involvements'
+ assert_equal 'involvements#index', @response.body
+ assert_equal '/projects/1/involvements', project_involvements_path(:project_id => '1')
- get '/projects/1/involvements/new'
- assert_equal 'involvements#new', @response.body
- assert_equal '/projects/1/involvements/new', new_project_involvement_path(:project_id => '1')
+ get '/projects/1/involvements/new'
+ assert_equal 'involvements#new', @response.body
+ assert_equal '/projects/1/involvements/new', new_project_involvement_path(:project_id => '1')
- get '/projects/1/involvements/1'
- assert_equal 'involvements#show', @response.body
- assert_equal '/projects/1/involvements/1', project_involvement_path(:project_id => '1', :id => '1')
+ get '/projects/1/involvements/1'
+ assert_equal 'involvements#show', @response.body
+ assert_equal '/projects/1/involvements/1', project_involvement_path(:project_id => '1', :id => '1')
- put '/projects/1/involvements/1'
- assert_equal 'involvements#update', @response.body
+ put '/projects/1/involvements/1'
+ assert_equal 'involvements#update', @response.body
- delete '/projects/1/involvements/1'
- assert_equal 'involvements#destroy', @response.body
+ delete '/projects/1/involvements/1'
+ assert_equal 'involvements#destroy', @response.body
- get '/projects/1/involvements/1/edit'
- assert_equal 'involvements#edit', @response.body
- assert_equal '/projects/1/involvements/1/edit', edit_project_involvement_path(:project_id => '1', :id => '1')
- end
+ get '/projects/1/involvements/1/edit'
+ assert_equal 'involvements#edit', @response.body
+ assert_equal '/projects/1/involvements/1/edit', edit_project_involvement_path(:project_id => '1', :id => '1')
end
def test_projects_attachments
- with_test_routes do
- get '/projects/1/attachments'
- assert_equal 'attachments#index', @response.body
- assert_equal '/projects/1/attachments', project_attachments_path(:project_id => '1')
- end
+ get '/projects/1/attachments'
+ assert_equal 'attachments#index', @response.body
+ assert_equal '/projects/1/attachments', project_attachments_path(:project_id => '1')
end
def test_projects_participants
- with_test_routes do
- get '/projects/1/participants'
- assert_equal 'participants#index', @response.body
- assert_equal '/projects/1/participants', project_participants_path(:project_id => '1')
-
- put '/projects/1/participants/update_all'
- assert_equal 'participants#update_all', @response.body
- assert_equal '/projects/1/participants/update_all', update_all_project_participants_path(:project_id => '1')
- end
+ get '/projects/1/participants'
+ assert_equal 'participants#index', @response.body
+ assert_equal '/projects/1/participants', project_participants_path(:project_id => '1')
+
+ put '/projects/1/participants/update_all'
+ assert_equal 'participants#update_all', @response.body
+ assert_equal '/projects/1/participants/update_all', update_all_project_participants_path(:project_id => '1')
end
def test_projects_companies
- with_test_routes do
- get '/projects/1/companies'
- assert_equal 'companies#index', @response.body
- assert_equal '/projects/1/companies', project_companies_path(:project_id => '1')
-
- get '/projects/1/companies/1/people'
- assert_equal 'people#index', @response.body
- assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1')
-
- get '/projects/1/companies/1/avatar'
- assert_equal 'avatar#show', @response.body
- assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1')
- end
+ get '/projects/1/companies'
+ assert_equal 'companies#index', @response.body
+ assert_equal '/projects/1/companies', project_companies_path(:project_id => '1')
+
+ get '/projects/1/companies/1/people'
+ assert_equal 'people#index', @response.body
+ assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1')
+
+ get '/projects/1/companies/1/avatar'
+ assert_equal 'avatar#show', @response.body
+ assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1')
end
def test_project_manager
- with_test_routes do
- get '/projects/1/manager'
- assert_equal 'managers#show', @response.body
- assert_equal '/projects/1/manager', project_super_manager_path(:project_id => '1')
-
- get '/projects/1/manager/new'
- assert_equal 'managers#new', @response.body
- assert_equal '/projects/1/manager/new', new_project_super_manager_path(:project_id => '1')
-
- post '/projects/1/manager/fire'
- assert_equal 'managers#fire', @response.body
- assert_equal '/projects/1/manager/fire', fire_project_super_manager_path(:project_id => '1')
- end
+ get '/projects/1/manager'
+ assert_equal 'managers#show', @response.body
+ assert_equal '/projects/1/manager', project_super_manager_path(:project_id => '1')
+
+ get '/projects/1/manager/new'
+ assert_equal 'managers#new', @response.body
+ assert_equal '/projects/1/manager/new', new_project_super_manager_path(:project_id => '1')
+
+ post '/projects/1/manager/fire'
+ assert_equal 'managers#fire', @response.body
+ assert_equal '/projects/1/manager/fire', fire_project_super_manager_path(:project_id => '1')
end
def test_project_images
- with_test_routes do
- get '/projects/1/images'
- assert_equal 'images#index', @response.body
- assert_equal '/projects/1/images', project_funny_images_path(:project_id => '1')
-
- get '/projects/1/images/new'
- assert_equal 'images#new', @response.body
- assert_equal '/projects/1/images/new', new_project_funny_image_path(:project_id => '1')
-
- post '/projects/1/images/1/revise'
- assert_equal 'images#revise', @response.body
- assert_equal '/projects/1/images/1/revise', revise_project_funny_image_path(:project_id => '1', :id => '1')
- end
+ get '/projects/1/images'
+ assert_equal 'images#index', @response.body
+ assert_equal '/projects/1/images', project_funny_images_path(:project_id => '1')
+
+ get '/projects/1/images/new'
+ assert_equal 'images#new', @response.body
+ assert_equal '/projects/1/images/new', new_project_funny_image_path(:project_id => '1')
+
+ post '/projects/1/images/1/revise'
+ assert_equal 'images#revise', @response.body
+ assert_equal '/projects/1/images/1/revise', revise_project_funny_image_path(:project_id => '1', :id => '1')
end
def test_projects_people
- with_test_routes do
- get '/projects/1/people'
- assert_equal 'people#index', @response.body
- assert_equal '/projects/1/people', project_people_path(:project_id => '1')
-
- get '/projects/1/people/1'
- assert_equal 'people#show', @response.body
- assert_equal '/projects/1/people/1', project_person_path(:project_id => '1', :id => '1')
-
- get '/projects/1/people/1/7a2dec8/avatar'
- assert_equal 'avatars#show', @response.body
- assert_equal '/projects/1/people/1/7a2dec8/avatar', project_person_avatar_path(:project_id => '1', :person_id => '1', :access_token => '7a2dec8')
-
- put '/projects/1/people/1/accessible_projects'
- assert_equal 'people#accessible_projects', @response.body
- assert_equal '/projects/1/people/1/accessible_projects', accessible_projects_project_person_path(:project_id => '1', :id => '1')
-
- post '/projects/1/people/1/resend'
- assert_equal 'people#resend', @response.body
- assert_equal '/projects/1/people/1/resend', resend_project_person_path(:project_id => '1', :id => '1')
-
- post '/projects/1/people/1/generate_new_password'
- assert_equal 'people#generate_new_password', @response.body
- assert_equal '/projects/1/people/1/generate_new_password', generate_new_password_project_person_path(:project_id => '1', :id => '1')
- end
+ get '/projects/1/people'
+ assert_equal 'people#index', @response.body
+ assert_equal '/projects/1/people', project_people_path(:project_id => '1')
+
+ get '/projects/1/people/1'
+ assert_equal 'people#show', @response.body
+ assert_equal '/projects/1/people/1', project_person_path(:project_id => '1', :id => '1')
+
+ get '/projects/1/people/1/7a2dec8/avatar'
+ assert_equal 'avatars#show', @response.body
+ assert_equal '/projects/1/people/1/7a2dec8/avatar', project_person_avatar_path(:project_id => '1', :person_id => '1', :access_token => '7a2dec8')
+
+ put '/projects/1/people/1/accessible_projects'
+ assert_equal 'people#accessible_projects', @response.body
+ assert_equal '/projects/1/people/1/accessible_projects', accessible_projects_project_person_path(:project_id => '1', :id => '1')
+
+ post '/projects/1/people/1/resend'
+ assert_equal 'people#resend', @response.body
+ assert_equal '/projects/1/people/1/resend', resend_project_person_path(:project_id => '1', :id => '1')
+
+ post '/projects/1/people/1/generate_new_password'
+ assert_equal 'people#generate_new_password', @response.body
+ assert_equal '/projects/1/people/1/generate_new_password', generate_new_password_project_person_path(:project_id => '1', :id => '1')
end
def test_projects_with_resources_path_names
- with_test_routes do
- get '/projects/info_about_correlation_indexes'
- assert_equal 'project#correlation_indexes', @response.body
- assert_equal '/projects/info_about_correlation_indexes', correlation_indexes_projects_path
- end
+ get '/projects/info_about_correlation_indexes'
+ assert_equal 'project#correlation_indexes', @response.body
+ assert_equal '/projects/info_about_correlation_indexes', correlation_indexes_projects_path
end
def test_projects_posts
- with_test_routes do
- get '/projects/1/posts'
- assert_equal 'posts#index', @response.body
- assert_equal '/projects/1/posts', project_posts_path(:project_id => '1')
-
- get '/projects/1/posts/archive'
- assert_equal 'posts#archive', @response.body
- assert_equal '/projects/1/posts/archive', archive_project_posts_path(:project_id => '1')
-
- get '/projects/1/posts/toggle_view'
- assert_equal 'posts#toggle_view', @response.body
- assert_equal '/projects/1/posts/toggle_view', toggle_view_project_posts_path(:project_id => '1')
-
- post '/projects/1/posts/1/preview'
- assert_equal 'posts#preview', @response.body
- assert_equal '/projects/1/posts/1/preview', preview_project_post_path(:project_id => '1', :id => '1')
-
- get '/projects/1/posts/1/subscription'
- assert_equal 'subscriptions#show', @response.body
- assert_equal '/projects/1/posts/1/subscription', project_post_subscription_path(:project_id => '1', :post_id => '1')
-
- get '/projects/1/posts/1/comments'
- assert_equal 'comments#index', @response.body
- assert_equal '/projects/1/posts/1/comments', project_post_comments_path(:project_id => '1', :post_id => '1')
-
- post '/projects/1/posts/1/comments/preview'
- assert_equal 'comments#preview', @response.body
- assert_equal '/projects/1/posts/1/comments/preview', preview_project_post_comments_path(:project_id => '1', :post_id => '1')
- end
+ get '/projects/1/posts'
+ assert_equal 'posts#index', @response.body
+ assert_equal '/projects/1/posts', project_posts_path(:project_id => '1')
+
+ get '/projects/1/posts/archive'
+ assert_equal 'posts#archive', @response.body
+ assert_equal '/projects/1/posts/archive', archive_project_posts_path(:project_id => '1')
+
+ get '/projects/1/posts/toggle_view'
+ assert_equal 'posts#toggle_view', @response.body
+ assert_equal '/projects/1/posts/toggle_view', toggle_view_project_posts_path(:project_id => '1')
+
+ post '/projects/1/posts/1/preview'
+ assert_equal 'posts#preview', @response.body
+ assert_equal '/projects/1/posts/1/preview', preview_project_post_path(:project_id => '1', :id => '1')
+
+ get '/projects/1/posts/1/subscription'
+ assert_equal 'subscriptions#show', @response.body
+ assert_equal '/projects/1/posts/1/subscription', project_post_subscription_path(:project_id => '1', :post_id => '1')
+
+ get '/projects/1/posts/1/comments'
+ assert_equal 'comments#index', @response.body
+ assert_equal '/projects/1/posts/1/comments', project_post_comments_path(:project_id => '1', :post_id => '1')
+
+ post '/projects/1/posts/1/comments/preview'
+ assert_equal 'comments#preview', @response.body
+ assert_equal '/projects/1/posts/1/comments/preview', preview_project_post_comments_path(:project_id => '1', :post_id => '1')
end
def test_replies
- with_test_routes do
- put '/replies/1/answer'
- assert_equal 'replies#mark_as_answer', @response.body
+ put '/replies/1/answer'
+ assert_equal 'replies#mark_as_answer', @response.body
- delete '/replies/1/answer'
- assert_equal 'replies#unmark_as_answer', @response.body
- end
+ delete '/replies/1/answer'
+ assert_equal 'replies#unmark_as_answer', @response.body
end
def test_resource_routes_with_only_and_except
- with_test_routes do
- get '/posts'
- assert_equal 'posts#index', @response.body
- assert_equal '/posts', posts_path
-
- get '/posts/1'
- assert_equal 'posts#show', @response.body
- assert_equal '/posts/1', post_path(:id => 1)
-
- get '/posts/1/comments'
- assert_equal 'comments#index', @response.body
- assert_equal '/posts/1/comments', post_comments_path(:post_id => 1)
-
- post '/posts'
- assert_equal 'pass', @response.headers['X-Cascade']
- put '/posts/1'
- assert_equal 'pass', @response.headers['X-Cascade']
- delete '/posts/1'
- assert_equal 'pass', @response.headers['X-Cascade']
- delete '/posts/1/comments'
- assert_equal 'pass', @response.headers['X-Cascade']
- end
+ get '/posts'
+ assert_equal 'posts#index', @response.body
+ assert_equal '/posts', posts_path
+
+ get '/posts/1'
+ assert_equal 'posts#show', @response.body
+ assert_equal '/posts/1', post_path(:id => 1)
+
+ get '/posts/1/comments'
+ assert_equal 'comments#index', @response.body
+ assert_equal '/posts/1/comments', post_comments_path(:post_id => 1)
+
+ post '/posts'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ put '/posts/1'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ delete '/posts/1'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ delete '/posts/1/comments'
+ assert_equal 'pass', @response.headers['X-Cascade']
end
def test_resource_routes_only_create_update_destroy
- with_test_routes do
- delete '/past'
- assert_equal 'pasts#destroy', @response.body
- assert_equal '/past', past_path
-
- put '/present'
- assert_equal 'presents#update', @response.body
- assert_equal '/present', present_path
-
- post '/future'
- assert_equal 'futures#create', @response.body
- assert_equal '/future', future_path
- end
+ delete '/past'
+ assert_equal 'pasts#destroy', @response.body
+ assert_equal '/past', past_path
+
+ patch '/present'
+ assert_equal 'presents#update', @response.body
+ assert_equal '/present', present_path
+
+ put '/present'
+ assert_equal 'presents#update', @response.body
+ assert_equal '/present', present_path
+
+ post '/future'
+ assert_equal 'futures#create', @response.body
+ assert_equal '/future', future_path
end
def test_resources_routes_only_create_update_destroy
- with_test_routes do
- post '/relationships'
- assert_equal 'relationships#create', @response.body
- assert_equal '/relationships', relationships_path
-
- delete '/relationships/1'
- assert_equal 'relationships#destroy', @response.body
- assert_equal '/relationships/1', relationship_path(1)
-
- put '/friendships/1'
- assert_equal 'friendships#update', @response.body
- assert_equal '/friendships/1', friendship_path(1)
- end
+ post '/relationships'
+ assert_equal 'relationships#create', @response.body
+ assert_equal '/relationships', relationships_path
+
+ delete '/relationships/1'
+ assert_equal 'relationships#destroy', @response.body
+ assert_equal '/relationships/1', relationship_path(1)
+
+ patch '/friendships/1'
+ assert_equal 'friendships#update', @response.body
+ assert_equal '/friendships/1', friendship_path(1)
+
+ put '/friendships/1'
+ assert_equal 'friendships#update', @response.body
+ assert_equal '/friendships/1', friendship_path(1)
end
def test_resource_with_slugs_in_ids
- with_test_routes do
- get '/posts/rails-rocks'
- assert_equal 'posts#show', @response.body
- assert_equal '/posts/rails-rocks', post_path(:id => 'rails-rocks')
- end
+ get '/posts/rails-rocks'
+ assert_equal 'posts#show', @response.body
+ assert_equal '/posts/rails-rocks', post_path(:id => 'rails-rocks')
end
def test_resources_for_uncountable_names
- with_test_routes do
- assert_equal '/sheep', sheep_index_path
- assert_equal '/sheep/1', sheep_path(1)
- assert_equal '/sheep/new', new_sheep_path
- assert_equal '/sheep/1/edit', edit_sheep_path(1)
- assert_equal '/sheep/1/_it', _it_sheep_path(1)
- end
+ assert_equal '/sheep', sheep_index_path
+ assert_equal '/sheep/1', sheep_path(1)
+ assert_equal '/sheep/new', new_sheep_path
+ assert_equal '/sheep/1/edit', edit_sheep_path(1)
+ assert_equal '/sheep/1/_it', _it_sheep_path(1)
end
def test_path_names
- with_test_routes do
- get '/pt/projetos'
- assert_equal 'projects#index', @response.body
- assert_equal '/pt/projetos', pt_projects_path
-
- get '/pt/projetos/1/editar'
- assert_equal 'projects#edit', @response.body
- assert_equal '/pt/projetos/1/editar', edit_pt_project_path(1)
-
- get '/pt/administrador'
- assert_equal 'admins#show', @response.body
- assert_equal '/pt/administrador', pt_admin_path
-
- get '/pt/administrador/novo'
- assert_equal 'admins#new', @response.body
- assert_equal '/pt/administrador/novo', new_pt_admin_path
-
- put '/pt/administrador/ativar'
- assert_equal 'admins#activate', @response.body
- assert_equal '/pt/administrador/ativar', activate_pt_admin_path
- end
+ get '/pt/projetos'
+ assert_equal 'projects#index', @response.body
+ assert_equal '/pt/projetos', pt_projects_path
+
+ get '/pt/projetos/1/editar'
+ assert_equal 'projects#edit', @response.body
+ assert_equal '/pt/projetos/1/editar', edit_pt_project_path(1)
+
+ get '/pt/administrador'
+ assert_equal 'admins#show', @response.body
+ assert_equal '/pt/administrador', pt_admin_path
+
+ get '/pt/administrador/novo'
+ assert_equal 'admins#new', @response.body
+ assert_equal '/pt/administrador/novo', new_pt_admin_path
+
+ put '/pt/administrador/ativar'
+ assert_equal 'admins#activate', @response.body
+ assert_equal '/pt/administrador/ativar', activate_pt_admin_path
end
def test_path_option_override
- with_test_routes do
- get '/pt/projetos/novo/abrir'
- assert_equal 'projects#open', @response.body
- assert_equal '/pt/projetos/novo/abrir', open_new_pt_project_path
-
- put '/pt/projetos/1/fechar'
- assert_equal 'projects#close', @response.body
- assert_equal '/pt/projetos/1/fechar', close_pt_project_path(1)
- end
+ get '/pt/projetos/novo/abrir'
+ assert_equal 'projects#open', @response.body
+ assert_equal '/pt/projetos/novo/abrir', open_new_pt_project_path
+
+ put '/pt/projetos/1/fechar'
+ assert_equal 'projects#close', @response.body
+ assert_equal '/pt/projetos/1/fechar', close_pt_project_path(1)
end
def test_sprockets
- with_test_routes do
- get '/sprockets.js'
- assert_equal 'javascripts', @response.body
- end
+ get '/sprockets.js'
+ assert_equal 'javascripts', @response.body
end
def test_update_person_route
- with_test_routes do
- get '/people/1/update'
- assert_equal 'people#update', @response.body
+ get '/people/1/update'
+ assert_equal 'people#update', @response.body
- assert_equal '/people/1/update', update_person_path(:id => 1)
- end
+ assert_equal '/people/1/update', update_person_path(:id => 1)
end
def test_update_project_person
- with_test_routes do
- get '/projects/1/people/2/update'
- assert_equal 'people#update', @response.body
+ get '/projects/1/people/2/update'
+ assert_equal 'people#update', @response.body
- assert_equal '/projects/1/people/2/update', update_project_person_path(:project_id => 1, :id => 2)
- end
+ assert_equal '/projects/1/people/2/update', update_project_person_path(:project_id => 1, :id => 2)
end
def test_forum_products
- with_test_routes do
- get '/forum'
- assert_equal 'forum/products#index', @response.body
- assert_equal '/forum', forum_products_path
-
- get '/forum/basecamp'
- assert_equal 'forum/products#show', @response.body
- assert_equal '/forum/basecamp', forum_product_path(:id => 'basecamp')
-
- get '/forum/basecamp/questions'
- assert_equal 'forum/questions#index', @response.body
- assert_equal '/forum/basecamp/questions', forum_product_questions_path(:product_id => 'basecamp')
-
- get '/forum/basecamp/questions/1'
- assert_equal 'forum/questions#show', @response.body
- assert_equal '/forum/basecamp/questions/1', forum_product_question_path(:product_id => 'basecamp', :id => 1)
- end
+ get '/forum'
+ assert_equal 'forum/products#index', @response.body
+ assert_equal '/forum', forum_products_path
+
+ get '/forum/basecamp'
+ assert_equal 'forum/products#show', @response.body
+ assert_equal '/forum/basecamp', forum_product_path(:id => 'basecamp')
+
+ get '/forum/basecamp/questions'
+ assert_equal 'forum/questions#index', @response.body
+ assert_equal '/forum/basecamp/questions', forum_product_questions_path(:product_id => 'basecamp')
+
+ get '/forum/basecamp/questions/1'
+ assert_equal 'forum/questions#show', @response.body
+ assert_equal '/forum/basecamp/questions/1', forum_product_question_path(:product_id => 'basecamp', :id => 1)
end
def test_articles_perma
- with_test_routes do
- get '/articles/2009/08/18/rails-3'
- assert_equal 'articles#show', @response.body
+ get '/articles/2009/08/18/rails-3'
+ assert_equal 'articles#show', @response.body
- assert_equal '/articles/2009/8/18/rails-3', article_path(:year => 2009, :month => 8, :day => 18, :title => 'rails-3')
- end
+ assert_equal '/articles/2009/8/18/rails-3', article_path(:year => 2009, :month => 8, :day => 18, :title => 'rails-3')
end
def test_account_namespace
- with_test_routes do
- get '/account/subscription'
- assert_equal 'account/subscriptions#show', @response.body
- assert_equal '/account/subscription', account_subscription_path
-
- get '/account/credit'
- assert_equal 'account/credits#show', @response.body
- assert_equal '/account/credit', account_credit_path
-
- get '/account/credit_card'
- assert_equal 'account/credit_cards#show', @response.body
- assert_equal '/account/credit_card', account_credit_card_path
- end
+ get '/account/subscription'
+ assert_equal 'account/subscriptions#show', @response.body
+ assert_equal '/account/subscription', account_subscription_path
+
+ get '/account/credit'
+ assert_equal 'account/credits#show', @response.body
+ assert_equal '/account/credit', account_credit_path
+
+ get '/account/credit_card'
+ assert_equal 'account/credit_cards#show', @response.body
+ assert_equal '/account/credit_card', account_credit_card_path
end
def test_nested_namespace
- with_test_routes do
- get '/account/admin/subscription'
- assert_equal 'account/admin/subscriptions#show', @response.body
- assert_equal '/account/admin/subscription', account_admin_subscription_path
- end
+ get '/account/admin/subscription'
+ assert_equal 'account/admin/subscriptions#show', @response.body
+ assert_equal '/account/admin/subscription', account_admin_subscription_path
end
def test_namespace_nested_in_resources
- with_test_routes do
- get '/clients/1/google/account'
- assert_equal '/clients/1/google/account', client_google_account_path(1)
- assert_equal 'google/accounts#show', @response.body
-
- get '/clients/1/google/account/secret/info'
- assert_equal '/clients/1/google/account/secret/info', client_google_account_secret_info_path(1)
- assert_equal 'google/secret/infos#show', @response.body
- end
+ get '/clients/1/google/account'
+ assert_equal '/clients/1/google/account', client_google_account_path(1)
+ assert_equal 'google/accounts#show', @response.body
+
+ get '/clients/1/google/account/secret/info'
+ assert_equal '/clients/1/google/account/secret/info', client_google_account_secret_info_path(1)
+ assert_equal 'google/secret/infos#show', @response.body
end
def test_namespace_with_options
- with_test_routes do
- get '/usuarios'
- assert_equal '/usuarios', users_root_path
- assert_equal 'users/home#index', @response.body
- end
+ get '/usuarios'
+ assert_equal '/usuarios', users_root_path
+ assert_equal 'users/home#index', @response.body
end
def test_articles_with_id
- with_test_routes do
- get '/articles/rails/1'
- assert_equal 'articles#with_id', @response.body
+ get '/articles/rails/1'
+ assert_equal 'articles#with_id', @response.body
- get '/articles/123/1'
- assert_equal 'pass', @response.headers['X-Cascade']
+ get '/articles/123/1'
+ assert_equal 'pass', @response.headers['X-Cascade']
- assert_equal '/articles/rails/1', article_with_title_path(:title => 'rails', :id => 1)
- end
+ assert_equal '/articles/rails/1', article_with_title_path(:title => 'rails', :id => 1)
end
def test_access_token_rooms
- with_test_routes do
- get '/12345/rooms'
- assert_equal 'rooms#index', @response.body
+ get '/12345/rooms'
+ assert_equal 'rooms#index', @response.body
- get '/12345/rooms/1'
- assert_equal 'rooms#show', @response.body
+ get '/12345/rooms/1'
+ assert_equal 'rooms#show', @response.body
- get '/12345/rooms/1/edit'
- assert_equal 'rooms#edit', @response.body
- end
+ get '/12345/rooms/1/edit'
+ assert_equal 'rooms#edit', @response.body
end
def test_root
- with_test_routes do
- assert_equal '/', root_path
- get '/'
- assert_equal 'projects#index', @response.body
- end
+ assert_equal '/', root_path
+ get '/'
+ assert_equal 'projects#index', @response.body
end
def test_index
- with_test_routes do
- assert_equal '/info', info_path
- get '/info'
- assert_equal 'projects#info', @response.body
- end
+ assert_equal '/info', info_path
+ get '/info'
+ assert_equal 'projects#info', @response.body
end
def test_match_shorthand_with_no_scope
- with_test_routes do
- assert_equal '/account/overview', account_overview_path
- get '/account/overview'
- assert_equal 'account#overview', @response.body
- end
+ assert_equal '/account/overview', account_overview_path
+ get '/account/overview'
+ assert_equal 'account#overview', @response.body
end
def test_match_shorthand_inside_namespace
- with_test_routes do
- assert_equal '/account/shorthand', account_shorthand_path
- get '/account/shorthand'
- assert_equal 'account#shorthand', @response.body
- end
+ assert_equal '/account/shorthand', account_shorthand_path
+ get '/account/shorthand'
+ assert_equal 'account#shorthand', @response.body
end
def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper
- with_test_routes do
- assert_equal '/replies', replies_path
- end
+ assert_equal '/replies', replies_path
end
def test_scoped_controller_with_namespace_and_action
- with_test_routes do
- assert_equal '/account/twitter/callback', account_callback_path("twitter")
- get '/account/twitter/callback'
- assert_equal 'account/callbacks#twitter', @response.body
+ assert_equal '/account/twitter/callback', account_callback_path("twitter")
+ get '/account/twitter/callback'
+ assert_equal 'account/callbacks#twitter', @response.body
- get '/account/whatever/callback'
- assert_equal 'Not Found', @response.body
- end
+ get '/account/whatever/callback'
+ assert_equal 'Not Found', @response.body
end
def test_convention_match_nested_and_with_leading_slash
- with_test_routes do
- assert_equal '/account/nested/overview', account_nested_overview_path
- get '/account/nested/overview'
- assert_equal 'account/nested#overview', @response.body
- end
+ assert_equal '/account/nested/overview', account_nested_overview_path
+ get '/account/nested/overview'
+ assert_equal 'account/nested#overview', @response.body
end
def test_convention_with_explicit_end
- with_test_routes do
- get '/sign_in'
- assert_equal 'sessions#new', @response.body
- assert_equal '/sign_in', sign_in_path
- end
+ get '/sign_in'
+ assert_equal 'sessions#new', @response.body
+ assert_equal '/sign_in', sign_in_path
end
def test_redirect_with_complete_url_and_status
- with_test_routes do
- get '/account/google'
- verify_redirect 'http://www.google.com/', 302
- end
+ get '/account/google'
+ verify_redirect 'http://www.google.com/', 302
end
def test_redirect_with_port
previous_host, self.host = self.host, 'www.example.com:3000'
- with_test_routes do
- get '/account/login'
- verify_redirect 'http://www.example.com:3000/login'
- end
+
+ get '/account/login'
+ verify_redirect 'http://www.example.com:3000/login'
ensure
self.host = previous_host
end
def test_normalize_namespaced_matches
- with_test_routes do
- assert_equal '/account/description', account_description_path
+ assert_equal '/account/description', account_description_path
- get '/account/description'
- assert_equal 'account#description', @response.body
- end
+ get '/account/description'
+ assert_equal 'account#description', @response.body
end
def test_namespaced_roots
- with_test_routes do
- assert_equal '/account', account_root_path
- get '/account'
- assert_equal 'account/account#index', @response.body
- end
+ assert_equal '/account', account_root_path
+ get '/account'
+ assert_equal 'account/account#index', @response.body
end
def test_optional_scoped_root
- with_test_routes do
- assert_equal '/en', root_path("en")
- get '/en'
- assert_equal 'projects#index', @response.body
- end
+ assert_equal '/en', root_path("en")
+ get '/en'
+ assert_equal 'projects#index', @response.body
end
def test_optional_scoped_path
- with_test_routes do
- assert_equal '/en/descriptions', descriptions_path("en")
- assert_equal '/descriptions', descriptions_path(nil)
- assert_equal '/en/descriptions/1', description_path("en", 1)
- assert_equal '/descriptions/1', description_path(nil, 1)
+ assert_equal '/en/descriptions', descriptions_path("en")
+ assert_equal '/descriptions', descriptions_path(nil)
+ assert_equal '/en/descriptions/1', description_path("en", 1)
+ assert_equal '/descriptions/1', description_path(nil, 1)
- get '/en/descriptions'
- assert_equal 'descriptions#index', @response.body
+ get '/en/descriptions'
+ assert_equal 'descriptions#index', @response.body
- get '/descriptions'
- assert_equal 'descriptions#index', @response.body
+ get '/descriptions'
+ assert_equal 'descriptions#index', @response.body
- get '/en/descriptions/1'
- assert_equal 'descriptions#show', @response.body
+ get '/en/descriptions/1'
+ assert_equal 'descriptions#show', @response.body
- get '/descriptions/1'
- assert_equal 'descriptions#show', @response.body
- end
+ get '/descriptions/1'
+ assert_equal 'descriptions#show', @response.body
end
def test_nested_optional_scoped_path
- with_test_routes do
- assert_equal '/admin/en/descriptions', admin_descriptions_path("en")
- assert_equal '/admin/descriptions', admin_descriptions_path(nil)
- assert_equal '/admin/en/descriptions/1', admin_description_path("en", 1)
- assert_equal '/admin/descriptions/1', admin_description_path(nil, 1)
+ assert_equal '/admin/en/descriptions', admin_descriptions_path("en")
+ assert_equal '/admin/descriptions', admin_descriptions_path(nil)
+ assert_equal '/admin/en/descriptions/1', admin_description_path("en", 1)
+ assert_equal '/admin/descriptions/1', admin_description_path(nil, 1)
- get '/admin/en/descriptions'
- assert_equal 'admin/descriptions#index', @response.body
+ get '/admin/en/descriptions'
+ assert_equal 'admin/descriptions#index', @response.body
- get '/admin/descriptions'
- assert_equal 'admin/descriptions#index', @response.body
+ get '/admin/descriptions'
+ assert_equal 'admin/descriptions#index', @response.body
- get '/admin/en/descriptions/1'
- assert_equal 'admin/descriptions#show', @response.body
+ get '/admin/en/descriptions/1'
+ assert_equal 'admin/descriptions#show', @response.body
- get '/admin/descriptions/1'
- assert_equal 'admin/descriptions#show', @response.body
- end
+ get '/admin/descriptions/1'
+ assert_equal 'admin/descriptions#show', @response.body
end
def test_nested_optional_path_shorthand
- with_test_routes do
- get '/registrations/new'
- assert_nil @request.params[:locale]
+ get '/registrations/new'
+ assert_nil @request.params[:locale]
- get '/en/registrations/new'
- assert_equal 'en', @request.params[:locale]
- end
+ get '/en/registrations/new'
+ assert_equal 'en', @request.params[:locale]
end
def test_default_params
- with_test_routes do
- get '/inline_pages'
- assert_equal 'home', @request.params[:id]
+ get '/inline_pages'
+ assert_equal 'home', @request.params[:id]
- get '/default_pages'
- assert_equal 'home', @request.params[:id]
+ get '/default_pages'
+ assert_equal 'home', @request.params[:id]
- get '/scoped_pages'
- assert_equal 'home', @request.params[:id]
- end
+ get '/scoped_pages'
+ assert_equal 'home', @request.params[:id]
end
def test_resource_constraints
- with_test_routes do
- get '/products/1'
- assert_equal 'pass', @response.headers['X-Cascade']
- get '/products'
- assert_equal 'products#root', @response.body
- get '/products/favorite'
- assert_equal 'products#favorite', @response.body
- get '/products/0001'
- assert_equal 'products#show', @response.body
-
- get '/products/1/images'
- assert_equal 'pass', @response.headers['X-Cascade']
- get '/products/0001/images'
- assert_equal 'images#index', @response.body
- get '/products/0001/images/0001'
- assert_equal 'images#show', @response.body
-
- get '/dashboard', {}, {'REMOTE_ADDR' => '10.0.0.100'}
- assert_equal 'pass', @response.headers['X-Cascade']
- get '/dashboard', {}, {'REMOTE_ADDR' => '192.168.1.100'}
- assert_equal 'dashboards#show', @response.body
- end
+ get '/products/1'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ get '/products'
+ assert_equal 'products#root', @response.body
+ get '/products/favorite'
+ assert_equal 'products#favorite', @response.body
+ get '/products/0001'
+ assert_equal 'products#show', @response.body
+
+ get '/products/1/images'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ get '/products/0001/images'
+ assert_equal 'images#index', @response.body
+ get '/products/0001/images/0001'
+ assert_equal 'images#show', @response.body
+
+ get '/dashboard', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ assert_equal 'pass', @response.headers['X-Cascade']
+ get '/dashboard', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'dashboards#show', @response.body
end
def test_root_works_in_the_resources_scope
@@ -1495,731 +1373,651 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_module_scope
- with_test_routes do
- get '/token'
- assert_equal 'api/tokens#show', @response.body
- assert_equal '/token', token_path
- end
+ get '/token'
+ assert_equal 'api/tokens#show', @response.body
+ assert_equal '/token', token_path
end
def test_path_scope
- with_test_routes do
- get '/api/me'
- assert_equal 'mes#show', @response.body
- assert_equal '/api/me', me_path
+ get '/api/me'
+ assert_equal 'mes#show', @response.body
+ assert_equal '/api/me', me_path
- get '/api'
- assert_equal 'mes#index', @response.body
- end
+ get '/api'
+ assert_equal 'mes#index', @response.body
end
def test_url_generator_for_generic_route
- with_test_routes do
- get 'whatever/foo/bar'
- assert_equal 'foo#bar', @response.body
+ get 'whatever/foo/bar'
+ assert_equal 'foo#bar', @response.body
- assert_equal 'http://www.example.com/whatever/foo/bar/1',
- url_for(:controller => "foo", :action => "bar", :id => 1)
- end
+ assert_equal 'http://www.example.com/whatever/foo/bar/1',
+ url_for(:controller => "foo", :action => "bar", :id => 1)
end
def test_url_generator_for_namespaced_generic_route
- with_test_routes do
- get 'whatever/foo/bar/show'
- assert_equal 'foo/bar#show', @response.body
+ get 'whatever/foo/bar/show'
+ assert_equal 'foo/bar#show', @response.body
- get 'whatever/foo/bar/show/1'
- assert_equal 'foo/bar#show', @response.body
+ get 'whatever/foo/bar/show/1'
+ assert_equal 'foo/bar#show', @response.body
- assert_equal 'http://www.example.com/whatever/foo/bar/show',
- url_for(:controller => "foo/bar", :action => "show")
+ assert_equal 'http://www.example.com/whatever/foo/bar/show',
+ url_for(:controller => "foo/bar", :action => "show")
- assert_equal 'http://www.example.com/whatever/foo/bar/show/1',
- url_for(:controller => "foo/bar", :action => "show", :id => '1')
- end
+ assert_equal 'http://www.example.com/whatever/foo/bar/show/1',
+ url_for(:controller => "foo/bar", :action => "show", :id => '1')
end
def test_assert_recognizes_account_overview
- with_test_routes do
- assert_recognizes({:controller => "account", :action => "overview"}, "/account/overview")
- end
+ assert_recognizes({:controller => "account", :action => "overview"}, "/account/overview")
end
def test_resource_new_actions
- with_test_routes do
- assert_equal '/replies/new/preview', preview_new_reply_path
- assert_equal '/pt/projetos/novo/preview', preview_new_pt_project_path
- assert_equal '/pt/administrador/novo/preview', preview_new_pt_admin_path
- assert_equal '/pt/products/novo/preview', preview_new_pt_product_path
- assert_equal '/profile/new/preview', preview_new_profile_path
+ assert_equal '/replies/new/preview', preview_new_reply_path
+ assert_equal '/pt/projetos/novo/preview', preview_new_pt_project_path
+ assert_equal '/pt/administrador/novo/preview', preview_new_pt_admin_path
+ assert_equal '/pt/products/novo/preview', preview_new_pt_product_path
+ assert_equal '/profile/new/preview', preview_new_profile_path
- post '/replies/new/preview'
- assert_equal 'replies#preview', @response.body
+ post '/replies/new/preview'
+ assert_equal 'replies#preview', @response.body
- post '/pt/projetos/novo/preview'
- assert_equal 'projects#preview', @response.body
+ post '/pt/projetos/novo/preview'
+ assert_equal 'projects#preview', @response.body
- post '/pt/administrador/novo/preview'
- assert_equal 'admins#preview', @response.body
+ post '/pt/administrador/novo/preview'
+ assert_equal 'admins#preview', @response.body
- post '/pt/products/novo/preview'
- assert_equal 'products#preview', @response.body
+ post '/pt/products/novo/preview'
+ assert_equal 'products#preview', @response.body
- post '/profile/new/preview'
- assert_equal 'profiles#preview', @response.body
- end
+ post '/profile/new/preview'
+ assert_equal 'profiles#preview', @response.body
end
def test_resource_merges_options_from_scope
- with_test_routes do
- assert_raise(NameError) { new_account_path }
+ assert_raise(NameError) { new_account_path }
- get '/account/new'
- assert_equal 404, status
- end
+ get '/account/new'
+ assert_equal 404, status
end
def test_resources_merges_options_from_scope
- with_test_routes do
- assert_raise(NoMethodError) { edit_product_path('1') }
+ assert_raise(NoMethodError) { edit_product_path('1') }
- get '/products/1/edit'
- assert_equal 404, status
+ get '/products/1/edit'
+ assert_equal 404, status
- assert_raise(NoMethodError) { edit_product_image_path('1', '2') }
+ assert_raise(NoMethodError) { edit_product_image_path('1', '2') }
- post '/products/1/images/2/edit'
- assert_equal 404, status
- end
+ post '/products/1/images/2/edit'
+ assert_equal 404, status
end
def test_shallow_nested_resources
- with_test_routes do
-
- get '/api/teams'
- assert_equal 'api/teams#index', @response.body
- assert_equal '/api/teams', api_teams_path
+ get '/api/teams'
+ assert_equal 'api/teams#index', @response.body
+ assert_equal '/api/teams', api_teams_path
- get '/api/teams/new'
- assert_equal 'api/teams#new', @response.body
- assert_equal '/api/teams/new', new_api_team_path
+ get '/api/teams/new'
+ assert_equal 'api/teams#new', @response.body
+ assert_equal '/api/teams/new', new_api_team_path
- get '/api/teams/1'
- assert_equal 'api/teams#show', @response.body
- assert_equal '/api/teams/1', api_team_path(:id => '1')
+ get '/api/teams/1'
+ assert_equal 'api/teams#show', @response.body
+ assert_equal '/api/teams/1', api_team_path(:id => '1')
- get '/api/teams/1/edit'
- assert_equal 'api/teams#edit', @response.body
- assert_equal '/api/teams/1/edit', edit_api_team_path(:id => '1')
+ get '/api/teams/1/edit'
+ assert_equal 'api/teams#edit', @response.body
+ assert_equal '/api/teams/1/edit', edit_api_team_path(:id => '1')
- get '/api/teams/1/players'
- assert_equal 'api/players#index', @response.body
- assert_equal '/api/teams/1/players', api_team_players_path(:team_id => '1')
+ get '/api/teams/1/players'
+ assert_equal 'api/players#index', @response.body
+ assert_equal '/api/teams/1/players', api_team_players_path(:team_id => '1')
- get '/api/teams/1/players/new'
- assert_equal 'api/players#new', @response.body
- assert_equal '/api/teams/1/players/new', new_api_team_player_path(:team_id => '1')
+ get '/api/teams/1/players/new'
+ assert_equal 'api/players#new', @response.body
+ assert_equal '/api/teams/1/players/new', new_api_team_player_path(:team_id => '1')
- get '/api/players/2'
- assert_equal 'api/players#show', @response.body
- assert_equal '/api/players/2', api_player_path(:id => '2')
+ get '/api/players/2'
+ assert_equal 'api/players#show', @response.body
+ assert_equal '/api/players/2', api_player_path(:id => '2')
- get '/api/players/2/edit'
- assert_equal 'api/players#edit', @response.body
- assert_equal '/api/players/2/edit', edit_api_player_path(:id => '2')
+ get '/api/players/2/edit'
+ assert_equal 'api/players#edit', @response.body
+ assert_equal '/api/players/2/edit', edit_api_player_path(:id => '2')
- get '/api/teams/1/captain'
- assert_equal 'api/captains#show', @response.body
- assert_equal '/api/teams/1/captain', api_team_captain_path(:team_id => '1')
+ get '/api/teams/1/captain'
+ assert_equal 'api/captains#show', @response.body
+ assert_equal '/api/teams/1/captain', api_team_captain_path(:team_id => '1')
- get '/api/teams/1/captain/new'
- assert_equal 'api/captains#new', @response.body
- assert_equal '/api/teams/1/captain/new', new_api_team_captain_path(:team_id => '1')
+ get '/api/teams/1/captain/new'
+ assert_equal 'api/captains#new', @response.body
+ assert_equal '/api/teams/1/captain/new', new_api_team_captain_path(:team_id => '1')
- get '/api/teams/1/captain/edit'
- assert_equal 'api/captains#edit', @response.body
- assert_equal '/api/teams/1/captain/edit', edit_api_team_captain_path(:team_id => '1')
+ get '/api/teams/1/captain/edit'
+ assert_equal 'api/captains#edit', @response.body
+ assert_equal '/api/teams/1/captain/edit', edit_api_team_captain_path(:team_id => '1')
- get '/threads'
- assert_equal 'threads#index', @response.body
- assert_equal '/threads', threads_path
+ get '/threads'
+ assert_equal 'threads#index', @response.body
+ assert_equal '/threads', threads_path
- get '/threads/new'
- assert_equal 'threads#new', @response.body
- assert_equal '/threads/new', new_thread_path
+ get '/threads/new'
+ assert_equal 'threads#new', @response.body
+ assert_equal '/threads/new', new_thread_path
- get '/threads/1'
- assert_equal 'threads#show', @response.body
- assert_equal '/threads/1', thread_path(:id => '1')
+ get '/threads/1'
+ assert_equal 'threads#show', @response.body
+ assert_equal '/threads/1', thread_path(:id => '1')
- get '/threads/1/edit'
- assert_equal 'threads#edit', @response.body
- assert_equal '/threads/1/edit', edit_thread_path(:id => '1')
+ get '/threads/1/edit'
+ assert_equal 'threads#edit', @response.body
+ assert_equal '/threads/1/edit', edit_thread_path(:id => '1')
- get '/threads/1/owner'
- assert_equal 'owners#show', @response.body
- assert_equal '/threads/1/owner', thread_owner_path(:thread_id => '1')
+ get '/threads/1/owner'
+ assert_equal 'owners#show', @response.body
+ assert_equal '/threads/1/owner', thread_owner_path(:thread_id => '1')
- get '/threads/1/messages'
- assert_equal 'messages#index', @response.body
- assert_equal '/threads/1/messages', thread_messages_path(:thread_id => '1')
+ get '/threads/1/messages'
+ assert_equal 'messages#index', @response.body
+ assert_equal '/threads/1/messages', thread_messages_path(:thread_id => '1')
- get '/threads/1/messages/new'
- assert_equal 'messages#new', @response.body
- assert_equal '/threads/1/messages/new', new_thread_message_path(:thread_id => '1')
+ get '/threads/1/messages/new'
+ assert_equal 'messages#new', @response.body
+ assert_equal '/threads/1/messages/new', new_thread_message_path(:thread_id => '1')
- get '/messages/2'
- assert_equal 'messages#show', @response.body
- assert_equal '/messages/2', message_path(:id => '2')
+ get '/messages/2'
+ assert_equal 'messages#show', @response.body
+ assert_equal '/messages/2', message_path(:id => '2')
- get '/messages/2/edit'
- assert_equal 'messages#edit', @response.body
- assert_equal '/messages/2/edit', edit_message_path(:id => '2')
+ get '/messages/2/edit'
+ assert_equal 'messages#edit', @response.body
+ assert_equal '/messages/2/edit', edit_message_path(:id => '2')
- get '/messages/2/comments'
- assert_equal 'comments#index', @response.body
- assert_equal '/messages/2/comments', message_comments_path(:message_id => '2')
+ get '/messages/2/comments'
+ assert_equal 'comments#index', @response.body
+ assert_equal '/messages/2/comments', message_comments_path(:message_id => '2')
- get '/messages/2/comments/new'
- assert_equal 'comments#new', @response.body
- assert_equal '/messages/2/comments/new', new_message_comment_path(:message_id => '2')
+ get '/messages/2/comments/new'
+ assert_equal 'comments#new', @response.body
+ assert_equal '/messages/2/comments/new', new_message_comment_path(:message_id => '2')
- get '/comments/3'
- assert_equal 'comments#show', @response.body
- assert_equal '/comments/3', comment_path(:id => '3')
+ get '/comments/3'
+ assert_equal 'comments#show', @response.body
+ assert_equal '/comments/3', comment_path(:id => '3')
- get '/comments/3/edit'
- assert_equal 'comments#edit', @response.body
- assert_equal '/comments/3/edit', edit_comment_path(:id => '3')
+ get '/comments/3/edit'
+ assert_equal 'comments#edit', @response.body
+ assert_equal '/comments/3/edit', edit_comment_path(:id => '3')
- post '/comments/3/preview'
- assert_equal 'comments#preview', @response.body
- assert_equal '/comments/3/preview', preview_comment_path(:id => '3')
- end
+ post '/comments/3/preview'
+ assert_equal 'comments#preview', @response.body
+ assert_equal '/comments/3/preview', preview_comment_path(:id => '3')
end
def test_shallow_nested_resources_within_scope
- with_test_routes do
-
- get '/hello/notes/1/trackbacks'
- assert_equal 'trackbacks#index', @response.body
- assert_equal '/hello/notes/1/trackbacks', note_trackbacks_path(:note_id => 1)
+ get '/hello/notes/1/trackbacks'
+ assert_equal 'trackbacks#index', @response.body
+ assert_equal '/hello/notes/1/trackbacks', note_trackbacks_path(:note_id => 1)
- get '/hello/notes/1/edit'
- assert_equal 'notes#edit', @response.body
- assert_equal '/hello/notes/1/edit', edit_note_path(:id => '1')
+ get '/hello/notes/1/edit'
+ assert_equal 'notes#edit', @response.body
+ assert_equal '/hello/notes/1/edit', edit_note_path(:id => '1')
- get '/hello/notes/1/trackbacks/new'
- assert_equal 'trackbacks#new', @response.body
- assert_equal '/hello/notes/1/trackbacks/new', new_note_trackback_path(:note_id => 1)
+ get '/hello/notes/1/trackbacks/new'
+ assert_equal 'trackbacks#new', @response.body
+ assert_equal '/hello/notes/1/trackbacks/new', new_note_trackback_path(:note_id => 1)
- get '/hello/trackbacks/1'
- assert_equal 'trackbacks#show', @response.body
- assert_equal '/hello/trackbacks/1', trackback_path(:id => '1')
+ get '/hello/trackbacks/1'
+ assert_equal 'trackbacks#show', @response.body
+ assert_equal '/hello/trackbacks/1', trackback_path(:id => '1')
- get '/hello/trackbacks/1/edit'
- assert_equal 'trackbacks#edit', @response.body
- assert_equal '/hello/trackbacks/1/edit', edit_trackback_path(:id => '1')
+ get '/hello/trackbacks/1/edit'
+ assert_equal 'trackbacks#edit', @response.body
+ assert_equal '/hello/trackbacks/1/edit', edit_trackback_path(:id => '1')
- put '/hello/trackbacks/1'
- assert_equal 'trackbacks#update', @response.body
+ put '/hello/trackbacks/1'
+ assert_equal 'trackbacks#update', @response.body
- post '/hello/notes/1/trackbacks'
- assert_equal 'trackbacks#create', @response.body
+ post '/hello/notes/1/trackbacks'
+ assert_equal 'trackbacks#create', @response.body
- delete '/hello/trackbacks/1'
- assert_equal 'trackbacks#destroy', @response.body
+ delete '/hello/trackbacks/1'
+ assert_equal 'trackbacks#destroy', @response.body
- get '/hello/notes'
- assert_equal 'notes#index', @response.body
+ get '/hello/notes'
+ assert_equal 'notes#index', @response.body
- post '/hello/notes'
- assert_equal 'notes#create', @response.body
+ post '/hello/notes'
+ assert_equal 'notes#create', @response.body
- get '/hello/notes/new'
- assert_equal 'notes#new', @response.body
- assert_equal '/hello/notes/new', new_note_path
+ get '/hello/notes/new'
+ assert_equal 'notes#new', @response.body
+ assert_equal '/hello/notes/new', new_note_path
- get '/hello/notes/1'
- assert_equal 'notes#show', @response.body
- assert_equal '/hello/notes/1', note_path(:id => 1)
+ get '/hello/notes/1'
+ assert_equal 'notes#show', @response.body
+ assert_equal '/hello/notes/1', note_path(:id => 1)
- put '/hello/notes/1'
- assert_equal 'notes#update', @response.body
+ put '/hello/notes/1'
+ assert_equal 'notes#update', @response.body
- delete '/hello/notes/1'
- assert_equal 'notes#destroy', @response.body
- end
+ delete '/hello/notes/1'
+ assert_equal 'notes#destroy', @response.body
end
def test_custom_resource_routes_are_scoped
- with_test_routes do
- assert_equal '/customers/recent', recent_customers_path
- assert_equal '/customers/1/profile', profile_customer_path(:id => '1')
- assert_equal '/customers/1/secret/profile', secret_profile_customer_path(:id => '1')
- assert_equal '/customers/new/preview', another_preview_new_customer_path
- assert_equal '/customers/1/avatar/thumbnail.jpg', thumbnail_customer_avatar_path(:customer_id => '1', :format => :jpg)
- assert_equal '/customers/1/invoices/outstanding', outstanding_customer_invoices_path(:customer_id => '1')
- assert_equal '/customers/1/invoices/2/print', print_customer_invoice_path(:customer_id => '1', :id => '2')
- assert_equal '/customers/1/invoices/new/preview', preview_new_customer_invoice_path(:customer_id => '1')
- assert_equal '/customers/1/notes/new/preview', preview_new_customer_note_path(:customer_id => '1')
- assert_equal '/notes/1/print', print_note_path(:id => '1')
- assert_equal '/api/customers/recent', recent_api_customers_path
- assert_equal '/api/customers/1/profile', profile_api_customer_path(:id => '1')
- assert_equal '/api/customers/new/preview', preview_new_api_customer_path
-
- get '/customers/1/invoices/overdue'
- assert_equal 'invoices#overdue', @response.body
-
- get '/customers/1/secret/profile'
- assert_equal 'customers#secret', @response.body
- end
+ assert_equal '/customers/recent', recent_customers_path
+ assert_equal '/customers/1/profile', profile_customer_path(:id => '1')
+ assert_equal '/customers/1/secret/profile', secret_profile_customer_path(:id => '1')
+ assert_equal '/customers/new/preview', another_preview_new_customer_path
+ assert_equal '/customers/1/avatar/thumbnail.jpg', thumbnail_customer_avatar_path(:customer_id => '1', :format => :jpg)
+ assert_equal '/customers/1/invoices/outstanding', outstanding_customer_invoices_path(:customer_id => '1')
+ assert_equal '/customers/1/invoices/2/print', print_customer_invoice_path(:customer_id => '1', :id => '2')
+ assert_equal '/customers/1/invoices/new/preview', preview_new_customer_invoice_path(:customer_id => '1')
+ assert_equal '/customers/1/notes/new/preview', preview_new_customer_note_path(:customer_id => '1')
+ assert_equal '/notes/1/print', print_note_path(:id => '1')
+ assert_equal '/api/customers/recent', recent_api_customers_path
+ assert_equal '/api/customers/1/profile', profile_api_customer_path(:id => '1')
+ assert_equal '/api/customers/new/preview', preview_new_api_customer_path
+
+ get '/customers/1/invoices/overdue'
+ assert_equal 'invoices#overdue', @response.body
+
+ get '/customers/1/secret/profile'
+ assert_equal 'customers#secret', @response.body
end
def test_shallow_nested_routes_ignore_module
- with_test_routes do
- get '/errors/1/notices'
- assert_equal 'api/notices#index', @response.body
- assert_equal '/errors/1/notices', error_notices_path(:error_id => '1')
-
- get '/notices/1'
- assert_equal 'api/notices#show', @response.body
- assert_equal '/notices/1', notice_path(:id => '1')
- end
+ get '/errors/1/notices'
+ assert_equal 'api/notices#index', @response.body
+ assert_equal '/errors/1/notices', error_notices_path(:error_id => '1')
+
+ get '/notices/1'
+ assert_equal 'api/notices#show', @response.body
+ assert_equal '/notices/1', notice_path(:id => '1')
end
def test_non_greedy_regexp
- with_test_routes do
- get '/api/1.0/users'
- assert_equal 'api/users#index', @response.body
- assert_equal '/api/1.0/users', api_users_path(:version => '1.0')
-
- get '/api/1.0/users.json'
- assert_equal 'api/users#index', @response.body
- assert_equal true, @request.format.json?
- assert_equal '/api/1.0/users.json', api_users_path(:version => '1.0', :format => :json)
-
- get '/api/1.0/users/first.last'
- assert_equal 'api/users#show', @response.body
- assert_equal 'first.last', @request.params[:id]
- assert_equal '/api/1.0/users/first.last', api_user_path(:version => '1.0', :id => 'first.last')
-
- get '/api/1.0/users/first.last.xml'
- assert_equal 'api/users#show', @response.body
- assert_equal 'first.last', @request.params[:id]
- assert_equal true, @request.format.xml?
- assert_equal '/api/1.0/users/first.last.xml', api_user_path(:version => '1.0', :id => 'first.last', :format => :xml)
- end
+ get '/api/1.0/users'
+ assert_equal 'api/users#index', @response.body
+ assert_equal '/api/1.0/users', api_users_path(:version => '1.0')
+
+ get '/api/1.0/users.json'
+ assert_equal 'api/users#index', @response.body
+ assert_equal true, @request.format.json?
+ assert_equal '/api/1.0/users.json', api_users_path(:version => '1.0', :format => :json)
+
+ get '/api/1.0/users/first.last'
+ assert_equal 'api/users#show', @response.body
+ assert_equal 'first.last', @request.params[:id]
+ assert_equal '/api/1.0/users/first.last', api_user_path(:version => '1.0', :id => 'first.last')
+
+ get '/api/1.0/users/first.last.xml'
+ assert_equal 'api/users#show', @response.body
+ assert_equal 'first.last', @request.params[:id]
+ assert_equal true, @request.format.xml?
+ assert_equal '/api/1.0/users/first.last.xml', api_user_path(:version => '1.0', :id => 'first.last', :format => :xml)
end
def test_glob_parameter_accepts_regexp
- with_test_routes do
- get '/en/path/to/existing/file.html'
- assert_equal 200, @response.status
- end
+ get '/en/path/to/existing/file.html'
+ assert_equal 200, @response.status
end
def test_resources_controller_name_is_not_pluralized
- with_test_routes do
- get '/content'
- assert_equal 'content#index', @response.body
- end
+ get '/content'
+ assert_equal 'content#index', @response.body
end
def test_url_generator_for_optional_prefix_dynamic_segment
- with_test_routes do
- get '/bob/followers'
- assert_equal 'followers#index', @response.body
- assert_equal 'http://www.example.com/bob/followers',
- url_for(:controller => "followers", :action => "index", :username => "bob")
-
- get '/followers'
- assert_equal 'followers#index', @response.body
- assert_equal 'http://www.example.com/followers',
- url_for(:controller => "followers", :action => "index", :username => nil)
- end
+ get '/bob/followers'
+ assert_equal 'followers#index', @response.body
+ assert_equal 'http://www.example.com/bob/followers',
+ url_for(:controller => "followers", :action => "index", :username => "bob")
+
+ get '/followers'
+ assert_equal 'followers#index', @response.body
+ assert_equal 'http://www.example.com/followers',
+ url_for(:controller => "followers", :action => "index", :username => nil)
end
def test_url_generator_for_optional_suffix_static_and_dynamic_segment
- with_test_routes do
- get '/groups/user/bob'
- assert_equal 'groups#index', @response.body
- assert_equal 'http://www.example.com/groups/user/bob',
- url_for(:controller => "groups", :action => "index", :username => "bob")
-
- get '/groups'
- assert_equal 'groups#index', @response.body
- assert_equal 'http://www.example.com/groups',
- url_for(:controller => "groups", :action => "index", :username => nil)
- end
+ get '/groups/user/bob'
+ assert_equal 'groups#index', @response.body
+ assert_equal 'http://www.example.com/groups/user/bob',
+ url_for(:controller => "groups", :action => "index", :username => "bob")
+
+ get '/groups'
+ assert_equal 'groups#index', @response.body
+ assert_equal 'http://www.example.com/groups',
+ url_for(:controller => "groups", :action => "index", :username => nil)
end
def test_url_generator_for_optional_prefix_static_and_dynamic_segment
- with_test_routes do
- get 'user/bob/photos'
- assert_equal 'photos#index', @response.body
- assert_equal 'http://www.example.com/user/bob/photos',
- url_for(:controller => "photos", :action => "index", :username => "bob")
-
- get 'photos'
- assert_equal 'photos#index', @response.body
- assert_equal 'http://www.example.com/photos',
- url_for(:controller => "photos", :action => "index", :username => nil)
- end
+ get 'user/bob/photos'
+ assert_equal 'photos#index', @response.body
+ assert_equal 'http://www.example.com/user/bob/photos',
+ url_for(:controller => "photos", :action => "index", :username => "bob")
+
+ get 'photos'
+ assert_equal 'photos#index', @response.body
+ assert_equal 'http://www.example.com/photos',
+ url_for(:controller => "photos", :action => "index", :username => nil)
end
def test_url_recognition_for_optional_static_segments
- with_test_routes do
- get '/groups/discussions/messages'
- assert_equal 'messages#index', @response.body
+ get '/groups/discussions/messages'
+ assert_equal 'messages#index', @response.body
- get '/groups/discussions/messages/1'
- assert_equal 'messages#show', @response.body
+ get '/groups/discussions/messages/1'
+ assert_equal 'messages#show', @response.body
- get '/groups/messages'
- assert_equal 'messages#index', @response.body
+ get '/groups/messages'
+ assert_equal 'messages#index', @response.body
- get '/groups/messages/1'
- assert_equal 'messages#show', @response.body
+ get '/groups/messages/1'
+ assert_equal 'messages#show', @response.body
- get '/discussions/messages'
- assert_equal 'messages#index', @response.body
+ get '/discussions/messages'
+ assert_equal 'messages#index', @response.body
- get '/discussions/messages/1'
- assert_equal 'messages#show', @response.body
+ get '/discussions/messages/1'
+ assert_equal 'messages#show', @response.body
- get '/messages'
- assert_equal 'messages#index', @response.body
+ get '/messages'
+ assert_equal 'messages#index', @response.body
- get '/messages/1'
- assert_equal 'messages#show', @response.body
- end
+ get '/messages/1'
+ assert_equal 'messages#show', @response.body
end
def test_router_removes_invalid_conditions
- with_test_routes do
- get '/tickets'
- assert_equal 'tickets#index', @response.body
- assert_equal '/tickets', tickets_path
- end
+ get '/tickets'
+ assert_equal 'tickets#index', @response.body
+ assert_equal '/tickets', tickets_path
end
def test_constraints_are_merged_from_scope
- with_test_routes do
- get '/movies/0001'
- assert_equal 'movies#show', @response.body
- assert_equal '/movies/0001', movie_path(:id => '0001')
-
- get '/movies/00001'
- assert_equal 'Not Found', @response.body
- assert_raises(ActionController::RoutingError){ movie_path(:id => '00001') }
-
- get '/movies/0001/reviews'
- assert_equal 'reviews#index', @response.body
- assert_equal '/movies/0001/reviews', movie_reviews_path(:movie_id => '0001')
-
- get '/movies/00001/reviews'
- assert_equal 'Not Found', @response.body
- assert_raises(ActionController::RoutingError){ movie_reviews_path(:movie_id => '00001') }
-
- get '/movies/0001/reviews/0001'
- assert_equal 'reviews#show', @response.body
- assert_equal '/movies/0001/reviews/0001', movie_review_path(:movie_id => '0001', :id => '0001')
-
- get '/movies/00001/reviews/0001'
- assert_equal 'Not Found', @response.body
- assert_raises(ActionController::RoutingError){ movie_path(:movie_id => '00001', :id => '00001') }
-
- get '/movies/0001/trailer'
- assert_equal 'trailers#show', @response.body
- assert_equal '/movies/0001/trailer', movie_trailer_path(:movie_id => '0001')
-
- get '/movies/00001/trailer'
- assert_equal 'Not Found', @response.body
- assert_raises(ActionController::RoutingError){ movie_trailer_path(:movie_id => '00001') }
- end
+ get '/movies/0001'
+ assert_equal 'movies#show', @response.body
+ assert_equal '/movies/0001', movie_path(:id => '0001')
+
+ get '/movies/00001'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::RoutingError){ movie_path(:id => '00001') }
+
+ get '/movies/0001/reviews'
+ assert_equal 'reviews#index', @response.body
+ assert_equal '/movies/0001/reviews', movie_reviews_path(:movie_id => '0001')
+
+ get '/movies/00001/reviews'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::RoutingError){ movie_reviews_path(:movie_id => '00001') }
+
+ get '/movies/0001/reviews/0001'
+ assert_equal 'reviews#show', @response.body
+ assert_equal '/movies/0001/reviews/0001', movie_review_path(:movie_id => '0001', :id => '0001')
+
+ get '/movies/00001/reviews/0001'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::RoutingError){ movie_path(:movie_id => '00001', :id => '00001') }
+
+ get '/movies/0001/trailer'
+ assert_equal 'trailers#show', @response.body
+ assert_equal '/movies/0001/trailer', movie_trailer_path(:movie_id => '0001')
+
+ get '/movies/00001/trailer'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::RoutingError){ movie_trailer_path(:movie_id => '00001') }
end
def test_only_should_be_read_from_scope
- with_test_routes do
- get '/only/clubs'
- assert_equal 'only/clubs#index', @response.body
- assert_equal '/only/clubs', only_clubs_path
-
- get '/only/clubs/1/edit'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { edit_only_club_path(:id => '1') }
-
- get '/only/clubs/1/players'
- assert_equal 'only/players#index', @response.body
- assert_equal '/only/clubs/1/players', only_club_players_path(:club_id => '1')
-
- get '/only/clubs/1/players/2/edit'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { edit_only_club_player_path(:club_id => '1', :id => '2') }
-
- get '/only/clubs/1/chairman'
- assert_equal 'only/chairmen#show', @response.body
- assert_equal '/only/clubs/1/chairman', only_club_chairman_path(:club_id => '1')
-
- get '/only/clubs/1/chairman/edit'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { edit_only_club_chairman_path(:club_id => '1') }
- end
+ get '/only/clubs'
+ assert_equal 'only/clubs#index', @response.body
+ assert_equal '/only/clubs', only_clubs_path
+
+ get '/only/clubs/1/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_only_club_path(:id => '1') }
+
+ get '/only/clubs/1/players'
+ assert_equal 'only/players#index', @response.body
+ assert_equal '/only/clubs/1/players', only_club_players_path(:club_id => '1')
+
+ get '/only/clubs/1/players/2/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_only_club_player_path(:club_id => '1', :id => '2') }
+
+ get '/only/clubs/1/chairman'
+ assert_equal 'only/chairmen#show', @response.body
+ assert_equal '/only/clubs/1/chairman', only_club_chairman_path(:club_id => '1')
+
+ get '/only/clubs/1/chairman/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_only_club_chairman_path(:club_id => '1') }
end
def test_except_should_be_read_from_scope
- with_test_routes do
- get '/except/clubs'
- assert_equal 'except/clubs#index', @response.body
- assert_equal '/except/clubs', except_clubs_path
-
- get '/except/clubs/1/edit'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { edit_except_club_path(:id => '1') }
-
- get '/except/clubs/1/players'
- assert_equal 'except/players#index', @response.body
- assert_equal '/except/clubs/1/players', except_club_players_path(:club_id => '1')
-
- get '/except/clubs/1/players/2/edit'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { edit_except_club_player_path(:club_id => '1', :id => '2') }
-
- get '/except/clubs/1/chairman'
- assert_equal 'except/chairmen#show', @response.body
- assert_equal '/except/clubs/1/chairman', except_club_chairman_path(:club_id => '1')
-
- get '/except/clubs/1/chairman/edit'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { edit_except_club_chairman_path(:club_id => '1') }
- end
+ get '/except/clubs'
+ assert_equal 'except/clubs#index', @response.body
+ assert_equal '/except/clubs', except_clubs_path
+
+ get '/except/clubs/1/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_except_club_path(:id => '1') }
+
+ get '/except/clubs/1/players'
+ assert_equal 'except/players#index', @response.body
+ assert_equal '/except/clubs/1/players', except_club_players_path(:club_id => '1')
+
+ get '/except/clubs/1/players/2/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_except_club_player_path(:club_id => '1', :id => '2') }
+
+ get '/except/clubs/1/chairman'
+ assert_equal 'except/chairmen#show', @response.body
+ assert_equal '/except/clubs/1/chairman', except_club_chairman_path(:club_id => '1')
+
+ get '/except/clubs/1/chairman/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_except_club_chairman_path(:club_id => '1') }
end
def test_only_option_should_override_scope
- with_test_routes do
- get '/only/sectors'
- assert_equal 'only/sectors#index', @response.body
- assert_equal '/only/sectors', only_sectors_path
-
- get '/only/sectors/1'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { only_sector_path(:id => '1') }
- end
+ get '/only/sectors'
+ assert_equal 'only/sectors#index', @response.body
+ assert_equal '/only/sectors', only_sectors_path
+
+ get '/only/sectors/1'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_path(:id => '1') }
end
def test_only_option_should_not_inherit
- with_test_routes do
- get '/only/sectors/1/companies/2'
- assert_equal 'only/companies#show', @response.body
- assert_equal '/only/sectors/1/companies/2', only_sector_company_path(:sector_id => '1', :id => '2')
-
- get '/only/sectors/1/leader'
- assert_equal 'only/leaders#show', @response.body
- assert_equal '/only/sectors/1/leader', only_sector_leader_path(:sector_id => '1')
- end
+ get '/only/sectors/1/companies/2'
+ assert_equal 'only/companies#show', @response.body
+ assert_equal '/only/sectors/1/companies/2', only_sector_company_path(:sector_id => '1', :id => '2')
+
+ get '/only/sectors/1/leader'
+ assert_equal 'only/leaders#show', @response.body
+ assert_equal '/only/sectors/1/leader', only_sector_leader_path(:sector_id => '1')
end
def test_except_option_should_override_scope
- with_test_routes do
- get '/except/sectors'
- assert_equal 'except/sectors#index', @response.body
- assert_equal '/except/sectors', except_sectors_path
-
- get '/except/sectors/1'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { except_sector_path(:id => '1') }
- end
+ get '/except/sectors'
+ assert_equal 'except/sectors#index', @response.body
+ assert_equal '/except/sectors', except_sectors_path
+
+ get '/except/sectors/1'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_path(:id => '1') }
end
def test_except_option_should_not_inherit
- with_test_routes do
- get '/except/sectors/1/companies/2'
- assert_equal 'except/companies#show', @response.body
- assert_equal '/except/sectors/1/companies/2', except_sector_company_path(:sector_id => '1', :id => '2')
-
- get '/except/sectors/1/leader'
- assert_equal 'except/leaders#show', @response.body
- assert_equal '/except/sectors/1/leader', except_sector_leader_path(:sector_id => '1')
- end
+ get '/except/sectors/1/companies/2'
+ assert_equal 'except/companies#show', @response.body
+ assert_equal '/except/sectors/1/companies/2', except_sector_company_path(:sector_id => '1', :id => '2')
+
+ get '/except/sectors/1/leader'
+ assert_equal 'except/leaders#show', @response.body
+ assert_equal '/except/sectors/1/leader', except_sector_leader_path(:sector_id => '1')
end
def test_except_option_should_override_scoped_only
- with_test_routes do
- get '/only/sectors/1/managers'
- assert_equal 'only/managers#index', @response.body
- assert_equal '/only/sectors/1/managers', only_sector_managers_path(:sector_id => '1')
-
- get '/only/sectors/1/managers/2'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { only_sector_manager_path(:sector_id => '1', :id => '2') }
- end
+ get '/only/sectors/1/managers'
+ assert_equal 'only/managers#index', @response.body
+ assert_equal '/only/sectors/1/managers', only_sector_managers_path(:sector_id => '1')
+
+ get '/only/sectors/1/managers/2'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_manager_path(:sector_id => '1', :id => '2') }
end
def test_only_option_should_override_scoped_except
- with_test_routes do
- get '/except/sectors/1/managers'
- assert_equal 'except/managers#index', @response.body
- assert_equal '/except/sectors/1/managers', except_sector_managers_path(:sector_id => '1')
-
- get '/except/sectors/1/managers/2'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { except_sector_manager_path(:sector_id => '1', :id => '2') }
- end
+ get '/except/sectors/1/managers'
+ assert_equal 'except/managers#index', @response.body
+ assert_equal '/except/sectors/1/managers', except_sector_managers_path(:sector_id => '1')
+
+ get '/except/sectors/1/managers/2'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_manager_path(:sector_id => '1', :id => '2') }
end
def test_only_scope_should_override_parent_scope
- with_test_routes do
- get '/only/sectors/1/companies/2/divisions'
- assert_equal 'only/divisions#index', @response.body
- assert_equal '/only/sectors/1/companies/2/divisions', only_sector_company_divisions_path(:sector_id => '1', :company_id => '2')
-
- get '/only/sectors/1/companies/2/divisions/3'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { only_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') }
- end
+ get '/only/sectors/1/companies/2/divisions'
+ assert_equal 'only/divisions#index', @response.body
+ assert_equal '/only/sectors/1/companies/2/divisions', only_sector_company_divisions_path(:sector_id => '1', :company_id => '2')
+
+ get '/only/sectors/1/companies/2/divisions/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') }
end
def test_except_scope_should_override_parent_scope
- with_test_routes do
- get '/except/sectors/1/companies/2/divisions'
- assert_equal 'except/divisions#index', @response.body
- assert_equal '/except/sectors/1/companies/2/divisions', except_sector_company_divisions_path(:sector_id => '1', :company_id => '2')
-
- get '/except/sectors/1/companies/2/divisions/3'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { except_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') }
- end
+ get '/except/sectors/1/companies/2/divisions'
+ assert_equal 'except/divisions#index', @response.body
+ assert_equal '/except/sectors/1/companies/2/divisions', except_sector_company_divisions_path(:sector_id => '1', :company_id => '2')
+
+ get '/except/sectors/1/companies/2/divisions/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') }
end
def test_except_scope_should_override_parent_only_scope
- with_test_routes do
- get '/only/sectors/1/companies/2/departments'
- assert_equal 'only/departments#index', @response.body
- assert_equal '/only/sectors/1/companies/2/departments', only_sector_company_departments_path(:sector_id => '1', :company_id => '2')
-
- get '/only/sectors/1/companies/2/departments/3'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { only_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') }
- end
+ get '/only/sectors/1/companies/2/departments'
+ assert_equal 'only/departments#index', @response.body
+ assert_equal '/only/sectors/1/companies/2/departments', only_sector_company_departments_path(:sector_id => '1', :company_id => '2')
+
+ get '/only/sectors/1/companies/2/departments/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') }
end
def test_only_scope_should_override_parent_except_scope
- with_test_routes do
- get '/except/sectors/1/companies/2/departments'
- assert_equal 'except/departments#index', @response.body
- assert_equal '/except/sectors/1/companies/2/departments', except_sector_company_departments_path(:sector_id => '1', :company_id => '2')
-
- get '/except/sectors/1/companies/2/departments/3'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { except_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') }
- end
+ get '/except/sectors/1/companies/2/departments'
+ assert_equal 'except/departments#index', @response.body
+ assert_equal '/except/sectors/1/companies/2/departments', except_sector_company_departments_path(:sector_id => '1', :company_id => '2')
+
+ get '/except/sectors/1/companies/2/departments/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') }
end
def test_resources_are_not_pluralized
- with_test_routes do
- get '/transport/taxis'
- assert_equal 'transport/taxis#index', @response.body
- assert_equal '/transport/taxis', transport_taxis_path
+ get '/transport/taxis'
+ assert_equal 'transport/taxis#index', @response.body
+ assert_equal '/transport/taxis', transport_taxis_path
- get '/transport/taxis/new'
- assert_equal 'transport/taxis#new', @response.body
- assert_equal '/transport/taxis/new', new_transport_taxi_path
+ get '/transport/taxis/new'
+ assert_equal 'transport/taxis#new', @response.body
+ assert_equal '/transport/taxis/new', new_transport_taxi_path
- post '/transport/taxis'
- assert_equal 'transport/taxis#create', @response.body
+ post '/transport/taxis'
+ assert_equal 'transport/taxis#create', @response.body
- get '/transport/taxis/1'
- assert_equal 'transport/taxis#show', @response.body
- assert_equal '/transport/taxis/1', transport_taxi_path(:id => '1')
+ get '/transport/taxis/1'
+ assert_equal 'transport/taxis#show', @response.body
+ assert_equal '/transport/taxis/1', transport_taxi_path(:id => '1')
- get '/transport/taxis/1/edit'
- assert_equal 'transport/taxis#edit', @response.body
- assert_equal '/transport/taxis/1/edit', edit_transport_taxi_path(:id => '1')
+ get '/transport/taxis/1/edit'
+ assert_equal 'transport/taxis#edit', @response.body
+ assert_equal '/transport/taxis/1/edit', edit_transport_taxi_path(:id => '1')
- put '/transport/taxis/1'
- assert_equal 'transport/taxis#update', @response.body
+ put '/transport/taxis/1'
+ assert_equal 'transport/taxis#update', @response.body
- delete '/transport/taxis/1'
- assert_equal 'transport/taxis#destroy', @response.body
- end
+ delete '/transport/taxis/1'
+ assert_equal 'transport/taxis#destroy', @response.body
end
def test_singleton_resources_are_not_singularized
- with_test_routes do
- get '/medical/taxis/new'
- assert_equal 'medical/taxes#new', @response.body
- assert_equal '/medical/taxis/new', new_medical_taxis_path
+ get '/medical/taxis/new'
+ assert_equal 'medical/taxis#new', @response.body
+ assert_equal '/medical/taxis/new', new_medical_taxis_path
- post '/medical/taxis'
- assert_equal 'medical/taxes#create', @response.body
+ post '/medical/taxis'
+ assert_equal 'medical/taxis#create', @response.body
- get '/medical/taxis'
- assert_equal 'medical/taxes#show', @response.body
- assert_equal '/medical/taxis', medical_taxis_path
+ get '/medical/taxis'
+ assert_equal 'medical/taxis#show', @response.body
+ assert_equal '/medical/taxis', medical_taxis_path
- get '/medical/taxis/edit'
- assert_equal 'medical/taxes#edit', @response.body
- assert_equal '/medical/taxis/edit', edit_medical_taxis_path
+ get '/medical/taxis/edit'
+ assert_equal 'medical/taxis#edit', @response.body
+ assert_equal '/medical/taxis/edit', edit_medical_taxis_path
- put '/medical/taxis'
- assert_equal 'medical/taxes#update', @response.body
+ put '/medical/taxis'
+ assert_equal 'medical/taxis#update', @response.body
- delete '/medical/taxis'
- assert_equal 'medical/taxes#destroy', @response.body
- end
+ delete '/medical/taxis'
+ assert_equal 'medical/taxis#destroy', @response.body
end
def test_greedy_resource_id_regexp_doesnt_match_edit_and_custom_action
- with_test_routes do
- get '/sections/1/edit'
- assert_equal 'sections#edit', @response.body
- assert_equal '/sections/1/edit', edit_section_path(:id => '1')
-
- get '/sections/1/preview'
- assert_equal 'sections#preview', @response.body
- assert_equal '/sections/1/preview', preview_section_path(:id => '1')
- end
+ get '/sections/1/edit'
+ assert_equal 'sections#edit', @response.body
+ assert_equal '/sections/1/edit', edit_section_path(:id => '1')
+
+ get '/sections/1/preview'
+ assert_equal 'sections#preview', @response.body
+ assert_equal '/sections/1/preview', preview_section_path(:id => '1')
end
def test_resource_constraints_are_pushed_to_scope
- with_test_routes do
- get '/wiki/articles/Ruby_on_Rails_3.0'
- assert_equal 'wiki/articles#show', @response.body
- assert_equal '/wiki/articles/Ruby_on_Rails_3.0', wiki_article_path(:id => 'Ruby_on_Rails_3.0')
-
- get '/wiki/articles/Ruby_on_Rails_3.0/comments/new'
- assert_equal 'wiki/comments#new', @response.body
- assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments/new', new_wiki_article_comment_path(:article_id => 'Ruby_on_Rails_3.0')
-
- post '/wiki/articles/Ruby_on_Rails_3.0/comments'
- assert_equal 'wiki/comments#create', @response.body
- assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments', wiki_article_comments_path(:article_id => 'Ruby_on_Rails_3.0')
- end
+ get '/wiki/articles/Ruby_on_Rails_3.0'
+ assert_equal 'wiki/articles#show', @response.body
+ assert_equal '/wiki/articles/Ruby_on_Rails_3.0', wiki_article_path(:id => 'Ruby_on_Rails_3.0')
+
+ get '/wiki/articles/Ruby_on_Rails_3.0/comments/new'
+ assert_equal 'wiki/comments#new', @response.body
+ assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments/new', new_wiki_article_comment_path(:article_id => 'Ruby_on_Rails_3.0')
+
+ post '/wiki/articles/Ruby_on_Rails_3.0/comments'
+ assert_equal 'wiki/comments#create', @response.body
+ assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments', wiki_article_comments_path(:article_id => 'Ruby_on_Rails_3.0')
end
def test_resources_path_can_be_a_symbol
- with_test_routes do
- get '/pages'
- assert_equal 'wiki_pages#index', @response.body
- assert_equal '/pages', wiki_pages_path
-
- get '/pages/Ruby_on_Rails'
- assert_equal 'wiki_pages#show', @response.body
- assert_equal '/pages/Ruby_on_Rails', wiki_page_path(:id => 'Ruby_on_Rails')
-
- get '/my_account'
- assert_equal 'wiki_accounts#show', @response.body
- assert_equal '/my_account', wiki_account_path
- end
+ get '/pages'
+ assert_equal 'wiki_pages#index', @response.body
+ assert_equal '/pages', wiki_pages_path
+
+ get '/pages/Ruby_on_Rails'
+ assert_equal 'wiki_pages#show', @response.body
+ assert_equal '/pages/Ruby_on_Rails', wiki_page_path(:id => 'Ruby_on_Rails')
+
+ get '/my_account'
+ assert_equal 'wiki_accounts#show', @response.body
+ assert_equal '/my_account', wiki_account_path
end
def test_redirect_https
- with_test_routes do
- with_https do
- get '/secure'
- verify_redirect 'https://www.example.com/secure/login'
- end
+ with_https do
+ get '/secure'
+ verify_redirect 'https://www.example.com/secure/login'
end
end
@@ -2297,7 +2095,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_named_routes_collision_is_avoided_unless_explicitly_given_as
assert_equal "/c/1", routes_collision_path(1)
- assert_equal "/fc", routes_forced_collision_path
+ assert_equal "/fc/1", routes_forced_collision_path(1)
end
def test_redirect_argument_error
@@ -2386,10 +2184,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
private
- def with_test_routes
- yield
- end
-
def with_https
old_https = https?
https!
@@ -2444,6 +2238,32 @@ class TestAppendingRoutes < ActionDispatch::IntegrationTest
end
end
+class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
+ module ::Admin
+ class StorageFilesController < ActionController::Base
+ def index
+ render :text => "admin/storage_files#index"
+ end
+ end
+ end
+
+ DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new
+ DefaultScopeRoutes.draw do
+ namespace :admin do
+ resources :storage_files, :controller => "StorageFiles"
+ end
+ end
+
+ def app
+ DefaultScopeRoutes
+ end
+
+ def test_controller_options
+ get '/admin/storage_files'
+ assert_equal "admin/storage_files#index", @response.body
+ end
+end
+
class TestDefaultScope < ActionDispatch::IntegrationTest
module ::Blog
class PostsController < ActionController::Base
@@ -2539,3 +2359,78 @@ class TestUriPathEscaping < ActionDispatch::IntegrationTest
assert_equal 'a b/c+d', @response.body
end
end
+
+class TestUnicodePaths < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ match "/#{Rack::Utils.escape("ほげ")}" => lambda { |env|
+ [200, { 'Content-Type' => 'text/plain' }, []]
+ }, :as => :unicode_path
+ end
+ end
+
+ include Routes.url_helpers
+ def app; Routes end
+
+ test 'recognizes unicode path' do
+ get "/#{Rack::Utils.escape("ほげ")}"
+ assert_equal "200", @response.code
+ end
+end
+
+class TestMultipleNestedController < ActionDispatch::IntegrationTest
+ module ::Foo
+ module Bar
+ class BazController < ActionController::Base
+ def index
+ render :inline => "<%= url_for :controller => '/pooh', :action => 'index' %>"
+ end
+ end
+ end
+ end
+
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ namespace :foo do
+ namespace :bar do
+ match "baz" => "baz#index"
+ end
+ end
+ match "pooh" => "pooh#index"
+ end
+ end
+
+ include Routes.url_helpers
+ def app; Routes end
+
+ test "controller option which starts with '/' from multiple nested controller" do
+ get "/foo/bar/baz"
+ assert_equal "/pooh", @response.body
+ end
+
+end
+
+class TestTildeAndMinusPaths < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ match "/~user" => ok
+ match "/young-and-fine" => ok
+ end
+ end
+
+ include Routes.url_helpers
+ def app; Routes end
+
+ test 'recognizes tilde path' do
+ get "/~user"
+ assert_equal "200", @response.code
+ end
+
+ test 'recognizes minus path' do
+ get "/young-and-fine"
+ assert_equal "200", @response.code
+ end
+
+end
diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb
index 73e056de23..12405bf45d 100644
--- a/actionpack/test/dispatch/session/cache_store_test.rb
+++ b/actionpack/test/dispatch/session/cache_store_test.rb
@@ -30,8 +30,6 @@ class CacheStoreTest < ActionDispatch::IntegrationTest
session[:bar] = "baz"
head :ok
end
-
- def rescue_action(e) raise end
end
def test_setting_and_getting_session_value
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index 92df6967d6..19969394cd 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -54,8 +54,6 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
request.session_options[:renew] = true
head :ok
end
-
- def rescue_action(e) raise end
end
def test_setting_session_value
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index 8502bc547b..5277c92b55 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -31,8 +31,6 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
session[:bar] = "baz"
head :ok
end
-
- def rescue_action(e) raise end
end
begin
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index e9504f3524..45f8fc11b3 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -7,11 +7,11 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
req = ActionDispatch::Request.new(env)
case req.path
when "/not_found"
- raise ActionController::UnknownAction
+ raise AbstractController::ActionNotFound
when "/method_not_allowed"
raise ActionController::MethodNotAllowed
when "/not_found_original_exception"
- raise ActionView::Template::Error.new('template', {}, AbstractController::ActionNotFound.new)
+ raise ActionView::Template::Error.new('template', AbstractController::ActionNotFound.new)
else
raise "puke!"
end
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index 9f3cbd19ef..092ca3e20a 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -1,4 +1,6 @@
+# encoding: utf-8
require 'abstract_unit'
+require 'rbconfig'
module StaticTests
def test_serves_dynamic_content
@@ -30,6 +32,91 @@ module StaticTests
assert_html "/foo/index.html", get("/foo")
end
+ def test_served_static_file_with_non_english_filename
+ assert_html "means hello in Japanese\n", get("/foo/#{Rack::Utils.escape("こんにちは.html")}")
+ end
+
+
+ def test_serves_static_file_with_exclamation_mark_in_filename
+ with_static_file "/foo/foo!bar.html" do |file|
+ assert_html file, get("/foo/foo%21bar.html")
+ assert_html file, get("/foo/foo!bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_dollar_sign_in_filename
+ with_static_file "/foo/foo$bar.html" do |file|
+ assert_html file, get("/foo/foo%24bar.html")
+ assert_html file, get("/foo/foo$bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_ampersand_in_filename
+ with_static_file "/foo/foo&bar.html" do |file|
+ assert_html file, get("/foo/foo%26bar.html")
+ assert_html file, get("/foo/foo&bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_apostrophe_in_filename
+ with_static_file "/foo/foo'bar.html" do |file|
+ assert_html file, get("/foo/foo%27bar.html")
+ assert_html file, get("/foo/foo'bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_parentheses_in_filename
+ with_static_file "/foo/foo(bar).html" do |file|
+ assert_html file, get("/foo/foo%28bar%29.html")
+ assert_html file, get("/foo/foo(bar).html")
+ end
+ end
+
+ def test_serves_static_file_with_plus_sign_in_filename
+ with_static_file "/foo/foo+bar.html" do |file|
+ assert_html file, get("/foo/foo%2Bbar.html")
+ assert_html file, get("/foo/foo+bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_comma_in_filename
+ with_static_file "/foo/foo,bar.html" do |file|
+ assert_html file, get("/foo/foo%2Cbar.html")
+ assert_html file, get("/foo/foo,bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_semi_colon_in_filename
+ with_static_file "/foo/foo;bar.html" do |file|
+ assert_html file, get("/foo/foo%3Bbar.html")
+ assert_html file, get("/foo/foo;bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_at_symbol_in_filename
+ with_static_file "/foo/foo@bar.html" do |file|
+ assert_html file, get("/foo/foo%40bar.html")
+ assert_html file, get("/foo/foo@bar.html")
+ end
+ end
+
+ # Windows doesn't allow \ / : * ? " < > | in filenames
+ unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
+ def test_serves_static_file_with_colon
+ with_static_file "/foo/foo:bar.html" do |file|
+ assert_html file, get("/foo/foo%3Abar.html")
+ assert_html file, get("/foo/foo:bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_asterisk
+ with_static_file "/foo/foo*bar.html" do |file|
+ assert_html file, get("/foo/foo%2Abar.html")
+ assert_html file, get("/foo/foo*bar.html")
+ end
+ end
+ end
+
private
def assert_html(body, response)
@@ -40,6 +127,14 @@ module StaticTests
def get(path)
Rack::MockRequest.new(@app).request("GET", path)
end
+
+ def with_static_file(file)
+ path = "#{FIXTURE_LOAD_PATH}/public" + file
+ File.open(path, "wb+") { |f| f.write(file) }
+ yield file
+ ensure
+ File.delete(path)
+ end
end
class StaticTest < ActiveSupport::TestCase
@@ -53,4 +148,4 @@ class StaticTest < ActiveSupport::TestCase
end
include StaticTests
-end \ No newline at end of file
+end
diff --git a/actionpack/test/fixtures/addresses/list.erb b/actionpack/test/fixtures/addresses/list.erb
deleted file mode 100644
index c75e01eece..0000000000
--- a/actionpack/test/fixtures/addresses/list.erb
+++ /dev/null
@@ -1 +0,0 @@
-We only need to get this far!
diff --git a/actionpack/test/fixtures/developers.yml b/actionpack/test/fixtures/developers.yml
index 308bf75de2..3656564f63 100644
--- a/actionpack/test/fixtures/developers.yml
+++ b/actionpack/test/fixtures/developers.yml
@@ -8,7 +8,7 @@ jamis:
name: Jamis
salary: 150000
-<% for digit in 3..10 %>
+<% (3..10).each do |digit| %>
dev_<%= digit %>:
id: <%= digit %>
name: fixture_<%= digit %>
diff --git a/actionpack/test/fixtures/public/foo/こんにちは.html b/actionpack/test/fixtures/public/foo/こんにちは.html
new file mode 100644
index 0000000000..1df9166522
--- /dev/null
+++ b/actionpack/test/fixtures/public/foo/こんにちは.html
@@ -0,0 +1 @@
+means hello in Japanese
diff --git a/actionpack/test/fixtures/test/one.html.erb b/actionpack/test/fixtures/test/one.html.erb
new file mode 100644
index 0000000000..0151874809
--- /dev/null
+++ b/actionpack/test/fixtures/test/one.html.erb
@@ -0,0 +1 @@
+<%= render :partial => "test/two" %> world \ No newline at end of file
diff --git a/actionpack/test/fixtures/with_format.json.erb b/actionpack/test/fixtures/with_format.json.erb
new file mode 100644
index 0000000000..a7f480ab1d
--- /dev/null
+++ b/actionpack/test/fixtures/with_format.json.erb
@@ -0,0 +1 @@
+<%= render :partial => 'missing', :formats => [:json] %>
diff --git a/actionpack/test/lib/controller/fake_controllers.rb b/actionpack/test/lib/controller/fake_controllers.rb
index 09692f77b5..1a2863b689 100644
--- a/actionpack/test/lib/controller/fake_controllers.rb
+++ b/actionpack/test/lib/controller/fake_controllers.rb
@@ -1,11 +1,7 @@
-class << Object; alias_method :const_available?, :const_defined?; end
-
class ContentController < ActionController::Base; end
module Admin
- class << self; alias_method :const_available?, :const_defined?; end
class AccountsController < ActionController::Base; end
- class NewsFeedController < ActionController::Base; end
class PostsController < ActionController::Base; end
class StuffController < ActionController::Base; end
class UserController < ActionController::Base; end
@@ -17,46 +13,23 @@ module Api
class ProductsController < ActionController::Base; end
end
-# TODO: Reduce the number of test controllers we use
class AccountController < ActionController::Base; end
-class AddressesController < ActionController::Base; end
class ArchiveController < ActionController::Base; end
class ArticlesController < ActionController::Base; end
class BarController < ActionController::Base; end
class BlogController < ActionController::Base; end
class BooksController < ActionController::Base; end
-class BraveController < ActionController::Base; end
class CarsController < ActionController::Base; end
class CcController < ActionController::Base; end
class CController < ActionController::Base; end
-class ElsewhereController < ActionController::Base; end
class FooController < ActionController::Base; end
class GeocodeController < ActionController::Base; end
-class HiController < ActionController::Base; end
-class ImageController < ActionController::Base; end
class NewsController < ActionController::Base; end
class NotesController < ActionController::Base; end
+class PagesController < ActionController::Base; end
class PeopleController < ActionController::Base; end
class PostsController < ActionController::Base; end
-class SessionsController < ActionController::Base; end
-class StuffController < ActionController::Base; end
class SubpathBooksController < ActionController::Base; end
class SymbolsController < ActionController::Base; end
class UserController < ActionController::Base; end
-class WeblogController < ActionController::Base; end
-
-# For speed test
-class SpeedController < ActionController::Base; end
-class SearchController < SpeedController; end
-class VideosController < SpeedController; end
-class VideoFileController < SpeedController; end
-class VideoSharesController < SpeedController; end
-class VideoAbusesController < SpeedController; end
-class VideoUploadsController < SpeedController; end
-class VideoVisitsController < SpeedController; end
-class UsersController < SpeedController; end
-class SettingsController < SpeedController; end
-class ChannelsController < SpeedController; end
-class ChannelVideosController < SpeedController; end
-class LostPasswordsController < SpeedController; end
-class PagesController < SpeedController; end
+class UsersController < ActionController::Base; end
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index f2362714d7..bbb4cc5ef3 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -34,6 +34,16 @@ end
class GoodCustomer < Customer
end
+class ValidatedCustomer < Customer
+ def errors
+ if name =~ /Sikachu/i
+ []
+ else
+ [{:name => "is invalid"}]
+ end
+ end
+end
+
module Quiz
class Question < Struct.new(:name, :id)
extend ActiveModel::Naming
diff --git a/actionpack/test/routing/helper_test.rb b/actionpack/test/routing/helper_test.rb
new file mode 100644
index 0000000000..a5588d95fa
--- /dev/null
+++ b/actionpack/test/routing/helper_test.rb
@@ -0,0 +1,31 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Routing
+ class HelperTest < ActiveSupport::TestCase
+ class Duck
+ def to_param
+ nil
+ end
+ end
+
+ def test_exception
+ rs = ::ActionDispatch::Routing::RouteSet.new
+ rs.draw do
+ resources :ducks do
+ member do
+ get :pond
+ end
+ end
+ end
+
+ x = Class.new {
+ include rs.url_helpers
+ }
+ assert_raises ActionController::RoutingError do
+ x.new.pond_duck_path Duck.new
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/template/active_model_helper_test.rb b/actionpack/test/template/active_model_helper_test.rb
index 52be0f1762..66a7bce71e 100644
--- a/actionpack/test/template/active_model_helper_test.rb
+++ b/actionpack/test/template/active_model_helper_test.rb
@@ -29,7 +29,7 @@ class ActiveModelHelperTest < ActionView::TestCase
def test_text_area_with_errors
assert_dom_equal(
- %(<div class="field_with_errors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div>),
+ %(<div class="field_with_errors"><textarea cols="40" id="post_body" name="post[body]" rows="20">\nBack to the hill and over it again!</textarea></div>),
text_area("post", "body")
)
end
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index aa7304b3ed..3d2eeea503 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -88,6 +88,18 @@ class AssetTagHelperTest < ActionView::TestCase
%(path_to_javascript("/super/xmlhr.js")) => %(/super/xmlhr.js)
}
+ JavascriptUrlToTag = {
+ %(javascript_url("xmlhr")) => %(http://www.example.com/javascripts/xmlhr.js),
+ %(javascript_url("super/xmlhr")) => %(http://www.example.com/javascripts/super/xmlhr.js),
+ %(javascript_url("/super/xmlhr.js")) => %(http://www.example.com/super/xmlhr.js)
+ }
+
+ UrlToJavascriptToTag = {
+ %(url_to_javascript("xmlhr")) => %(http://www.example.com/javascripts/xmlhr.js),
+ %(url_to_javascript("super/xmlhr")) => %(http://www.example.com/javascripts/super/xmlhr.js),
+ %(url_to_javascript("/super/xmlhr.js")) => %(http://www.example.com/super/xmlhr.js)
+ }
+
JavascriptIncludeToTag = {
%(javascript_include_tag("bank")) => %(<script src="/javascripts/bank.js" type="text/javascript"></script>),
%(javascript_include_tag("bank.js")) => %(<script src="/javascripts/bank.js" type="text/javascript"></script>),
@@ -119,6 +131,20 @@ class AssetTagHelperTest < ActionView::TestCase
%(path_to_stylesheet('/dir/file.rcss')) => %(/dir/file.rcss)
}
+ StyleUrlToTag = {
+ %(stylesheet_url("bank")) => %(http://www.example.com/stylesheets/bank.css),
+ %(stylesheet_url("bank.css")) => %(http://www.example.com/stylesheets/bank.css),
+ %(stylesheet_url('subdir/subdir')) => %(http://www.example.com/stylesheets/subdir/subdir.css),
+ %(stylesheet_url('/subdir/subdir.css')) => %(http://www.example.com/subdir/subdir.css)
+ }
+
+ UrlToStyleToTag = {
+ %(url_to_stylesheet("style")) => %(http://www.example.com/stylesheets/style.css),
+ %(url_to_stylesheet("style.css")) => %(http://www.example.com/stylesheets/style.css),
+ %(url_to_stylesheet('dir/file')) => %(http://www.example.com/stylesheets/dir/file.css),
+ %(url_to_stylesheet('/dir/file.rcss')) => %(http://www.example.com/dir/file.rcss)
+ }
+
StyleLinkToTag = {
%(stylesheet_link_tag("bank")) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />),
%(stylesheet_link_tag("bank.css")) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />),
@@ -149,6 +175,20 @@ class AssetTagHelperTest < ActionView::TestCase
%(path_to_image("/dir/xml.png")) => %(/dir/xml.png)
}
+ ImageUrlToTag = {
+ %(image_url("xml")) => %(http://www.example.com/images/xml),
+ %(image_url("xml.png")) => %(http://www.example.com/images/xml.png),
+ %(image_url("dir/xml.png")) => %(http://www.example.com/images/dir/xml.png),
+ %(image_url("/dir/xml.png")) => %(http://www.example.com/dir/xml.png)
+ }
+
+ UrlToImageToTag = {
+ %(url_to_image("xml")) => %(http://www.example.com/images/xml),
+ %(url_to_image("xml.png")) => %(http://www.example.com/images/xml.png),
+ %(url_to_image("dir/xml.png")) => %(http://www.example.com/images/dir/xml.png),
+ %(url_to_image("/dir/xml.png")) => %(http://www.example.com/dir/xml.png)
+ }
+
ImageLinkToTag = {
%(image_tag("xml.png")) => %(<img alt="Xml" src="/images/xml.png" />),
%(image_tag("rss.gif", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/images/rss.gif" />),
@@ -168,7 +208,7 @@ class AssetTagHelperTest < ActionView::TestCase
}
FaviconLinkToTag = {
- %(favicon_link_tag) => %(<link href="/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />),
+ %(favicon_link_tag) => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />),
%(favicon_link_tag 'favicon.ico') => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />),
%(favicon_link_tag 'favicon.ico', :rel => 'foo') => %(<link href="/images/favicon.ico" rel="foo" type="image/vnd.microsoft.icon" />),
%(favicon_link_tag 'favicon.ico', :rel => 'foo', :type => 'bar') => %(<link href="/images/favicon.ico" rel="foo" type="bar" />),
@@ -189,23 +229,38 @@ class AssetTagHelperTest < ActionView::TestCase
%(path_to_video("/dir/xml.ogg")) => %(/dir/xml.ogg)
}
+ VideoUrlToTag = {
+ %(video_url("xml")) => %(http://www.example.com/videos/xml),
+ %(video_url("xml.ogg")) => %(http://www.example.com/videos/xml.ogg),
+ %(video_url("dir/xml.ogg")) => %(http://www.example.com/videos/dir/xml.ogg),
+ %(video_url("/dir/xml.ogg")) => %(http://www.example.com/dir/xml.ogg)
+ }
+
+ UrlToVideoToTag = {
+ %(url_to_video("xml")) => %(http://www.example.com/videos/xml),
+ %(url_to_video("xml.ogg")) => %(http://www.example.com/videos/xml.ogg),
+ %(url_to_video("dir/xml.ogg")) => %(http://www.example.com/videos/dir/xml.ogg),
+ %(url_to_video("/dir/xml.ogg")) => %(http://www.example.com/dir/xml.ogg)
+ }
+
VideoLinkToTag = {
- %(video_tag("xml.ogg")) => %(<video src="/videos/xml.ogg" />),
- %(video_tag("rss.m4v", :autoplay => true, :controls => true)) => %(<video autoplay="autoplay" controls="controls" src="/videos/rss.m4v" />),
- %(video_tag("rss.m4v", :autobuffer => true)) => %(<video autobuffer="autobuffer" src="/videos/rss.m4v" />),
- %(video_tag("gold.m4v", :size => "160x120")) => %(<video height="120" src="/videos/gold.m4v" width="160" />),
- %(video_tag("gold.m4v", "size" => "320x240")) => %(<video height="240" src="/videos/gold.m4v" width="320" />),
- %(video_tag("trailer.ogg", :poster => "screenshot.png")) => %(<video poster="/images/screenshot.png" src="/videos/trailer.ogg" />),
- %(video_tag("error.avi", "size" => "100")) => %(<video src="/videos/error.avi" />),
- %(video_tag("error.avi", "size" => "100 x 100")) => %(<video src="/videos/error.avi" />),
- %(video_tag("error.avi", "size" => "x")) => %(<video src="/videos/error.avi" />),
- %(video_tag("http://media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="http://media.rubyonrails.org/video/rails_blog_2.mov" />),
- %(video_tag("//media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="//media.rubyonrails.org/video/rails_blog_2.mov" />),
- %(video_tag(["multiple.ogg", "multiple.avi"])) => %(<video><source src="multiple.ogg" /><source src="multiple.avi" /></video>),
- %(video_tag(["multiple.ogg", "multiple.avi"], :size => "160x120", :controls => true)) => %(<video controls="controls" height="120" width="160"><source src="multiple.ogg" /><source src="multiple.avi" /></video>)
+ %(video_tag("xml.ogg")) => %(<video src="/videos/xml.ogg"></video>),
+ %(video_tag("rss.m4v", :autoplay => true, :controls => true)) => %(<video autoplay="autoplay" controls="controls" src="/videos/rss.m4v"></video>),
+ %(video_tag("rss.m4v", :autobuffer => true)) => %(<video autobuffer="autobuffer" src="/videos/rss.m4v"></video>),
+ %(video_tag("gold.m4v", :size => "160x120")) => %(<video height="120" src="/videos/gold.m4v" width="160"></video>),
+ %(video_tag("gold.m4v", "size" => "320x240")) => %(<video height="240" src="/videos/gold.m4v" width="320"></video>),
+ %(video_tag("trailer.ogg", :poster => "screenshot.png")) => %(<video poster="/images/screenshot.png" src="/videos/trailer.ogg"></video>),
+ %(video_tag("error.avi", "size" => "100")) => %(<video src="/videos/error.avi"></video>),
+ %(video_tag("error.avi", "size" => "100 x 100")) => %(<video src="/videos/error.avi"></video>),
+ %(video_tag("error.avi", "size" => "x")) => %(<video src="/videos/error.avi"></video>),
+ %(video_tag("http://media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="http://media.rubyonrails.org/video/rails_blog_2.mov"></video>),
+ %(video_tag("//media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="//media.rubyonrails.org/video/rails_blog_2.mov"></video>),
+ %(video_tag("multiple.ogg", "multiple.avi")) => %(<video><source src="/videos/multiple.ogg" /><source src="/videos/multiple.avi" /></video>),
+ %(video_tag(["multiple.ogg", "multiple.avi"])) => %(<video><source src="/videos/multiple.ogg" /><source src="/videos/multiple.avi" /></video>),
+ %(video_tag(["multiple.ogg", "multiple.avi"], :size => "160x120", :controls => true)) => %(<video controls="controls" height="120" width="160"><source src="/videos/multiple.ogg" /><source src="/videos/multiple.avi" /></video>)
}
- AudioPathToTag = {
+ AudioPathToTag = {
%(audio_path("xml")) => %(/audios/xml),
%(audio_path("xml.wav")) => %(/audios/xml.wav),
%(audio_path("dir/xml.wav")) => %(/audios/dir/xml.wav),
@@ -219,11 +274,28 @@ class AssetTagHelperTest < ActionView::TestCase
%(path_to_audio("/dir/xml.wav")) => %(/dir/xml.wav)
}
+ AudioUrlToTag = {
+ %(audio_url("xml")) => %(http://www.example.com/audios/xml),
+ %(audio_url("xml.wav")) => %(http://www.example.com/audios/xml.wav),
+ %(audio_url("dir/xml.wav")) => %(http://www.example.com/audios/dir/xml.wav),
+ %(audio_url("/dir/xml.wav")) => %(http://www.example.com/dir/xml.wav)
+ }
+
+ UrlToAudioToTag = {
+ %(url_to_audio("xml")) => %(http://www.example.com/audios/xml),
+ %(url_to_audio("xml.wav")) => %(http://www.example.com/audios/xml.wav),
+ %(url_to_audio("dir/xml.wav")) => %(http://www.example.com/audios/dir/xml.wav),
+ %(url_to_audio("/dir/xml.wav")) => %(http://www.example.com/dir/xml.wav)
+ }
+
AudioLinkToTag = {
- %(audio_tag("xml.wav")) => %(<audio src="/audios/xml.wav" />),
- %(audio_tag("rss.wav", :autoplay => true, :controls => true)) => %(<audio autoplay="autoplay" controls="controls" src="/audios/rss.wav" />),
- %(audio_tag("http://media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="http://media.rubyonrails.org/audio/rails_blog_2.mov" />),
- %(audio_tag("//media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="//media.rubyonrails.org/audio/rails_blog_2.mov" />),
+ %(audio_tag("xml.wav")) => %(<audio src="/audios/xml.wav"></audio>),
+ %(audio_tag("rss.wav", :autoplay => true, :controls => true)) => %(<audio autoplay="autoplay" controls="controls" src="/audios/rss.wav"></audio>),
+ %(audio_tag("http://media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="http://media.rubyonrails.org/audio/rails_blog_2.mov"></audio>),
+ %(audio_tag("//media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="//media.rubyonrails.org/audio/rails_blog_2.mov"></audio>),
+ %(audio_tag("audio.mp3", "audio.ogg")) => %(<audio><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>),
+ %(audio_tag(["audio.mp3", "audio.ogg"])) => %(<audio><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>),
+ %(audio_tag(["audio.mp3", "audio.ogg"], :autobuffer => true, :controls => true)) => %(<audio autobuffer="autobuffer" controls="controls"><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>)
}
def test_auto_discovery_link_tag
@@ -238,6 +310,14 @@ class AssetTagHelperTest < ActionView::TestCase
PathToJavascriptToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_javascript_url
+ JavascriptUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_url_to_javascript_alias_for_javascript_url
+ UrlToJavascriptToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
def test_javascript_include_tag_with_blank_asset_id
ENV["RAILS_ASSET_ID"] = ""
JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
@@ -345,6 +425,15 @@ class AssetTagHelperTest < ActionView::TestCase
PathToStyleToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_stylesheet_url
+ ENV["RAILS_ASSET_ID"] = ""
+ StyleUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_url_to_stylesheet_alias_for_stylesheet_url
+ UrlToStyleToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
def test_stylesheet_link_tag
ENV["RAILS_ASSET_ID"] = ""
StyleLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
@@ -429,6 +518,14 @@ class AssetTagHelperTest < ActionView::TestCase
PathToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_image_url
+ ImageUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_url_to_image_alias_for_image_url
+ UrlToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
def test_image_alt
[nil, '/', '/foo/bar/', 'foo/bar/'].each do |prefix|
assert_equal 'Rails', image_alt("#{prefix}rails.png")
@@ -441,6 +538,12 @@ class AssetTagHelperTest < ActionView::TestCase
ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_image_tag_does_not_modify_options
+ options = {:size => '16x10'}
+ image_tag('icon', options)
+ assert_equal({:size => '16x10'}, options)
+ end
+
def test_favicon_link_tag
FaviconLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
@@ -468,6 +571,14 @@ class AssetTagHelperTest < ActionView::TestCase
PathToVideoToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_video_url
+ VideoUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_url_to_video_alias_for_video_url
+ UrlToVideoToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
def test_video_tag
VideoLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
@@ -480,10 +591,26 @@ class AssetTagHelperTest < ActionView::TestCase
PathToAudioToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_audio_url
+ AudioUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_url_to_audio_alias_for_audio_url
+ UrlToAudioToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
def test_audio_tag
AudioLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_video_audio_tag_does_not_modify_options
+ options = {:autoplay => true}
+ video_tag('video', options)
+ assert_equal({:autoplay => true}, options)
+ audio_tag('audio', options)
+ assert_equal({:autoplay => true}, options)
+ end
+
def test_timebased_asset_id
expected_time = File.mtime(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).to_i.to_s
assert_equal %(<img alt="Rails" src="/images/rails.png?#{expected_time}" />), image_tag("rails.png")
@@ -555,7 +682,6 @@ class AssetTagHelperTest < ActionView::TestCase
end
end
-
@controller.request.stubs(:ssl?).returns(false)
assert_equal "http://assets15.example.com/images/xml.png", image_path("xml.png")
@@ -1123,6 +1249,22 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png")))
end
+ def test_should_compute_proper_url_with_asset_host
+ @controller.config.asset_host = "assets.example.com"
+ assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag)
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_url("xmlhr"))
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_url("style"))
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_url("xml.png"))
+ end
+
+ def test_should_compute_proper_url_with_asset_host_and_default_protocol
+ @controller.config.asset_host = "assets.example.com"
+ @controller.config.default_asset_host_protocol = :request
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_url("xmlhr"))
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_url("style"))
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_url("xml.png"))
+ end
+
def test_should_ignore_asset_host_on_complete_url
@controller.config.asset_host = "http://assets.example.com"
assert_dom_equal(%(<link href="http://bar.example.com/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag("http://bar.example.com/stylesheets/style.css"))
@@ -1136,16 +1278,19 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
def test_should_wildcard_asset_host_between_zero_and_four
@controller.config.asset_host = 'http://a%d.example.com'
assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path('xml.png'))
+ assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_url('xml.png'))
end
def test_asset_host_without_protocol_should_be_protocol_relative
@controller.config.asset_host = 'a.example.com'
assert_equal 'gopher://a.example.com/collaboration/hieraki/images/xml.png', image_path('xml.png')
+ assert_equal 'gopher://a.example.com/collaboration/hieraki/images/xml.png', image_url('xml.png')
end
def test_asset_host_without_protocol_should_be_protocol_relative_even_if_path_present
@controller.config.asset_host = 'a.example.com/files/go/here'
assert_equal 'gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_path('xml.png')
+ assert_equal 'gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_url('xml.png')
end
def test_assert_css_and_js_of_the_same_name_return_correct_extension
diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb
index 81d7444cf8..d26aa9aa85 100644
--- a/actionpack/test/template/atom_feed_helper_test.rb
+++ b/actionpack/test/template/atom_feed_helper_test.rb
@@ -16,7 +16,7 @@ class ScrollsController < ActionController::Base
feed.title("My great blog!")
feed.updated((@scrolls.first.created_at))
- @scrolls.each do |scroll|
+ @scrolls.each do |scroll|
feed.entry(scroll) do |entry|
entry.title(scroll.title)
entry.content(scroll.body, :type => 'html')
@@ -185,11 +185,6 @@ class ScrollsController < ActionController::Base
render :inline => FEEDS[params[:id]], :type => :builder
end
-
- protected
- def rescue_action(e)
- raise(e)
- end
end
class AtomFeedTest < ActionController::TestCase
diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb
index e3d3d5ff77..82b62e64f3 100644
--- a/actionpack/test/template/date_helper_i18n_test.rb
+++ b/actionpack/test/template/date_helper_i18n_test.rb
@@ -103,7 +103,7 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase
I18n.expects(:translate).with(('datetime.prompts.' + key.to_s).to_sym, :locale => 'en').returns prompt
end
- I18n.expects(:translate).with(:'date.order', :locale => 'en').returns [:year, :month, :day]
+ I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:year, :month, :day]
datetime_select('post', 'updated_at', :locale => 'en', :include_seconds => true, :prompt => true)
end
@@ -115,7 +115,15 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase
end
def test_date_or_time_select_given_no_order_options_translates_order
- I18n.expects(:translate).with(:'date.order', :locale => 'en').returns [:year, :month, :day]
+ I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:year, :month, :day]
datetime_select('post', 'updated_at', :locale => 'en')
end
+
+ def test_date_or_time_select_given_invalid_order
+ I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:invalid, :month, :day]
+
+ assert_raise StandardError do
+ datetime_select('post', 'updated_at', :locale => 'en')
+ end
+ end
end
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index fadfb59572..9e2f4ec347 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -1587,7 +1587,7 @@ class DateHelperTest < ActionView::TestCase
start_year = Time.now.year-5
end_year = Time.now.year+5
- expected = '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i"/>' + "\n"
+ expected = '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i" value="1"/>' + "\n"
expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
expected << "<option value=\"\"></option>\n"
start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) }
@@ -1601,6 +1601,40 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, date_select("post", "written_on", :order=>[:year, :month], :include_blank=>true)
end
+ def test_date_select_with_nil_and_blank_and_discard_month
+ @post = Post.new
+
+ start_year = Time.now.year-5
+ end_year = Time.now.year+5
+
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected << "<option value=\"\"></option>\n"
+ start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) }
+ expected << "</select>\n"
+ expected << '<input name="post[written_on(2i)]" type="hidden" id="post_written_on_2i" value="1"/>' + "\n"
+ expected << '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i" value="1"/>' + "\n"
+
+ assert_dom_equal expected, date_select("post", "written_on", :discard_month => true, :include_blank=>true)
+ end
+
+ def test_date_select_with_nil_and_blank_and_discard_year
+ @post = Post.new
+
+ expected = '<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="1" />' + "\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ expected << "<option value=\"\"></option>\n"
+ 1.upto(12) { |i| expected << %(<option value="#{i}">#{Date::MONTHNAMES[i]}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
+ expected << "<option value=\"\"></option>\n"
+ 1.upto(31) { |i| expected << %(<option value="#{i}">#{i}</option>\n) }
+ expected << "</select>\n"
+
+ assert_dom_equal expected, date_select("post", "written_on", :discard_year => true, :include_blank=>true)
+ end
+
def test_date_select_cant_override_discard_hour
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
diff --git a/actionpack/test/template/erb_util_test.rb b/actionpack/test/template/erb_util_test.rb
index eba2ef64e0..ca2710e9b3 100644
--- a/actionpack/test/template/erb_util_test.rb
+++ b/actionpack/test/template/erb_util_test.rb
@@ -44,4 +44,18 @@ class ErbUtilTest < ActiveSupport::TestCase
assert_equal chr, html_escape(chr)
end
end
+
+ def test_html_escape_once
+ assert_equal '1 &lt; 2 &amp; 3', html_escape_once('1 < 2 &amp; 3')
+ end
+
+ def test_html_escape_once_returns_unsafe_strings_when_passed_unsafe_strings
+ value = html_escape_once('1 < 2 &amp; 3')
+ assert !value.html_safe?
+ end
+
+ def test_html_escape_once_returns_safe_strings_when_passed_safe_strings
+ value = html_escape_once('1 < 2 &amp; 3'.html_safe)
+ assert value.html_safe?
+ end
end
diff --git a/actionpack/test/template/form_collections_helper_test.rb b/actionpack/test/template/form_collections_helper_test.rb
new file mode 100644
index 0000000000..4d878635ef
--- /dev/null
+++ b/actionpack/test/template/form_collections_helper_test.rb
@@ -0,0 +1,327 @@
+require 'abstract_unit'
+
+class Category < Struct.new(:id, :name)
+end
+
+class FormCollectionsHelperTest < ActionView::TestCase
+ def assert_no_select(selector, value = nil)
+ assert_select(selector, :text => value, :count => 0)
+ end
+
+ def with_collection_radio_buttons(*args, &block)
+ @output_buffer = collection_radio_buttons(*args, &block)
+ end
+
+ def with_collection_check_boxes(*args, &block)
+ @output_buffer = collection_check_boxes(*args, &block)
+ end
+
+ # COLLECTION RADIO BUTTONS
+ test 'collection radio accepts a collection and generate inputs from value method' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s
+
+ assert_select 'input[type=radio][value=true]#user_active_true'
+ assert_select 'input[type=radio][value=false]#user_active_false'
+ end
+
+ test 'collection radio accepts a collection and generate inputs from label method' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s
+
+ assert_select 'label[for=user_active_true]', 'true'
+ assert_select 'label[for=user_active_false]', 'false'
+ end
+
+ test 'collection radio handles camelized collection values for labels correctly' do
+ with_collection_radio_buttons :user, :active, ['Yes', 'No'], :to_s, :to_s
+
+ assert_select 'label[for=user_active_yes]', 'Yes'
+ assert_select 'label[for=user_active_no]', 'No'
+ end
+
+ test 'colection radio should sanitize collection values for labels correctly' do
+ with_collection_radio_buttons :user, :name, ['$0.99', '$1.99'], :to_s, :to_s
+ assert_select 'label[for=user_name_099]', '$0.99'
+ assert_select 'label[for=user_name_199]', '$1.99'
+ end
+
+ test 'collection radio accepts checked item' do
+ with_collection_radio_buttons :user, :active, [[1, true], [0, false]], :last, :first, :checked => true
+
+ assert_select 'input[type=radio][value=true][checked=checked]'
+ assert_no_select 'input[type=radio][value=false][checked=checked]'
+ end
+
+ test 'collection radio accepts multiple disabled items' do
+ collection = [[1, true], [0, false], [2, 'other']]
+ with_collection_radio_buttons :user, :active, collection, :last, :first, :disabled => [true, false]
+
+ assert_select 'input[type=radio][value=true][disabled=disabled]'
+ assert_select 'input[type=radio][value=false][disabled=disabled]'
+ assert_no_select 'input[type=radio][value=other][disabled=disabled]'
+ end
+
+ test 'collection radio accepts single disable item' do
+ collection = [[1, true], [0, false]]
+ with_collection_radio_buttons :user, :active, collection, :last, :first, :disabled => true
+
+ assert_select 'input[type=radio][value=true][disabled=disabled]'
+ assert_no_select 'input[type=radio][value=false][disabled=disabled]'
+ end
+
+ test 'collection radio accepts html options as input' do
+ collection = [[1, true], [0, false]]
+ with_collection_radio_buttons :user, :active, collection, :last, :first, {}, :class => 'special-radio'
+
+ assert_select 'input[type=radio][value=true].special-radio#user_active_true'
+ assert_select 'input[type=radio][value=false].special-radio#user_active_false'
+ end
+
+ test 'collection radio does not wrap input inside the label' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s
+
+ assert_select 'input[type=radio] + label'
+ assert_no_select 'label input'
+ end
+
+ test 'collection radio accepts a block to render the label as radio button wrapper' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label { b.radio_button }
+ end
+
+ assert_select 'label[for=user_active_true] > input#user_active_true[type=radio]'
+ assert_select 'label[for=user_active_false] > input#user_active_false[type=radio]'
+ end
+
+ test 'collection radio accepts a block to change the order of label and radio button' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label + b.radio_button
+ end
+
+ assert_select 'label[for=user_active_true] + input#user_active_true[type=radio]'
+ assert_select 'label[for=user_active_false] + input#user_active_false[type=radio]'
+ end
+
+ test 'collection radio with block helpers accept extra html options' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label(:class => "radio_button") + b.radio_button(:class => "radio_button")
+ end
+
+ assert_select 'label.radio_button[for=user_active_true] + input#user_active_true.radio_button[type=radio]'
+ assert_select 'label.radio_button[for=user_active_false] + input#user_active_false.radio_button[type=radio]'
+ end
+
+ test 'collection radio with block helpers allows access to current text and value' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label(:"data-value" => b.value) { b.radio_button + b.text }
+ end
+
+ assert_select 'label[for=user_active_true][data-value=true]', 'true' do
+ assert_select 'input#user_active_true[type=radio]'
+ end
+ assert_select 'label[for=user_active_false][data-value=false]', 'false' do
+ assert_select 'input#user_active_false[type=radio]'
+ end
+ end
+
+ test 'collection radio with block helpers allows access to the current object item in the collection to access extra properties' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label(:class => b.object) { b.radio_button + b.text }
+ end
+
+ assert_select 'label.true[for=user_active_true]', 'true' do
+ assert_select 'input#user_active_true[type=radio]'
+ end
+ assert_select 'label.false[for=user_active_false]', 'false' do
+ assert_select 'input#user_active_false[type=radio]'
+ end
+ end
+
+ test 'collection radio buttons with fields for' do
+ collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
+ @output_buffer = fields_for(:post) do |p|
+ p.collection_radio_buttons :category_id, collection, :id, :name
+ end
+
+ assert_select 'input#post_category_id_1[type=radio][value=1]'
+ assert_select 'input#post_category_id_2[type=radio][value=2]'
+
+ assert_select 'label[for=post_category_id_1]', 'Category 1'
+ assert_select 'label[for=post_category_id_2]', 'Category 2'
+ end
+
+ # COLLECTION CHECK BOXES
+ test 'collection check boxes accepts a collection and generate a serie of checkboxes for value method' do
+ collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
+ with_collection_check_boxes :user, :category_ids, collection, :id, :name
+
+ assert_select 'input#user_category_ids_1[type=checkbox][value=1]'
+ assert_select 'input#user_category_ids_2[type=checkbox][value=2]'
+ end
+
+ test 'collection check boxes generates only one hidden field for the entire collection, to ensure something will be sent back to the server when posting an empty collection' do
+ collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
+ with_collection_check_boxes :user, :category_ids, collection, :id, :name
+
+ assert_select "input[type=hidden][name='user[category_ids][]'][value=]", :count => 1
+ end
+
+ test 'collection check boxes accepts a collection and generate a serie of checkboxes with labels for label method' do
+ collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
+ with_collection_check_boxes :user, :category_ids, collection, :id, :name
+
+ assert_select 'label[for=user_category_ids_1]', 'Category 1'
+ assert_select 'label[for=user_category_ids_2]', 'Category 2'
+ end
+
+ test 'collection check boxes handles camelized collection values for labels correctly' do
+ with_collection_check_boxes :user, :active, ['Yes', 'No'], :to_s, :to_s
+
+ assert_select 'label[for=user_active_yes]', 'Yes'
+ assert_select 'label[for=user_active_no]', 'No'
+ end
+
+ test 'colection check box should sanitize collection values for labels correctly' do
+ with_collection_check_boxes :user, :name, ['$0.99', '$1.99'], :to_s, :to_s
+ assert_select 'label[for=user_name_099]', '$0.99'
+ assert_select 'label[for=user_name_199]', '$1.99'
+ end
+
+ test 'collection check boxes accepts selected values as :checked option' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => [1, 3]
+
+ assert_select 'input[type=checkbox][value=1][checked=checked]'
+ assert_select 'input[type=checkbox][value=3][checked=checked]'
+ assert_no_select 'input[type=checkbox][value=2][checked=checked]'
+ end
+
+ test 'collection check boxes accepts a single checked value' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => 3
+
+ assert_select 'input[type=checkbox][value=3][checked=checked]'
+ assert_no_select 'input[type=checkbox][value=1][checked=checked]'
+ assert_no_select 'input[type=checkbox][value=2][checked=checked]'
+ end
+
+ test 'collection check boxes accepts selected values as :checked option and override the model values' do
+ user = Struct.new(:category_ids).new(2)
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+
+ @output_buffer = fields_for(:user, user) do |p|
+ p.collection_check_boxes :category_ids, collection, :first, :last, :checked => [1, 3]
+ end
+
+ assert_select 'input[type=checkbox][value=1][checked=checked]'
+ assert_select 'input[type=checkbox][value=3][checked=checked]'
+ assert_no_select 'input[type=checkbox][value=2][checked=checked]'
+ end
+
+ test 'collection check boxes accepts multiple disabled items' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :disabled => [1, 3]
+
+ assert_select 'input[type=checkbox][value=1][disabled=disabled]'
+ assert_select 'input[type=checkbox][value=3][disabled=disabled]'
+ assert_no_select 'input[type=checkbox][value=2][disabled=disabled]'
+ end
+
+ test 'collection check boxes accepts single disable item' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :disabled => 1
+
+ assert_select 'input[type=checkbox][value=1][disabled=disabled]'
+ assert_no_select 'input[type=checkbox][value=3][disabled=disabled]'
+ assert_no_select 'input[type=checkbox][value=2][disabled=disabled]'
+ end
+
+ test 'collection check boxes accepts a proc to disabled items' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :disabled => proc { |i| i.first == 1 }
+
+ assert_select 'input[type=checkbox][value=1][disabled=disabled]'
+ assert_no_select 'input[type=checkbox][value=3][disabled=disabled]'
+ assert_no_select 'input[type=checkbox][value=2][disabled=disabled]'
+ end
+
+ test 'collection check boxes accepts html options' do
+ collection = [[1, 'Category 1'], [2, 'Category 2']]
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, {}, :class => 'check'
+
+ assert_select 'input.check[type=checkbox][value=1]'
+ assert_select 'input.check[type=checkbox][value=2]'
+ end
+
+ test 'collection check boxes with fields for' do
+ collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
+ @output_buffer = fields_for(:post) do |p|
+ p.collection_check_boxes :category_ids, collection, :id, :name
+ end
+
+ assert_select 'input#post_category_ids_1[type=checkbox][value=1]'
+ assert_select 'input#post_category_ids_2[type=checkbox][value=2]'
+
+ assert_select 'label[for=post_category_ids_1]', 'Category 1'
+ assert_select 'label[for=post_category_ids_2]', 'Category 2'
+ end
+
+ test 'collection check boxes does not wrap input inside the label' do
+ with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s
+
+ assert_select 'input[type=checkbox] + label'
+ assert_no_select 'label input'
+ end
+
+ test 'collection check boxes accepts a block to render the label as check box wrapper' do
+ with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label { b.check_box }
+ end
+
+ assert_select 'label[for=user_active_true] > input#user_active_true[type=checkbox]'
+ assert_select 'label[for=user_active_false] > input#user_active_false[type=checkbox]'
+ end
+
+ test 'collection check boxes accepts a block to change the order of label and check box' do
+ with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label + b.check_box
+ end
+
+ assert_select 'label[for=user_active_true] + input#user_active_true[type=checkbox]'
+ assert_select 'label[for=user_active_false] + input#user_active_false[type=checkbox]'
+ end
+
+ test 'collection check boxes with block helpers accept extra html options' do
+ with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label(:class => "check_box") + b.check_box(:class => "check_box")
+ end
+
+ assert_select 'label.check_box[for=user_active_true] + input#user_active_true.check_box[type=checkbox]'
+ assert_select 'label.check_box[for=user_active_false] + input#user_active_false.check_box[type=checkbox]'
+ end
+
+ test 'collection check boxes with block helpers allows access to current text and value' do
+ with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label(:"data-value" => b.value) { b.check_box + b.text }
+ end
+
+ assert_select 'label[for=user_active_true][data-value=true]', 'true' do
+ assert_select 'input#user_active_true[type=checkbox]'
+ end
+ assert_select 'label[for=user_active_false][data-value=false]', 'false' do
+ assert_select 'input#user_active_false[type=checkbox]'
+ end
+ end
+
+ test 'collection check boxes with block helpers allows access to the current object item in the collection to access extra properties' do
+ with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label(:class => b.object) { b.check_box + b.text }
+ end
+
+ assert_select 'label.true[for=user_active_true]', 'true' do
+ assert_select 'input#user_active_true[type=checkbox]'
+ end
+ assert_select 'label.false[for=user_active_false]', 'false' do
+ assert_select 'input#user_active_false[type=checkbox]'
+ end
+ end
+end
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 82e001732d..4eed5615f6 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -115,6 +115,14 @@ class FormHelperTest < ActionView::TestCase
super
end
+ class FooTag < ActionView::Helpers::Tags::Base
+ def initialize; end
+ end
+
+ def test_tags_base_child_without_render_method
+ assert_raise(NotImplementedError) { FooTag.new.render }
+ end
+
def test_label
assert_dom_equal('<label for="post_title">Title</label>', label("post", "title"))
assert_dom_equal('<label for="post_title">The title goes here</label>', label("post", "title", "The title goes here"))
@@ -173,7 +181,7 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form("/posts/123", "create-post" , "edit_post", :method => "put") do
+ expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch') do
"<label for=\"post_comments_attributes_0_body\">Write body here</label>"
end
@@ -190,7 +198,7 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form("/posts/123", "create-post" , "edit_post", :method => "put") do
+ expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch') do
"<label for=\"post_tags_attributes_0_value\">Tag</label>"
end
@@ -207,6 +215,10 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, "for" => "my_for"))
end
+ def test_label_does_not_generate_for_attribute_when_given_nil
+ assert_dom_equal('<label>Title</label>', label(:post, :title, :for => nil))
+ 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
@@ -232,6 +244,10 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal('<label for="post_title">The title, please:</label>', label(:post, :title) { "The title, please:" })
end
+ def test_label_with_block_and_options
+ assert_dom_equal('<label for="my_for">The title, please:</label>', label(:post, :title, "for" => "my_for") { "The title, please:" })
+ end
+
def test_label_with_block_in_erb
assert_equal "<label for=\"post_message\">\n Message\n <input id=\"post_message\" name=\"post[message]\" size=\"30\" type=\"text\" />\n</label>", view.render("test/label_with_block")
end
@@ -289,6 +305,11 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, text_field("post", "title", :value => nil)
end
+ def test_text_field_with_nil_name
+ expected = '<input id="post_title" size="30" type="text" value="Hello World" />'
+ assert_dom_equal expected, text_field("post", "title", :name => nil)
+ end
+
def test_text_field_doesnt_change_param_values
object_name = 'post[]'
expected = '<input id="post_123_title" name="post[123][title]" size="30" type="text" value="Hello World" />'
@@ -382,6 +403,10 @@ class FormHelperTest < ActionView::TestCase
)
end
+ def test_check_box_with_nil_unchecked_value_is_html_safe
+ assert check_box("post", "secret", {}, "on", nil).html_safe?
+ end
+
def test_check_box_with_multiple_behavior
@post.comment_ids = [2,3]
assert_dom_equal(
@@ -401,6 +426,13 @@ class FormHelperTest < ActionView::TestCase
)
end
+ def test_checkbox_form_html5_attribute
+ assert_dom_equal(
+ '<input form="new_form" name="post[secret]" type="hidden" value="0" /><input checked="checked" form="new_form" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
+ check_box("post", "secret", :form => "new_form")
+ )
+ end
+
def test_radio_button
assert_dom_equal('<input checked="checked" id="post_title_hello_world" name="post[title]" type="radio" value="Hello World" />',
radio_button("post", "title", "Hello World")
@@ -442,7 +474,7 @@ class FormHelperTest < ActionView::TestCase
def test_text_area
assert_dom_equal(
- '<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
+ %{<textarea cols="40" id="post_body" name="post[body]" rows="20">\nBack to the hill and over it again!</textarea>},
text_area("post", "body")
)
end
@@ -450,14 +482,14 @@ class FormHelperTest < ActionView::TestCase
def test_text_area_with_escapes
@post.body = "Back to <i>the</i> hill and over it again!"
assert_dom_equal(
- '<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to &lt;i&gt;the&lt;/i&gt; hill and over it again!</textarea>',
+ %{<textarea cols="40" id="post_body" name="post[body]" rows="20">\nBack to &lt;i&gt;the&lt;/i&gt; hill and over it again!</textarea>},
text_area("post", "body")
)
end
def test_text_area_with_alternate_value
assert_dom_equal(
- '<textarea cols="40" id="post_body" name="post[body]" rows="20">Testing alternate values.</textarea>',
+ %{<textarea cols="40" id="post_body" name="post[body]" rows="20">\nTesting alternate values.</textarea>},
text_area("post", "body", :value => 'Testing alternate values.')
)
end
@@ -465,14 +497,14 @@ class FormHelperTest < ActionView::TestCase
def test_text_area_with_html_entities
@post.body = "The HTML Entity for & is &amp;"
assert_dom_equal(
- '<textarea cols="40" id="post_body" name="post[body]" rows="20">The HTML Entity for &amp; is &amp;amp;</textarea>',
+ %{<textarea cols="40" id="post_body" name="post[body]" rows="20">\nThe HTML Entity for &amp; is &amp;amp;</textarea>},
text_area("post", "body")
)
end
def test_text_area_with_size_option
assert_dom_equal(
- '<textarea cols="183" id="post_body" name="post[body]" rows="820">Back to the hill and over it again!</textarea>',
+ %{<textarea cols="183" id="post_body" name="post[body]" rows="820">\nBack to the hill and over it again!</textarea>},
text_area("post", "body", :size => "183x820")
)
end
@@ -487,6 +519,32 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal(expected, telephone_field("user", "cell"))
end
+ def test_date_field
+ expected = %{<input id="post_written_on" name="post[written_on]" type="date" value="2004-06-15" />}
+ assert_dom_equal(expected, date_field("post", "written_on"))
+ end
+
+ def test_date_field_with_datetime_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="date" value="2004-06-15" />}
+ @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
+ assert_dom_equal(expected, date_field("post", "written_on"))
+ end
+
+ def test_date_field_with_timewithzone_value
+ previous_time_zone, Time.zone = Time.zone, 'UTC'
+ expected = %{<input id="post_written_on" name="post[written_on]" type="date" value="2004-06-15" />}
+ @post.written_on = Time.zone.parse('2004-06-15 15:30:45')
+ assert_dom_equal(expected, date_field("post", "written_on"))
+ ensure
+ Time.zone = previous_time_zone
+ end
+
+ def test_date_field_with_nil_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="date" />}
+ @post.written_on = nil
+ assert_dom_equal(expected, date_field("post", "written_on"))
+ end
+
def test_url_field
expected = %{<input id="user_homepage" size="30" name="user[homepage]" type="url" />}
assert_dom_equal(expected, url_field("user", "homepage"))
@@ -516,7 +574,7 @@ class FormHelperTest < ActionView::TestCase
'<input id="post_title" name="dont guess" size="30" type="text" value="Hello World" />', text_field("post", "title", "name" => "dont guess")
)
assert_dom_equal(
- '<textarea cols="40" id="post_body" name="really!" rows="20">Back to the hill and over it again!</textarea>',
+ %{<textarea cols="40" id="post_body" name="really!" rows="20">\nBack to the hill and over it again!</textarea>},
text_area("post", "body", "name" => "really!")
)
assert_dom_equal(
@@ -536,7 +594,7 @@ class FormHelperTest < ActionView::TestCase
'<input id="dont guess" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title", "id" => "dont guess")
)
assert_dom_equal(
- '<textarea cols="40" id="really!" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
+ %{<textarea cols="40" id="really!" name="post[body]" rows="20">\nBack to the hill and over it again!</textarea>},
text_area("post", "body", "id" => "really!")
)
assert_dom_equal(
@@ -556,7 +614,7 @@ class FormHelperTest < ActionView::TestCase
'<input name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title", "id" => nil)
)
assert_dom_equal(
- '<textarea cols="40" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
+ %{<textarea cols="40" name="post[body]" rows="20">\nBack to the hill and over it again!</textarea>},
text_area("post", "body", "id" => nil)
)
assert_dom_equal(
@@ -587,7 +645,7 @@ class FormHelperTest < ActionView::TestCase
text_field("post", "title", "index" => 5)
)
assert_dom_equal(
- '<textarea cols="40" name="post[5][body]" id="post_5_body" rows="20">Back to the hill and over it again!</textarea>',
+ %{<textarea cols="40" name="post[5][body]" id="post_5_body" rows="20">\nBack to the hill and over it again!</textarea>},
text_area("post", "body", "index" => 5)
)
assert_dom_equal(
@@ -614,7 +672,7 @@ class FormHelperTest < ActionView::TestCase
text_field("post", "title", "index" => 5, 'id' => nil)
)
assert_dom_equal(
- '<textarea cols="40" name="post[5][body]" rows="20">Back to the hill and over it again!</textarea>',
+ %{<textarea cols="40" name="post[5][body]" rows="20">\nBack to the hill and over it again!</textarea>},
text_area("post", "body", "index" => 5, 'id' => nil)
)
assert_dom_equal(
@@ -645,7 +703,7 @@ class FormHelperTest < ActionView::TestCase
"<input id=\"post_#{pid}_title\" name=\"post[#{pid}][title]\" size=\"30\" type=\"text\" value=\"Hello World\" />", text_field("post[]","title")
)
assert_dom_equal(
- "<textarea cols=\"40\" id=\"post_#{pid}_body\" name=\"post[#{pid}][body]\" rows=\"20\">Back to the hill and over it again!</textarea>",
+ "<textarea cols=\"40\" id=\"post_#{pid}_body\" name=\"post[#{pid}][body]\" rows=\"20\">\nBack to the hill and over it again!</textarea>",
text_area("post[]", "body")
)
assert_dom_equal(
@@ -653,7 +711,7 @@ class FormHelperTest < ActionView::TestCase
check_box("post[]", "secret")
)
assert_dom_equal(
-"<input checked=\"checked\" id=\"post_#{pid}_title_hello_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Hello World\" />",
+ "<input checked=\"checked\" id=\"post_#{pid}_title_hello_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Hello World\" />",
radio_button("post[]", "title", "Hello World")
)
assert_dom_equal("<input id=\"post_#{pid}_title_goodbye_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Goodbye World\" />",
@@ -668,7 +726,7 @@ class FormHelperTest < ActionView::TestCase
text_field("post[]","title", :id => nil)
)
assert_dom_equal(
- "<textarea cols=\"40\" name=\"post[#{pid}][body]\" rows=\"20\">Back to the hill and over it again!</textarea>",
+ "<textarea cols=\"40\" name=\"post[#{pid}][body]\" rows=\"20\">\nBack to the hill and over it again!</textarea>",
text_area("post[]", "body", :id => nil)
)
assert_dom_equal(
@@ -700,10 +758,10 @@ class FormHelperTest < ActionView::TestCase
concat f.button('Create post')
end
- expected = whole_form("/posts/123", "create-post" , "edit_post", :method => "put") do
+ expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch') do
"<label for='post_title'>The Title</label>" +
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
"<input name='commit' type='submit' value='Create post' />" +
@@ -713,6 +771,44 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_with_collection_radio_buttons
+ post = Post.new
+ def post.active; false; end
+ form_for(post) do |f|
+ concat f.collection_radio_buttons(:active, [true, false], :to_s, :to_s)
+ end
+
+ expected = whole_form("/posts", "new_post" , "new_post") do
+ "<input id='post_active_true' name='post[active]' type='radio' value='true' />" +
+ "<label for='post_active_true'>true</label>" +
+ "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" +
+ "<label for='post_active_false'>false</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_for_with_collection_check_boxes
+ post = Post.new
+ def post.tag_ids; [1, 3]; end
+ collection = (1..3).map{|i| [i, "Tag #{i}"] }
+ form_for(post) do |f|
+ concat f.collection_check_boxes(:tag_ids, collection, :first, :last)
+ end
+
+ expected = whole_form("/posts", "new_post" , "new_post") do
+ "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
+ "<label for='post_tag_ids_1'>Tag 1</label>" +
+ "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" +
+ "<label for='post_tag_ids_2'>Tag 2</label>" +
+ "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" +
+ "<label for='post_tag_ids_3'>Tag 3</label>" +
+ "<input name='post[tag_ids][]' type='hidden' value='' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_file_field_generate_multipart
Post.send :attr_accessor, :file
@@ -720,7 +816,7 @@ class FormHelperTest < ActionView::TestCase
concat f.file_field(:file)
end
- expected = whole_form("/posts/123", "create-post" , "edit_post", :method => "put", :multipart => true) do
+ expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch', :multipart => true) do
"<input name='post[file]' type='file' id='post_file' />"
end
@@ -736,7 +832,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form("/posts/123", "edit_post_123" , "edit_post", :method => "put", :multipart => true) do
+ expected = whole_form("/posts/123", "edit_post_123" , "edit_post", :method => 'patch', :multipart => true) do
"<input name='post[comment][file]' type='file' id='post_comment_file' />"
end
@@ -749,7 +845,7 @@ class FormHelperTest < ActionView::TestCase
concat f.label(:title)
end
- expected = whole_form("/posts/123.json", "edit_post_123" , "edit_post", :method => "put") do
+ expected = whole_form("/posts/123.json", "edit_post_123" , "edit_post", :method => 'patch') do
"<label for='post_title'>Title</label>"
end
@@ -762,7 +858,7 @@ class FormHelperTest < ActionView::TestCase
concat f.submit('Edit post')
end
- expected = whole_form("/posts/44", "edit_post_44" , "edit_post", :method => "put") do
+ expected = whole_form("/posts/44", "edit_post_44" , "edit_post", :method => 'patch') do
"<input name='post[title]' size='30' type='text' id='post_title' value='And his name will be forty and four.' />" +
"<input name='commit' type='submit' value='Edit post' />"
end
@@ -779,10 +875,10 @@ class FormHelperTest < ActionView::TestCase
concat f.submit('Create post')
end
- expected = whole_form("/posts/123", "create-post", "edit_other_name", :method => "put") do
+ expected = whole_form("/posts/123", "create-post", "edit_other_name", :method => 'patch') do
"<label for='other_name_title' class='post_title'>Title</label>" +
"<input name='other_name[title]' size='30' id='other_name_title' value='Hello World' type='text' />" +
- "<textarea name='other_name[body]' id='other_name_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='other_name[body]' id='other_name_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='other_name[secret]' value='0' type='hidden' />" +
"<input name='other_name[secret]' checked='checked' id='other_name_secret' value='1' type='checkbox' />" +
"<input name='commit' value='Create post' type='submit' />"
@@ -800,7 +896,7 @@ class FormHelperTest < ActionView::TestCase
expected = whole_form("/", "create-post", "edit_post", "delete") do
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
end
@@ -817,7 +913,7 @@ class FormHelperTest < ActionView::TestCase
expected = whole_form("/", "create-post", "edit_post", "delete") do
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
end
@@ -840,15 +936,15 @@ class FormHelperTest < ActionView::TestCase
end
def test_form_for_with_remote
- form_for(@post, :url => '/', :remote => true, :html => { :id => 'create-post', :method => :put }) do |f|
+ form_for(@post, :url => '/', :remote => true, :html => { :id => 'create-post', :method => :patch}) do |f|
concat f.text_field(:title)
concat f.text_area(:body)
concat f.check_box(:secret)
end
- expected = whole_form("/", "create-post", "edit_post", :method => "put", :remote => true) do
+ expected = whole_form("/", "create-post", "edit_post", :method => 'patch', :remote => true) do
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
end
@@ -857,15 +953,15 @@ class FormHelperTest < ActionView::TestCase
end
def test_form_for_with_remote_in_html
- form_for(@post, :url => '/', :html => { :remote => true, :id => 'create-post', :method => :put }) do |f|
+ form_for(@post, :url => '/', :html => { :remote => true, :id => 'create-post', :method => :patch }) do |f|
concat f.text_field(:title)
concat f.text_area(:body)
concat f.check_box(:secret)
end
- expected = whole_form("/", "create-post", "edit_post", :method => "put", :remote => true) do
+ expected = whole_form("/", "create-post", "edit_post", :method => 'patch', :remote => true) do
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
end
@@ -884,7 +980,7 @@ class FormHelperTest < ActionView::TestCase
expected = whole_form("/posts", 'new_post', 'new_post', :remote => true) do
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
end
@@ -901,7 +997,7 @@ class FormHelperTest < ActionView::TestCase
expected = whole_form("/", "create-post") do
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
end
@@ -917,10 +1013,10 @@ class FormHelperTest < ActionView::TestCase
concat f.check_box(:secret)
end
- expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'put') do
+ expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do
"<label for='post_123_title'>Title</label>" +
"<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" +
- "<textarea name='post[123][body]' id='post_123_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[123][body]' id='post_123_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[123][secret]' type='hidden' value='0' />" +
"<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />"
end
@@ -935,9 +1031,9 @@ class FormHelperTest < ActionView::TestCase
concat f.check_box(:secret)
end
- expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'put') do
+ expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do
"<input name='post[][title]' size='30' type='text' id='post__title' value='Hello World' />" +
- "<textarea name='post[][body]' id='post__body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[][body]' id='post__body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[][secret]' type='hidden' value='0' />" +
"<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />"
end
@@ -952,9 +1048,9 @@ class FormHelperTest < ActionView::TestCase
concat f.check_box(:secret)
end
- expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'put') do
+ expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'patch') do
"<input name='post[title]' size='30' type='text' id='namespace_post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='namespace_post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[body]' id='namespace_post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='namespace_post_secret' value='1' />"
end
@@ -968,7 +1064,7 @@ class FormHelperTest < ActionView::TestCase
concat f.text_field(:title)
end
- expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'put') do
+ expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'patch') do
"<label for='namespace_post_title'>Title</label>" +
"<input name='post[title]' size='30' type='text' id='namespace_post_title' value='Hello World' />"
end
@@ -982,7 +1078,7 @@ class FormHelperTest < ActionView::TestCase
concat f.text_field(:title)
end
- expected_1 = whole_form('/posts/123', 'namespace_1_edit_post_123', 'edit_post', 'put') do
+ expected_1 = whole_form('/posts/123', 'namespace_1_edit_post_123', 'edit_post', 'patch') do
"<label for='namespace_1_post_title'>Title</label>" +
"<input name='post[title]' size='30' type='text' id='namespace_1_post_title' value='Hello World' />"
end
@@ -994,7 +1090,7 @@ class FormHelperTest < ActionView::TestCase
concat f.text_field(:title)
end
- expected_2 = whole_form('/posts/123', 'namespace_2_edit_post_123', 'edit_post', 'put') do
+ expected_2 = whole_form('/posts/123', 'namespace_2_edit_post_123', 'edit_post', 'patch') do
"<label for='namespace_2_post_title'>Title</label>" +
"<input name='post[title]' size='30' type='text' id='namespace_2_post_title' value='Hello World' />"
end
@@ -1012,9 +1108,9 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'put') do
+ expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'patch') do
"<input name='post[title]' size='30' type='text' id='namespace_post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='namespace_post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[body]' id='namespace_post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[comment][body]' size='30' type='text' id='namespace_post_comment_body' value='Hello World' />"
end
@@ -1046,7 +1142,7 @@ class FormHelperTest < ActionView::TestCase
concat f.submit
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
"<input name='commit' type='submit' value='Confirm Post changes' />"
end
@@ -1078,7 +1174,7 @@ class FormHelperTest < ActionView::TestCase
concat f.submit
end
- expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', :method => 'patch') do
"<input name='commit' type='submit' value='Update your Post' />"
end
@@ -1095,7 +1191,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
"<input name='post[comment][body]' size='30' type='text' id='post_comment_body' value='Hello World' />"
end
@@ -1110,7 +1206,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'put') do
+ expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do
"<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" +
"<input name='post[123][comment][][name]' size='30' type='text' id='post_123_comment__name' value='new comment' />"
end
@@ -1126,7 +1222,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do
"<input name='post[1][title]' size='30' type='text' id='post_1_title' value='Hello World' />" +
"<input name='post[1][comment][1][name]' size='30' type='text' id='post_1_comment_1_name' value='new comment' />"
end
@@ -1141,7 +1237,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do
"<input name='post[1][comment][title]' size='30' type='text' id='post_1_comment_title' value='Hello World' />"
end
@@ -1155,7 +1251,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do
"<input name='post[1][comment][5][title]' size='30' type='text' id='post_1_comment_5_title' value='Hello World' />"
end
@@ -1169,7 +1265,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'put') do
+ expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do
"<input name='post[123][comment][title]' size='30' type='text' id='post_123_comment_title' value='Hello World' />"
end
@@ -1183,7 +1279,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do
"<input name='post[comment][5][title]' type='radio' id='post_comment_5_title_hello' value='hello' />"
end
@@ -1197,7 +1293,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'put') do
+ expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do
"<input name='post[123][comment][123][title]' size='30' type='text' id='post_123_comment_123_title' value='Hello World' />"
end
@@ -1217,9 +1313,9 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'put') do
+ expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do
"<input name='post[123][comment][5][title]' size='30' type='text' id='post_123_comment_5_title' value='Hello World' />"
- end + whole_form('/posts/123', 'edit_post', 'edit_post', 'put') do
+ end + whole_form('/posts/123', 'edit_post', 'edit_post', 'patch') do
"<input name='post[1][comment][123][title]' size='30' type='text' id='post_1_comment_123_title' value='Hello World' />"
end
@@ -1236,7 +1332,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="new author" />'
end
@@ -1263,7 +1359,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />'
@@ -1282,7 +1378,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />'
@@ -1301,7 +1397,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />'
end
@@ -1319,7 +1415,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />'
end
@@ -1337,7 +1433,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />'
@@ -1357,7 +1453,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
'<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />'
@@ -1378,7 +1474,7 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
@@ -1405,7 +1501,7 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
@@ -1432,7 +1528,7 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
@@ -1458,7 +1554,7 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
@@ -1481,7 +1577,7 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
@@ -1505,7 +1601,7 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
@@ -1528,7 +1624,7 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="new comment" />' +
'<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />'
@@ -1549,7 +1645,7 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
@@ -1567,7 +1663,7 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />'
end
@@ -1584,7 +1680,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
@@ -1605,7 +1701,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
@@ -1627,7 +1723,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
@@ -1650,7 +1746,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
@@ -1670,7 +1766,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" size="30" type="text" value="comment #321" />' +
'<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
end
@@ -1706,7 +1802,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
'<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="commentrelevance #314" />' +
'<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' +
@@ -1733,7 +1829,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
'<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="hash backed author" />'
end
@@ -1749,7 +1845,7 @@ class FormHelperTest < ActionView::TestCase
expected =
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
@@ -1765,7 +1861,7 @@ class FormHelperTest < ActionView::TestCase
expected =
"<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" +
- "<textarea name='post[123][body]' id='post_123_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[123][body]' id='post_123_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[123][secret]' type='hidden' value='0' />" +
"<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />"
@@ -1781,7 +1877,7 @@ class FormHelperTest < ActionView::TestCase
expected =
"<input name='post[][title]' size='30' type='text' id='post__title' value='Hello World' />" +
- "<textarea name='post[][body]' id='post__body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[][body]' id='post__body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[][secret]' type='hidden' value='0' />" +
"<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />"
@@ -1797,7 +1893,7 @@ class FormHelperTest < ActionView::TestCase
expected =
"<input name='post[abc][title]' size='30' type='text' id='post_abc_title' value='Hello World' />" +
- "<textarea name='post[abc][body]' id='post_abc_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[abc][body]' id='post_abc_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[abc][secret]' type='hidden' value='0' />" +
"<input name='post[abc][secret]' checked='checked' type='checkbox' id='post_abc_secret' value='1' />"
@@ -1813,7 +1909,7 @@ class FormHelperTest < ActionView::TestCase
expected =
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
@@ -1829,7 +1925,7 @@ class FormHelperTest < ActionView::TestCase
expected =
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
@@ -1872,9 +1968,9 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'create-post', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'create-post', 'edit_post', :method => 'patch') do
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='parent_post[secret]' type='hidden' value='0' />" +
"<input name='parent_post[secret]' checked='checked' type='checkbox' id='parent_post_secret' value='1' />"
end
@@ -1892,9 +1988,9 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'create-post', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'create-post', 'edit_post', :method => 'patch') do
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' size='30' />"
end
@@ -1908,7 +2004,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do
"<input name='post[category][name]' type='text' size='30' id='post_category_name' />"
end
@@ -1932,46 +2028,15 @@ class FormHelperTest < ActionView::TestCase
concat f.check_box(:secret)
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
"<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
- "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
+ "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea><br/>" +
"<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>"
end
assert_dom_equal expected, output_buffer
end
- def hidden_fields(method = nil)
- txt = %{<div style="margin:0;padding:0;display:inline">}
- txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
- if method && !method.to_s.in?(['get', 'post'])
- txt << %{<input name="_method" type="hidden" value="#{method}" />}
- end
- txt << %{</div>}
- end
-
- def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil)
- txt = %{<form accept-charset="UTF-8" action="#{action}"}
- txt << %{ enctype="multipart/form-data"} if multipart
- txt << %{ data-remote="true"} if remote
- txt << %{ class="#{html_class}"} if html_class
- txt << %{ id="#{id}"} if id
- method = method.to_s == "get" ? "get" : "post"
- txt << %{ method="#{method}">}
- end
-
- def whole_form(action = "/", id = nil, html_class = nil, options = nil)
- contents = block_given? ? yield : ""
-
- if options.is_a?(Hash)
- method, remote, multipart = options.values_at(:method, :remote, :multipart)
- else
- method = options
- end
-
- form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>"
- end
-
def test_default_form_builder
old_default_form_builder, ActionView::Base.default_form_builder =
ActionView::Base.default_form_builder, LabelledFormBuilder
@@ -1982,9 +2047,9 @@ class FormHelperTest < ActionView::TestCase
concat f.check_box(:secret)
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
"<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
- "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
+ "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea><br/>" +
"<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>"
end
@@ -2002,7 +2067,7 @@ class FormHelperTest < ActionView::TestCase
expected =
"<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
- "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
+ "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>\nBack to the hill and over it again!</textarea><br/>" +
"<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>"
assert_dom_equal expected, output_buffer
@@ -2062,7 +2127,7 @@ class FormHelperTest < ActionView::TestCase
def test_form_for_with_html_options_adds_options_to_form_tag
form_for(@post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end
- expected = whole_form("/posts/123", "some_form", "some_class", 'put')
+ expected = whole_form("/posts/123", "some_form", "some_class", 'patch')
assert_dom_equal expected, output_buffer
end
@@ -2070,7 +2135,7 @@ class FormHelperTest < ActionView::TestCase
def test_form_for_with_string_url_option
form_for(@post, :url => 'http://www.otherdomain.com') do |f| end
- assert_equal whole_form("http://www.otherdomain.com", 'edit_post_123', 'edit_post', 'put'), output_buffer
+ assert_equal whole_form("http://www.otherdomain.com", 'edit_post_123', 'edit_post', 'patch'), output_buffer
end
def test_form_for_with_hash_url_option
@@ -2083,14 +2148,14 @@ class FormHelperTest < ActionView::TestCase
def test_form_for_with_record_url_option
form_for(@post, :url => @post) do |f| end
- expected = whole_form("/posts/123", 'edit_post_123', 'edit_post', 'put')
+ expected = whole_form("/posts/123", 'edit_post_123', 'edit_post', 'patch')
assert_equal expected, output_buffer
end
def test_form_for_with_existing_object
form_for(@post) do |f| end
- expected = whole_form("/posts/123", "edit_post_123", "edit_post", "put")
+ expected = whole_form("/posts/123", "edit_post_123", "edit_post", 'patch')
assert_equal expected, output_buffer
end
@@ -2109,7 +2174,7 @@ class FormHelperTest < ActionView::TestCase
@comment.save
form_for([@post, @comment]) {}
- expected = whole_form(post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put")
+ expected = whole_form(post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", 'patch')
assert_dom_equal expected, output_buffer
end
@@ -2124,7 +2189,7 @@ class FormHelperTest < ActionView::TestCase
@comment.save
form_for([:admin, @post, @comment]) {}
- expected = whole_form(admin_post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put")
+ expected = whole_form(admin_post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", 'patch')
assert_dom_equal expected, output_buffer
end
@@ -2138,18 +2203,55 @@ class FormHelperTest < ActionView::TestCase
def test_form_for_with_existing_object_and_custom_url
form_for(@post, :url => "/super_posts") do |f| end
- expected = whole_form("/super_posts", "edit_post_123", "edit_post", "put")
+ expected = whole_form("/super_posts", "edit_post_123", "edit_post", 'patch')
assert_equal expected, output_buffer
end
+ def test_form_for_with_default_method_as_patch
+ form_for(@post) {}
+ expected = whole_form("/posts/123", "edit_post_123", "edit_post", "patch")
+ assert_dom_equal expected, output_buffer
+ end
+
def test_fields_for_returns_block_result
output = fields_for(Post.new) { |f| "fields" }
assert_equal "fields", output
end
protected
- def protect_against_forgery?
- false
+
+ def hidden_fields(method = nil)
+ txt = %{<div style="margin:0;padding:0;display:inline">}
+ txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ if method && !method.to_s.in?(['get', 'post'])
+ txt << %{<input name="_method" type="hidden" value="#{method}" />}
+ end
+ txt << %{</div>}
+ end
+
+ def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil)
+ txt = %{<form accept-charset="UTF-8" action="#{action}"}
+ txt << %{ enctype="multipart/form-data"} if multipart
+ txt << %{ data-remote="true"} if remote
+ txt << %{ class="#{html_class}"} if html_class
+ txt << %{ id="#{id}"} if id
+ method = method.to_s == "get" ? "get" : "post"
+ txt << %{ method="#{method}">}
+ end
+
+ def whole_form(action = "/", id = nil, html_class = nil, options = nil)
+ contents = block_given? ? yield : ""
+
+ if options.is_a?(Hash)
+ method, remote, multipart = options.values_at(:method, :remote, :multipart)
+ else
+ method = options
end
+ form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>"
+ end
+
+ def protect_against_forgery?
+ false
+ end
end
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 4a889beadd..6b8d62df62 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -87,6 +87,20 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_collection_options_with_proc_for_value_method
+ assert_dom_equal(
+ "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
+ options_from_collection_for_select(dummy_posts, lambda { |p| p.author_name }, "title")
+ )
+ end
+
+ def test_collection_options_with_proc_for_text_method
+ assert_dom_equal(
+ "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
+ options_from_collection_for_select(dummy_posts, "author_name", lambda { |p| p.title })
+ )
+ end
+
def test_string_options_for_select
options = "<option value=\"Denmark\">Denmark</option><option value=\"USA\">USA</option><option value=\"Sweden\">Sweden</option>"
assert_dom_equal(
@@ -144,6 +158,13 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_range_options_for_select
+ assert_dom_equal(
+ "<option value=\"1\">1</option>\n<option value=\"2\">2</option>\n<option value=\"3\">3</option>",
+ options_for_select(1..3)
+ )
+ end
+
def test_array_options_for_string_include_in_other_string_bug_fix
assert_dom_equal(
"<option value=\"ruby\">ruby</option>\n<option value=\"rubyonrails\" selected=\"selected\">rubyonrails</option>",
@@ -161,16 +182,16 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_hash_options_for_select
assert_dom_equal(
- "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\">$</option>",
- options_for_select("$" => "Dollar", "<DKR>" => "<Kroner>").split("\n").sort.join("\n")
+ "<option value=\"Dollar\">$</option>\n<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>",
+ options_for_select("$" => "Dollar", "<DKR>" => "<Kroner>").split("\n").join("\n")
)
assert_dom_equal(
- "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
- options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, "Dollar").split("\n").sort.join("\n")
+ "<option value=\"Dollar\" selected=\"selected\">$</option>\n<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>",
+ options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, "Dollar").split("\n").join("\n")
)
assert_dom_equal(
- "<option value=\"&lt;Kroner&gt;\" selected=\"selected\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
- options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, [ "Dollar", "<Kroner>" ]).split("\n").sort.join("\n")
+ "<option value=\"Dollar\" selected=\"selected\">$</option>\n<option value=\"&lt;Kroner&gt;\" selected=\"selected\">&lt;DKR&gt;</option>",
+ options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, [ "Dollar", "<Kroner>" ]).split("\n").join("\n")
)
end
@@ -671,6 +692,15 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_with_range
+ @post = Post.new
+ @post.category = 0
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"1\">1</option>\n<option value=\"2\">2</option>\n<option value=\"3\">3</option></select>",
+ select("post", "category", 1..3)
+ )
+ end
+
def test_collection_select
@post = Post.new
@post.author_name = "Babe"
@@ -774,7 +804,25 @@ class FormOptionsHelperTest < ActionView::TestCase
assert_dom_equal(
"<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\" disabled=\"disabled\">Cabe</option></select>",
collection_select("post", "author_name", dummy_posts, "author_name", "author_name", :disabled => 'Cabe')
- )
+ )
+ end
+
+ def test_collection_select_with_proc_for_value_method
+ @post = Post.new
+
+ assert_dom_equal(
+ "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option></select>",
+ collection_select("post", "author_name", dummy_posts, lambda { |p| p.author_name }, "title")
+ )
+ end
+
+ def test_collection_select_with_proc_for_text_method
+ @post = Post.new
+
+ assert_dom_equal(
+ "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option></select>",
+ collection_select("post", "author_name", dummy_posts, "author_name", lambda { |p| p.title })
+ )
end
def test_time_zone_select
@@ -1008,36 +1056,36 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_option_html_attributes_from_without_hash
- assert_dom_equal(
- "",
+ assert_equal(
+ {},
option_html_attributes([ 'foo', 'bar' ])
)
end
def test_option_html_attributes_with_single_element_hash
- assert_dom_equal(
- " class=\"fancy\"",
+ assert_equal(
+ {:class => 'fancy'},
option_html_attributes([ 'foo', 'bar', { :class => 'fancy' } ])
)
end
def test_option_html_attributes_with_multiple_element_hash
- assert_dom_equal(
- " class=\"fancy\" onclick=\"alert('Hello World');\"",
+ assert_equal(
+ {:class => 'fancy', 'onclick' => "alert('Hello World');"},
option_html_attributes([ 'foo', 'bar', { :class => 'fancy', 'onclick' => "alert('Hello World');" } ])
)
end
def test_option_html_attributes_with_multiple_hashes
- assert_dom_equal(
- " class=\"fancy\" onclick=\"alert('Hello World');\"",
+ assert_equal(
+ {:class => 'fancy', 'onclick' => "alert('Hello World');"},
option_html_attributes([ 'foo', 'bar', { :class => 'fancy' }, { 'onclick' => "alert('Hello World');" } ])
)
end
def test_option_html_attributes_with_special_characters
- assert_dom_equal(
- " onclick=\"alert(&quot;&lt;code&gt;&quot;)\"",
+ assert_equal(
+ {:onclick => "alert(&quot;&lt;code&gt;&quot;)"},
option_html_attributes([ 'foo', 'bar', { :onclick => %(alert("<code>")) } ])
)
end
@@ -1052,6 +1100,24 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_grouped_collection_select_with_selected
+ @post = Post.new
+
+ 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", dummy_continents, :countries, :continent_name, :country_id, :country_name, :selected => 'dk')
+ )
+ end
+
+ def test_grouped_collection_select_with_disabled_value
+ @post = Post.new
+
+ 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 disabled="disabled" value="dk">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>},
+ grouped_collection_select("post", "origin", dummy_continents, :countries, :continent_name, :country_id, :country_name, :disabled => 'dk')
+ )
+ end
+
def test_grouped_collection_select_under_fields_for
@post = Post.new
@post.origin = 'dk'
@@ -1068,14 +1134,14 @@ class FormOptionsHelperTest < ActionView::TestCase
private
- def dummy_posts
- [ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
- Post.new("Babe went home", "Babe", "To a little house", "shh!"),
- Post.new("Cabe went home", "Cabe", "To a little house", "shh!") ]
- end
+ def dummy_posts
+ [ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!") ]
+ end
- def dummy_continents
- [ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
- Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] ) ]
- end
+ def dummy_continents
+ [ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")]),
+ Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")]) ]
+ end
end
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index 233907d07a..6ef4cf4dd2 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -78,6 +78,12 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal expected, actual
end
+ def test_form_tag_with_method_patch
+ actual = form_tag({}, { :method => :patch })
+ expected = whole_form("http://www.example.com", :method => :patch)
+ assert_dom_equal expected, actual
+ end
+
def test_form_tag_with_method_put
actual = form_tag({}, { :method => :put })
expected = whole_form("http://www.example.com", :method => :put)
@@ -457,11 +463,16 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal(expected, search_field_tag("query"))
end
- def telephone_field_tag
+ def test_telephone_field_tag
expected = %{<input id="cell" name="cell" type="tel" />}
assert_dom_equal(expected, telephone_field_tag("cell"))
end
+ def test_date_field_tag
+ expected = %{<input id="cell" name="cell" type="date" />}
+ assert_dom_equal(expected, date_field_tag("cell"))
+ end
+
def test_url_field_tag
expected = %{<input id="homepage" name="homepage" type="url" />}
assert_dom_equal(expected, url_field_tag("homepage"))
@@ -482,10 +493,6 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal(expected, range_field_tag("volume", nil, :in => 0..11, :step => 0.1))
end
- def test_pass
- assert_equal 1, 1
- end
-
def test_field_set_tag_in_erb
output_buffer = render_erb("<%= field_set_tag('Your details') do %>Hello world!<% end %>")
diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb
index 62ad6be680..32c655c5fd 100644
--- a/actionpack/test/template/html-scanner/sanitizer_test.rb
+++ b/actionpack/test/template/html-scanner/sanitizer_test.rb
@@ -56,7 +56,6 @@ class SanitizerTest < ActionController::TestCase
assert_sanitized "a b c<script language=\"Javascript\">blah blah blah</script>d e f", "a b cd e f"
end
- # TODO: Clean up
def test_sanitize_js_handlers
raw = %{onthis="do that" <a href="#" onclick="hello" name="foo" onbogus="remove me">hello</a>}
assert_sanitized raw, %{onthis="do that" <a name="foo" href="#">hello</a>}
@@ -138,7 +137,7 @@ class SanitizerTest < ActionController::TestCase
assert sanitizer.send(:contains_bad_protocols?, 'src', "#{proto}://bad")
end
end
-
+
def test_should_accept_good_protocols_ignoring_case
sanitizer = HTML::WhiteListSanitizer.new
HTML::WhiteListSanitizer.allowed_protocols.each do |proto|
@@ -146,6 +145,13 @@ class SanitizerTest < ActionController::TestCase
end
end
+ def test_should_accept_good_protocols_ignoring_space
+ sanitizer = HTML::WhiteListSanitizer.new
+ HTML::WhiteListSanitizer.allowed_protocols.each do |proto|
+ assert !sanitizer.send(:contains_bad_protocols?, 'src', " #{proto}://good")
+ end
+ end
+
def test_should_accept_good_protocols
sanitizer = HTML::WhiteListSanitizer.new
HTML::WhiteListSanitizer.allowed_protocols.each do |proto|
@@ -208,7 +214,6 @@ class SanitizerTest < ActionController::TestCase
assert_sanitized img_hack, "<img>"
end
- # TODO: Clean up
def test_should_sanitize_attributes
assert_sanitized %(<SPAN title="'><script>alert()</script>">blah</SPAN>), %(<span title="'&gt;&lt;script&gt;alert()&lt;/script&gt;">blah</span>)
end
diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb
index d98ffe8fa7..9441bd6b38 100644
--- a/actionpack/test/template/javascript_helper_test.rb
+++ b/actionpack/test/template/javascript_helper_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'active_support/core_ext/string/encoding'
class JavaScriptHelperTest < ActionView::TestCase
tests ActionView::Helpers::JavaScriptHelper
diff --git a/actionpack/test/template/log_subscriber_test.rb b/actionpack/test/template/log_subscriber_test.rb
index 752b0f23a8..1713ce935c 100644
--- a/actionpack/test/template/log_subscriber_test.rb
+++ b/actionpack/test/template/log_subscriber_test.rb
@@ -8,7 +8,6 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
def setup
super
- @old_logger = ActionController::Base.logger
@controller = Object.new
@controller.stubs(:_prefixes).returns(%w(test))
@view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller)
@@ -19,11 +18,10 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
def teardown
super
ActiveSupport::LogSubscriber.log_subscribers.clear
- ActionController::Base.logger = @old_logger
end
def set_logger(logger)
- ActionController::Base.logger = logger
+ ActionView::Base.logger = logger
end
def test_render_file_template
diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb
index c65f707da0..96b14a0acd 100644
--- a/actionpack/test/template/lookup_context_test.rb
+++ b/actionpack/test/template/lookup_context_test.rb
@@ -78,9 +78,9 @@ class LookupContextTest < ActiveSupport::TestCase
end
test "found templates respects given formats if one cannot be found from template or handler" do
- ActionView::Template::Handlers::ERB.expects(:default_format).returns(nil)
+ ActionView::Template::Handlers::Builder.expects(:default_format).returns(nil)
@lookup_context.formats = [:text]
- template = @lookup_context.find("hello_world", %w(test))
+ template = @lookup_context.find("hello", %w(test))
assert_equal [:text], template.formats
end
diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index 8d679aac1d..482d953907 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -57,6 +57,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("1000.000%", number_to_percentage("1000"))
assert_equal("123.4%", number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true))
assert_equal("1.000,000%", number_to_percentage(1000, :delimiter => '.', :separator => ','))
+ assert_equal("1000.000 %", number_to_percentage(1000, :format => "%n %"))
end
def test_number_with_delimiter
@@ -267,6 +268,31 @@ class NumberHelperTest < ActionView::TestCase
assert_nil number_to_human(nil)
end
+ def test_number_helpers_do_not_mutate_options_hash
+ options = { 'raise' => true }
+
+ number_to_phone(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_to_currency(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_to_percentage(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_with_delimiter(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_with_precision(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_to_human_size(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_to_human(1, options)
+ assert_equal({ 'raise' => true }, options)
+ end
+
def test_number_helpers_should_return_non_numeric_param_unchanged
assert_equal("+1-x x 123", number_to_phone("x", :country_code => 1, :extension => 123))
assert_equal("x", number_to_phone("x"))
diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb
index ec777d15c4..a84034c02e 100644
--- a/actionpack/test/template/record_tag_helper_test.rb
+++ b/actionpack/test/template/record_tag_helper_test.rb
@@ -1,23 +1,15 @@
require 'abstract_unit'
-require 'controller/fake_models'
-class Post
+class RecordTagPost
extend ActiveModel::Naming
include ActiveModel::Conversion
- attr_writer :id, :body
+ attr_accessor :id, :body
def initialize
- @id = nil
- @body = nil
- super
- end
+ @id = 45
+ @body = "What a wonderful world!"
- def id
- @id || 45
- end
-
- def body
- super || @body || "What a wonderful world!"
+ yield self if block_given?
end
end
@@ -28,73 +20,84 @@ class RecordTagHelperTest < ActionView::TestCase
def setup
super
- @post = Post.new
- @post.persisted = true
+ @post = RecordTagPost.new
end
def test_content_tag_for
- expected = %(<li class="post bar" id="post_45"></li>)
- actual = content_tag_for(:li, @post, :class => 'bar') { }
+ expected = %(<li class="record_tag_post" id="record_tag_post_45"></li>)
+ actual = content_tag_for(:li, @post) { }
assert_dom_equal expected, actual
end
def test_content_tag_for_prefix
- expected = %(<ul class="archived_post" id="archived_post_45"></ul>)
+ expected = %(<ul class="archived_record_tag_post" id="archived_record_tag_post_45"></ul>)
actual = content_tag_for(:ul, @post, :archived) { }
assert_dom_equal expected, actual
end
- def test_content_tag_for_with_extra_html_tags
- expected = %(<tr class="post bar" id="post_45" style='background-color: #f0f0f0'></tr>)
- actual = content_tag_for(:tr, @post, {:class => "bar", :style => "background-color: #f0f0f0"}) { }
+ def test_content_tag_for_with_extra_html_options
+ expected = %(<tr class="record_tag_post special" id="record_tag_post_45" style='background-color: #f0f0f0'></tr>)
+ actual = content_tag_for(:tr, @post, :class => "special", :style => "background-color: #f0f0f0") { }
+ assert_dom_equal expected, actual
+ end
+
+ def test_content_tag_for_with_prefix_and_extra_html_options
+ expected = %(<tr class="archived_record_tag_post special" id="archived_record_tag_post_45" style='background-color: #f0f0f0'></tr>)
+ actual = content_tag_for(:tr, @post, :archived, :class => "special", :style => "background-color: #f0f0f0") { }
assert_dom_equal expected, actual
end
def test_block_not_in_erb_multiple_calls
- expected = %(<div class="post bar" id="post_45">#{@post.body}</div>)
- actual = div_for(@post, :class => "bar") { @post.body }
+ expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>)
+ actual = div_for(@post, :class => "special") { @post.body }
assert_dom_equal expected, actual
- actual = div_for(@post, :class => "bar") { @post.body }
+ actual = div_for(@post, :class => "special") { @post.body }
assert_dom_equal expected, actual
end
def test_block_works_with_content_tag_for_in_erb
- expected = %(<tr class="post" id="post_45">#{@post.body}</tr>)
+ expected = %(<tr class="record_tag_post" id="record_tag_post_45">What a wonderful world!</tr>)
actual = render_erb("<%= content_tag_for(:tr, @post) do %><%= @post.body %><% end %>")
assert_dom_equal expected, actual
end
def test_div_for_in_erb
- expected = %(<div class="post bar" id="post_45">#{@post.body}</div>)
- actual = render_erb("<%= div_for(@post, :class => 'bar') do %><%= @post.body %><% end %>")
+ expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>)
+ actual = render_erb("<%= div_for(@post, :class => 'special') do %><%= @post.body %><% end %>")
assert_dom_equal expected, actual
end
def test_content_tag_for_collection
- post_1 = Post.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true }
- post_2 = Post.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true }
- expected = %(<li class="post" id="post_101">Hello!</li>\n<li class="post" id="post_102">World!</li>)
- actual = content_tag_for(:li, [post_1, post_2]) { |post| concat post.body }
+ post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" }
+ post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" }
+ expected = %(<li class="record_tag_post" id="record_tag_post_101">Hello!</li>\n<li class="record_tag_post" id="record_tag_post_102">World!</li>)
+ actual = content_tag_for(:li, [post_1, post_2]) { |post| post.body }
assert_dom_equal expected, actual
end
def test_div_for_collection
- post_1 = Post.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true }
- post_2 = Post.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true }
- expected = %(<div class="post" id="post_101">Hello!</div>\n<div class="post" id="post_102">World!</div>)
- actual = div_for([post_1, post_2]) { |post| concat post.body }
+ post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" }
+ post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" }
+ expected = %(<div class="record_tag_post" id="record_tag_post_101">Hello!</div>\n<div class="record_tag_post" id="record_tag_post_102">World!</div>)
+ actual = div_for([post_1, post_2]) { |post| post.body }
assert_dom_equal expected, actual
end
def test_content_tag_for_single_record_is_html_safe
- result = div_for(@post, :class => "bar") { concat @post.body }
+ result = div_for(@post, :class => "special") { @post.body }
assert result.html_safe?
end
def test_content_tag_for_collection_is_html_safe
- post_1 = Post.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true }
- post_2 = Post.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true }
- result = content_tag_for(:li, [post_1, post_2]) { |post| concat post.body }
+ post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" }
+ post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" }
+ result = content_tag_for(:li, [post_1, post_2]) { |post| post.body }
assert result.html_safe?
end
+
+ def test_content_tag_for_does_not_change_options_hash
+ options = { :class => "important" }
+ content_tag_for(:li, @post, options) { }
+ assert_equal({ :class => "important" }, options)
+ end
end
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 5d3dc73ed2..7347e15373 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -51,6 +51,18 @@ module RenderTestCases
assert_match "<error>No Comment</error>", @view.render(:template => "comments/empty", :formats => [:xml])
end
+ def test_render_partial_implicitly_use_format_of_the_rendered_template
+ @view.lookup_context.formats = [:json]
+ assert_equal "Hello world", @view.render(:template => "test/one", :formats => [:html])
+ end
+
+ def test_render_template_with_a_missing_partial_of_another_format
+ @view.lookup_context.formats = [:html]
+ assert_raise ActionView::Template::Error, "Missing partial /missing with {:locale=>[:en], :formats=>[:json], :handlers=>[:erb, :builder]}" do
+ @view.render(:template => "with_format", :formats => [:json])
+ end
+ end
+
def test_render_file_with_locale
assert_equal "<h1>Kein Kommentar</h1>", @view.render(:file => "comments/empty", :locale => [:de])
assert_equal "<h1>Kein Kommentar</h1>", @view.render(:file => "comments/empty", :locale => :de)
@@ -299,6 +311,12 @@ module RenderTestCases
ActionView::Template.register_template_handler :foo, CustomHandler
assert_equal 'source: "Hello, <%= name %>!"', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo)
end
+
+ def test_render_knows_about_types_registered_when_extensions_are_checked_earlier_in_initialization
+ ActionView::Template::Handlers.extensions
+ ActionView::Template.register_template_handler :foo, CustomHandler
+ assert ActionView::Template::Handlers.extensions.include?(:foo)
+ end
def test_render_ignores_templates_with_malformed_template_handlers
ActiveSupport::Deprecation.silence do
diff --git a/actionpack/test/template/template_error_test.rb b/actionpack/test/template/template_error_test.rb
index 3a874082d9..91424daeed 100644
--- a/actionpack/test/template/template_error_test.rb
+++ b/actionpack/test/template/template_error_test.rb
@@ -2,12 +2,12 @@ require "abstract_unit"
class TemplateErrorTest < ActiveSupport::TestCase
def test_provides_original_message
- error = ActionView::Template::Error.new("test", {}, Exception.new("original"))
+ error = ActionView::Template::Error.new("test", Exception.new("original"))
assert_equal "original", error.message
end
def test_provides_useful_inspect
- error = ActionView::Template::Error.new("test", {}, Exception.new("original"))
+ error = ActionView::Template::Error.new("test", Exception.new("original"))
assert_equal "#<ActionView::Template::Error: original>", error.inspect
end
end
diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb
index 3084527f70..8c57ada587 100644
--- a/actionpack/test/template/template_test.rb
+++ b/actionpack/test/template/template_test.rb
@@ -11,6 +11,8 @@ class TestERBTemplate < ActiveSupport::TestCase
def find_template(*args)
end
+
+ attr_accessor :formats
end
class Context
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index aa185d9cb0..5865b7f23c 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -91,12 +91,12 @@ class TextHelperTest < ActionView::TestCase
def test_highlight
assert_equal(
- "This is a <strong class=\"highlight\">beautiful</strong> morning",
+ "This is a <mark>beautiful</mark> morning",
highlight("This is a beautiful morning", "beautiful")
)
assert_equal(
- "This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day",
+ "This is a <mark>beautiful</mark> morning, but also a <mark>beautiful</mark> day",
highlight("This is a beautiful morning, but also a beautiful day", "beautiful")
)
@@ -115,31 +115,31 @@ class TextHelperTest < ActionView::TestCase
def test_highlight_should_sanitize_input
assert_equal(
- "This is a <strong class=\"highlight\">beautiful</strong> morning",
+ "This is a <mark>beautiful</mark> morning",
highlight("This is a beautiful morning<script>code!</script>", "beautiful")
)
end
def test_highlight_should_not_sanitize_if_sanitize_option_if_false
assert_equal(
- "This is a <strong class=\"highlight\">beautiful</strong> morning<script>code!</script>",
+ "This is a <mark>beautiful</mark> morning<script>code!</script>",
highlight("This is a beautiful morning<script>code!</script>", "beautiful", :sanitize => false)
)
end
def test_highlight_with_regexp
assert_equal(
- "This is a <strong class=\"highlight\">beautiful!</strong> morning",
+ "This is a <mark>beautiful!</mark> morning",
highlight("This is a beautiful! morning", "beautiful!")
)
assert_equal(
- "This is a <strong class=\"highlight\">beautiful! morning</strong>",
+ "This is a <mark>beautiful! morning</mark>",
highlight("This is a beautiful! morning", "beautiful! morning")
)
assert_equal(
- "This is a <strong class=\"highlight\">beautiful? morning</strong>",
+ "This is a <mark>beautiful? morning</mark>",
highlight("This is a beautiful? morning", "beautiful? morning")
)
end
@@ -157,23 +157,23 @@ class TextHelperTest < ActionView::TestCase
def test_highlight_with_html
assert_equal(
- "<p>This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ "<p>This is a <mark>beautiful</mark> morning, but also a <mark>beautiful</mark> day</p>",
highlight("<p>This is a beautiful morning, but also a beautiful day</p>", "beautiful")
)
assert_equal(
- "<p>This is a <em><strong class=\"highlight\">beautiful</strong></em> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ "<p>This is a <em><mark>beautiful</mark></em> morning, but also a <mark>beautiful</mark> day</p>",
highlight("<p>This is a <em>beautiful</em> morning, but also a beautiful day</p>", "beautiful")
)
assert_equal(
- "<p>This is a <em class=\"error\"><strong class=\"highlight\">beautiful</strong></em> morning, but also a <strong class=\"highlight\">beautiful</strong> <span class=\"last\">day</span></p>",
+ "<p>This is a <em class=\"error\"><mark>beautiful</mark></em> morning, but also a <mark>beautiful</mark> <span class=\"last\">day</span></p>",
highlight("<p>This is a <em class=\"error\">beautiful</em> morning, but also a beautiful <span class=\"last\">day</span></p>", "beautiful")
)
assert_equal(
- "<p class=\"beautiful\">This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ "<p class=\"beautiful\">This is a <mark>beautiful</mark> morning, but also a <mark>beautiful</mark> day</p>",
highlight("<p class=\"beautiful\">This is a beautiful morning, but also a beautiful day</p>", "beautiful")
)
assert_equal(
- "<p>This is a <strong class=\"highlight\">beautiful</strong> <a href=\"http://example.com/beautiful#top?what=beautiful%20morning&amp;when=now+then\">morning</a>, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ "<p>This is a <mark>beautiful</mark> <a href=\"http://example.com/beautiful#top?what=beautiful%20morning&amp;when=now+then\">morning</a>, but also a <mark>beautiful</mark> day</p>",
highlight("<p>This is a beautiful <a href=\"http://example.com/beautiful\#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a beautiful day</p>", "beautiful")
)
assert_equal(
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index bc45fabf34..b482bd3251 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -1,6 +1,5 @@
# encoding: utf-8
require 'abstract_unit'
-require 'active_support/ordered_hash'
require 'controller/fake_controllers'
class UrlHelperTest < ActiveSupport::TestCase
@@ -11,6 +10,9 @@ class UrlHelperTest < ActiveSupport::TestCase
# In those cases, we'll set up a simple mock
attr_accessor :controller, :request
+ cattr_accessor :request_forgery
+ self.request_forgery = false
+
routes = ActionDispatch::Routing::RouteSet.new
routes.draw do
match "/" => "foo#bar"
@@ -28,13 +30,13 @@ class UrlHelperTest < ActiveSupport::TestCase
setup :_prepare_context
- def hash_for(opts = [])
- ActiveSupport::OrderedHash[*([:controller, "foo", :action, "bar"].concat(opts))]
+ def hash_for(options = {})
+ { :controller => "foo", :action => "bar" }.merge!(options)
end
alias url_hash hash_for
def test_url_for_does_not_escape_urls
- assert_equal "/?a=b&c=d", url_for(hash_for([:a, :b, :c, :d]))
+ assert_equal "/?a=b&c=d", url_for(hash_for(:a => :b, :c => :d))
end
def test_url_for_with_back
@@ -49,11 +51,22 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal 'javascript:history.back()', url_for(:back)
end
- # todo: missing test cases
+ # TODO: missing test cases
def test_button_to_with_straight_url
assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com")
end
+ def test_button_to_with_straight_url_and_request_forgery
+ self.request_forgery = true
+
+ assert_dom_equal(
+ %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /><input name="form_token" type="hidden" value="secret" /></div></form>},
+ button_to("Hello", "http://www.example.com")
+ )
+ ensure
+ self.request_forgery = false
+ end
+
def test_button_to_with_form_class
assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"custom-class\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com", :form_class => 'custom-class')
end
@@ -91,7 +104,7 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_button_to_with_remote_and_form_options
assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"custom-class\" data-remote=\"true\" data-type=\"json\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com", :remote => true, :form => { :class => "custom-class", "data-type" => "json" } )
end
-
+
def test_button_to_with_remote_and_javascript_confirm
assert_dom_equal(
"<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\" data-remote=\"true\"><div><input data-confirm=\"Are you sure?\" type=\"submit\" value=\"Hello\" /></div></form>",
@@ -154,7 +167,7 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def test_link_tag_with_host_option
- hash = hash_for([:host, "www.example.com"])
+ hash = hash_for(:host => "www.example.com")
expected = %q{<a href="http://www.example.com/">Test Link</a>}
assert_dom_equal(expected, link_to('Test Link', hash))
end
@@ -329,7 +342,7 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_current_page_with_params_that_match
@request = request_for_url("/?order=desc&page=1")
- assert current_page?(hash_for([:order, "desc", :page, "1"]))
+ assert current_page?(hash_for(:order => "desc", :page => "1"))
assert current_page?("http://www.example.com/?order=desc&page=1")
end
@@ -357,20 +370,20 @@ class UrlHelperTest < ActiveSupport::TestCase
@request = request_for_url("/?order=desc&page=1")
assert_equal "Showing",
- link_to_unless_current("Showing", hash_for([:order, 'desc', :page, '1']))
+ link_to_unless_current("Showing", hash_for(:order => 'desc', :page => '1'))
assert_equal "Showing",
link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=1")
@request = request_for_url("/?order=desc")
assert_equal %{<a href="/?order=asc">Showing</a>},
- link_to_unless_current("Showing", hash_for([:order, :asc]))
+ link_to_unless_current("Showing", hash_for(:order => :asc))
assert_equal %{<a href="http://www.example.com/?order=asc">Showing</a>},
link_to_unless_current("Showing", "http://www.example.com/?order=asc")
@request = request_for_url("/?order=desc")
assert_equal %{<a href="/?order=desc&amp;page=2\">Showing</a>},
- link_to_unless_current("Showing", hash_for([:order, "desc", :page, 2]))
+ link_to_unless_current("Showing", hash_for(:order => "desc", :page => 2))
assert_equal %{<a href="http://www.example.com/?order=desc&amp;page=2">Showing</a>},
link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=2")
@@ -435,9 +448,16 @@ class UrlHelperTest < ActiveSupport::TestCase
assert mail_to("me@domain.com", "My email", :encode => "hex").html_safe?
end
- # TODO: button_to looks at this ... why?
def protect_against_forgery?
- false
+ self.request_forgery
+ end
+
+ def form_authenticity_token
+ "secret"
+ end
+
+ def request_forgery_protection_token
+ "form_token"
end
private
@@ -506,8 +526,6 @@ class UrlHelperControllerTest < ActionController::TestCase
render :inline => '<%= url_for(:action => :show_url_for) %>'
end
- def rescue_action(e) raise e end
-
def override_url_helper
render :inline => '<%= override_url_helper_path %>'
end
@@ -595,8 +613,6 @@ class TasksController < ActionController::Base
render_default
end
- def rescue_action(e) raise e end
-
protected
def render_default
render :inline =>
@@ -655,8 +671,6 @@ class WorkshopsController < ActionController::Base
@workshop = Workshop.new(params[:id])
render :inline => "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>"
end
-
- def rescue_action(e) raise e end
end
class SessionsController < ActionController::Base
@@ -677,8 +691,6 @@ class SessionsController < ActionController::Base
@session = Session.new(params[:id])
render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>"
end
-
- def rescue_action(e) raise e end
end
class PolymorphicControllerTest < ActionController::TestCase
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index bd9ed996fe..0d70edd0ba 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,4 +1,12 @@
-## Rails 3.2.0 (unreleased) ##
+* `AM::Errors#to_json`: support `:full_messages` parameter *Bogdan Gusiev*
+
+* Trim down Active Model API by removing `valid?` and `errors.full_messages` *José Valim*
+
+## Rails 3.2.1 (January 26, 2012) ##
+
+* No changes.
+
+## Rails 3.2.0 (January 20, 2012) ##
* Deprecated `define_attr_method` in `ActiveModel::AttributeMethods`, because this only existed to
support methods like `set_table_name` in Active Record, which are themselves being deprecated.
@@ -13,6 +21,23 @@
* Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior *Bogdan Gusiev*
+## Rails 3.1.3 (November 20, 2011) ##
+
+* No changes
+
+## Rails 3.1.2 (November 18, 2011) ##
+
+* No changes
+
+## Rails 3.1.1 (October 7, 2011) ##
+
+* Remove hard dependency on bcrypt-ruby to avoid make ActiveModel dependent on a binary library.
+ You must add the gem explicitly to your Gemfile if you want use ActiveModel::SecurePassword:
+
+ gem 'bcrypt-ruby', '~> 3.0.0'
+
+ See GH #2687. *Guillermo Iguaran*
+
## Rails 3.1.0 (August 30, 2011) ##
* Alternate I18n namespace lookup is no longer supported.
@@ -36,12 +61,37 @@
* Add support for selectively enabling/disabling observers *Myron Marston*
+## Rails 3.0.12 (unreleased) ##
+
+* No changes.
+
+
+## Rails 3.0.11 (November 18, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.10 (August 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.9 (June 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.8 (June 7, 2011) ##
+
+* No changes.
+
+
## Rails 3.0.7 (April 18, 2011) ##
* No changes.
-* Rails 3.0.6 (April 5, 2011)
+## Rails 3.0.6 (April 5, 2011) ##
* Fix when database column name has some symbolic characters (e.g. Oracle CASE# VARCHAR2(20)) #5818 #6850 *Robert Pankowecki, Santiago Pastorino*
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index 9208145507..a7ba27ba73 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -9,10 +9,12 @@ Prior to Rails 3.0, if a plugin or gem developer wanted to have an object
interact with Action Pack helpers, it was required to either copy chunks of
code from Rails, or monkey patch entire helpers to make them handle objects
that did not exactly conform to the Active Record interface. This would result
-in code duplication and fragile applications that broke on upgrades.
+in code duplication and fragile applications that broke on upgrades. Active
+Model solves this by defining an explicit API. You can read more about the
+API in ActiveModel::Lint::Tests.
-Active Model solves this. You can include functionality from the following
-modules:
+Active Model also provides the following functionality to have ORM-like
+behavior out of the box:
* Add attribute magic to objects
@@ -87,10 +89,9 @@ modules:
errors.add(:name, "can not be nil") if name.nil?
end
- def ErrorsPerson.human_attribute_name(attr, options = {})
+ def self.human_attribute_name(attr, options = {})
"Name"
end
-
end
person.errors.full_messages
@@ -163,7 +164,7 @@ modules:
* Custom validators
- class Person
+ class ValidatorPerson
include ActiveModel::Validations
validates_with HasNameValidator
attr_accessor :name
@@ -171,7 +172,7 @@ modules:
class HasNameValidator < ActiveModel::Validator
def validate(record)
- record.errors[:name] = "must exist" if record.name.blank?
+ record.errors[:name] = "must exist" if record.name.blank?
end
end
@@ -182,7 +183,7 @@ modules:
p.valid? # => true
{Learn more}[link:classes/ActiveModel/Validator.html]
-
+
== Download and installation
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index c4b020196d..fc5aaf9f8f 100755
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -8,6 +8,7 @@ Rake::TestTask.new do |t|
t.libs << "test"
t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb").sort
t.warning = true
+ t.verbose = true
end
namespace :test do
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 71ab1501c8..97a83e58af 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -223,7 +223,7 @@ module ActiveModel
unless instance_method_already_implemented?(method_name)
generate_method = "define_method_#{matcher.method_missing_target}"
- if respond_to?(generate_method)
+ if respond_to?(generate_method, true)
send(generate_method, attr_name)
else
define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
@@ -325,14 +325,14 @@ module ActiveModel
end
@prefix, @suffix = options[:prefix] || '', options[:suffix] || ''
- @regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/
+ @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
@method_missing_target = "#{@prefix}attribute#{@suffix}"
@method_name = "#{prefix}%s#{suffix}"
end
def match(method_name)
if @regex =~ method_name
- AttributeMethodMatch.new(method_missing_target, $2, method_name)
+ AttributeMethodMatch.new(method_missing_target, $1, method_name)
else
nil
end
@@ -394,7 +394,7 @@ module ActiveModel
protected
def attribute_method?(attr_name)
- attributes.include?(attr_name)
+ respond_to_without_attributes?(:attributes) && attributes.include?(attr_name)
end
private
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 25d26ede52..ebb4b51aa3 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -88,6 +88,7 @@ module ActiveModel
options = callbacks.extract_options!
options = {
:terminator => "result == false",
+ :skip_after_callbacks_if_terminated => true,
:scope => [:kind, :name],
:only => [:before, :around, :after]
}.merge(options)
@@ -124,7 +125,7 @@ module ActiveModel
def self.after_#{callback}(*args, &block)
options = args.extract_options!
options[:prepend] = true
- options[:if] = Array(options[:if]) << "!halted && value != false"
+ options[:if] = Array(options[:if]) << "value != false"
set_callback(:#{callback}, :after, *(args << options), &block)
end
CALLBACK
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 025915fe6f..042f9cd7e2 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -4,12 +4,11 @@ require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/reverse_merge'
-require 'active_support/ordered_hash'
module ActiveModel
# == Active Model Errors
#
- # Provides a modified +OrderedHash+ that you can include in your object
+ # Provides a modified +Hash+ that you can include in your object
# for handling error messages and interacting with Action Pack helpers.
#
# A minimal implementation could be:
@@ -75,7 +74,12 @@ module ActiveModel
# end
def initialize(base)
@base = base
- @messages = ActiveSupport::OrderedHash.new
+ @messages = {}
+ end
+
+ def initialize_dup(other)
+ @messages = other.messages.dup
+ super
end
# Clear the messages
@@ -99,6 +103,11 @@ module ActiveModel
messages[key] = value
end
+ # Delete messages for +key+
+ def delete(key)
+ messages.delete(key)
+ end
+
# When passed a symbol or a name of a method, returns an array of errors
# for the method.
#
@@ -113,7 +122,7 @@ module ActiveModel
# p.errors[:name] = "must be set"
# p.errors[:name] # => ['must be set']
def []=(attribute, error)
- self[attribute.to_sym] << error
+ self[attribute] << error
end
# Iterates through each error key, value pair in the error messages hash.
@@ -196,25 +205,36 @@ module ActiveModel
to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true)
end
- # Returns an ActiveSupport::OrderedHash that can be used as the JSON representation for this object.
+ # Returns an Hash that can be used as the JSON representation for this object.
+ # Options:
+ # * <tt>:full_messages</tt> - determines if json object should contain
+ # full messages or not. Default: <tt>false</tt>.
def as_json(options=nil)
- to_hash
+ to_hash(options && options[:full_messages])
end
- def to_hash
- messages.dup
+ def to_hash(full_messages = false)
+ if full_messages
+ messages = {}
+ self.messages.each do |attribute, array|
+ messages[attribute] = array.map{|message| full_message(attribute, message) }
+ end
+ messages
+ else
+ self.messages.dup
+ end
end
# Adds +message+ to the error messages on +attribute+. More than one error can be added to the same
# +attribute+.
# If no +message+ is supplied, <tt>:invalid</tt> is assumed.
#
- # If +message+ is a symbol, it will be translated using the appropriate scope (see +translate_error+).
+ # If +message+ is a symbol, it will be translated using the appropriate scope (see +generate_message+).
# If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error.
def add(attribute, message = nil, options = {})
message = normalize_message(attribute, message, options)
if options[:strict]
- raise ActiveModel::StrictValidationFailed, message
+ raise ActiveModel::StrictValidationFailed, full_message(attribute, message)
end
self[attribute] << message
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index bfe7ea1869..a10fdefd1a 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -3,9 +3,13 @@ module ActiveModel
# == Active Model Lint Tests
#
# You can test whether an object is compliant with the Active Model API by
- # including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will include
- # tests that tell you whether your object is fully compliant, or if not,
- # which aspects of the API are not implemented.
+ # including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will
+ # include tests that tell you whether your object is fully compliant,
+ # or if not, which aspects of the API are not implemented.
+ #
+ # Note an object is not required to implement all APIs in order to work
+ # with Action Pack. This module only intends to provide guidance in case
+ # you want all features out of the box.
#
# These tests do not attempt to determine the semantic correctness of the
# returned values. For instance, you could implement valid? to always
@@ -19,7 +23,8 @@ module ActiveModel
# == Responds to <tt>to_key</tt>
#
# Returns an Enumerable of all (primary) key attributes
- # or nil if model.persisted? is false
+ # or nil if model.persisted? is false. This is used by
+ # dom_id to generate unique ids for the object.
def test_to_key
assert model.respond_to?(:to_key), "The model should respond to to_key"
def model.persisted?() false end
@@ -53,22 +58,13 @@ module ActiveModel
assert_kind_of String, model.to_partial_path
end
- # == Responds to <tt>valid?</tt>
- #
- # Returns a boolean that specifies whether the object is in a valid or invalid
- # state.
- def test_valid?
- assert model.respond_to?(:valid?), "The model should respond to valid?"
- assert_boolean model.valid?, "valid?"
- end
-
# == Responds to <tt>persisted?</tt>
#
# 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 be PUT to the
- # URL for the object.
+ # not persisted, a form for that object, for instance, will route to the
+ # create action. If it is persisted, a form for the object will routes to
+ # the update action.
def test_persisted?
assert model.respond_to?(:persisted?), "The model should respond to persisted?"
assert_boolean model.persisted?, "persisted?"
@@ -90,25 +86,15 @@ module ActiveModel
# == Errors Testing
#
- # Returns an object that has :[] and :full_messages defined on it. See below
- # for more details.
- #
- # 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.
+ # Returns an object that implements [](attribute) defined which 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 model.respond_to?(:errors), "The model should respond to errors"
assert model.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 model.respond_to?(:errors), "The model should respond to errors"
- assert model.errors.full_messages.is_a?(Array), "errors#full_messages should return an Array"
- end
-
private
def model
assert @model.respond_to?(:to_model), "The object should respond_to to_model"
diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb
index 13495d6786..95de039676 100644
--- a/activemodel/lib/active_model/mass_assignment_security.rb
+++ b/activemodel/lib/active_model/mass_assignment_security.rb
@@ -226,12 +226,12 @@ module ActiveModel
protected
- def sanitize_for_mass_assignment(attributes, role = :default)
+ def sanitize_for_mass_assignment(attributes, role = nil)
_mass_assignment_sanitizer.sanitize(attributes, mass_assignment_authorizer(role))
end
- def mass_assignment_authorizer(role = :default)
- self.class.active_authorizer[role]
+ def mass_assignment_authorizer(role)
+ self.class.active_authorizer[role || :default]
end
end
end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index a8309bf682..32f2aa46bd 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -63,7 +63,7 @@ module ActiveModel
# raises an +ArgumentError+ exception.
def add_observer(observer)
unless observer.respond_to? :update
- raise ArgumentError, "observer needs to respond to `update'"
+ raise ArgumentError, "observer needs to respond to 'update'"
end
observer_instances << observer
end
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index db78864c67..e7a57cf691 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -29,7 +29,7 @@ module ActiveModel
# user.save # => true
# user.authenticate("notright") # => false
# user.authenticate("mUc3m00RsqyRe") # => user
- # User.find_by_name("david").try(:authenticate, "notright") # => nil
+ # User.find_by_name("david").try(:authenticate, "notright") # => false
# User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
def has_secure_password
# Load bcrypt-ruby only when has_secure_password is used.
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index ba9721cc70..51f078e662 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -17,7 +17,7 @@ module ActiveModel
# attr_accessor :name
#
# def attributes
- # {'name' => name}
+ # {'name' => nil}
# end
#
# end
@@ -29,8 +29,11 @@ module ActiveModel
# person.name = "Bob"
# person.serializable_hash # => {"name"=>"Bob"}
#
- # You need to declare some sort of attributes hash which contains the attributes
- # you want to serialize and their current value.
+ # You need to declare an attributes hash which contains the attributes
+ # you want to serialize. When called, serializable hash will use
+ # instance methods that match the name of the attributes hash's keys.
+ # In order to override this behavior, take a look at the private
+ # method read_attribute_for_serialization.
#
# Most of the time though, you will want to include the JSON or XML
# serializations. Both of these modules automatically include the
@@ -47,7 +50,7 @@ module ActiveModel
# attr_accessor :name
#
# def attributes
- # {'name' => name}
+ # {'name' => nil}
# end
#
# end
@@ -82,7 +85,7 @@ module ActiveModel
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
method_names = Array(options[:methods]).select { |n| respond_to?(n) }
- method_names.each { |n| hash[n] = send(n) }
+ method_names.each { |n| hash[n.to_s] = send(n) }
serializable_add_includes(options) do |association, records, opts|
hash[association] = if records.is_a?(Enumerable)
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 15b8e824ac..0e15155b85 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -33,7 +33,7 @@ module ActiveModel
# person.first_name = 'zoolander'
# person.valid? # => false
# person.invalid? # => true
- # person.errors # => #<OrderedHash {:first_name=>["starts with z."]}>
+ # person.errors # => #<Hash {:first_name=>["starts with z."]}>
#
# Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+ method
# to your instances initialized with a new <tt>ActiveModel::Errors</tt> object, so
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index c80ace7b82..c39c85e1af 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -23,7 +23,7 @@ module ActiveModel
included do
include ActiveSupport::Callbacks
- define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
+ define_callbacks :validation, :terminator => "result == false", :skip_after_callbacks_if_terminated => true, :scope => [:kind, :name]
end
module ClassMethods
@@ -40,7 +40,6 @@ module ActiveModel
options = args.extract_options!
options[:prepend] = true
options[:if] = Array(options[:if])
- options[:if] << "!halted"
options[:if].unshift("self.validation_context == :#{options[:on]}") if options[:on]
set_callback(:validation, :after, *(args << options), &block)
end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index 6573a7d264..e8526303e2 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -37,7 +37,7 @@ module ActiveModel
# attribute.
#
# NOTE: This check is performed only if +password_confirmation+ is not
- # +nil+, and by default only on save. To require confirmation, make sure
+ # +nil+. To require confirmation, make sure
# to add a presence check for the confirmation attribute:
#
# validates_presence_of :password_confirmation, :if => :password_changed?
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index 0eba241333..037f8c2db8 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -1,5 +1,3 @@
-require "active_support/core_ext/string/encoding"
-
module ActiveModel
# == Active Model Length Validator
@@ -29,8 +27,8 @@ module ActiveModel
keys.each do |key|
value = options[key]
- unless value.is_a?(Integer) && value >= 0
- raise ArgumentError, ":#{key} must be a nonnegative Integer"
+ unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY
+ raise ArgumentError, ":#{key} must be a nonnegative Integer or Infinity"
end
end
end
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 8e09f6ac35..d94c4e3f4f 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -1,7 +1,6 @@
require 'active_support/core_ext/hash/slice'
module ActiveModel
-
# == Active Model validates method
module Validations
module ClassMethods
@@ -59,7 +58,7 @@ module ActiveModel
#
# validates :name, :'film/title' => true
#
- # The validators hash can also handle regular expressions, ranges,
+ # The validators hash can also handle regular expressions, ranges,
# arrays and strings in shortcut form, e.g.
#
# validates :email, :format => /@/
@@ -70,7 +69,7 @@ module ActiveModel
# validator's initializer as +options[:in]+ while other types including
# regular expressions and strings are passed as +options[:with]+
#
- # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+ and +:strict+
+ # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+ and +:strict+
# can be given to one specific validator, as a hash:
#
# validates :password, :presence => { :if => :password_required? }, :confirmation => true
@@ -80,7 +79,7 @@ module ActiveModel
# validates :password, :presence => true, :confirmation => true, :if => :password_required?
#
def validates(*attributes)
- defaults = attributes.extract_options!
+ defaults = attributes.extract_options!.dup
validations = defaults.slice!(*_validates_default_keys)
raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
@@ -101,12 +100,12 @@ module ActiveModel
end
end
- # This method is used to define validation that can not be corrected by end user
- # and is considered exceptional.
- # So each validator defined with bang or <tt>:strict</tt> option set to <tt>true</tt>
- # will always raise <tt>ActiveModel::InternalValidationFailed</tt> instead of adding error
- # when validation fails
- # See <tt>validates</tt> for more information about validation itself.
+ # This method is used to define validations that cannot be corrected by end
+ # users and are considered exceptional. So each validator defined with bang
+ # or <tt>:strict</tt> option set to <tt>true</tt> will always raise
+ # <tt>ActiveModel::StrictValidationFailed</tt> instead of adding error
+ # when validation fails.
+ # See <tt>validates</tt> for more information about the validation itself.
def validates!(*attributes)
options = attributes.extract_options!
options[:strict] = true
@@ -118,7 +117,7 @@ module ActiveModel
# When creating custom validators, it might be useful to be able to specify
# additional default keys. This can be done by overwriting this method.
def _validates_default_keys
- [ :if, :unless, :on, :allow_blank, :allow_nil , :strict]
+ [:if, :unless, :on, :allow_blank, :allow_nil , :strict]
end
def _parse_validates_options(options) #:nodoc:
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index 0c6e49bee2..9406328d3e 100644
--- a/activemodel/test/cases/attribute_methods_test.rb
+++ b/activemodel/test/cases/attribute_methods_test.rb
@@ -76,7 +76,15 @@ private
end
end
+class ModelWithoutAttributesMethod
+ include ActiveModel::AttributeMethods
+end
+
class AttributeMethodsTest < ActiveModel::TestCase
+ test 'method missing works correctly even if attributes method is not defined' do
+ assert_raises(NoMethodError) { ModelWithoutAttributesMethod.new.foo }
+ end
+
test 'unrelated classes should not share attribute method matchers' do
assert_not_equal ModelWithAttributes.send(:attribute_method_matchers),
ModelWithAttributes2.send(:attribute_method_matchers)
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 8ddedb160a..3bc0d58351 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -27,12 +27,27 @@ class ErrorsTest < ActiveModel::TestCase
end
end
+ def test_delete
+ errors = ActiveModel::Errors.new(self)
+ errors[:foo] = 'omg'
+ errors.delete(:foo)
+ assert_empty errors[:foo]
+ end
+
def test_include?
errors = ActiveModel::Errors.new(self)
errors[:foo] = 'omg'
assert errors.include?(:foo), 'errors should include :foo'
end
+ def test_dup
+ errors = ActiveModel::Errors.new(self)
+ errors[:foo] = 'bar'
+ errors_dup = errors.dup
+ errors_dup[:bar] = 'omg'
+ assert_not_same errors_dup.messages, errors.messages
+ end
+
def test_has_key?
errors = ActiveModel::Errors.new(self)
errors[:foo] = 'omg'
@@ -136,10 +151,10 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a
end
- test 'to_hash should return an ordered hash' do
+ test 'to_hash should return a hash' do
person = Person.new
person.errors.add(:name, "can not be blank")
- assert_instance_of ActiveSupport::OrderedHash, person.errors.to_hash
+ assert_instance_of ::Hash, person.errors.to_hash
end
test 'full_messages should return an array of error messages, with the attribute name included' do
@@ -169,6 +184,16 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal ["is invalid"], hash[:email]
end
+ test 'should return a JSON hash representation of the errors with full messages' do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ person.errors.add(:name, "can not be nil")
+ person.errors.add(:email, "is invalid")
+ hash = person.errors.as_json(:full_messages => true)
+ assert_equal ["name can not be blank", "name can not be nil"], hash[:name]
+ assert_equal ["email is invalid"], hash[:email]
+ end
+
test "generate_message should work without i18n_scope" do
person = Person.new
assert !Person.respond_to?(:i18n_scope)
diff --git a/activemodel/test/cases/lint_test.rb b/activemodel/test/cases/lint_test.rb
index 68372160cd..8faf93c056 100644
--- a/activemodel/test/cases/lint_test.rb
+++ b/activemodel/test/cases/lint_test.rb
@@ -7,14 +7,10 @@ class LintTest < ActiveModel::TestCase
extend ActiveModel::Naming
include ActiveModel::Conversion
- def valid?() true end
def persisted?() false end
def errors
- obj = Object.new
- def obj.[](key) [] end
- def obj.full_messages() [] end
- obj
+ Hash.new([])
end
end
diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb
index be07e59a2f..a197dbe748 100644
--- a/activemodel/test/cases/mass_assignment_security_test.rb
+++ b/activemodel/test/cases/mass_assignment_security_test.rb
@@ -19,6 +19,13 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase
assert_equal expected, sanitized
end
+ def test_attribute_protection_when_role_is_nil
+ user = User.new
+ expected = { "name" => "John Smith", "email" => "john@smith.com" }
+ sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), nil)
+ assert_equal expected, sanitized
+ end
+
def test_only_moderator_role_attribute_accessible
user = SpecialUser.new
expected = { "name" => "John Smith", "email" => "john@smith.com" }
diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb
index b8dad9d51f..3b201a70f5 100644
--- a/activemodel/test/cases/serialization_test.rb
+++ b/activemodel/test/cases/serialization_test.rb
@@ -43,38 +43,38 @@ class SerializationTest < ActiveModel::TestCase
end
def test_method_serializable_hash_should_work
- expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"}
- assert_equal expected , @user.serializable_hash
+ expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"}
+ assert_equal expected, @user.serializable_hash
end
def test_method_serializable_hash_should_work_with_only_option
- expected = {"name"=>"David"}
- assert_equal expected , @user.serializable_hash(:only => [:name])
+ expected = {"name"=>"David"}
+ assert_equal expected, @user.serializable_hash(:only => [:name])
end
def test_method_serializable_hash_should_work_with_except_option
- expected = {"gender"=>"male", "email"=>"david@example.com"}
- assert_equal expected , @user.serializable_hash(:except => [:name])
+ expected = {"gender"=>"male", "email"=>"david@example.com"}
+ assert_equal expected, @user.serializable_hash(:except => [:name])
end
def test_method_serializable_hash_should_work_with_methods_option
- expected = {"name"=>"David", "gender"=>"male", :foo=>"i_am_foo", "email"=>"david@example.com"}
- assert_equal expected , @user.serializable_hash(:methods => [:foo])
+ expected = {"name"=>"David", "gender"=>"male", "foo"=>"i_am_foo", "email"=>"david@example.com"}
+ assert_equal expected, @user.serializable_hash(:methods => [:foo])
end
def test_method_serializable_hash_should_work_with_only_and_methods
- expected = {:foo=>"i_am_foo"}
- assert_equal expected , @user.serializable_hash(:only => [], :methods => [:foo])
+ expected = {"foo"=>"i_am_foo"}
+ assert_equal expected, @user.serializable_hash(:only => [], :methods => [:foo])
end
def test_method_serializable_hash_should_work_with_except_and_methods
- expected = {"gender"=>"male", :foo=>"i_am_foo"}
- assert_equal expected , @user.serializable_hash(:except => [:name, :email], :methods => [:foo])
+ expected = {"gender"=>"male", "foo"=>"i_am_foo"}
+ assert_equal expected, @user.serializable_hash(:except => [:name, :email], :methods => [:foo])
end
def test_should_not_call_methods_that_dont_respond
- expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"}
- assert_equal expected , @user.serializable_hash(:methods => [:bar])
+ expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"}
+ assert_equal expected, @user.serializable_hash(:methods => [:bar])
end
def test_should_use_read_attribute_for_serialization
@@ -87,65 +87,64 @@ class SerializationTest < ActiveModel::TestCase
end
def test_include_option_with_singular_association
- expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com",
- :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}}
- assert_equal expected , @user.serializable_hash(:include => :address)
+ expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com",
+ :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}}
+ assert_equal expected, @user.serializable_hash(:include => :address)
end
def test_include_option_with_plural_association
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
- {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
- assert_equal expected , @user.serializable_hash(:include => :friends)
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
+ {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
+ assert_equal expected, @user.serializable_hash(:include => :friends)
end
def test_include_option_with_empty_association
@user.friends = []
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", :friends=>[]}
- assert_equal expected , @user.serializable_hash(:include => :friends)
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", :friends=>[]}
+ assert_equal expected, @user.serializable_hash(:include => :friends)
end
def test_multiple_includes
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111},
- :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
- {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
- assert_equal expected , @user.serializable_hash(:include => [:address, :friends])
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111},
+ :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
+ {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
+ assert_equal expected, @user.serializable_hash(:include => [:address, :friends])
end
def test_include_with_options
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- :address=>{"street"=>"123 Lane"}}
- assert_equal expected , @user.serializable_hash(:include => {:address => {:only => "street"}})
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ :address=>{"street"=>"123 Lane"}}
+ assert_equal expected, @user.serializable_hash(:include => {:address => {:only => "street"}})
end
def test_nested_include
@user.friends.first.friends = [@user]
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male',
- :friends => [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]},
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male',
+ :friends => [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]},
{"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', :friends => []}]}
- assert_equal expected , @user.serializable_hash(:include => {:friends => {:include => :friends}})
+ assert_equal expected, @user.serializable_hash(:include => {:friends => {:include => :friends}})
end
def test_only_include
expected = {"name"=>"David", :friends => [{"name" => "Joe"}, {"name" => "Sue"}]}
- assert_equal expected , @user.serializable_hash(:only => :name, :include => {:friends => {:only => :name}})
+ assert_equal expected, @user.serializable_hash(:only => :name, :include => {:friends => {:only => :name}})
end
def test_except_include
expected = {"name"=>"David", "email"=>"david@example.com",
- :friends => [{"name" => 'Joe', "email" => 'joe@example.com'},
- {"name" => "Sue", "email" => 'sue@example.com'}]}
- assert_equal expected , @user.serializable_hash(:except => :gender, :include => {:friends => {:except => :gender}})
+ :friends => [{"name" => 'Joe', "email" => 'joe@example.com'},
+ {"name" => "Sue", "email" => 'sue@example.com'}]}
+ assert_equal expected, @user.serializable_hash(:except => :gender, :include => {:friends => {:except => :gender}})
end
def test_multiple_includes_with_options
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- :address=>{"street"=>"123 Lane"},
- :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
- {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
- assert_equal expected , @user.serializable_hash(:include => [{:address => {:only => "street"}}, :friends])
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ :address=>{"street"=>"123 Lane"},
+ :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
+ {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
+ assert_equal expected, @user.serializable_hash(:include => [{:address => {:only => "street"}}, :friends])
end
-
end
diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index 4ac5fb1779..7160635eb4 100644
--- a/activemodel/test/cases/serializers/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
@@ -130,13 +130,13 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json
end
- test "should return OrderedHash for errors" do
+ test "should return Hash for errors" do
contact = Contact.new
contact.errors.add :name, "can't be blank"
contact.errors.add :name, "is too short (minimum is 2 characters)"
contact.errors.add :age, "must be 16 or over"
- hash = ActiveSupport::OrderedHash.new
+ hash = {}
hash[:name] = ["can't be blank", "is too short (minimum is 2 characters)"]
hash[:age] = ["must be 16 or over"]
assert_equal hash.to_json, contact.errors.to_json
diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb
index 1cf09758f9..e4f602bd80 100644
--- a/activemodel/test/cases/validations/callbacks_test.rb
+++ b/activemodel/test/cases/validations/callbacks_test.rb
@@ -5,11 +5,10 @@ class Dog
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
- attr_accessor :name
- attr_writer :history
+ attr_accessor :name, :history
- def history
- @history ||= []
+ def initialize
+ @history = []
end
end
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index aa86d9d959..113bfd6337 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -357,4 +357,22 @@ class LengthValidationTest < ActiveModel::TestCase
ensure
Person.reset_callbacks(:validate)
end
+
+ def test_validates_length_of_for_infinite_maxima
+ Topic.validates_length_of(:title, :within => 5..Float::INFINITY)
+
+ t = Topic.new("title" => "1234")
+ assert t.invalid?
+ assert t.errors[:title].any?
+
+ t.title = "12345"
+ assert t.valid?
+
+ Topic.validates_length_of(:author_name, :maximum => Float::INFINITY)
+
+ assert t.valid?
+
+ t.author_name = "A very long author name that should still be valid." * 100
+ assert t.valid?
+ end
end
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 2f4376bd41..a716d0896e 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -58,8 +58,7 @@ class ValidationsTest < ActiveModel::TestCase
r = Reply.new
r.valid?
- errors = []
- r.errors.each {|attr, messages| errors << [attr.to_s, messages] }
+ errors = r.errors.collect {|attr, messages| [attr.to_s, messages]}
assert errors.include?(["title", "is Empty"])
assert errors.include?(["content", "is Empty"])
@@ -181,7 +180,7 @@ class ValidationsTest < ActiveModel::TestCase
assert_match %r{<error>Title can't be blank</error>}, xml
assert_match %r{<error>Content can't be blank</error>}, xml
- hash = ActiveSupport::OrderedHash.new
+ hash = {}
hash[:title] = ["can't be blank"]
hash[:content] = ["can't be blank"]
assert_equal t.errors.to_json, hash.to_json
@@ -311,7 +310,7 @@ class ValidationsTest < ActiveModel::TestCase
end
def test_strict_validation_particular_validator
- Topic.validates :title, :presence => {:strict => true}
+ Topic.validates :title, :presence => { :strict => true }
assert_raises ActiveModel::StrictValidationFailed do
Topic.new.valid?
end
@@ -330,4 +329,19 @@ class ValidationsTest < ActiveModel::TestCase
Topic.new.valid?
end
end
+
+ def test_strict_validation_error_message
+ Topic.validates :title, :strict => true, :presence => true
+
+ exception = assert_raises(ActiveModel::StrictValidationFailed) do
+ Topic.new.valid?
+ end
+ assert_equal "Title can't be blank", exception.message
+ end
+
+ def test_does_not_modify_options_argument
+ options = { :presence => true }
+ Topic.validates :title, options
+ assert_equal({ :presence => true }, options)
+ end
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index c4feabfb5f..cea50aaa9d 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,99 @@
## Rails 4.0.0 (unreleased) ##
+* Added support for partial indices to PostgreSQL adapter
+
+ The `add_index` method now supports a `where` option that receives a
+ string with the partial index criteria.
+
+ add_index(:accounts, :code, :where => "active")
+
+ Generates
+
+ CREATE INDEX index_accounts_on_code ON accounts(code) WHERE active
+
+ *Marcelo Silveira*
+
+* Implemented ActiveRecord::Relation#none method
+
+ The `none` method returns a chainable relation with zero records
+ (an instance of the NullRelation class).
+
+ Any subsequent condition chained to the returned relation will continue
+ generating an empty relation and will not fire any query to the database.
+
+ *Juanjo Bazán*
+
+* Added the `ActiveRecord::NullRelation` class implementing the null
+ object pattern for the Relation class. *Juanjo Bazán*
+
+* Added deprecation for the `:dependent => :restrict` association option.
+
+ Please note:
+
+ * Up until now `has_many` and `has_one`, `:dependent => :restrict`
+ option raised a `DeleteRestrictionError` at the time of destroying
+ the object. Instead, it will add an error on the model.
+
+ * To fix this warning, make sure your code isn't relying on a
+ `DeleteRestrictionError` and then add
+ `config.active_record.dependent_restrict_raises = false` to your
+ application config.
+
+ * New rails application would be generated with the
+ `config.active_record.dependent_restrict_raises = false` in the
+ application config.
+
+ *Manoj Kumar*
+
+* Added `create_join_table` migration helper to create HABTM join tables
+
+ create_join_table :products, :categories
+ # =>
+ # create_table :categories_products, :id => false do |td|
+ # td.integer :product_id, :null => false
+ # td.integer :category_id, :null => false
+ # end
+
+ *Rafael Mendonça França*
+
+* The primary key is always initialized in the @attributes hash to nil (unless
+ another value has been specified).
+
+* In previous releases, the following would generate a single query with
+ an `OUTER JOIN comments`, rather than two separate queries:
+
+ Post.includes(:comments)
+ .where("comments.name = 'foo'")
+
+ This behaviour relies on matching SQL string, which is an inherently
+ flawed idea unless we write an SQL parser, which we do not wish to
+ do.
+
+ Therefore, it is now deprecated.
+
+ To avoid deprecation warnings and for future compatibility, you must
+ explicitly state which tables you reference, when using SQL snippets:
+
+ Post.includes(:comments)
+ .where("comments.name = 'foo'")
+ .references(:comments)
+
+ Note that you do not need to explicitly specify references in the
+ following cases, as they can be automatically inferred:
+
+ Post.where(comments: { name: 'foo' })
+ Post.where('comments.name' => 'foo')
+ Post.order('comments.name')
+
+ You also do not need to worry about this unless you are doing eager
+ loading. Basically, don't worry unless you see a deprecation warning
+ or (in future releases) an SQL error due to a missing JOIN.
+
+ [Jon Leighton]
+
+* Support for the `schema_info` table has been dropped. Please
+ switch to `schema_migrations`.
+
* Connections *must* be closed at the end of a thread. If not, your
connection pool can fill and an exception will be raised.
@@ -35,7 +129,49 @@
* PostgreSQL hstore types are automatically deserialized from the database.
-## Rails 3.2.0 (unreleased) ##
+
+## Rails 3.2.1 (January 26, 2012) ##
+
+* The threshold for auto EXPLAIN is ignored if there's no logger. *fxn*
+
+* Call `to_s` on the value passed to `table_name=`, in particular symbols
+ are supported (regression). *Sergey Nartimov*
+
+* Fix possible race condition when two threads try to define attribute
+ methods for the same class. *Jon Leighton*
+
+
+## Rails 3.2.0 (January 20, 2012) ##
+
+* Added a `with_lock` method to ActiveRecord objects, which starts
+ a transaction, locks the object (pessimistically) and yields to the block.
+ The method takes one (optional) parameter and passes it to `lock!`.
+
+ Before:
+
+ class Order < ActiveRecord::Base
+ def cancel!
+ transaction do
+ lock!
+ # ... cancelling logic
+ end
+ end
+ end
+
+ After:
+
+ class Order < ActiveRecord::Base
+ def cancel!
+ with_lock do
+ # ... cancelling logic
+ end
+ end
+ end
+
+ *Olek Janiszewski*
+
+* 'on' and 'ON' boolean columns values are type casted to true
+ *Santiago Pastorino*
* Added ability to run migrations only for given scope, which allows
to run migrations only from one engine (for example to revert changes
@@ -44,7 +180,7 @@
Example:
rake db:migrate SCOPE=blog
- *Piotr Sarnacki*
+ *Piotr Sarnacki*
* Migrations copied from engines are now scoped with engine's name,
for example 01_create_posts.blog.rb. *Piotr Sarnacki*
@@ -131,7 +267,7 @@
Client.select(:name).uniq
- This also allows you to revert the unqueness in a relation:
+ This also allows you to revert the uniqueness in a relation:
Client.select(:name).uniq.uniq(false)
@@ -184,7 +320,34 @@
*Aaron Christy*
-## Rails 3.1.3 (unreleased) ##
+ ## Rails 3.1.4 (unreleased) ##
+
+ * Fix a custom primary key regression *GH 3987*
+
+ *Jon Leighton*
+
+ * Perf fix (second try): don't load records for `has many :dependent =>
+ :delete_all` *GH 3672*
+
+ *Jon Leighton*
+
+ * Fix accessing `proxy_association` method from an association extension
+ where the calls are chained. *GH #3890*
+
+ (E.g. `post.comments.where(bla).my_proxy_method`)
+
+ *Jon Leighton*
+
+ * Perf fix: MySQL primary key lookup was still slow for very large
+ tables. *GH 3678*
+
+ *Kenny J*
+
+ * Perf fix: If a table has no primary key, don't repeatedly ask the database for it.
+
+ *Julius de Bruijn*
+
+### Rails 3.1.3 (November 20, 2011) ##
* Perf fix: If we're deleting all records in an association, don't add a IN(..) clause
to the query. *GH 3672*
@@ -197,7 +360,7 @@
*Christos Zisopoulos and Kenny J*
-## Rails 3.1.2 (unreleased) ##
+### Rails 3.1.2 (November 18, 2011) ##
* Fix bug with PostgreSQLAdapter#indexes. When the search path has multiple schemas, spaces
were not being stripped from the schema names after the first.
@@ -616,6 +779,58 @@
*Aaron Patterson*
+## Rails 3.0.12 (unreleased) ##
+
+* No changes.
+
+
+## Rails 3.0.11 (November 18, 2011) ##
+
+* Exceptions from database adapters should not lose their backtrace.
+
+* Backport "ActiveRecord::Persistence#touch should not use default_scope" (GH #1519)
+
+* Psych errors with poor yaml formatting are proxied. Fixes GH #2645 and
+ GH #2731
+
+* Fix ActiveRecord#exists? when passsed a nil value
+
+
+## Rails 3.0.10 (August 16, 2011) ##
+
+* Magic encoding comment added to schema.rb files
+
+* schema.rb is written as UTF-8 by default.
+
+* Ensuring an established connection when running `rake db:schema:dump`
+
+* Association conditions will not clobber join conditions.
+
+* Destroying a record will destroy the HABTM record before destroying itself.
+ GH #402.
+
+* Make `ActiveRecord::Batches#find_each` to not return `self`.
+
+* Update `table_exists?` in PG to to always use current search_path or schema if explictly set.
+
+
+## Rails 3.0.9 (June 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.8 (June 7, 2011) ##
+
+* Fix various problems with using :primary_key and :foreign_key options in conjunction with
+ :through associations. [Jon Leighton]
+
+* Correctly handle inner joins on polymorphic relationships.
+
+* Fixed infinity and negative infinity cases in PG date columns.
+
+* Creating records with invalid associations via `create` or `save` will no longer raise exceptions.
+
+
## Rails 3.0.7 (April 18, 2011) ##
* Destroying records via nested attributes works independent of reject_if LH #6006 *Durran Jordan*
diff --git a/activerecord/RUNNING_UNIT_TESTS b/activerecord/RUNNING_UNIT_TESTS
index 6a2e23b01f..a1ed6712c5 100644
--- a/activerecord/RUNNING_UNIT_TESTS
+++ b/activerecord/RUNNING_UNIT_TESTS
@@ -3,7 +3,7 @@
Copy test/config.example.yml to test/config.yml and edit as needed. Or just run the tests for
the first time, which will do the copy automatically and use the default (sqlite3).
-You can build postgres and mysql databases using the build_postgresql and build_mysql rake tasks.
+You can build postgres and mysql databases using the postgresql:build_databases and mysql:build_databases rake tasks.
== Running the tests
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index d769a73dba..4090293b56 100755
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -48,8 +48,8 @@ end
|x| x =~ /\/adapters\//
} + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")).sort
- t.verbose = true
t.warning = true
+ t.verbose = true
}
task "isolated_test_#{adapter}" do
@@ -187,15 +187,15 @@ end
task :lines do
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
- for file_name in FileList["lib/active_record/**/*.rb"]
+ FileList["lib/active_record/**/*.rb"].each do |file_name|
next if file_name =~ /vendor/
- f = File.open(file_name)
-
- while line = f.gets
- lines += 1
- next if line =~ /^\s*$/
- next if line =~ /^\s*#/
- codelines += 1
+ File.open(file_name, 'r') do |f|
+ while line = f.gets
+ lines += 1
+ next if line =~ /^\s*$/
+ next if line =~ /^\s*#/
+ codelines += 1
+ end
end
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 4a889c1340..26eb74df0f 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -21,6 +21,5 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
- s.add_dependency('arel', '~> 3.0.0.rc1')
- s.add_dependency('tzinfo', '~> 0.3.29')
+ s.add_dependency('arel', '~> 3.0.2')
end
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 78e958442f..73c8a06ab7 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -43,6 +43,7 @@ module ActiveRecord
autoload :AutosaveAssociation
autoload :Relation
+ autoload :NullRelation
autoload_under 'relation' do
autoload :QueryMethods
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 5a8addc4e4..c39284539c 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -46,7 +46,7 @@ module ActiveRecord
#
# def <=>(other_money)
# if currency == other_money.currency
- # amount <=> amount
+ # amount <=> other_money.amount
# else
# amount <=> other_money.exchange_to(currency).amount
# end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 58725246c8..958821add6 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1097,8 +1097,8 @@ module ActiveRecord
# alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated
# objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated
# objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. If set to
- # <tt>:restrict</tt> this object raises an <tt>ActiveRecord::DeleteRestrictionError</tt> exception and
- # cannot be deleted if it has any associated objects.
+ # <tt>:restrict</tt> an error will be added to the object, preventing its deletion, if any associated
+ # objects are present.
#
# If using with the <tt>:through</tt> option, the association on the join model must be
# a +belongs_to+, and the records which get deleted are the join records, rather than
@@ -1251,8 +1251,8 @@ module ActiveRecord
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
# If set to <tt>:nullify</tt>, the associated object's foreign key is set to +NULL+.
- # Also, association is assigned. If set to <tt>:restrict</tt> this object raises an
- # <tt>ActiveRecord::DeleteRestrictionError</tt> exception and cannot be deleted if it has any associated object.
+ # If set to <tt>:restrict</tt>, an error will be added to the object, preventing its deletion, if an
+ # associated object is present.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 0248c7483c..84540a7000 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -5,12 +5,13 @@ module ActiveRecord
# Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
# ActiveRecord::Associations::ThroughAssociationScope
class AliasTracker # :nodoc:
- attr_reader :aliases, :table_joins
+ attr_reader :aliases, :table_joins, :connection
# table_joins is an array of arel joins which might conflict with the aliases we assign here
- def initialize(table_joins = [])
+ def initialize(connection = ActiveRecord::Model.connection, table_joins = [])
@aliases = Hash.new { |h,k| h[k] = initial_count_for(k) }
@table_joins = table_joins
+ @connection = connection
end
def aliased_table_for(table_name, aliased_name = nil)
@@ -70,10 +71,6 @@ module ActiveRecord
def truncate(name)
name.slice(0, connection.table_alias_length - 2)
end
-
- def connection
- ActiveRecord::Base.connection
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 8a17f0ced4..2972b7e13e 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -10,7 +10,7 @@ module ActiveRecord
def initialize(association)
@association = association
- @alias_tracker = AliasTracker.new
+ @alias_tracker = AliasTracker.new klass.connection
end
def scope
@@ -20,7 +20,7 @@ module ActiveRecord
# It's okay to just apply all these like this. The options will only be present if the
# association supports that option; this is enforced by the association builder.
scope = scope.apply_finder_options(options.slice(
- :readonly, :include, :eager_load, :order, :limit, :joins, :group, :having, :offset, :select))
+ :readonly, :include, :references, :order, :limit, :joins, :group, :having, :offset, :select))
if options[:through] && !options[:include]
scope = scope.includes(source_options[:include])
@@ -33,6 +33,23 @@ module ActiveRecord
private
+ def column_for(table_name, column_name)
+ columns = alias_tracker.connection.schema_cache.columns_hash[table_name]
+ columns[column_name]
+ end
+
+ def bind_value(scope, column, value)
+ substitute = alias_tracker.connection.substitute_at(
+ column, scope.bind_values.length)
+ scope.bind_values += [[column, value]]
+ substitute
+ end
+
+ def bind(scope, table_name, column_name, value)
+ column = column_for table_name, column_name
+ bind_value scope, column, value
+ end
+
def add_constraints(scope)
tables = construct_tables
@@ -67,10 +84,13 @@ module ActiveRecord
conditions = self.conditions[i]
if reflection == chain.last
- scope = scope.where(table[key].eq(owner[foreign_key]))
+ bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key]
+ scope = scope.where(table[key].eq(bind_val))
if reflection.type
- scope = scope.where(table[reflection.type].eq(owner.class.base_class.name))
+ value = owner.class.base_class.name
+ bind_val = bind scope, table.table_name, reflection.type.to_s, value
+ scope = scope.where(table[reflection.type].eq(bind_val))
end
conditions.each do |condition|
@@ -90,8 +110,11 @@ module ActiveRecord
scope = scope.joins(join(foreign_table, constraint))
- unless conditions.empty?
- scope = scope.where(sanitize(conditions, table))
+ conditions.each do |condition|
+ condition = interpolate(condition)
+ condition = { (table.table_alias || table.name) => condition } unless i == 0
+
+ scope = scope.where(condition)
end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 776f0d0469..2059d8acdf 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -1,7 +1,7 @@
module ActiveRecord::Associations::Builder
class Association #:nodoc:
class_attribute :valid_options
- self.valid_options = [:class_name, :foreign_key, :select, :conditions, :include, :eager_load, :extend, :readonly, :validate]
+ self.valid_options = [:class_name, :foreign_key, :select, :conditions, :include, :extend, :readonly, :validate, :references]
# Set by subclasses
class_attribute :macro
@@ -51,5 +51,36 @@ module ActiveRecord::Associations::Builder
association(name).writer(value)
end
end
+
+ def dependent_restrict_raises?
+ ActiveRecord::Base.dependent_restrict_raises == true
+ end
+
+ def dependent_restrict_deprecation_warning
+ if dependent_restrict_raises?
+ msg = "In the next release, `:dependent => :restrict` will not raise a `DeleteRestrictionError`. "\
+ "Instead, it will add an error on the model. To fix this warning, make sure your code " \
+ "isn't relying on a `DeleteRestrictionError` and then add " \
+ "`config.active_record.dependent_restrict_raises = false` to your application config."
+ ActiveSupport::Deprecation.warn msg
+ end
+ end
+
+ def define_restrict_dependency_method
+ name = self.name
+ mixin.redefine_method(dependency_method_name) do
+ # has_many or has_one associations
+ if send(name).respond_to?(:exists?) ? send(name).exists? : !send(name).nil?
+ if dependent_restrict_raises?
+ raise ActiveRecord::DeleteRestrictionError.new(name)
+ else
+ key = association(name).reflection.macro == :has_one ? "one" : "many"
+ errors.add(:base, :"restrict_dependent_destroy.#{key}",
+ :record => self.class.human_attribute_name(name).downcase)
+ return false
+ end
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index fc6799fb15..9ddfd433e4 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -21,6 +21,7 @@ module ActiveRecord::Associations::Builder
":nullify or :restrict (#{options[:dependent].inspect})"
end
+ dependent_restrict_deprecation_warning if options[:dependent] == :restrict
send("define_#{options[:dependent]}_dependency_method")
model.before_destroy dependency_method_name
end
@@ -52,13 +53,6 @@ module ActiveRecord::Associations::Builder
end
end
- def define_restrict_dependency_method
- name = self.name
- mixin.redefine_method(dependency_method_name) do
- raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).empty?
- end
- end
-
def dependency_method_name
"has_many_dependent_for_#{name}"
end
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index 7a6cd3890f..bc8a212bee 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -34,15 +34,12 @@ module ActiveRecord::Associations::Builder
":nullify or :restrict (#{options[:dependent].inspect})"
end
+ dependent_restrict_deprecation_warning if options[:dependent] == :restrict
send("define_#{options[:dependent]}_dependency_method")
model.before_destroy dependency_method_name
end
end
- def dependency_method_name
- "has_one_dependent_#{options[:dependent]}_for_#{name}"
- end
-
def define_destroy_dependency_method
name = self.name
mixin.redefine_method(dependency_method_name) do
@@ -52,11 +49,8 @@ module ActiveRecord::Associations::Builder
alias :define_delete_dependency_method :define_destroy_dependency_method
alias :define_nullify_dependency_method :define_destroy_dependency_method
- def define_restrict_dependency_method
- name = self.name
- mixin.redefine_method(dependency_method_name) do
- raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).nil?
- end
+ def dependency_method_name
+ "has_one_dependent_#{options[:dependent]}_for_#{name}"
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 7aed64d48c..b2136605e1 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -58,7 +58,7 @@ module ActiveRecord
end
end
- relation.uniq.pluck(column)
+ relation.pluck(column)
end
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index ba01df00e3..5eda0387c4 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -82,9 +82,8 @@ module ActiveRecord
proxy_association.send :add_to_target, r
yield(r) if block_given?
end
- end
- if target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
+ elsif target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
if load_target
if target.respond_to?(method)
target.send(method, *args, &block)
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 827b01c5ac..cd366ac8b7 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -13,7 +13,7 @@ module ActiveRecord
@join_parts = [JoinBase.new(base)]
@associations = {}
@reflections = []
- @alias_tracker = AliasTracker.new(joins)
+ @alias_tracker = AliasTracker.new(base.connection, joins)
@alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
build(associations)
end
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 03963ab060..0d7d28e458 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -95,8 +95,11 @@ module ActiveRecord
conditions = self.conditions[i].dup
conditions << { reflection.type => foreign_klass.base_class.name } if reflection.type
- unless conditions.empty?
- constraint = constraint.and(sanitize(conditions, table))
+ conditions.each do |condition|
+ condition = active_record.send(:sanitize_sql, interpolate(condition), table.table_alias || table.name)
+ condition = Arel.sql(condition) unless condition.is_a?(Arel::Node)
+
+ constraint = constraint.and(condition)
end
relation.from(join(table, constraint))
diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb
index f83138195c..cea6ad6944 100644
--- a/activerecord/lib/active_record/associations/join_helper.rb
+++ b/activerecord/lib/active_record/associations/join_helper.rb
@@ -40,16 +40,6 @@ module ActiveRecord
def join(table, constraint)
table.create_join(table, table.create_on(constraint), join_type)
end
-
- def sanitize(conditions, table)
- conditions = conditions.map do |condition|
- condition = active_record.send(:sanitize_sql, interpolate(condition), table.table_alias || table.name)
- condition = Arel.sql(condition) unless condition.is_a?(Arel::Node)
- condition
- end
-
- conditions.length == 1 ? conditions.first : Arel::Nodes::And.new(conditions)
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 298decb0f1..253998fb23 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -95,12 +95,11 @@ module ActiveRecord
def build_scope
scope = klass.scoped
- scope = scope.where(process_conditions(options[:conditions]))
- scope = scope.where(process_conditions(preload_options[:conditions]))
+ scope = scope.where(interpolate(options[:conditions]))
+ scope = scope.where(interpolate(preload_options[:conditions]))
scope = scope.select(preload_options[:select] || options[:select] || table[Arel.star])
scope = scope.includes(preload_options[:include] || options[:include])
- scope = scope.eager_load(preload_options[:eager_load] || options[:eager_load])
if options[:as]
scope = scope.where(
@@ -113,13 +112,11 @@ module ActiveRecord
scope
end
- def process_conditions(conditions)
+ def interpolate(conditions)
if conditions.respond_to?(:to_proc)
- conditions = klass.send(:instance_eval, &conditions)
- end
-
- if conditions
- klass.send(:sanitize_sql, conditions)
+ klass.send(:instance_eval, &conditions)
+ else
+ conditions
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 97898c53ae..ad6374d09a 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -53,7 +53,6 @@ module ActiveRecord
else
if options[:conditions]
through_options[:include] = options[:include] || options[:source]
- through_options[:eager_load] = options[:eager_load] || options[:source]
through_options[:conditions] = options[:conditions]
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index c5834e2fef..3e27e85f02 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -20,23 +20,29 @@ module ActiveRecord
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
# (Alias for the protected read_attribute method).
- alias [] read_attribute
+ def [](attr_name)
+ read_attribute(attr_name)
+ end
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
# (Alias for the protected write_attribute method).
- alias []= write_attribute
-
- public :[], :[]=
+ def []=(attr_name, value)
+ write_attribute(attr_name, value)
+ end
end
module ClassMethods
# Generates all the attribute related methods for columns in the database
# accessors, mutators and query methods.
def define_attribute_methods
- return if attribute_methods_generated?
- superclass.define_attribute_methods unless self == base_class
- super(column_names)
- @attribute_methods_generated = true
+ # Use a mutex; we don't want two thread simaltaneously trying to define
+ # attribute methods.
+ @attribute_methods_mutex.synchronize do
+ return if attribute_methods_generated?
+ superclass.define_attribute_methods unless self == base_class
+ super(column_names)
+ @attribute_methods_generated = true
+ end
end
def attribute_methods_generated?
@@ -188,6 +194,7 @@ module ActiveRecord
# Returns the column object for the named attribute.
def column_for_attribute(name)
+ # FIXME: should this return a null object for columns that don't exist?
self.class.columns_hash[name.to_s]
end
@@ -237,7 +244,7 @@ module ActiveRecord
end
def attribute_method?(attr_name)
- attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
+ defined?(@attributes) && @attributes.include?(attr_name)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index bde11d0494..d4f529acbf 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -20,11 +20,7 @@ module ActiveRecord
# Handle *_before_type_cast for method_missing.
def attribute_before_type_cast(attribute_name)
- if attribute_name == 'id'
- read_attribute_before_type_cast(self.class.primary_key)
- else
- read_attribute_before_type_cast(attribute_name)
- end
+ read_attribute_before_type_cast(attribute_name)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 40e4a97e73..3a737e5b35 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -55,12 +55,10 @@ module ActiveRecord
# The attribute already has an unsaved change.
if attribute_changed?(attr)
old = @changed_attributes[attr]
- @changed_attributes.delete(attr) unless field_changed?(attr, old, value)
+ @changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
else
old = clone_attribute_value(:read_attribute, attr)
- # Save Time objects as TimeWithZone if time_zone_aware_attributes == true
- old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
- @changed_attributes[attr] = old if field_changed?(attr, old, value)
+ @changed_attributes[attr] = old if _field_changed?(attr, old, value)
end
# Carry on.
@@ -77,7 +75,7 @@ module ActiveRecord
end
end
- def field_changed?(attr, old, value)
+ def _field_changed?(attr, old, value)
if column = column_for_attribute(attr)
if column.number? && column.null && (old.nil? || old == 0) && value.blank?
# For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
@@ -92,10 +90,6 @@ module ActiveRecord
old != value
end
-
- def clone_with_time_zone_conversion_attribute?(attr, old)
- old.class.name == "Time" && time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
- end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index a7785f8786..8e0b3e1402 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -1,3 +1,5 @@
+require 'set'
+
module ActiveRecord
module AttributeMethods
module PrimaryKey
@@ -24,6 +26,17 @@ module ActiveRecord
query_attribute(self.class.primary_key)
end
+ # Returns the primary key value before type cast
+ def id_before_type_cast
+ read_attribute_before_type_cast(self.class.primary_key)
+ end
+
+ protected
+
+ def attribute_method?(attr_name)
+ attr_name == 'id' || super
+ end
+
module ClassMethods
def define_method_attribute(attr_name)
super
@@ -39,12 +52,15 @@ module ActiveRecord
end
end
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast).to_set
+
def dangerous_attribute_method?(method_name)
- super && !['id', 'id=', 'id?'].include?(method_name)
+ super && !ID_ATTRIBUTE_METHODS.include?(method_name)
end
# Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
- # primary_key_prefix_type setting, though.
+ # primary_key_prefix_type setting, though. Since primary keys are usually protected from mass assignment,
+ # remember to let your database generate them or include the key in +attr_accessible+.
def primary_key
@primary_key = reset_primary_key unless defined? @primary_key
@primary_key
diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb
index 948809c65a..1e841dc8e0 100644
--- a/activerecord/lib/active_record/attribute_methods/query.rb
+++ b/activerecord/lib/active_record/attribute_methods/query.rb
@@ -10,8 +10,11 @@ module ActiveRecord
end
def query_attribute(attr_name)
- unless value = read_attribute(attr_name)
- false
+ value = read_attribute(attr_name)
+
+ case value
+ when true then true
+ when false, nil then false
else
column = self.class.columns_hash[attr_name]
if column.nil?
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 964c4123ef..846ac03d82 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -58,78 +58,88 @@ module ActiveRecord
end
protected
- # We want to generate the methods via module_eval rather than define_method,
- # because define_method is slower on dispatch and uses more memory (because it
- # creates a closure).
- #
- # But sometimes the database might return columns with characters that are not
- # allowed in normal method names (like 'my_column(omg)'. So to work around this
- # we first define with the __temp__ identifier, and then use alias method to
- # rename it to what we want.
- def define_method_attribute(attr_name)
- cast_code = attribute_cast_code(attr_name)
-
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def __temp__
- #{internal_attribute_access_code(attr_name, cast_code)}
- end
- alias_method '#{attr_name}', :__temp__
- undef_method :__temp__
- STR
-
- generated_external_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def __temp__(v, attributes, attributes_cache, attr_name)
- #{external_attribute_access_code(attr_name, cast_code)}
- end
- alias_method '#{attr_name}', :__temp__
- undef_method :__temp__
- STR
- end
-
- private
- def cacheable_column?(column)
- attribute_types_cached_by_default.include?(column.type)
- end
-
- def internal_attribute_access_code(attr_name, cast_code)
- access_code = "(v=@attributes[attr_name]) && #{cast_code}"
-
- unless attr_name == primary_key
- access_code.insert(0, "missing_attribute(attr_name, caller) unless @attributes.has_key?(attr_name); ")
+ # We want to generate the methods via module_eval rather than define_method,
+ # because define_method is slower on dispatch and uses more memory (because it
+ # creates a closure).
+ #
+ # But sometimes the database might return columns with characters that are not
+ # allowed in normal method names (like 'my_column(omg)'. So to work around this
+ # we first define with the __temp__ identifier, and then use alias method to
+ # rename it to what we want.
+ def define_method_attribute(attr_name)
+ cast_code = attribute_cast_code(attr_name)
+
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
+ def __temp__
+ #{internal_attribute_access_code(attr_name, cast_code)}
end
+ alias_method '#{attr_name}', :__temp__
+ undef_method :__temp__
+ STR
- if cache_attribute?(attr_name)
- access_code = "@attributes_cache[attr_name] ||= (#{access_code})"
+ generated_external_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
+ def __temp__(v, attributes, attributes_cache, attr_name)
+ #{external_attribute_access_code(attr_name, cast_code)}
end
+ alias_method '#{attr_name}', :__temp__
+ undef_method :__temp__
+ STR
+ end
- "attr_name = '#{attr_name}'; #{access_code}"
+ private
+ def cacheable_column?(column)
+ if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
+ ! serialized_attributes.include? column.name
+ else
+ attribute_types_cached_by_default.include?(column.type)
end
+ end
- def external_attribute_access_code(attr_name, cast_code)
- access_code = "v && #{cast_code}"
+ def internal_attribute_access_code(attr_name, cast_code)
+ "read_attribute('#{attr_name}') { |n| missing_attribute(n, caller) }"
+ end
- if cache_attribute?(attr_name)
- access_code = "attributes_cache[attr_name] ||= (#{access_code})"
- end
+ def external_attribute_access_code(attr_name, cast_code)
+ access_code = "v && #{cast_code}"
- access_code
+ if cache_attribute?(attr_name)
+ access_code = "attributes_cache[attr_name] ||= (#{access_code})"
end
- def attribute_cast_code(attr_name)
- columns_hash[attr_name].type_cast_code('v')
- end
+ access_code
+ end
+
+ def attribute_cast_code(attr_name)
+ columns_hash[attr_name].type_cast_code('v')
+ end
end
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
def read_attribute(attr_name)
- self.class.type_cast_attribute(attr_name, @attributes, @attributes_cache)
+ # If it's cached, just return it
+ @attributes_cache.fetch(attr_name) { |name|
+ column = @columns_hash.fetch(name) {
+ return self.class.type_cast_attribute(name, @attributes, @attributes_cache)
+ }
+
+ value = @attributes.fetch(name) {
+ return block_given? ? yield(name) : nil
+ }
+
+ if self.class.cache_attribute?(name)
+ @attributes_cache[name] = column.type_cast(value)
+ else
+ column.type_cast value
+ end
+ }
end
private
- def attribute(attribute_name)
- read_attribute(attribute_name)
- end
+
+ def attribute(attribute_name)
+ read_attribute(attribute_name)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 0c8e4e4b9a..165785c8fb 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -10,6 +10,20 @@ module ActiveRecord
self.serialized_attributes = {}
end
+ class Type # :nodoc:
+ def initialize(column)
+ @column = column
+ end
+
+ def type_cast(value)
+ value.unserialized_value
+ end
+
+ def type
+ @column.type
+ end
+ end
+
class Attribute < Struct.new(:coder, :value, :state)
def unserialized_value
state == :serialized ? unserialize : value
@@ -88,6 +102,14 @@ module ActiveRecord
super
end
end
+
+ def read_attribute_before_type_cast(attr_name)
+ if serialized_attributes.include?(attr_name)
+ super.unserialized_value
+ else
+ super
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 2f86e32f41..20372c5c18 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -4,6 +4,21 @@ require 'active_support/core_ext/object/inclusion'
module ActiveRecord
module AttributeMethods
module TimeZoneConversion
+ class Type # :nodoc:
+ def initialize(column)
+ @column = column
+ end
+
+ def type_cast(value)
+ value = @column.type_cast(value)
+ value.acts_like?(:time) ? value.in_time_zone : value
+ end
+
+ def type
+ @column.type
+ end
+ end
+
extend ActiveSupport::Concern
included do
@@ -16,46 +31,48 @@ module ActiveRecord
module ClassMethods
protected
- # The enhanced read method automatically converts the UTC time stored in the database to the time
- # zone stored in Time.zone.
- def attribute_cast_code(attr_name)
- column = columns_hash[attr_name]
-
- if create_time_zone_conversion_attribute?(attr_name, column)
- typecast = "v = #{super}"
- time_zone_conversion = "v.acts_like?(:time) ? v.in_time_zone : v"
-
- "((#{typecast}) && (#{time_zone_conversion}))"
- else
- super
- end
+ # The enhanced read method automatically converts the UTC time stored in the database to the time
+ # zone stored in Time.zone.
+ def attribute_cast_code(attr_name)
+ column = columns_hash[attr_name]
+
+ if create_time_zone_conversion_attribute?(attr_name, column)
+ typecast = "v = #{super}"
+ time_zone_conversion = "v.acts_like?(:time) ? v.in_time_zone : v"
+
+ "((#{typecast}) && (#{time_zone_conversion}))"
+ else
+ super
end
+ end
- # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
- # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
- def define_method_attribute=(attr_name)
- if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
- method_body, line = <<-EOV, __LINE__ + 1
- def #{attr_name}=(original_time)
- time = original_time
- unless time.acts_like?(:time)
- time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
- end
- time = time.in_time_zone rescue nil if time
- write_attribute(:#{attr_name}, original_time)
- @attributes_cache["#{attr_name}"] = time
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
+ # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
+ def define_method_attribute=(attr_name)
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
+ method_body, line = <<-EOV, __LINE__ + 1
+ def #{attr_name}=(original_time)
+ time = original_time
+ unless time.acts_like?(:time)
+ time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
end
- EOV
- generated_attribute_methods.module_eval(method_body, __FILE__, line)
- else
- super
- end
+ time = time.in_time_zone rescue nil if time
+ write_attribute(:#{attr_name}, original_time)
+ @attributes_cache["#{attr_name}"] = time
+ end
+ EOV
+ generated_attribute_methods.module_eval(method_body, __FILE__, line)
+ else
+ super
end
+ end
private
- def create_time_zone_conversion_attribute?(name, column)
- time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && column.type.in?([:datetime, :timestamp])
- end
+ def create_time_zone_conversion_attribute?(name, column)
+ time_zone_aware_attributes &&
+ !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
+ [:datetime, :timestamp].include?(column.type)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index fde55b95da..50435921b1 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -28,6 +28,12 @@ module ActiveRecord
@attributes_cache.delete(attr_name)
column = column_for_attribute(attr_name)
+ # If we're dealing with a binary column, write the data to the cache
+ # so we don't attempt to typecast multiple times.
+ if column && column.binary?
+ @attributes_cache[attr_name] = value
+ end
+
if column || @attributes.has_key?(attr_name)
@attributes[attr_name] = type_cast_attribute_for_write(column, value)
else
@@ -37,30 +43,16 @@ module ActiveRecord
alias_method :raw_write_attribute, :write_attribute
private
- # Handle *= for method_missing.
- def attribute=(attribute_name, value)
- write_attribute(attribute_name, value)
- end
+ # Handle *= for method_missing.
+ def attribute=(attribute_name, value)
+ write_attribute(attribute_name, value)
+ end
- def type_cast_attribute_for_write(column, value)
- if column && column.number?
- convert_number_column_value(value)
- else
- value
- end
- end
+ def type_cast_attribute_for_write(column, value)
+ return value unless column
- def convert_number_column_value(value)
- if value == false
- 0
- elsif value == true
- 1
- elsif value.is_a?(String) && value.blank?
- nil
- else
- value
- end
- end
+ column.type_cast_for_write value
+ end
end
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 8031912669..4bafadc666 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -78,7 +78,7 @@ module ActiveRecord
# When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
#
# class Post
- # has_many :comments # :autosave option is no declared
+ # has_many :comments # :autosave option is not declared
# end
#
# post = Post.new(:title => 'ruby rocks')
@@ -93,7 +93,8 @@ module ActiveRecord
# post.comments.create(:body => 'hello world')
# post.save # => saves both post and comment
#
- # When <tt>:autosave</tt> is true all children is saved, no matter whether they are new records:
+ # When <tt>:autosave</tt> is true all children are saved, no matter whether they
+ # are new records or not:
#
# class Post
# has_many :comments, :autosave => true
@@ -295,7 +296,7 @@ module ActiveRecord
def association_valid?(reflection, record)
return true if record.destroyed? || record.marked_for_destruction?
- unless valid = record.valid?
+ unless valid = record.valid?(validation_context)
if reflection.options[:autosave]
record.errors.each do |attribute, message|
attribute = "#{reflection.name}.#{attribute}"
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index fb59d9fb07..77af540c3e 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -15,7 +15,7 @@ module ActiveRecord
end
def dump(obj)
- YAML.dump obj
+ YAML.dump(obj) unless obj.nil?
end
def load(yaml)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index b8f99adc22..37a9d216df 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -184,16 +184,6 @@ module ActiveRecord
end
end
- # Verify active connections and remove and disconnect connections
- # associated with stale threads.
- def verify_active_connections! #:nodoc:
- synchronize do
- @connections.each do |connection|
- connection.verify!
- end
- end
- end
-
def clear_stale_cached_connections! # :nodoc:
end
deprecate :clear_stale_cached_connections!
@@ -328,16 +318,18 @@ module ActiveRecord
# ActiveRecord::Base.connection_handler. Active Record models use this to
# determine that connection pool that they should use.
class ConnectionHandler
- attr_reader :connection_pools
-
- def initialize(pools = {})
+ def initialize(pools = Hash.new { |h,k| h[k] = {} })
@connection_pools = pools
- @class_to_pool = {}
+ @class_to_pool = Hash.new { |h,k| h[k] = {} }
+ end
+
+ def connection_pools
+ @connection_pools[Process.pid]
end
def establish_connection(name, spec)
- @connection_pools[spec] ||= ConnectionAdapters::ConnectionPool.new(spec)
- @class_to_pool[name] = @connection_pools[spec]
+ set_pool_for_spec spec, ConnectionAdapters::ConnectionPool.new(spec)
+ set_class_to_pool name, connection_pools[spec]
end
# Returns true if there are any active connections among the connection
@@ -350,21 +342,16 @@ module ActiveRecord
# and also returns connections to the pool cached by threads that are no
# longer alive.
def clear_active_connections!
- @connection_pools.each_value {|pool| pool.release_connection }
+ connection_pools.each_value {|pool| pool.release_connection }
end
# Clears the cache which maps classes.
def clear_reloadable_connections!
- @connection_pools.each_value {|pool| pool.clear_reloadable_connections! }
+ connection_pools.each_value {|pool| pool.clear_reloadable_connections! }
end
def clear_all_connections!
- @connection_pools.each_value {|pool| pool.disconnect! }
- end
-
- # Verify active connections.
- def verify_active_connections! #:nodoc:
- @connection_pools.each_value {|pool| pool.verify_active_connections! }
+ connection_pools.each_value {|pool| pool.disconnect! }
end
# Locate the connection of the nearest super class. This can be an
@@ -388,53 +375,56 @@ module ActiveRecord
# can be used as an argument for establish_connection, for easily
# re-establishing the connection.
def remove_connection(klass)
- pool = @class_to_pool.delete(klass.name)
+ pool = class_to_pool.delete(klass.name)
return nil unless pool
- @connection_pools.delete pool.spec
+ connection_pools.delete pool.spec
pool.automatic_reconnect = false
pool.disconnect!
pool.spec.config
end
def retrieve_connection_pool(klass)
- pool = @class_to_pool[klass.name]
+ pool = get_pool_for_class klass.name
return pool if pool
return nil if ActiveRecord::Model == klass
retrieve_connection_pool klass.active_record_super
end
- end
- class ConnectionManagement
- class Proxy # :nodoc:
- attr_reader :body, :testing
-
- def initialize(body, testing = false)
- @body = body
- @testing = testing
- end
+ private
- def method_missing(method_sym, *arguments, &block)
- @body.send(method_sym, *arguments, &block)
- end
+ def class_to_pool
+ @class_to_pool[Process.pid]
+ end
- def respond_to?(method_sym, include_private = false)
- super || @body.respond_to?(method_sym)
- end
+ def set_pool_for_spec(spec, pool)
+ @connection_pools[Process.pid][spec] = pool
+ end
- def each(&block)
- body.each(&block)
- end
+ def set_class_to_pool(name, pool)
+ @class_to_pool[Process.pid][name] = pool
+ pool
+ end
- def close
- body.close if body.respond_to?(:close)
+ def get_pool_for_class(klass)
+ @class_to_pool[Process.pid].fetch(klass) {
+ c_to_p = @class_to_pool.values.find { |class_to_pool|
+ class_to_pool[klass]
+ }
- # Don't return connection (and perform implicit rollback) if
- # this request is a part of integration test
- ActiveRecord::Base.clear_active_connections! unless testing
- end
+ if c_to_p
+ pool = c_to_p[klass]
+ pool = ConnectionAdapters::ConnectionPool.new pool.spec
+ set_pool_for_spec pool.spec, pool
+ set_class_to_pool klass, pool
+ else
+ set_class_to_pool klass, nil
+ end
+ }
end
+ end
+ class ConnectionManagement
def initialize(app)
@app = app
end
@@ -442,9 +432,12 @@ module ActiveRecord
def call(env)
testing = env.key?('rack.test')
- status, headers, body = @app.call(env)
+ response = @app.call(env)
+ response[2] = ::Rack::BodyProxy.new(response[2]) do
+ ActiveRecord::Base.clear_active_connections! unless testing
+ end
- [status, headers, Proxy.new(body, testing)]
+ response
rescue
ActiveRecord::Base.clear_active_connections! unless testing
raise
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index eb8cff9610..174450eb00 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -2,9 +2,11 @@ module ActiveRecord
module ConnectionAdapters # :nodoc:
module DatabaseStatements
# Converts an arel AST to SQL
- def to_sql(arel)
+ def to_sql(arel, binds = [])
if arel.respond_to?(:ast)
- visitor.accept(arel.ast)
+ visitor.accept(arel.ast) do
+ quote(*binds.shift.reverse)
+ end
else
arel
end
@@ -13,19 +15,19 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select_all(arel, name = nil, binds = [])
- select(to_sql(arel), name, binds)
+ select(to_sql(arel, binds), name, binds)
end
# Returns a record hash with the column names as keys and column values
# as values.
- def select_one(arel, name = nil)
- result = select_all(arel, name)
+ def select_one(arel, name = nil, binds = [])
+ result = select_all(arel, name, binds)
result.first if result
end
# Returns a single value from a record
- def select_value(arel, name = nil)
- if result = select_one(arel, name)
+ def select_value(arel, name = nil, binds = [])
+ if result = select_one(arel, name, binds)
result.values.first
end
end
@@ -33,7 +35,7 @@ module ActiveRecord
# Returns an array of the values of the first column in a select:
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
def select_values(arel, name = nil)
- result = select_rows(to_sql(arel), name)
+ result = select_rows(to_sql(arel, []), name)
result.map { |v| v[0] }
end
@@ -84,19 +86,19 @@ module ActiveRecord
# If the next id was calculated in advance (as in Oracle), it should be
# passed in as +id_value+.
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
- sql, binds = sql_for_insert(to_sql(arel), pk, id_value, sequence_name, binds)
+ sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
value = exec_insert(sql, name, binds)
id_value || last_inserted_id(value)
end
# Executes the update statement and returns the number of rows affected.
def update(arel, name = nil, binds = [])
- exec_update(to_sql(arel), name, binds)
+ exec_update(to_sql(arel, binds), name, binds)
end
# Executes the delete statement and returns the number of rows affected.
def delete(arel, name = nil, binds = [])
- exec_delete(to_sql(arel), name, binds)
+ exec_delete(to_sql(arel, binds), name, binds)
end
# Checks whether there is currently no transaction active. This is done
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 27ff13ad89..17377bad96 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -57,7 +57,7 @@ module ActiveRecord
def select_all(arel, name = nil, binds = [])
if @query_cache_enabled
- sql = to_sql(arel)
+ sql = to_sql(arel, binds)
cache_sql(sql, binds) { super(sql, name, binds) }
else
super
@@ -65,18 +65,24 @@ module ActiveRecord
end
private
- def cache_sql(sql, binds)
- result =
- if @query_cache[sql].key?(binds)
- ActiveSupport::Notifications.instrument("sql.active_record",
- :sql => sql, :name => "CACHE", :connection_id => object_id)
- @query_cache[sql][binds]
- else
- @query_cache[sql][binds] = yield
- end
+ def cache_sql(sql, binds)
+ result =
+ if @query_cache[sql].key?(binds)
+ ActiveSupport::Notifications.instrument("sql.active_record",
+ :sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id)
+ @query_cache[sql][binds]
+ else
+ @query_cache[sql][binds] = yield
+ end
+ # FIXME: we should guarantee that all cached items are Result
+ # objects. Then we can avoid this conditional
+ if ActiveRecord::Result === result
+ result.dup
+ else
result.collect { |row| row.dup }
end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index f93c7cd74a..44ac37c498 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -71,7 +71,8 @@ module ActiveRecord
when Date, Time then quoted_date(value)
when Symbol then value.to_s
else
- YAML.dump(value)
+ to_type = column ? " to #{column.type}" : ""
+ raise TypeError, "can't cast #{value.class}#{to_type}"
end
end
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 132ca10f79..ad2e8634eb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -6,7 +6,7 @@ require 'bigdecimal/util'
module ActiveRecord
module ConnectionAdapters #:nodoc:
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders) #:nodoc:
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where) #:nodoc:
end
# Abstract representation of a column definition. Instances of this type
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 54393e24a3..6bac05191d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1,8 +1,12 @@
require 'active_support/deprecation/reporting'
+require 'active_record/schema_migration'
+require 'active_record/migration/join_table'
module ActiveRecord
module ConnectionAdapters # :nodoc:
module SchemaStatements
+ include ActiveRecord::Migration::JoinTable
+
# Returns a Hash of mappings from the abstract data types to the native
# database types. See TableDefinition#column for details on the recognized
# abstract data types.
@@ -52,7 +56,7 @@ module ActiveRecord
# Returns an array of Column objects for the table specified by +table_name+.
# See the concrete implementation for details on the expected parameter values.
- def columns(table_name, name = nil) end
+ def columns(table_name) end
# Checks to see if a column exists in a given table.
#
@@ -159,7 +163,7 @@ module ActiveRecord
yield td if block_given?
if options[:force] && table_exists?(table_name)
- drop_table(table_name)
+ drop_table(table_name, options)
end
create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
@@ -169,6 +173,45 @@ module ActiveRecord
execute create_sql
end
+ # Creates a new join table with the name created using the lexical order of the first two
+ # arguments. These arguments can be be a String or a Symbol.
+ #
+ # # Creates a table called 'assemblies_parts' with no id.
+ # create_join_table(:assemblies, :parts)
+ #
+ # You can pass a +options+ hash can include the following keys:
+ # [<tt>:table_name</tt>]
+ # Sets the table name overriding the default
+ # [<tt>:column_options</tt>]
+ # Any extra options you want appended to the columns definition.
+ # [<tt>:options</tt>]
+ # Any extra options you want appended to the table definition.
+ # [<tt>:temporary</tt>]
+ # Make a temporary table.
+ # [<tt>:force</tt>]
+ # Set to true to drop the table before creating it.
+ # Defaults to false.
+ #
+ # ===== Examples
+ # ====== Add a backend specific option to the generated SQL (MySQL)
+ # create_join_table(:assemblies, :parts, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
+ # generates:
+ # CREATE TABLE assemblies_parts (
+ # assembly_id int NOT NULL,
+ # part_id int NOT NULL,
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
+ def create_join_table(table_1, table_2, options = {})
+ join_table_name = find_join_table_name(table_1, table_2, options)
+
+ column_options = options.delete(:column_options) || {}
+ column_options.reverse_merge!({:null => false})
+
+ create_table(join_table_name, options.merge!(:id => false)) do |td|
+ td.integer :"#{table_1.to_s.singularize}_id", column_options
+ td.integer :"#{table_2.to_s.singularize}_id", column_options
+ end
+ end
+
# A block for changing columns in +table+.
#
# === Example
@@ -251,7 +294,7 @@ module ActiveRecord
end
# Drops a table from the database.
- def drop_table(table_name)
+ def drop_table(table_name, options = {})
execute "DROP TABLE #{quote_table_name(table_name)}"
end
@@ -338,9 +381,16 @@ module ActiveRecord
#
# Note: mysql doesn't yet support index order (it accepts the syntax but ignores it)
#
+ # ====== Creating a partial index
+ # add_index(:accounts, [:branch_id, :party_id], :unique => true, :where => "active")
+ # generates
+ # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
+ #
+ # Note: only supported by PostgreSQL
+ #
def add_index(table_name, column_name, options = {})
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})"
+ index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
end
# Remove the given index from the table.
@@ -404,34 +454,16 @@ module ActiveRecord
def dump_schema_information #:nodoc:
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
- migrated = select_values("SELECT version FROM #{sm_table} ORDER BY version")
- migrated.map { |v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}');" }.join("\n\n")
+
+ ActiveRecord::SchemaMigration.order('version').all.map { |sm|
+ "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');"
+ }.join "\n\n"
end
# Should not be called normally, but this operation is non-destructive.
# The migrations module handles this automatically.
def initialize_schema_migrations_table
- sm_table = ActiveRecord::Migrator.schema_migrations_table_name
-
- unless table_exists?(sm_table)
- create_table(sm_table, :id => false) do |schema_migrations_table|
- schema_migrations_table.column :version, :string, :null => false
- end
- add_index sm_table, :version, :unique => true,
- :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:
- si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix
-
- if table_exists?(si_table)
- ActiveRecord::Deprecation.warn "Usage of the schema table `#{si_table}` is deprecated. Please switch to using `schema_migrations` table"
-
- old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i
- assume_migrated_upto_version(old_version)
- drop_table(si_table)
- end
- end
+ ActiveRecord::SchemaMigration.create_table
end
def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
@@ -556,6 +588,9 @@ module ActiveRecord
if Hash === options # legacy support, since this param was a string
index_type = options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
+ if supports_partial_index?
+ index_options = options[:where] ? " WHERE #{options[:where]}" : ""
+ end
else
index_type = options
end
@@ -568,7 +603,7 @@ module ActiveRecord
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
- [index_name, index_type, index_columns]
+ [index_name, index_type, index_columns, index_options]
end
def index_name_for_remove(table_name, options = {})
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index edea414db7..c6af682d28 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -13,21 +13,24 @@ module ActiveRecord
autoload :Column
autoload :ConnectionSpecification
- autoload_under 'abstract' do
- autoload :IndexDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
- autoload :ColumnDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
- autoload :TableDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
- autoload :Table, 'active_record/connection_adapters/abstract/schema_definitions'
+ autoload_at 'active_record/connection_adapters/abstract/schema_definitions' do
+ autoload :IndexDefinition
+ autoload :ColumnDefinition
+ autoload :TableDefinition
+ autoload :Table
+ end
+ autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
+ autoload :ConnectionHandler
+ autoload :ConnectionManagement
+ end
+
+ autoload_under 'abstract' do
autoload :SchemaStatements
autoload :DatabaseStatements
autoload :DatabaseLimits
autoload :Quoting
-
autoload :ConnectionPool
- autoload :ConnectionHandler, 'active_record/connection_adapters/abstract/connection_pool'
- autoload :ConnectionManagement, 'active_record/connection_adapters/abstract/connection_pool'
-
autoload :QueryCache
end
@@ -142,6 +145,11 @@ module ActiveRecord
false
end
+ # Does this adapter support partial indices?
+ def supports_partial_index?
+ false
+ end
+
# Does this adapter support explain? As of this writing sqlite3,
# mysql2, and postgresql are the only ones that do.
def supports_explain?
@@ -158,7 +166,7 @@ module ActiveRecord
# Returns a bind substitution value given a +column+ and list of current
# +binds+
def substitute_at(column, index)
- Arel.sql '?'
+ Arel::Nodes::BindParam.new '?'
end
# REFERENTIAL INTEGRITY ====================================
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 560773ca86..e33903622b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/object/blank'
+require 'arel/visitors/bind_visitor'
module ActiveRecord
module ConnectionAdapters
@@ -122,12 +123,21 @@ module ActiveRecord
:boolean => { :name => "tinyint", :limit => 1 }
}
+ class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
+ include Arel::Visitors::BindVisitor
+ end
+
# FIXME: Make the first parameter more similar for the two adapters
def initialize(connection, logger, connection_options, config)
super(connection, logger)
@connection_options, @config = connection_options, config
@quoted_column_names, @quoted_table_names = {}, {}
- @visitor = Arel::Visitors::MySQL.new self
+
+ if config.fetch(:prepared_statements) { true }
+ @visitor = Arel::Visitors::MySQL.new self
+ else
+ @visitor = BindSubstitution.new self
+ end
end
def adapter_name #:nodoc:
@@ -409,7 +419,7 @@ module ActiveRecord
end
# Returns an array of +Column+ objects for the table specified by +table_name+.
- def columns(table_name, name = nil)#:nodoc:
+ def columns(table_name)#:nodoc:
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
execute_and_free(sql, 'SCHEMA') do |result|
each_hash(result).map do |field|
@@ -427,7 +437,7 @@ module ActiveRecord
table, arguments = args.shift, args
method = :"#{command}_sql"
- if respond_to?(method)
+ if respond_to?(method, true)
send(method, table, *arguments)
else
raise "Unknown method called : #{method}(#{arguments.inspect})"
@@ -474,15 +484,26 @@ module ActiveRecord
# Maps logical Rails types to MySQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
- return super unless type.to_s == 'integer'
-
- case limit
- when 1; 'tinyint'
- when 2; 'smallint'
- when 3; 'mediumint'
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
- when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
+ case type.to_s
+ when 'integer'
+ case limit
+ when 1; 'tinyint'
+ when 2; 'smallint'
+ when 3; 'mediumint'
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
+ when 5..8; 'bigint'
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
+ end
+ when 'text'
+ case limit
+ when 0..0xff; 'tinytext'
+ when nil, 0x100..0xffff; 'text'
+ when 0x10000..0xffffff; 'mediumtext'
+ when 0x1000000..0xffffffff; 'longtext'
+ else raise(ActiveRecordError, "No text type has character length #{limit}")
+ end
+ else
+ super
end
end
@@ -505,7 +526,7 @@ module ActiveRecord
execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
create_table = each_hash(result).first[:"Create Table"]
if create_table.to_s =~ /PRIMARY KEY\s+\((.+)\)/
- keys = $1.split(",").map { |key| key.gsub(/`/, "") }
+ keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") }
keys.length == 1 ? [keys.first, nil] : nil
else
nil
@@ -541,7 +562,7 @@ module ActiveRecord
if options.is_a?(Hash) && length = options[:length]
case length
when Hash
- column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name)}
+ column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
when Fixnum
column_names.each {|name| option_strings[name] += "(#{length})"}
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 6aa4a2c5b3..78e54c4c9b 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -5,8 +5,8 @@ module ActiveRecord
module ConnectionAdapters
# An abstract definition of a column in a table.
class Column
- TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
- FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
+ TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set
+ FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
module Format
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
@@ -66,6 +66,25 @@ module ActiveRecord
end
end
+ def binary?
+ type == :binary
+ end
+
+ # Casts a Ruby value to something appropriate for writing to the database.
+ def type_cast_for_write(value)
+ return value unless number?
+
+ if value == false
+ 0
+ elsif value == true
+ 1
+ elsif value.is_a?(String) && value.blank?
+ nil
+ else
+ value
+ end
+ end
+
# Casts value (which is a String) to an appropriate instance.
def type_cast(value)
return nil if value.nil?
@@ -83,7 +102,6 @@ module ActiveRecord
when :date then klass.value_to_date(value)
when :binary then klass.binary_to_string(value)
when :boolean then klass.value_to_boolean(value)
- when :hstore then klass.cast_hstore(value)
else value
end
end
@@ -101,7 +119,7 @@ module ActiveRecord
when :date then "#{klass}.value_to_date(#{var_name})"
when :binary then "#{klass}.binary_to_string(#{var_name})"
when :boolean then "#{klass}.value_to_boolean(#{var_name})"
- when :hstore then "#{klass}.cast_hstore(#{var_name})"
+ when :hstore then "#{klass}.string_to_hstore(#{var_name})"
else var_name
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 6086c32dbe..3f45f23de8 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -32,6 +32,7 @@ module ActiveRecord
def initialize(connection, logger, connection_options, config)
super
+ @visitor = BindSubstitution.new self
configure_connection
end
@@ -65,10 +66,6 @@ module ActiveRecord
@connection.escape(string)
end
- def substitute_at(column, index)
- Arel.sql "\0"
- end
-
# CONNECTION MANAGEMENT ====================================
def active?
@@ -80,6 +77,7 @@ module ActiveRecord
disconnect!
connect
end
+ alias :reset! :reconnect!
# Disconnects from the database if already connected.
# Otherwise, this method does nothing.
@@ -90,15 +88,10 @@ module ActiveRecord
end
end
- def reset!
- disconnect!
- connect
- end
-
# DATABASE STATEMENTS ======================================
def explain(arel, binds = [])
- sql = "EXPLAIN #{to_sql(arel)}"
+ sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
start = Time.now
result = exec_query(sql, 'EXPLAIN', binds)
elapsed = Time.now - start
@@ -224,8 +217,7 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select(sql, name = nil, binds = [])
- binds = binds.dup
- exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a
+ exec_query(sql, name)
end
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
@@ -235,17 +227,11 @@ module ActiveRecord
alias :create :insert_sql
def exec_insert(sql, name, binds)
- binds = binds.dup
-
- # Pretend to support bind parameters
- execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
+ execute to_sql(sql, binds), name
end
def exec_delete(sql, name, binds)
- binds = binds.dup
-
- # Pretend to support bind parameters
- execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
+ execute to_sql(sql, binds), name
@connection.affected_rows
end
alias :exec_update :exec_delete
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index e432c5af32..724dbff1f0 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -119,7 +119,7 @@ module ActiveRecord
private
def cache
- @cache[$$]
+ @cache[Process.pid]
end
end
@@ -408,7 +408,7 @@ module ActiveRecord
def select(sql, name = nil, binds = [])
@connection.query_with_result = true
- rows = exec_query(sql, name, binds).to_a
+ rows = exec_query(sql, name, binds)
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
rows
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
new file mode 100644
index 0000000000..c82afc232c
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -0,0 +1,243 @@
+require 'active_record/connection_adapters/abstract_adapter'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class PostgreSQLAdapter < AbstractAdapter
+ module OID
+ class Type
+ def type; end
+
+ def type_cast_for_write(value)
+ value
+ end
+ end
+
+ class Identity < Type
+ def type_cast(value)
+ value
+ end
+ end
+
+ class Bytea < Type
+ def type_cast(value)
+ PGconn.unescape_bytea value
+ end
+ end
+
+ class Money < Type
+ def type_cast(value)
+ return if value.nil?
+
+ # Because money output is formatted according to the locale, there are two
+ # cases to consider (note the decimal separators):
+ # (1) $12,345,678.12
+ # (2) $12.345.678,12
+
+ case value
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
+ value.gsub!(/[^-\d.]/, '')
+ when /^-?\D+[\d.]+,\d{2}$/ # (2)
+ value.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
+ end
+
+ ConnectionAdapters::Column.value_to_decimal value
+ end
+ end
+
+ class Vector < Type
+ attr_reader :delim, :subtype
+
+ # +delim+ corresponds to the `typdelim` column in the pg_types
+ # table. +subtype+ is derived from the `typelem` column in the
+ # pg_types table.
+ def initialize(delim, subtype)
+ @delim = delim
+ @subtype = subtype
+ end
+
+ # FIXME: this should probably split on +delim+ and use +subtype+
+ # to cast the values. Unfortunately, the current Rails behavior
+ # is to just return the string.
+ def type_cast(value)
+ value
+ end
+ end
+
+ class Integer < Type
+ def type_cast(value)
+ return if value.nil?
+
+ value.to_i rescue value ? 1 : 0
+ end
+ end
+
+ class Boolean < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::Column.value_to_boolean value
+ end
+ end
+
+ class Timestamp < Type
+ def type; :timestamp; end
+
+ def type_cast(value)
+ return if value.nil?
+
+ # FIXME: probably we can improve this since we know it is PG
+ # specific
+ ConnectionAdapters::PostgreSQLColumn.string_to_time value
+ end
+ end
+
+ class Date < Type
+ def type; :datetime; end
+
+ def type_cast(value)
+ return if value.nil?
+
+ # FIXME: probably we can improve this since we know it is PG
+ # specific
+ ConnectionAdapters::Column.value_to_date value
+ end
+ end
+
+ class Time < Type
+ def type_cast(value)
+ return if value.nil?
+
+ # FIXME: probably we can improve this since we know it is PG
+ # specific
+ ConnectionAdapters::Column.string_to_dummy_time value
+ end
+ end
+
+ class Float < Type
+ def type_cast(value)
+ return if value.nil?
+
+ value.to_f
+ end
+ end
+
+ class Decimal < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::Column.value_to_decimal value
+ end
+ end
+
+ class Hstore < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::PostgreSQLColumn.string_to_hstore value
+ end
+ end
+
+ class TypeMap
+ def initialize
+ @mapping = {}
+ end
+
+ def []=(oid, type)
+ @mapping[oid] = type
+ end
+
+ def [](oid)
+ @mapping[oid]
+ end
+
+ def key?(oid)
+ @mapping.key? oid
+ end
+
+ def fetch(ftype, fmod)
+ # The type for the numeric depends on the width of the field,
+ # so we'll do something special here.
+ #
+ # When dealing with decimal columns:
+ #
+ # places after decimal = fmod - 4 & 0xffff
+ # places before decimal = (fmod - 4) >> 16 & 0xffff
+ if ftype == 1700 && (fmod - 4 & 0xffff).zero?
+ ftype = 23
+ end
+
+ @mapping.fetch(ftype) { |oid| yield oid, fmod }
+ end
+ end
+
+ TYPE_MAP = TypeMap.new # :nodoc:
+
+ # When the PG adapter connects, the pg_type table is queried. The
+ # key of this hash maps to the `typname` column from the table.
+ # TYPE_MAP is then dynamically built with oids as the key and type
+ # objects as values.
+ NAMES = Hash.new { |h,k| # :nodoc:
+ h[k] = OID::Identity.new
+ }
+
+ # Register an OID type named +name+ with a typcasting object in
+ # +type+. +name+ should correspond to the `typname` column in
+ # the `pg_type` table.
+ def self.register_type(name, type)
+ NAMES[name] = type
+ end
+
+ # Alias the +old+ type to the +new+ type.
+ def self.alias_type(new, old)
+ NAMES[new] = NAMES[old]
+ end
+
+ # Is +name+ a registered type?
+ def self.registered_type?(name)
+ NAMES.key? name
+ end
+
+ register_type 'int2', OID::Integer.new
+ alias_type 'int4', 'int2'
+ alias_type 'int8', 'int2'
+ alias_type 'oid', 'int2'
+
+ register_type 'numeric', OID::Decimal.new
+ register_type 'text', OID::Identity.new
+ alias_type 'varchar', 'text'
+ alias_type 'char', 'text'
+ alias_type 'bpchar', 'text'
+ alias_type 'xml', 'text'
+
+ # FIXME: why are we keeping these types as strings?
+ alias_type 'tsvector', 'text'
+ alias_type 'interval', 'text'
+ alias_type 'cidr', 'text'
+ alias_type 'inet', 'text'
+ alias_type 'macaddr', 'text'
+ alias_type 'bit', 'text'
+ alias_type 'varbit', 'text'
+
+ # FIXME: I don't think this is correct. We should probably be returning a parsed date,
+ # but the tests pass with a string returned.
+ register_type 'timestamptz', OID::Identity.new
+
+ register_type 'money', OID::Money.new
+ register_type 'bytea', OID::Bytea.new
+ register_type 'bool', OID::Boolean.new
+
+ register_type 'float4', OID::Float.new
+ alias_type 'float8', 'float4'
+
+ register_type 'timestamp', OID::Timestamp.new
+ register_type 'date', OID::Date.new
+ register_type 'time', OID::Time.new
+
+ register_type 'path', OID::Identity.new
+ register_type 'polygon', OID::Identity.new
+ register_type 'circle', OID::Identity.new
+ register_type 'hstore', OID::Hstore.new
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index e6ddf8bf8f..d2126a3e19 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1,6 +1,8 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_support/core_ext/object/blank'
require 'active_record/connection_adapters/statement_pool'
+require 'active_record/connection_adapters/postgresql/oid'
+require 'arel/visitors/bind_visitor'
# Make sure we're using pg high enough for PGResult#values
gem 'pg', '~> 0.11'
@@ -14,7 +16,7 @@ module ActiveRecord
# Forward any unused config params to PGconn.connect.
[:statement_limit, :encoding, :min_messages, :schema_search_path,
- :schema_order, :adapter, :pool, :wait_timeout,
+ :schema_order, :adapter, :pool, :wait_timeout, :template,
:reaping_frequency].each do |key|
conn_params.delete key
end
@@ -34,7 +36,8 @@ module ActiveRecord
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
# Instantiates a new PostgreSQL column definition in a table.
- def initialize(name, default, sql_type = nil, null = true)
+ def initialize(name, default, oid_type, sql_type = nil, null = true)
+ @oid_type = oid_type
super(name, self.class.extract_value_from_default(default), sql_type, null)
end
@@ -52,180 +55,192 @@ module ActiveRecord
end
end
- def cast_hstore(object)
+ def hstore_to_string(object)
if Hash === object
object.map { |k,v|
"#{escape_hstore(k)}=>#{escape_hstore(v)}"
- }.join ', '
+ }.join ','
else
- kvs = object.scan(/(?<!\\)".*?(?<!\\)"/).map { |o|
- unescape_hstore(o[1...-1])
- }
- Hash[kvs.each_slice(2).to_a]
+ object
end
end
- private
- HSTORE_ESCAPE = {
- ' ' => '\\ ',
- '\\' => '\\\\',
- '"' => '\\"',
- '=' => '\\=',
- }
- HSTORE_ESCAPE_RE = Regexp.union(HSTORE_ESCAPE.keys)
- HSTORE_UNESCAPE = HSTORE_ESCAPE.invert
- HSTORE_UNESCAPE_RE = Regexp.union(HSTORE_UNESCAPE.keys)
-
- def unescape_hstore(value)
- value.gsub(HSTORE_UNESCAPE_RE) do |match|
- HSTORE_UNESCAPE[match]
+ def string_to_hstore(string)
+ if string.nil?
+ nil
+ elsif String === string
+ Hash[string.scan(HstorePair).map { |k,v|
+ v = v.upcase == 'NULL' ? nil : v.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1')
+ k = k.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1')
+ [k,v]
+ }]
+ else
+ string
end
end
+ private
+ HstorePair = begin
+ quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
+ unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
+ /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/
+ end
+
def escape_hstore(value)
- value.gsub(HSTORE_ESCAPE_RE) do |match|
- HSTORE_ESCAPE[match]
- end
+ value.nil? ? 'NULL'
+ : value =~ /[=\s,>]/ ? '"%s"' % value.gsub(/(["\\])/, '\\\\\1')
+ : value == "" ? '""'
+ : value.to_s.gsub(/(["\\])/, '\\\\\1')
end
end
# :startdoc:
- private
- def extract_limit(sql_type)
- case sql_type
- when /^bigint/i; 8
- when /^smallint/i; 2
- else super
- end
- end
-
- # Extracts the scale from PostgreSQL-specific data types.
- def extract_scale(sql_type)
- # Money type has a fixed scale of 2.
- sql_type =~ /^money/ ? 2 : super
- end
-
- # Extracts the precision from PostgreSQL-specific data types.
- def extract_precision(sql_type)
- if sql_type == 'money'
- self.class.money_precision
- else
- super
- end
- end
-
- # Maps PostgreSQL-specific data types to logical Rails types.
- def simplified_type(field_type)
- case field_type
- # Numeric and monetary types
- when /^(?:real|double precision)$/
- :float
- # Monetary types
- when 'money'
- :decimal
- when 'hstore'
- :hstore
+ # Extracts the value from a PostgreSQL column default definition.
+ def self.extract_value_from_default(default)
+ # This is a performance optimization for Ruby 1.9.2 in development.
+ # If the value is nil, we return nil straight away without checking
+ # the regular expressions. If we check each regular expression,
+ # Regexp#=== will call NilClass#to_str, which will trigger
+ # method_missing (defined by whiny nil in ActiveSupport) which
+ # makes this method very very slow.
+ return default unless default
+
+ case default
+ # Numeric types
+ when /\A\(?(-?\d+(\.\d*)?\)?)\z/
+ $1
# Character types
- when /^(?:character varying|bpchar)(?:\(\d+\))?$/
- :string
+ when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
+ $1
+ # Character types (8.1 formatting)
+ when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
+ $1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
# Binary data types
- when 'bytea'
- :binary
+ when /\A'(.*)'::bytea\z/m
+ $1
# Date/time types
- when /^timestamp with(?:out)? time zone$/
- :datetime
- when 'interval'
- :string
+ when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
+ $1
+ when /\A'(.*)'::interval\z/
+ $1
+ # Boolean type
+ when 'true'
+ true
+ when 'false'
+ false
# Geometric types
- when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
- :string
+ when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
+ $1
# Network address types
- when /^(?:cidr|inet|macaddr)$/
- :string
- # Bit strings
- when /^bit(?: varying)?(?:\(\d+\))?$/
- :string
+ when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
+ $1
+ # Bit string types
+ when /\AB'(.*)'::"?bit(?: varying)?"?\z/
+ $1
# XML type
- when 'xml'
- :xml
- # tsvector type
- when 'tsvector'
- :tsvector
+ when /\A'(.*)'::xml\z/m
+ $1
# Arrays
- when /^\D+\[\]$/
- :string
+ when /\A'(.*)'::"?\D+"?\[\]\z/
+ $1
+ # Hstore
+ when /\A'(.*)'::hstore\z/
+ $1
# Object identifier types
- when 'oid'
- :integer
- # UUID type
- when 'uuid'
- :string
- # Small and big integer types
- when /^(?:small|big)int$/
- :integer
- # Pass through all types that are not specific to PostgreSQL.
+ when /\A-?\d+\z/
+ $1
else
- super
- end
+ # Anything else is blank, some user type, or some function
+ # and we can't know the value of that, so return nil.
+ nil
end
+ end
- # Extracts the value from a PostgreSQL column default definition.
- def self.extract_value_from_default(default)
- case default
- # This is a performance optimization for Ruby 1.9.2 in development.
- # If the value is nil, we return nil straight away without checking
- # the regular expressions. If we check each regular expression,
- # Regexp#=== will call NilClass#to_str, which will trigger
- # method_missing (defined by whiny nil in ActiveSupport) which
- # makes this method very very slow.
- when NilClass
- nil
- # Numeric types
- when /\A\(?(-?\d+(\.\d*)?\)?)\z/
- $1
- # Character types
- when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
- $1
- # Character types (8.1 formatting)
- when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
- $1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
- # Binary data types
- when /\A'(.*)'::bytea\z/m
- $1
- # Date/time types
- when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
- $1
- when /\A'(.*)'::interval\z/
- $1
- # Boolean type
- when 'true'
- true
- when 'false'
- false
- # Geometric types
- when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
- $1
- # Network address types
- when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
- $1
- # Bit string types
- when /\AB'(.*)'::"?bit(?: varying)?"?\z/
- $1
- # XML type
- when /\A'(.*)'::xml\z/m
- $1
- # Arrays
- when /\A'(.*)'::"?\D+"?\[\]\z/
- $1
- # Object identifier types
- when /\A-?\d+\z/
- $1
- else
- # Anything else is blank, some user type, or some function
- # and we can't know the value of that, so return nil.
- nil
- end
+ def type_cast(value)
+ return if value.nil?
+ return super if encoded?
+
+ @oid_type.type_cast value
+ end
+
+ private
+ def extract_limit(sql_type)
+ case sql_type
+ when /^bigint/i; 8
+ when /^smallint/i; 2
+ else super
+ end
+ end
+
+ # Extracts the scale from PostgreSQL-specific data types.
+ def extract_scale(sql_type)
+ # Money type has a fixed scale of 2.
+ sql_type =~ /^money/ ? 2 : super
+ end
+
+ # Extracts the precision from PostgreSQL-specific data types.
+ def extract_precision(sql_type)
+ if sql_type == 'money'
+ self.class.money_precision
+ else
+ super
end
+ end
+
+ # Maps PostgreSQL-specific data types to logical Rails types.
+ def simplified_type(field_type)
+ case field_type
+ # Numeric and monetary types
+ when /^(?:real|double precision)$/
+ :float
+ # Monetary types
+ when 'money'
+ :decimal
+ when 'hstore'
+ :hstore
+ # Character types
+ when /^(?:character varying|bpchar)(?:\(\d+\))?$/
+ :string
+ # Binary data types
+ when 'bytea'
+ :binary
+ # Date/time types
+ when /^timestamp with(?:out)? time zone$/
+ :datetime
+ when 'interval'
+ :string
+ # Geometric types
+ when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
+ :string
+ # Network address types
+ when /^(?:cidr|inet|macaddr)$/
+ :string
+ # Bit strings
+ when /^bit(?: varying)?(?:\(\d+\))?$/
+ :string
+ # XML type
+ when 'xml'
+ :xml
+ # tsvector type
+ when 'tsvector'
+ :tsvector
+ # Arrays
+ when /^\D+\[\]$/
+ :string
+ # Object identifier types
+ when 'oid'
+ :integer
+ # UUID type
+ when 'uuid'
+ :string
+ # Small and big integer types
+ when /^(?:small|big)int$/
+ :integer
+ # Pass through all types that are not specific to PostgreSQL.
+ else
+ super
+ end
+ end
end
# The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
@@ -284,7 +299,8 @@ module ActiveRecord
:binary => { :name => "bytea" },
:boolean => { :name => "boolean" },
:xml => { :name => "xml" },
- :tsvector => { :name => "tsvector" }
+ :tsvector => { :name => "tsvector" },
+ :hstore => { :name => "hstore" }
}
# Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -302,6 +318,10 @@ module ActiveRecord
true
end
+ def supports_partial_index?
+ true
+ end
+
class StatementPool < ConnectionAdapters::StatementPool
def initialize(connection, max)
super
@@ -340,7 +360,7 @@ module ActiveRecord
private
def cache
- @cache[$$]
+ @cache[Process.pid]
end
def dealloc(key)
@@ -354,11 +374,23 @@ module ActiveRecord
end
end
+ class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
+ include Arel::Visitors::BindVisitor
+ end
+
# Initializes and connects a PostgreSQL adapter.
def initialize(connection, logger, connection_parameters, config)
super(connection, logger)
+
+ if config.fetch(:prepared_statements) { true }
+ @visitor = Arel::Visitors::PostgreSQL.new self
+ else
+ @visitor = BindSubstitution.new self
+ end
+
+ connection_parameters.delete :prepared_statements
+
@connection_parameters, @config = connection_parameters, config
- @visitor = Arel::Visitors::PostgreSQL.new self
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
@local_tz = nil
@@ -372,6 +404,7 @@ module ActiveRecord
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
end
+ initialize_type_map
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
end
@@ -455,14 +488,14 @@ module ActiveRecord
# Escapes binary strings for bytea input to the database.
def escape_bytea(value)
- @connection.escape_bytea(value) if value
+ PGconn.escape_bytea(value) if value
end
# Unescapes bytea output from a database to the binary string it represents.
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
# on escaped binary output from database drive.
def unescape_bytea(value)
- @connection.unescape_bytea(value) if value
+ PGconn.unescape_bytea(value) if value
end
# Quotes PostgreSQL-specific data types for SQL input.
@@ -470,6 +503,11 @@ module ActiveRecord
return super unless column
case value
+ when Hash
+ case column.sql_type
+ when 'hstore' then super(PostgreSQLColumn.hstore_to_string(value), column)
+ else super
+ end
when Float
return super unless value.infinite? && column.type == :datetime
"'#{value.to_s.downcase}'"
@@ -501,6 +539,9 @@ module ActiveRecord
when String
return super unless 'bytea' == column.sql_type
{ :value => value, :format => 1 }
+ when Hash
+ return super unless 'hstore' == column.sql_type
+ PostgreSQLColumn.hstore_to_string(value)
else
super
end
@@ -571,7 +612,7 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
def explain(arel, binds = [])
- sql = "EXPLAIN #{to_sql(arel)}"
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
end
@@ -693,7 +734,14 @@ module ActiveRecord
end
def substitute_at(column, index)
- Arel.sql("$#{index + 1}")
+ Arel::Nodes::BindParam.new "$#{index + 1}"
+ end
+
+ class Result < ActiveRecord::Result
+ def initialize(columns, rows, column_types)
+ super(columns, rows)
+ @column_types = column_types
+ end
end
def exec_query(sql, name = 'SQL', binds = [])
@@ -701,7 +749,17 @@ module ActiveRecord
result = binds.empty? ? exec_no_cache(sql, binds) :
exec_cache(sql, binds)
- ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
+ types = {}
+ result.fields.each_with_index do |fname, i|
+ ftype = result.ftype i
+ fmod = result.fmod i
+ types[fname] = OID::TYPE_MAP.fetch(ftype, fmod) { |oid, mod|
+ warn "unknown OID: #{fname}(#{oid}) (#{sql})"
+ OID::Identity.new
+ }
+ end
+
+ ret = Result.new(result.fields, result.values, types)
result.clear
return ret
end
@@ -885,16 +943,20 @@ module ActiveRecord
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
+ where = inddef.scan(/WHERE (.+)$/).flatten[0]
- column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where)
end.compact
end
# Returns the list of all column definitions for a table.
- def columns(table_name, name = nil)
+ def columns(table_name)
# Limit, precision, and scale are all handled by the superclass.
- column_definitions(table_name).collect do |column_name, type, default, notnull|
- PostgreSQLColumn.new(column_name, default, type, notnull == 'f')
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
+ oid = OID::TYPE_MAP.fetch(oid.to_i, fmod.to_i) {
+ OID::Identity.new
+ }
+ PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
end
end
@@ -983,26 +1045,46 @@ module ActiveRecord
def pk_and_sequence_for(table) #:nodoc:
# First try looking for a sequence with a dependency on the
# given table's primary key.
- result = exec_query(<<-end_sql, 'SCHEMA').rows.first
- SELECT attr.attname, ns.nspname, seq.relname
- FROM pg_class seq
- INNER JOIN pg_depend dep ON seq.oid = dep.objid
- INNER JOIN pg_attribute attr ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
- INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
- INNER JOIN pg_namespace ns ON seq.relnamespace = ns.oid
- WHERE seq.relkind = 'S'
- AND cons.contype = 'p'
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
+ result = query(<<-end_sql, 'PK and serial sequence')[0]
+ SELECT attr.attname, seq.relname
+ FROM pg_class seq,
+ pg_attribute attr,
+ pg_depend dep,
+ pg_namespace name,
+ pg_constraint cons
+ WHERE seq.oid = dep.objid
+ AND seq.relkind = 'S'
+ AND attr.attrelid = dep.refobjid
+ AND attr.attnum = dep.refobjsubid
+ AND attr.attrelid = cons.conrelid
+ AND attr.attnum = cons.conkey[1]
+ AND cons.contype = 'p'
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
end_sql
- # [primary_key, sequence]
- if result.second == 'public' then
- sequence = result.last
- else
- sequence = result.second+'.'+result.last
+ if result.nil? or result.empty?
+ # If that fails, try parsing the primary key's default value.
+ # Support the 7.x and 8.0 nextval('foo'::text) as well as
+ # the 8.1+ nextval('foo'::regclass).
+ result = query(<<-end_sql, 'PK and custom sequence')[0]
+ SELECT attr.attname,
+ CASE
+ WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
+ substr(split_part(def.adsrc, '''', 2),
+ strpos(split_part(def.adsrc, '''', 2), '.')+1)
+ ELSE split_part(def.adsrc, '''', 2)
+ END
+ FROM pg_class t
+ JOIN pg_attribute attr ON (t.oid = attrelid)
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
+ WHERE t.oid = '#{quote_table_name(table)}'::regclass
+ AND cons.contype = 'p'
+ AND def.adsrc ~* 'nextval'
+ end_sql
end
- [result.first, sequence]
+ [result.first, result.last]
rescue
nil
end
@@ -1107,7 +1189,7 @@ module ActiveRecord
# Construct a clean list of column names from the ORDER BY clause, removing
# any ASC/DESC modifiers
- order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*/i, '') }
+ order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '') }
order_columns.delete_if { |c| c.blank? }
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
@@ -1151,6 +1233,22 @@ module ActiveRecord
end
private
+ def initialize_type_map
+ result = execute('SELECT oid, typname, typelem, typdelim FROM pg_type', 'SCHEMA')
+ leaves, nodes = result.partition { |row| row['typelem'] == '0' }
+
+ # populate the leaf nodes
+ leaves.find_all { |row| OID.registered_type? row['typname'] }.each do |row|
+ OID::TYPE_MAP[row['oid'].to_i] = OID::NAMES[row['typname']]
+ end
+
+ # populate composite types
+ nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
+ vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
+ OID::TYPE_MAP[row['oid'].to_i] = vector
+ end
+ end
+
FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
def exec_no_cache(sql, binds)
@@ -1173,7 +1271,11 @@ module ActiveRecord
# prepared statements whose return value may have changed is
# FEATURE_NOT_SUPPORTED. Check here for more details:
# http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
- code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
+ begin
+ code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
+ rescue
+ raise e
+ end
if FEATURE_NOT_SUPPORTED == code
@statements.delete sql_key(sql)
retry
@@ -1249,7 +1351,7 @@ module ActiveRecord
# Executes a SELECT query and returns the results, performing any data type
# conversions that are required to be performed here instead of in PostgreSQLColumn.
def select(sql, name = nil, binds = [])
- exec_query(sql, name, binds).to_a
+ exec_query(sql, name, binds)
end
def select_raw(sql, name = nil)
@@ -1280,7 +1382,7 @@ module ActiveRecord
# - ::regclass is a function that gives the id for a table name
def column_definitions(table_name) #:nodoc:
exec_query(<<-end_sql, 'SCHEMA').rows
- SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull, a.atttypid, a.atttypmod
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index 4e8932a695..962718da56 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -9,7 +9,7 @@ module ActiveRecord
@tables = {}
@columns = Hash.new do |h, table_name|
- h[table_name] = conn.columns(table_name, "#{table_name} Columns")
+ h[table_name] = conn.columns(table_name)
end
@columns_hash = Hash.new do |h, table_name|
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 69750a911d..c85c4c6e51 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -1,31 +1,16 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/connection_adapters/statement_pool'
-require 'active_support/core_ext/string/encoding'
+require 'arel/visitors/bind_visitor'
module ActiveRecord
module ConnectionAdapters #:nodoc:
class SQLiteColumn < Column #:nodoc:
class << self
- def string_to_binary(value)
- value.gsub(/\0|\%/n) do |b|
- case b
- when "\0" then "%00"
- when "%" then "%25"
- end
- end
- end
-
def binary_to_string(value)
if value.encoding != Encoding::ASCII_8BIT
value = value.force_encoding(Encoding::ASCII_8BIT)
end
-
- value.gsub(/%00|%25/n) do |b|
- case b
- when "%00" then "\0"
- when "%25" then "%"
- end
- end
+ value
end
end
end
@@ -84,12 +69,21 @@ module ActiveRecord
end
end
+ class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
+ include Arel::Visitors::BindVisitor
+ end
+
def initialize(connection, logger, config)
super(connection, logger)
@statements = StatementPool.new(@connection,
config.fetch(:statement_limit) { 1000 })
@config = config
- @visitor = Arel::Visitors::SQLite.new self
+
+ if config.fetch(:prepared_statements) { true }
+ @visitor = Arel::Visitors::SQLite.new self
+ else
+ @visitor = BindSubstitution.new self
+ end
end
def adapter_name #:nodoc:
@@ -217,7 +211,7 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
def explain(arel, binds = [])
- sql = "EXPLAIN QUERY PLAN #{to_sql(arel)}"
+ sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
end
@@ -339,7 +333,7 @@ module ActiveRecord
end
# Returns an array of +SQLiteColumn+ objects for the table specified by +table_name+.
- def columns(table_name, name = nil) #:nodoc:
+ def columns(table_name) #:nodoc:
table_structure(table_name).map do |field|
case field["dflt_value"]
when /^null$/i
@@ -454,7 +448,7 @@ module ActiveRecord
protected
def select(sql, name = nil, binds = []) #:nodoc:
- exec_query(sql, name, binds).to_a
+ exec_query(sql, name, binds)
end
def table_structure(table_name)
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index e9cd3d5e57..7b218a5570 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -90,11 +90,7 @@ module ActiveRecord
connection_handler.remove_connection(klass)
end
- def clear_active_connections!
- connection_handler.clear_active_connections!
- end
-
- delegate :clear_reloadable_connections!,
- :clear_all_connections!,:verify_active_connections!, :to => :connection_handler
+ delegate :clear_active_connections!, :clear_reloadable_connections!,
+ :clear_all_connections!, :to => :connection_handler
end
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 89f6eccbd2..3f90d25962 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -1,4 +1,5 @@
require 'active_support/concern'
+require 'thread'
module ActiveRecord
module Core
@@ -44,10 +45,10 @@ module ActiveRecord
##
# :singleton-method:
- # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling
- # dates and times from the database. This is set to :local by default.
+ # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
+ # dates and times from the database. This is set to :utc by default.
config_attribute :default_timezone, :global => true
- self.default_timezone = :local
+ self.default_timezone = :utc
##
# :singleton-method:
@@ -71,6 +72,16 @@ module ActiveRecord
# The connection handler
config_attribute :connection_handler
self.connection_handler = ConnectionAdapters::ConnectionHandler.new
+
+ ##
+ # :singleton-method:
+ # Specifies wether or not has_many or has_one association option
+ # :dependent => :restrict raises an exception. If set to true, the
+ # ActiveRecord::DeleteRestrictionError exception will be raised
+ # along with a DEPRECATION WARNING. If set to false, an error would
+ # be added to the model instead.
+ config_attribute :dependent_restrict_raises, :global => true
+ self.dependent_restrict_raises = true
end
module ClassMethods
@@ -80,6 +91,8 @@ module ActiveRecord
end
def initialize_generated_modules
+ @attribute_methods_mutex = Mutex.new
+
# force attribute methods to be higher in inheritance hierarchy than other generated methods
generated_attribute_methods
generated_feature_methods
@@ -152,16 +165,9 @@ module ActiveRecord
# User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
def initialize(attributes = nil, options = {})
@attributes = self.class.initialize_attributes(self.class.column_defaults.dup)
- @association_cache = {}
- @aggregation_cache = {}
- @attributes_cache = {}
- @new_record = true
- @readonly = false
- @destroyed = false
- @marked_for_destruction = false
- @previously_changed = {}
- @changed_attributes = {}
- @relation = nil
+ @columns_hash = self.class.column_types.dup
+
+ init_internals
ensure_proper_type
@@ -185,13 +191,13 @@ module ActiveRecord
# post.title # => 'hello world'
def init_with(coder)
@attributes = self.class.initialize_attributes(coder['attributes'])
- @relation = nil
+ @columns_hash = self.class.column_types.merge(coder['column_types'] || {})
+
+
+ init_internals
- @attributes_cache, @previously_changed, @changed_attributes = {}, {}, {}
- @association_cache = {}
- @aggregation_cache = {}
- @readonly = @destroyed = @marked_for_destruction = false
@new_record = false
+
run_callbacks :find
run_callbacks :initialize
@@ -206,20 +212,24 @@ module ActiveRecord
# The dup method does not preserve the timestamps (created|updated)_(at|on).
def initialize_dup(other)
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
+ self.class.initialize_attributes(cloned_attributes)
+
cloned_attributes.delete(self.class.primary_key)
@attributes = cloned_attributes
+ @attributes[self.class.primary_key] = nil
- _run_initialize_callbacks if _initialize_callbacks.any?
+ run_callbacks(:initialize) if _initialize_callbacks.any?
@changed_attributes = {}
self.class.column_defaults.each do |attr, orig_value|
- @changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr])
+ @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
end
@aggregation_cache = {}
@association_cache = {}
- @attributes_cache = {}
+ @attributes_cache = {}
+
@new_record = true
ensure_proper_type
@@ -238,7 +248,7 @@ module ActiveRecord
# end
# coder = {}
# Post.new.encode_with(coder)
- # coder # => { 'id' => nil, ... }
+ # coder # => {"attributes" => {"id" => nil, ... }}
def encode_with(coder)
coder['attributes'] = attributes
end
@@ -330,5 +340,22 @@ module ActiveRecord
def to_ary # :nodoc:
nil
end
+
+ def init_internals
+ pk = self.class.primary_key
+
+ @attributes[pk] = nil unless @attributes.key?(pk)
+
+ @relation = nil
+ @aggregation_cache = {}
+ @association_cache = {}
+ @attributes_cache = {}
+ @previously_changed = {}
+ @changed_attributes = {}
+ @readonly = false
+ @destroyed = false
+ @marked_for_destruction = false
+ @new_record = true
+ end
end
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 031918712a..224f5276eb 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -19,15 +19,10 @@ module ActiveRecord
counters.each do |association|
has_many_association = reflect_on_association(association.to_sym)
- expected_name = if has_many_association.options[:as]
- has_many_association.options[:as].to_s.classify
- else
- self.name
- end
-
+ foreign_key = has_many_association.foreign_key.to_s
child_class = has_many_association.klass
belongs_to = child_class.reflect_on_all_associations(:belongs_to)
- reflection = belongs_to.find { |e| e.class_name == expected_name }
+ reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key }
counter_name = reflection.counter_cache_column
stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb
index b309df9b1b..38dbbef5fc 100644
--- a/activerecord/lib/active_record/dynamic_finder_match.rb
+++ b/activerecord/lib/active_record/dynamic_finder_match.rb
@@ -6,33 +6,23 @@ module ActiveRecord
#
class DynamicFinderMatch
def self.match(method)
- finder = :first
- bang = false
- instantiator = nil
-
- case method.to_s
- when /^find_(all_|last_)?by_([_a-zA-Z]\w*)$/
- finder = :last if $1 == 'last_'
- finder = :all if $1 == 'all_'
- names = $2
- when /^find_by_([_a-zA-Z]\w*)\!$/
- bang = true
- names = $1
- when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
- instantiator = $1 == 'initialize' ? :new : :create
- names = $2
- else
- return nil
+ method = method.to_s
+ klass = [FindBy, FindByBang, FindOrInitializeCreateBy].find do |_klass|
+ _klass.matches?(method)
end
+ klass.new(method) if klass
+ end
- new(finder, instantiator, bang, names.split('_and_'))
+ def self.matches?(method)
+ method =~ self::METHOD_PATTERN
end
- def initialize(finder, instantiator, bang, attribute_names)
- @finder = finder
- @instantiator = instantiator
- @bang = bang
- @attribute_names = attribute_names
+ def initialize(method)
+ @finder = :first
+ @instantiator = nil
+ match_data = method.match(self.class::METHOD_PATTERN)
+ @attribute_names = match_data[-1].split("_and_")
+ initialize_from_match_data(match_data)
end
attr_reader :finder, :attribute_names, :instantiator
@@ -41,16 +31,54 @@ module ActiveRecord
@finder && !@instantiator
end
+ def creator?
+ @finder == :first && @instantiator == :create
+ end
+
def instantiator?
- @finder == :first && @instantiator
+ @instantiator
end
- def creator?
- @finder == :first && @instantiator == :create
+ def bang?
+ false
+ end
+
+ def valid_arguments?(arguments)
+ arguments.size >= @attribute_names.size
end
+ private
+
+ def initialize_from_match_data(match_data)
+ end
+ end
+
+ class FindBy < DynamicFinderMatch
+ METHOD_PATTERN = /^find_(all_|last_)?by_([_a-zA-Z]\w*)$/
+
+ def initialize_from_match_data(match_data)
+ @finder = :last if match_data[1] == 'last_'
+ @finder = :all if match_data[1] == 'all_'
+ end
+ end
+
+ class FindByBang < DynamicFinderMatch
+ METHOD_PATTERN = /^find_by_([_a-zA-Z]\w*)\!$/
+
def bang?
- @bang
+ true
+ end
+ end
+
+ class FindOrInitializeCreateBy < DynamicFinderMatch
+ METHOD_PATTERN = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
+
+ def initialize_from_match_data(match_data)
+ @instantiator = match_data[1] == 'initialize' ? :new : :create
+ end
+
+ def valid_arguments?(arguments)
+ arguments.size == 1 && arguments.first.is_a?(Hash) || super
end
end
end
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index e9068089f0..60ce3dd4f1 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -25,7 +25,7 @@ module ActiveRecord
if match = (DynamicFinderMatch.match(method_id) || DynamicScopeMatch.match(method_id))
attribute_names = match.attribute_names
super unless all_attributes_exists?(attribute_names)
- if arguments.size < attribute_names.size
+ unless match.valid_arguments?(arguments)
method_trace = "#{__FILE__}:#{__LINE__}:in `#{method_id}'"
backtrace = [method_trace] + caller
raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{attribute_names.size})", backtrace
diff --git a/activerecord/lib/active_record/dynamic_scope_match.rb b/activerecord/lib/active_record/dynamic_scope_match.rb
index c832e927d6..a502155aac 100644
--- a/activerecord/lib/active_record/dynamic_scope_match.rb
+++ b/activerecord/lib/active_record/dynamic_scope_match.rb
@@ -19,5 +19,9 @@ module ActiveRecord
attr_reader :scope, :attribute_names
alias :scope? :scope
+
+ def valid_arguments?(arguments)
+ arguments.size >= @attribute_names.size
+ end
end
end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index e502d7e52b..01cacf6153 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -19,6 +19,8 @@ module ActiveRecord
# currently collected. A false value indicates collecting is turned
# off. Otherwise it is an array of queries.
def logging_query_plan # :nodoc:
+ return yield unless logger
+
threshold = auto_explain_threshold_in_seconds
current = Thread.current
if threshold && current[:available_queries_for_explain].nil?
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index fc76410499..1f8c4fc203 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -11,7 +11,10 @@ module ActiveRecord
# SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
# our own EXPLAINs now matter how loopingly beautiful that would be.
- IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN)
+ #
+ # On the other hand, we want to monitor the performance of our real database
+ # queries, not the performance of the access to the query cache.
+ IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE)
def ignore_payload?(payload)
payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name])
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index cf315b687c..b82d5b5621 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -3,7 +3,6 @@ require 'yaml'
require 'zlib'
require 'active_support/dependencies'
require 'active_support/core_ext/object/blank'
-require 'active_support/ordered_hash'
require 'active_record/fixtures/file'
if defined? ActiveRecord
@@ -508,7 +507,7 @@ module ActiveRecord
@name = fixture_name
@class_name = class_name
- @fixtures = ActiveSupport::OrderedHash.new
+ @fixtures = {}
# Should be an AR::Base type class
if class_name.is_a?(Class)
diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb
index 680d9ffea0..d9777bb2f6 100644
--- a/activerecord/lib/active_record/identity_map.rb
+++ b/activerecord/lib/active_record/identity_map.rb
@@ -123,24 +123,6 @@ module ActiveRecord
end
class Middleware
- class Body #:nodoc:
- def initialize(target, original)
- @target = target
- @original = original
- end
-
- def each(&block)
- @target.each(&block)
- end
-
- def close
- @target.close if @target.respond_to?(:close)
- ensure
- IdentityMap.enabled = @original
- IdentityMap.clear
- end
- end
-
def initialize(app)
@app = app
end
@@ -148,8 +130,14 @@ module ActiveRecord
def call(env)
enabled = IdentityMap.enabled
IdentityMap.enabled = true
- status, headers, body = @app.call(env)
- [status, headers, Body.new(body, enabled)]
+
+ response = @app.call(env)
+ response[2] = Rack::BodyProxy.new(response[2]) do
+ IdentityMap.enabled = enabled
+ IdentityMap.clear
+ end
+
+ response
end
end
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index eaa7deac5a..2c766411a0 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -62,7 +62,7 @@ module ActiveRecord
# Finder methods must instantiate through this method to work with the
# single-table inheritance model that makes it possible to create
# objects of different types from the same table.
- def instantiate(record)
+ def instantiate(record, column_types = {})
sti_class = find_sti_class(record[inheritance_column])
record_id = sti_class.primary_key && record[sti_class.primary_key]
@@ -77,7 +77,9 @@ module ActiveRecord
IdentityMap.add(instance)
end
else
- instance = sti_class.allocate.init_with('attributes' => record)
+ column_types = sti_class.decorate_columns(column_types)
+ instance = sti_class.allocate.init_with('attributes' => record,
+ 'column_types' => column_types)
end
instance
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index 44328f63b6..896132d566 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -10,6 +10,9 @@ en:
messages:
taken: "has already been taken"
record_invalid: "Validation failed: %{errors}"
+ restrict_dependent_destroy:
+ one: "Cannot delete record because a dependent %{record} exists"
+ many: "Cannot delete record because dependent %{record} exist"
# 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/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index e643c0d437..4d73cdd37a 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -40,11 +40,13 @@ module ActiveRecord
# This locking mechanism will function inside a single Ruby process. To make it work across all
# web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
#
- # You must ensure that your database schema defaults the +lock_version+ column to 0.
- #
# This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
- # To override the name of the +lock_version+ column, invoke the <tt>set_locking_column</tt> method.
- # This method uses the same syntax as <tt>set_table_name</tt>
+ # To override the name of the +lock_version+ column, set the <tt>locking_column</tt> class attribute:
+ #
+ # class Person < ActiveRecord::Base
+ # self.locking_column = :lock_person
+ # end
+ #
module Optimistic
extend ActiveSupport::Concern
diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb
index 66994e4797..58af92f0b1 100644
--- a/activerecord/lib/active_record/locking/pessimistic.rb
+++ b/activerecord/lib/active_record/locking/pessimistic.rb
@@ -38,6 +38,18 @@ module ActiveRecord
# account2.save!
# end
#
+ # You can start a transaction and acquire the lock in one go by calling
+ # <tt>with_lock</tt> with a block. The block is called from within
+ # a transaction, the object is already locked. Example:
+ #
+ # account = Account.first
+ # account.with_lock do
+ # # This block is called within a transaction,
+ # # account is already locked.
+ # account.balance -= 100
+ # account.save!
+ # end
+ #
# Database-specific information on row locking:
# MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
# PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
@@ -50,6 +62,16 @@ module ActiveRecord
reload(:lock => lock) if persisted?
self
end
+
+ # Wraps the passed block in a transaction, locking the object
+ # before yielding. You pass can the SQL locking clause
+ # as argument (see <tt>lock!</tt>).
+ def with_lock(lock = true)
+ transaction do
+ lock!(lock)
+ yield
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 3d3918c043..2a9139749d 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,5 +1,8 @@
require "active_support/core_ext/module/delegation"
require "active_support/core_ext/class/attribute_accessors"
+require 'active_support/deprecation'
+require 'active_record/schema_migration'
+require 'set'
module ActiveRecord
# Exception that can be raised to stop migrations from going backwards.
@@ -340,16 +343,28 @@ module ActiveRecord
attr_accessor :name, :version
- def initialize
- @name = self.class.name
- @version = nil
+ def initialize(name = self.class.name, version = nil)
+ @name = name
+ @version = version
@connection = nil
+ @reverting = false
end
# instantiate the delegate object after initialize is defined
self.verbose = true
self.delegate = new
+ def revert
+ @reverting = true
+ yield
+ ensure
+ @reverting = false
+ end
+
+ def reverting?
+ @reverting
+ end
+
def up
self.class.delegate = self
return unless self.class.respond_to?(:up)
@@ -383,9 +398,11 @@ module ActiveRecord
end
@connection = conn
time = Benchmark.measure {
- recorder.inverse.each do |cmd, args|
- send(cmd, *args)
- end
+ self.revert {
+ recorder.inverse.each do |cmd, args|
+ send(cmd, *args)
+ end
+ }
}
else
time = Benchmark.measure { change }
@@ -440,9 +457,11 @@ module ActiveRecord
arg_list = arguments.map{ |a| a.inspect } * ', '
say_with_time "#{method}(#{arg_list})" do
- unless arguments.empty? || method == :execute
- arguments[0] = Migrator.proper_table_name(arguments.first)
- arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table
+ unless reverting?
+ unless arguments.empty? || method == :execute
+ arguments[0] = Migrator.proper_table_name(arguments.first)
+ arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table
+ end
end
return super unless connection.respond_to?(method)
connection.send(method, *arguments, &block)
@@ -507,7 +526,7 @@ module ActiveRecord
File.basename(filename)
end
- delegate :migrate, :announce, :write, :to=>:migration
+ delegate :migrate, :announce, :write, :to => :migration
private
@@ -529,14 +548,14 @@ module ActiveRecord
def migrate(migrations_paths, target_version = nil, &block)
case
- when target_version.nil?
- up(migrations_paths, target_version, &block)
- when current_version == 0 && target_version == 0
- []
- when current_version > target_version
- down(migrations_paths, target_version, &block)
- else
- up(migrations_paths, target_version, &block)
+ when target_version.nil?
+ up(migrations_paths, target_version, &block)
+ when current_version == 0 && target_version == 0
+ []
+ when current_version > target_version
+ down(migrations_paths, target_version, &block)
+ else
+ up(migrations_paths, target_version, &block)
end
end
@@ -548,25 +567,34 @@ module ActiveRecord
move(:up, migrations_paths, steps)
end
- def up(migrations_paths, target_version = nil, &block)
- self.new(:up, migrations_paths, target_version).migrate(&block)
+ def up(migrations_paths, target_version = nil)
+ migrations = migrations(migrations_paths)
+ migrations.select! { |m| yield m } if block_given?
+
+ self.new(:up, migrations, target_version).migrate
end
def down(migrations_paths, target_version = nil, &block)
- self.new(:down, migrations_paths, target_version).migrate(&block)
+ migrations = migrations(migrations_paths)
+ migrations.select! { |m| yield m } if block_given?
+
+ self.new(:down, migrations, target_version).migrate
end
def run(direction, migrations_paths, target_version)
- self.new(direction, migrations_paths, target_version).run
+ self.new(direction, migrations(migrations_paths), target_version).run
+ end
+
+ def open(migrations_paths)
+ self.new(:up, migrations(migrations_paths), nil)
end
def schema_migrations_table_name
- Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
+ SchemaMigration.table_name
end
def get_all_versions
- table = Arel::Table.new(schema_migrations_table_name)
- Base.connection.select_values(table.project(table['version'])).map{ |v| v.to_i }.sort
+ SchemaMigration.all.map { |x| x.version.to_i }.sort
end
def current_version
@@ -593,13 +621,10 @@ module ActiveRecord
migrations_paths.first
end
- def migrations(paths, subdirectories = true)
+ def migrations(paths)
paths = Array(paths)
- glob = subdirectories ? "**/" : ""
- files = Dir[*paths.map { |p| "#{p}/#{glob}[0-9]*_*.rb" }]
-
- seen = Hash.new false
+ files = Dir[*paths.map { |p| "#{p}/**/[0-9]*_*.rb" }]
migrations = files.map do |file|
version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?.rb/).first
@@ -608,11 +633,6 @@ module ActiveRecord
version = version.to_i
name = name.camelize
- raise DuplicateMigrationVersionError.new(version) if seen[version]
- raise DuplicateMigrationNameError.new(name) if seen[name]
-
- seen[version] = seen[name] = true
-
MigrationProxy.new(name, version, file, scope)
end
@@ -622,7 +642,7 @@ module ActiveRecord
private
def move(direction, migrations_paths, steps)
- migrator = self.new(direction, migrations_paths)
+ migrator = self.new(direction, migrations(migrations_paths))
start_index = migrator.migrations.index(migrator.current_migration)
if start_index
@@ -633,19 +653,33 @@ module ActiveRecord
end
end
- def initialize(direction, migrations_paths, target_version = nil)
+ def initialize(direction, migrations, target_version = nil)
raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
- Base.connection.initialize_schema_migrations_table
- @direction, @migrations_paths, @target_version = direction, migrations_paths, target_version
+
+ @direction = direction
+ @target_version = target_version
+ @migrated_versions = nil
+
+ if Array(migrations).grep(String).empty?
+ @migrations = migrations
+ else
+ ActiveSupport::Deprecation.warn "instantiate this class with a list of migrations"
+ @migrations = self.class.migrations(migrations)
+ end
+
+ validate(@migrations)
+
+ ActiveRecord::SchemaMigration.create_table
end
def current_version
- migrated.last || 0
+ migrated.sort.last || 0
end
def current_migration
migrations.detect { |m| m.version == current_version }
end
+ alias :current :current_migration
def run
target = migrations.detect { |m| m.version == @target_version }
@@ -656,101 +690,109 @@ module ActiveRecord
end
end
- def migrate(&block)
- current = migrations.detect { |m| m.version == current_version }
- target = migrations.detect { |m| m.version == @target_version }
-
- if target.nil? && @target_version && @target_version > 0
+ def migrate
+ if !target && @target_version && @target_version > 0
raise UnknownMigrationVersionError.new(@target_version)
end
- start = up? ? 0 : (migrations.index(current) || 0)
- finish = migrations.index(target) || migrations.size - 1
- runnable = migrations[start..finish]
-
- # skip the last migration if we're headed down, but not ALL the way down
- runnable.pop if down? && target
+ running = runnable
- ran = []
- runnable.each do |migration|
- if block && !block.call(migration)
- next
- end
+ if block_given?
+ ActiveSupport::Deprecation.warn(<<-eomsg)
+block argument to migrate is deprecated, please filter migrations before constructing the migrator
+ eomsg
+ running.select! { |m| yield m }
+ end
+ running.each do |migration|
Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
- seen = migrated.include?(migration.version.to_i)
-
- # On our way up, we skip migrating the ones we've already migrated
- next if up? && seen
-
- # On our way down, we skip reverting the ones we've never migrated
- if down? && !seen
- migration.announce 'never migrated, skipping'; migration.write
- next
- end
-
begin
ddl_transaction do
migration.migrate(@direction)
record_version_state_after_migrating(migration.version)
end
- ran << migration
rescue => e
canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : ""
raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
end
end
- ran
end
- def migrations
- @migrations ||= begin
- migrations = self.class.migrations(@migrations_paths)
- down? ? migrations.reverse : migrations
+ def runnable
+ runnable = migrations[start..finish]
+ if up?
+ runnable.reject { |m| ran?(m) }
+ else
+ # skip the last migration if we're headed down, but not ALL the way down
+ runnable.pop if target
+ runnable.find_all { |m| ran?(m) }
end
end
+ def migrations
+ down? ? @migrations.reverse : @migrations.sort_by(&:version)
+ end
+
def pending_migrations
already_migrated = migrated
- migrations.reject { |m| already_migrated.include?(m.version.to_i) }
+ migrations.reject { |m| already_migrated.include?(m.version) }
end
def migrated
- @migrated_versions ||= self.class.get_all_versions
+ @migrated_versions ||= Set.new(self.class.get_all_versions)
end
private
- def record_version_state_after_migrating(version)
- table = Arel::Table.new(self.class.schema_migrations_table_name)
-
- @migrated_versions ||= []
- if down?
- @migrated_versions.delete(version)
- stmt = table.where(table["version"].eq(version.to_s)).compile_delete
- Base.connection.delete stmt
- else
- @migrated_versions.push(version).sort!
- stmt = table.compile_insert table["version"] => version.to_s
- Base.connection.insert stmt
- end
- end
+ def ran?(migration)
+ migrated.include?(migration.version.to_i)
+ end
- def up?
- @direction == :up
- end
+ def target
+ migrations.detect { |m| m.version == @target_version }
+ end
+
+ def finish
+ migrations.index(target) || migrations.size - 1
+ end
- def down?
- @direction == :down
+ def start
+ up? ? 0 : (migrations.index(current) || 0)
+ end
+
+ def validate(migrations)
+ name ,= migrations.group_by(&:name).find { |_,v| v.length > 1 }
+ raise DuplicateMigrationNameError.new(name) if name
+
+ version ,= migrations.group_by(&:version).find { |_,v| v.length > 1 }
+ raise DuplicateMigrationVersionError.new(version) if version
+ end
+
+ def record_version_state_after_migrating(version)
+ if down?
+ migrated.delete(version)
+ ActiveRecord::SchemaMigration.where(:version => version.to_s).delete_all
+ else
+ migrated << version
+ ActiveRecord::SchemaMigration.create!(:version => version.to_s)
end
+ end
- # Wrap the migration in a transaction only if supported by the adapter.
- def ddl_transaction(&block)
- if Base.connection.supports_ddl_transactions?
- Base.transaction { block.call }
- else
- block.call
- end
+ def up?
+ @direction == :up
+ end
+
+ def down?
+ @direction == :down
+ end
+
+ # Wrap the migration in a transaction only if supported by the adapter.
+ def ddl_transaction
+ if Base.connection.supports_ddl_transactions?
+ Base.transaction { yield }
+ else
+ yield
end
+ end
end
end
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index 4e27293cb4..96b62fdd61 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -8,11 +8,14 @@ module ActiveRecord
# * add_index
# * add_timestamps
# * create_table
+ # * create_join_table
# * remove_timestamps
# * rename_column
# * rename_index
# * rename_table
class CommandRecorder
+ include JoinTable
+
attr_accessor :commands, :delegate
def initialize(delegate = nil)
@@ -48,7 +51,7 @@ module ActiveRecord
super || delegate.respond_to?(*args)
end
- [:create_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method|
+ [:create_table, :create_join_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method|
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{method}(*args) # def create_table(*args)
record(:"#{method}", args) # record(:create_table, args)
@@ -62,6 +65,12 @@ module ActiveRecord
[:drop_table, [args.first]]
end
+ def invert_create_join_table(args)
+ table_name = find_join_table_name(*args)
+
+ [:drop_table, [table_name]]
+ end
+
def invert_rename_table(args)
[:rename_table, args.reverse]
end
@@ -99,7 +108,6 @@ module ActiveRecord
rescue NoMethodError => e
raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@delegate}")
end
-
end
end
end
diff --git a/activerecord/lib/active_record/migration/join_table.rb b/activerecord/lib/active_record/migration/join_table.rb
new file mode 100644
index 0000000000..01a580781b
--- /dev/null
+++ b/activerecord/lib/active_record/migration/join_table.rb
@@ -0,0 +1,17 @@
+module ActiveRecord
+ class Migration
+ module JoinTable #:nodoc:
+ private
+
+ def find_join_table_name(table_1, table_2, options = {})
+ options.delete(:table_name) { join_table_name(table_1, table_2) }
+ end
+
+ def join_table_name(table_1, table_2)
+ tables_names = [table_1, table_2].map(&:to_s).sort
+
+ tables_names.join("_").to_sym
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index c1a8583119..b8764217d3 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -115,7 +115,7 @@ module ActiveRecord
# the documentation for ActiveRecord::Base#table_name.
def table_name=(value)
@original_table_name = @table_name if defined?(@table_name)
- @table_name = value
+ @table_name = value && value.to_s
@quoted_table_name = nil
@arel_table = nil
@relation = Relation.new(self, arel_table)
@@ -128,10 +128,14 @@ module ActiveRecord
# Computes the table name, (re)sets it internally, and returns it.
def reset_table_name #:nodoc:
- if active_record_super.abstract_class?
+ if abstract_class?
+ self.table_name = if active_record_super == Base || active_record_super.abstract_class?
+ nil
+ else
+ active_record_super.table_name
+ end
+ elsif active_record_super.abstract_class?
self.table_name = active_record_super.table_name || compute_table_name
- elsif abstract_class?
- self.table_name = active_record_super == Base ? nil : active_record_super.table_name
else
self.table_name = compute_table_name
end
@@ -202,6 +206,26 @@ module ActiveRecord
@columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
end
+ def column_types # :nodoc:
+ @column_types ||= decorate_columns(columns_hash.dup)
+ end
+
+ def decorate_columns(columns_hash) # :nodoc:
+ return if columns_hash.empty?
+
+ serialized_attributes.keys.each do |key|
+ columns_hash[key] = AttributeMethods::Serialization::Type.new(columns_hash[key])
+ end
+
+ columns_hash.each do |name, col|
+ if create_time_zone_conversion_attribute?(name, col)
+ columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(col)
+ end
+ end
+
+ columns_hash
+ end
+
# Returns a hash where the keys are column names and the values are
# default values when instantiating the AR object for this table.
def column_defaults
@@ -264,9 +288,16 @@ module ActiveRecord
undefine_attribute_methods
connection.schema_cache.clear_table_cache!(table_name) if table_exists?
- @column_names = @content_columns = @column_defaults = @columns = @columns_hash = nil
- @dynamic_methods_hash = @inheritance_column = nil
- @arel_engine = @relation = nil
+ @arel_engine = nil
+ @column_defaults = nil
+ @column_names = nil
+ @columns = nil
+ @columns_hash = nil
+ @column_types = nil
+ @content_columns = nil
+ @dynamic_methods_hash = nil
+ @inheritance_column = nil
+ @relation = nil
end
def clear_cache! # :nodoc:
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
new file mode 100644
index 0000000000..60c37ac2b7
--- /dev/null
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+
+module ActiveRecord
+ # = Active Record Null Relation
+ class NullRelation < Relation
+ def exec_queries
+ @records = []
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 09ee2ba61d..9bc046c775 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -287,6 +287,7 @@ module ActiveRecord
IdentityMap.without do
fresh_object = self.class.unscoped { self.class.find(id, options) }
@attributes.update(fresh_object.instance_variable_get('@attributes'))
+ @columns_hash = fresh_object.instance_variable_get('@columns_hash')
end
@attributes_cache = {}
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 466d148901..9701898415 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -27,47 +27,22 @@ module ActiveRecord
@app = app
end
- class BodyProxy # :nodoc:
- def initialize(original_cache_value, target, connection_id)
- @original_cache_value = original_cache_value
- @target = target
- @connection_id = connection_id
- end
-
- def method_missing(method_sym, *arguments, &block)
- @target.send(method_sym, *arguments, &block)
- end
-
- def respond_to?(method_sym, include_private = false)
- super || @target.respond_to?(method_sym)
- end
-
- def each(&block)
- @target.each(&block)
- end
+ def call(env)
+ enabled = ActiveRecord::Base.connection.query_cache_enabled
+ connection_id = ActiveRecord::Base.connection_id
+ ActiveRecord::Base.connection.enable_query_cache!
- def close
- @target.close if @target.respond_to?(:close)
- ensure
- ActiveRecord::Base.connection_id = @connection_id
+ response = @app.call(env)
+ response[2] = Rack::BodyProxy.new(response[2]) do
+ ActiveRecord::Base.connection_id = connection_id
ActiveRecord::Base.connection.clear_query_cache
- unless @original_cache_value
- ActiveRecord::Base.connection.disable_query_cache!
- end
+ ActiveRecord::Base.connection.disable_query_cache! unless enabled
end
- end
- def call(env)
- old = ActiveRecord::Base.connection.query_cache_enabled
- ActiveRecord::Base.connection.enable_query_cache!
-
- status, headers, body = @app.call(env)
- [status, headers, BodyProxy.new(old, body, ActiveRecord::Base.connection_id)]
+ response
rescue Exception => e
ActiveRecord::Base.connection.clear_query_cache
- unless old
- ActiveRecord::Base.connection.disable_query_cache!
- end
+ ActiveRecord::Base.connection.disable_query_cache! unless enabled
raise e
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 09da9ad1d1..0e6fecbc4b 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/module/delegation'
+require 'active_support/deprecation'
module ActiveRecord
module Querying
@@ -8,7 +9,7 @@ module ActiveRecord
delegate :find_each, :find_in_batches, :to => :scoped
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
:where, :preload, :eager_load, :includes, :from, :lock, :readonly,
- :having, :create_with, :uniq, :to => :scoped
+ :having, :create_with, :uniq, :references, :none, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
@@ -35,7 +36,16 @@ module ActiveRecord
# > [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
def find_by_sql(sql, binds = [])
logging_query_plan do
- connection.select_all(sanitize_sql(sql), "#{name} Load", binds).collect! { |record| instantiate(record) }
+ result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
+ column_types = {}
+
+ if result_set.respond_to? :column_types
+ column_types = result_set.column_types
+ else
+ ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`"
+ end
+
+ result_set.map { |record| instantiate(record, column_types) }
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index b1c8ae5b77..058dd58efb 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -38,7 +38,8 @@ module ActiveRecord
# first time. Also, make it output to STDERR.
console do |app|
require "active_record/railties/console_sandbox" if app.sandbox?
- ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDERR)
+ console = ActiveSupport::Logger.new(STDERR)
+ Rails.logger.extend ActiveSupport::Logger.broadcast console
end
initializer "active_record.initialize_timezone" do
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 822b51e838..14bb0a724e 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -162,6 +162,9 @@ db_namespace = namespace :db do
else
raise "unknown schema format #{ActiveRecord::Base.schema_format}"
end
+ # Allow this task to be called as many times as required. An example is the
+ # migrate:redo task, which calls other two internally that depend on this one.
+ db_namespace['_dump'].reenable
end
namespace :migrate do
@@ -204,11 +207,12 @@ db_namespace = namespace :db do
next # means "return" for rake task
end
db_list = ActiveRecord::Base.connection.select_values("SELECT version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}")
+ db_list.map! { |version| "%.3d" % version }
file_list = []
ActiveRecord::Migrator.migrations_paths.each do |path|
Dir.foreach(path) do |file|
- # only files matching "20091231235959_some_name.rb" pattern
- if match_data = /^(\d{14})_(.+)\.rb$/.match(file)
+ # match "20091231235959_some_name.rb" and "001_some_name.rb" pattern
+ if match_data = /^(\d{3,})_(.+)\.rb$/.match(file)
status = db_list.delete(match_data[1]) ? 'up' : 'down'
file_list << [status, match_data[1], match_data[2].humanize]
end
@@ -420,7 +424,7 @@ db_namespace = namespace :db do
end
when /postgresql/
set_psql_env(abcs[env])
- `psql -f "#{filename}" #{abcs[env]['database']} #{abcs[env]['template']}`
+ `psql -f "#{filename}" #{abcs[env]['database']}`
when /sqlite/
dbfile = abcs[env]['database']
`sqlite3 #{dbfile} < "#{filename}"`
@@ -612,7 +616,7 @@ def firebird_db_string(config)
end
def set_psql_env(config)
- ENV['PGHOST'] = config['host'] if config['host']
+ ENV['PGHOST'] = config['host'] if config['host']
ENV['PGPORT'] = config['port'].to_s if config['port']
ENV['PGPASSWORD'] = config['password'].to_s if config['password']
ENV['PGUSER'] = config['username'].to_s if config['username']
diff --git a/activerecord/lib/active_record/railties/jdbcmysql_error.rb b/activerecord/lib/active_record/railties/jdbcmysql_error.rb
index 6b9af2a0cb..0b75983484 100644
--- a/activerecord/lib/active_record/railties/jdbcmysql_error.rb
+++ b/activerecord/lib/active_record/railties/jdbcmysql_error.rb
@@ -1,5 +1,5 @@
#FIXME Remove if ArJdbcMysql will give.
-module ArJdbcMySQL
+module ArJdbcMySQL #:nodoc:
class Error < StandardError
attr_accessor :error_number, :sql_state
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 60b73e9fe5..8f8a3ec3bb 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -175,7 +175,7 @@ module ActiveRecord
def initialize(macro, name, options, active_record)
super
- @collection = macro.in?([:has_many, :has_and_belongs_to_many])
+ @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
end
# Returns a new, unsaved instance of the associated class. +options+ will
@@ -229,8 +229,8 @@ module ActiveRecord
end
end
- def columns(tbl_name, log_msg)
- @columns ||= klass.connection.columns(tbl_name, log_msg)
+ def columns(tbl_name)
+ @columns ||= klass.connection.columns(tbl_name)
end
def reset_column_information
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index bf1de4ba9d..7531e1fe6f 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -7,7 +7,7 @@ module ActiveRecord
class Relation
JoinOperation = Struct.new(:relation, :join_class, :on)
ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
- MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind]
+ MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind, :references]
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, :reverse_order, :uniq]
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
@@ -78,6 +78,7 @@ module ActiveRecord
end
def initialize_copy(other)
+ @bind_values = @bind_values.dup
reset
end
@@ -454,7 +455,7 @@ module ActiveRecord
end
def to_sql
- @to_sql ||= klass.connection.to_sql(arel)
+ @to_sql ||= klass.connection.to_sql(arel, @bind_values.dup)
end
def where_values_hash
@@ -462,7 +463,12 @@ module ActiveRecord
node.left.relation.name == table_name
}
- Hash[equalities.map { |where| [where.left.name, where.right] }]
+ binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
+
+ Hash[equalities.map { |where|
+ name = where.left.name
+ [name, binds.fetch(name.to_s) { where.right }]
+ }]
end
def scope_for_create
@@ -496,6 +502,10 @@ module ActiveRecord
to_a.inspect
end
+ def pretty_print(q)
+ q.pp(self.to_a)
+ end
+
def with_default_scope #:nodoc:
if default_scoped? && default_scope = klass.send(:build_default_scope)
default_scope = default_scope.merge(self)
@@ -521,14 +531,25 @@ module ActiveRecord
# always convert table names to downcase as in Oracle quoted table names are in uppercase
joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq
+ string_tables = tables_in_string(to_sql)
- referenced_tables = (tables_in_string(to_sql) - joined_tables)
- if referenced_tables.any?
+ if (references_values - joined_tables).any?
+ true
+ elsif (string_tables - joined_tables).any?
ActiveSupport::Deprecation.warn(
- "Your query appears to reference tables (#{referenced_tables.join(', ')}) that are not " \
- "explicitly joined. This implicit joining is deprecated, so you must explicitly " \
- "reference the tables. For example, instead of Author.includes(:posts).where(\"posts.name = 'foo'\"), " \
- "you should write Author.eager_load(:posts).where(\"posts.name = 'foo'\")."
+ "It looks like you are eager loading table(s) (one of: #{string_tables.join(', ')}) " \
+ "that are referenced in a string SQL snippet. For example: \n" \
+ "\n" \
+ " Post.includes(:comments).where(\"comments.title = 'foo'\")\n" \
+ "\n" \
+ "Currently, Active Record recognises the table in the string, and knows to JOIN the " \
+ "comments table to the query, rather than loading comments in a separate query. " \
+ "However, doing this without writing a full-blown SQL parser is inherently flawed. " \
+ "Since we don't want to write an SQL parser, we are removing this functionality. " \
+ "From now on, you must explicitly tell Active Record when you are referencing a table " \
+ "from a string:\n" \
+ "\n" \
+ " Post.includes(:comments).where(\"comments.title = 'foo'\").references(:comments)\n\n"
)
true
else
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index bf9b4bf1c9..510d01085d 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -177,8 +177,23 @@ module ActiveRecord
# Person.where(:confirmed => true).limit(5).pluck(:id)
#
def pluck(column_name)
- klass.connection.select_all(select(column_name).arel).map! do |attributes|
- klass.type_cast_attribute(attributes.keys.first, klass.initialize_attributes(attributes))
+ key = column_name.to_s.split('.', 2).last
+
+ if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
+ column_name = "#{table_name}.#{column_name}"
+ end
+
+ result = klass.connection.select_all(select(column_name).arel, nil, bind_values)
+ types = result.column_types.merge klass.column_types
+ column = types[key]
+
+ result.map do |attributes|
+ value = klass.initialize_attributes(attributes)[key]
+ if column
+ column.type_cast value
+ else
+ value
+ end
end
end
@@ -239,7 +254,8 @@ module ActiveRecord
query_builder = relation.arel
end
- type_cast_calculated_value(@klass.connection.select_value(query_builder), column_for(column_name), operation)
+ result = @klass.connection.select_value(query_builder, nil, relation.bind_values)
+ type_cast_calculated_value(result, column_for(column_name), operation)
end
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
@@ -275,7 +291,7 @@ module ActiveRecord
relation = except(:group).group(group.join(','))
relation.select_values = select_values
- calculated_data = @klass.connection.select_all(relation)
+ calculated_data = @klass.connection.select_all(relation, nil, bind_values)
if association
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
@@ -333,7 +349,7 @@ module ActiveRecord
def select_for_count
if @select_values.present?
select = @select_values.join(", ")
- select if select !~ /(,|\*)/
+ select if select !~ /[,*]/
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index f1ac421a50..4cd703e0a5 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -200,7 +200,7 @@ module ActiveRecord
relation = relation.where(table[primary_key].eq(id)) if id
end
- connection.select_value(relation, "#{name} Exists") ? true : false
+ connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false
end
protected
@@ -208,7 +208,7 @@ module ActiveRecord
def find_with_associations
join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
- rows = connection.select_all(relation, 'SQL', relation.bind_values)
+ rows = connection.select_all(relation, 'SQL', relation.bind_values.dup)
join_dependency.instantiate(rows)
rescue ThrowResult
[]
@@ -331,7 +331,7 @@ module ActiveRecord
substitute = connection.substitute_at(column, @bind_values.length)
relation = where(table[primary_key].eq(substitute))
- relation.bind_values = [[column, id]]
+ relation.bind_values += [[column, id]]
record = relation.first
unless record
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 88081edae2..1d04e763f6 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -21,6 +21,18 @@ module ActiveRecord
predicates.flatten
end
+ def self.references(attributes)
+ references = attributes.map do |key, value|
+ if value.is_a?(Hash)
+ key
+ else
+ key = key.to_s
+ key.split('.').first.to_sym if key.include?('.')
+ end
+ end
+ references.compact
+ end
+
private
def self.build(attribute, value)
case value
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 456afbb4cf..87dd513880 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -10,7 +10,7 @@ module ActiveRecord
:where_values, :having_values, :bind_values,
:limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value,
:from_value, :reordering_value, :reverse_order_value,
- :uniq_value
+ :uniq_value, :references_values
def includes(*args)
args.reject! {|a| a.blank? }
@@ -38,6 +38,24 @@ module ActiveRecord
relation
end
+ # Used to indicate that an association is referenced by an SQL string, and should
+ # therefore be JOINed in any query rather than loaded separately.
+ #
+ # For example:
+ #
+ # User.includes(:posts).where("posts.name = 'foo'")
+ # # => Doesn't JOIN the posts table, resulting in an error.
+ #
+ # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
+ # # => Query now knows the string references posts, so adds a JOIN
+ def references(*args)
+ return self if args.blank?
+
+ relation = clone
+ relation.references_values = (references_values + args.flatten.map(&:to_s)).uniq
+ relation
+ end
+
# Works in two unique ways.
#
# First: takes a block so it can be used just like Array#select.
@@ -57,16 +75,16 @@ module ActiveRecord
# array, it actually returns a relation object and can have other query
# methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
#
- # This method will also take multiple parameters:
+ # The argument to the method can also be an array of fields.
#
- # >> Model.select(:field, :other_field, :and_one_more)
+ # >> Model.select([:field, :other_field, :and_one_more])
# => [#<Model field: "value", other_field: "value", and_one_more: "value">]
#
- # Any attributes that do not have fields retrieved by a select
- # will return `nil` when the getter method for that attribute is used:
+ # Accessing attributes of an object that do not have fields retrieved by a select
+ # will throw <tt>ActiveModel::MissingAttributeError</tt>:
#
# >> Model.select(:field).first.other_field
- # => nil
+ # => ActiveModel::MissingAttributeError: missing attribute: other_field
def select(value = Proc.new)
if block_given?
to_a.select {|*block_args| value.call(*block_args) }
@@ -88,11 +106,27 @@ module ActiveRecord
def order(*args)
return self if args.blank?
+ args = args.flatten
+ references = args.reject { |arg| Arel::Node === arg }
+ .map { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }
+ .compact
+
relation = clone
- relation.order_values += args.flatten
+ relation = relation.references(references) if references.any?
+ relation.order_values += args
relation
end
+ # Replaces any existing order defined on the relation with the specified order.
+ #
+ # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
+ #
+ # Subsequent calls to order on the same relation will be appended. For example:
+ #
+ # User.order('email DESC').reorder('id ASC').order('name ASC')
+ #
+ # generates a query with 'ORDER BY id ASC, name ASC'.
+ #
def reorder(*args)
return self if args.blank?
@@ -123,6 +157,7 @@ module ActiveRecord
return self if opts.blank?
relation = clone
+ relation = relation.references(PredicateBuilder.references(opts)) if Hash === opts
relation.where_values += build_where(opts, rest)
relation
end
@@ -131,6 +166,7 @@ module ActiveRecord
return self if opts.blank?
relation = clone
+ relation = relation.references(PredicateBuilder.references(opts)) if Hash === opts
relation.having_values += build_where(opts, rest)
relation
end
@@ -160,6 +196,39 @@ module ActiveRecord
relation
end
+ # Returns a chainable relation with zero records, specifically an
+ # instance of the NullRelation class.
+ #
+ # The returned NullRelation inherits from Relation and implements the
+ # Null Object pattern so it is an object with defined null behavior:
+ # it always returns an empty array of records and does not query the database.
+ #
+ # Any subsequent condition chained to the returned relation will continue
+ # generating an empty relation and will not fire any query to the database.
+ #
+ # Used in cases where a method or scope could return zero records but the
+ # result needs to be chainable.
+ #
+ # For example:
+ #
+ # @posts = current_user.visible_posts.where(:name => params[:name])
+ # # => the visible_posts method is expected to return a chainable Relation
+ #
+ # def visible_posts
+ # case role
+ # when 'Country Manager'
+ # Post.where(:country => country)
+ # when 'Reviewer'
+ # Post.published
+ # when 'Bad User'
+ # Post.none # => returning [] instead breaks the previous code
+ # end
+ # end
+ #
+ def none
+ NullRelation.new(@klass, @table)
+ end
+
def readonly(value = true)
relation = clone
relation.readonly_value = value
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index de639a48f2..03ba8c8628 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -22,15 +22,20 @@ module ActiveRecord
end
end
- (Relation::MULTI_VALUE_METHODS - [:joins, :where, :order]).each do |method|
+ (Relation::MULTI_VALUE_METHODS - [:joins, :where, :order, :binds]).each do |method|
value = r.send(:"#{method}_values")
- merged_relation.send(:"#{method}_values=", merged_relation.send(:"#{method}_values") + value) if value.present?
+ next if value.empty?
+
+ value += merged_relation.send(:"#{method}_values")
+ merged_relation.send :"#{method}_values=", value
end
merged_relation.joins_values += r.joins_values
merged_wheres = @where_values + r.where_values
+ merged_binds = (@bind_values + r.bind_values).uniq(&:first)
+
unless @where_values.empty?
# Remove duplicates, last one wins.
seen = Hash.new { |h,table| h[table] = {} }
@@ -47,6 +52,7 @@ module ActiveRecord
end
merged_relation.where_values = merged_wheres
+ merged_relation.bind_values = merged_binds
(Relation::SINGLE_VALUE_METHODS - [:lock, :create_with, :reordering]).each do |method|
value = r.send(:"#{method}_value")
@@ -122,7 +128,7 @@ module ActiveRecord
result
end
- VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :extend, :eager_load,
+ VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :extend, :references,
:order, :select, :readonly, :group, :having, :from, :lock ]
def apply_finder_options(options)
@@ -133,7 +139,7 @@ module ActiveRecord
finders = options.dup
finders.delete_if { |key, value| value.nil? && key != :limit }
- ([:joins, :select, :group, :order, :having, :limit, :offset, :from, :lock, :readonly, :eager_load] & finders.keys).each do |finder|
+ ((VALID_FIND_OPTIONS - [:conditions, :include, :extend]) & finders.keys).each do |finder|
relation = relation.send(finder, finders[finder])
end
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 9ceab2eabc..fb4b89b87b 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -8,12 +8,13 @@ module ActiveRecord
class Result
include Enumerable
- attr_reader :columns, :rows
+ attr_reader :columns, :rows, :column_types
def initialize(columns, rows)
- @columns = columns
- @rows = rows
- @hash_rows = nil
+ @columns = columns
+ @rows = rows
+ @hash_rows = nil
+ @column_types = {}
end
def each
@@ -24,6 +25,31 @@ module ActiveRecord
hash_rows
end
+ alias :map! :map
+ alias :collect! :map
+
+ def empty?
+ rows.empty?
+ end
+
+ def to_ary
+ hash_rows
+ end
+
+ def [](idx)
+ hash_rows[idx]
+ end
+
+ def last
+ hash_rows.last
+ end
+
+ def initialize_copy(other)
+ @columns = columns.dup
+ @rows = rows.dup
+ @hash_rows = nil
+ end
+
private
def hash_rows
@hash_rows ||= @rows.map { |row|
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 2a565b51c6..dcbd165e58 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -197,6 +197,8 @@ HEADER
index_orders = (index.orders || {})
statement_parts << (':order => ' + index.orders.inspect) unless index_orders.empty?
+ statement_parts << (':where => ' + index.where.inspect) if index.where
+
' ' + statement_parts.join(', ')
end
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
new file mode 100644
index 0000000000..236ec563d2
--- /dev/null
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -0,0 +1,34 @@
+require 'active_record/scoping/default'
+require 'active_record/scoping/named'
+require 'active_record/base'
+
+module ActiveRecord
+ class SchemaMigration < ActiveRecord::Base
+ attr_accessible :version
+
+ def self.table_name
+ Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
+ end
+
+ def self.create_table
+ unless connection.table_exists?(table_name)
+ connection.create_table(table_name, :id => false) do |t|
+ t.column :version, :string, :null => false
+ end
+ connection.add_index table_name, :version, :unique => true,
+ :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
+ end
+ end
+
+ def self.drop_table
+ if connection.table_exists?(table_name)
+ connection.remove_index table_name, :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
+ connection.drop_table(table_name)
+ end
+ end
+
+ def version
+ super.to_i
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index 7f1dba5095..2e60521638 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -162,8 +162,9 @@ module ActiveRecord #:nodoc:
#
# class IHaveMyOwnXML < ActiveRecord::Base
# def to_xml(options = {})
+ # require 'builder'
# options[:indent] ||= 2
- # xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
+ # xml = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
# xml.instruct! unless options[:skip_instruct]
# xml.level_one do
# xml.tag!(:second_level, 'content')
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index 8cc84f81d0..1c7b839e5e 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -15,7 +15,7 @@ module ActiveRecord
# class User < ActiveRecord::Base
# store :settings, accessors: [ :color, :homepage ]
# end
- #
+ #
# u = User.new(color: 'black', homepage: '37signals.com')
# u.color # Accessor stored attribute
# u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
@@ -26,7 +26,7 @@ module ActiveRecord
# end
module Store
extend ActiveSupport::Concern
-
+
module ClassMethods
def store(store_attribute, options = {})
serialize store_attribute, Hash
@@ -34,17 +34,19 @@ module ActiveRecord
end
def store_accessor(store_attribute, *keys)
- Array(keys).flatten.each do |key|
+ keys.flatten.each do |key|
define_method("#{key}=") do |value|
+ send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
send(store_attribute)[key] = value
send("#{store_attribute}_will_change!")
end
-
+
define_method(key) do
+ send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
send(store_attribute)[key]
end
end
end
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index 64ecef2077..4d881f0f7d 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -1,3 +1,7 @@
+require 'active_support/deprecation'
+require 'active_support/test_case'
+
+ActiveSupport::Deprecation.warn('ActiveRecord::TestCase is deprecated, please use ActiveSupport::TestCase')
module ActiveRecord
# = Active Record Test Case
#
@@ -10,7 +14,7 @@ module ActiveRecord
end
def teardown
- ActiveRecord::SQLCounter.log.clear
+ SQLCounter.log.clear
end
def cleanup_identity_map
@@ -28,34 +32,63 @@ module ActiveRecord
end
def assert_sql(*patterns_to_match)
- ActiveRecord::SQLCounter.log = []
+ SQLCounter.log = []
yield
- ActiveRecord::SQLCounter.log
+ SQLCounter.log
ensure
failed_patterns = []
patterns_to_match.each do |pattern|
- failed_patterns << pattern unless ActiveRecord::SQLCounter.log.any?{ |sql| pattern === sql }
+ failed_patterns << pattern unless SQLCounter.log.any?{ |sql| pattern === sql }
end
- assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}"
+ assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
end
def assert_queries(num = 1)
- ActiveRecord::SQLCounter.log = []
+ SQLCounter.log = []
yield
ensure
- assert_equal num, ActiveRecord::SQLCounter.log.size, "#{ActiveRecord::SQLCounter.log.size} instead of #{num} queries were executed.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}"
+ assert_equal num, SQLCounter.log.size, "#{SQLCounter.log.size} instead of #{num} queries were executed.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
end
def assert_no_queries(&block)
- prev_ignored_sql = ActiveRecord::SQLCounter.ignored_sql
- ActiveRecord::SQLCounter.ignored_sql = []
+ prev_ignored_sql = SQLCounter.ignored_sql
+ SQLCounter.ignored_sql = []
assert_queries(0, &block)
ensure
- ActiveRecord::SQLCounter.ignored_sql = prev_ignored_sql
+ SQLCounter.ignored_sql = prev_ignored_sql
+ end
+
+ end
+
+ class SQLCounter
+ class << self
+ attr_accessor :ignored_sql, :log
+ end
+
+ self.log = []
+
+ self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/]
+
+ # FIXME: this needs to be refactored so specific database can add their own
+ # ignored SQL. This ignored SQL is for Oracle.
+ ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
+
+
+ attr_reader :ignore
+
+ def initialize(ignore = Regexp.union(self.class.ignored_sql))
+ @ignore = ignore
end
- def sqlite3? connection
- connection.class.name.split('::').last == "SQLite3Adapter"
+ def call(name, start, finish, message_id, values)
+ sql = values[:sql]
+
+ # FIXME: this seems bad. we should probably have a better way to indicate
+ # the query was cached
+ return if 'CACHE' == values[:name] || ignore =~ sql
+ self.class.log << sql
end
end
+
+ ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
end
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index 9f072c4c39..afce149da9 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -2,7 +2,7 @@ module ActiveRecord
module Validations
class AssociatedValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
- if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?}.any?
+ if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?(record.validation_context) }.any?
record.errors.add(attribute, :invalid, options.merge(:value => value))
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 55edb57206..9556878f63 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/array/prepend_and_append'
+
module ActiveRecord
module Validations
class UniquenessValidator < ActiveModel::EachValidator
@@ -25,6 +27,11 @@ module ActiveRecord
Array(options[:scope]).each do |scope_item|
scope_value = record.send(scope_item)
+ reflection = record.class.reflect_on_association(scope_item)
+ if reflection
+ scope_value = record.send(reflection.foreign_key)
+ scope_item = reflection.foreign_key
+ end
relation = relation.and(table[scope_item].eq(scope_value))
end
@@ -44,14 +51,21 @@ module ActiveRecord
class_hierarchy = [record.class]
while class_hierarchy.first != @klass
- class_hierarchy.insert(0, class_hierarchy.first.superclass)
+ class_hierarchy.prepend(class_hierarchy.first.superclass)
end
class_hierarchy.detect { |klass| !klass.abstract_class? }
end
def build_relation(klass, table, attribute, value) #:nodoc:
- column = klass.columns_hash[attribute.to_s]
+ reflection = klass.reflect_on_association(attribute)
+ if reflection
+ column = klass.columns_hash[reflection.foreign_key]
+ attribute = reflection.foreign_key
+ value = value.attributes[reflection.primary_key_column.name]
+ else
+ column = klass.columns_hash[attribute.to_s]
+ end
value = column.limit ? value.to_s[0, column.limit] : value.to_s if !value.nil? && column.text?
if !options[:case_sensitive] && value && column.text?
diff --git a/activerecord/lib/rails/generators/active_record.rb b/activerecord/lib/rails/generators/active_record.rb
index 4b3d1db216..297cd094c2 100644
--- a/activerecord/lib/rails/generators/active_record.rb
+++ b/activerecord/lib/rails/generators/active_record.rb
@@ -1,14 +1,12 @@
require 'rails/generators/named_base'
require 'rails/generators/migration'
require 'rails/generators/active_model'
-require 'rails/generators/active_record/migration'
require 'active_record'
module ActiveRecord
module Generators
class Base < Rails::Generators::NamedBase #:nodoc:
include Rails::Generators::Migration
- extend ActiveRecord::Generators::Migration
# Set the current directory as base for the inherited generators.
def self.base_root
diff --git a/activerecord/lib/rails/generators/active_record/migration.rb b/activerecord/lib/rails/generators/active_record/migration.rb
deleted file mode 100644
index 7f2f2e06a5..0000000000
--- a/activerecord/lib/rails/generators/active_record/migration.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module ActiveRecord
- module Generators
- module Migration
- # Implement the required interface for Rails::Generators::Migration.
- def next_migration_number(dirname) #:nodoc:
- next_migration_number = current_migration_number(dirname) + 1
- if ActiveRecord::Base.timestamped_migrations
- [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
- else
- "%.3d" % next_migration_number
- end
- end
- end
- end
-end
diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
index 267ea8bb6b..69dfd2503e 100644
--- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb
+++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
@@ -33,7 +33,7 @@ module ActiveRecord
options[:null])
end
- def columns(table_name, message)
+ def columns(table_name)
@columns[table_name]
end
end
diff --git a/activerecord/test/assets/test.txt b/activerecord/test/assets/test.txt
new file mode 100644
index 0000000000..6754f0612e
--- /dev/null
+++ b/activerecord/test/assets/test.txt
@@ -0,0 +1 @@
+%00
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index e4746d4aa3..447d729e52 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -21,6 +21,18 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1)
end
+ def test_add_index
+ # add_index calls index_name_exists? which can't work since execute is stubbed
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) do |*|
+ false
+ end
+
+ expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active')
+ assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'")
+
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:remove_method, :index_name_exists?)
+ end
+
private
def method_missing(method_symbol, *arguments)
ActiveRecord::Base.connection.send(method_symbol, *arguments)
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index 33bf4478cc..1644a58d92 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -1,4 +1,6 @@
require "cases/helper"
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlHstoreTest < ActiveRecord::TestCase
class Hstore < ActiveRecord::Base
@@ -10,12 +12,13 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
begin
@connection.transaction do
@connection.create_table('hstores') do |t|
- t.hstore 'tags'
+ t.hstore 'tags', :default => ''
end
end
rescue ActiveRecord::StatementInvalid
return skip "do not test on PG without hstore"
end
+ @column = Hstore.columns.find { |c| c.name == 'tags' }
end
def teardown
@@ -23,21 +26,74 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
end
def test_column
- column = Hstore.columns.find { |c| c.name == 'tags' }
- assert column
- assert_equal :hstore, column.type
+ assert_equal :hstore, @column.type
end
def test_type_cast_hstore
- column = Hstore.columns.find { |c| c.name == 'tags' }
- assert column
+ assert @column
data = "\"1\"=>\"2\""
- hash = column.class.cast_hstore data
+ hash = @column.class.string_to_hstore data
assert_equal({'1' => '2'}, hash)
- assert_equal({'1' => '2'}, column.type_cast(data))
+ assert_equal({'1' => '2'}, @column.type_cast(data))
+
+ assert_equal({}, @column.type_cast(""))
+ assert_equal({'key'=>nil}, @column.type_cast('key => NULL'))
+ assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast(%q(c=>"}", "\"a\""=>"b \"a b")))
+ end
+
+ def test_gen1
+ assert_equal(%q(" "=>""), @column.class.hstore_to_string({' '=>''}))
+ end
+
+ def test_gen2
+ assert_equal(%q(","=>""), @column.class.hstore_to_string({','=>''}))
+ end
+
+ def test_gen3
+ assert_equal(%q("="=>""), @column.class.hstore_to_string({'='=>''}))
+ end
+
+ def test_gen4
+ assert_equal(%q(">"=>""), @column.class.hstore_to_string({'>'=>''}))
+ end
+
+ def test_parse1
+ assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast('a=>null,b=>NuLl,c=>"NuLl",null=>c'))
+ end
+
+ def test_parse2
+ assert_equal({" " => " "}, @column.type_cast("\\ =>\\ "))
+ end
+
+ def test_parse3
+ assert_equal({"=" => ">"}, @column.type_cast("==>>"))
end
+ def test_parse4
+ assert_equal({"=a"=>"q=w"}, @column.type_cast('\=a=>q=w'))
+ end
+
+ def test_parse5
+ assert_equal({"=a"=>"q=w"}, @column.type_cast('"=a"=>q\=w'))
+ end
+
+ def test_parse6
+ assert_equal({"\"a"=>"q>w"}, @column.type_cast('"\"a"=>q>w'))
+ end
+
+ def test_parse7
+ assert_equal({"\"a"=>"q\"w"}, @column.type_cast('\"a=>q"w'))
+ end
+
+ def test_rewrite
+ @connection.execute "insert into hstores (tags) VALUES ('1=>2')"
+ x = Hstore.find :first
+ x.tags = { '"a\'' => 'b' }
+ assert x.save!
+ end
+
+
def test_select
@connection.execute "insert into hstores (tags) VALUES ('1=>2')"
x = Hstore.find :first
@@ -54,6 +110,10 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
assert_cycle('a' => 'b', '1' => '2')
end
+ def test_nil
+ assert_cycle('a' => nil)
+ end
+
def test_quotes
assert_cycle('a' => 'b"ar', '1"foo' => '2')
end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index d57794daf8..a71d0bb848 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -179,6 +179,17 @@ module ActiveRecord
assert_equal Arel.sql('$2'), bind
end
+ def test_partial_index
+ @connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100"
+ index = @connection.indexes('ex').find { |idx| idx.name == 'partial' }
+ assert_equal "(number > 100)", index.where
+ end
+
+ def test_distinct_with_nulls
+ assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls first"])
+ assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls last"])
+ end
+
private
def insert(ctx, data)
binds = data.map { |name, value|
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 18670b4177..18c81d2b09 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -24,6 +24,8 @@ class SchemaTest < ActiveRecord::TestCase
'moment timestamp without time zone default now()'
]
PK_TABLE_NAME = 'table_with_pk'
+ UNMATCHED_SEQUENCE_NAME = 'unmatched_primary_key_default_value_seq'
+ UNMATCHED_PK_TABLE_NAME = 'table_with_unmatched_sequence_for_pk'
class Thing1 < ActiveRecord::Base
self.table_name = "test_schema.things"
@@ -60,6 +62,8 @@ class SchemaTest < ActiveRecord::TestCase
@connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);"
@connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);"
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)"
+ @connection.execute "CREATE SEQUENCE #{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}"
+ @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))"
end
def teardown
@@ -241,12 +245,12 @@ class SchemaTest < ActiveRecord::TestCase
def test_pk_and_sequence_for_with_schema_specified
[
%("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"),
- %(#{SCHEMA_NAME}."#{PK_TABLE_NAME}"),
- %(#{SCHEMA_NAME}.#{PK_TABLE_NAME})
+ %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
].each do |given|
pk, seq = @connection.pk_and_sequence_for(given)
assert_equal 'id', pk, "primary key should be found when table referenced as #{given}"
- assert_equal "#{SCHEMA_NAME}.#{PK_TABLE_NAME}_id_seq", seq, "sequence name should be found when table referenced as #{given}"
+ assert_equal "#{PK_TABLE_NAME}_id_seq", seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}")
+ assert_equal "#{UNMATCHED_SEQUENCE_NAME}", seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index e0152e7ccf..46da1b0a2b 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -70,9 +70,9 @@ module ActiveRecord
assert_equal bd.to_f, @conn.type_cast(bd, nil)
end
- def test_type_cast_unknown
+ def test_type_cast_unknown_should_raise_error
obj = Class.new.new
- assert_equal YAML.dump(obj), @conn.type_cast(obj, nil)
+ assert_raise(TypeError) { @conn.type_cast(obj, nil) }
end
def test_quoted_id
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index 90e5609782..6733f3e889 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -61,31 +61,15 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_cascaded_eager_association_loading_with_duplicated_includes
- categories = Category.includes(:categorizations).includes(:categorizations => :author)
- assert_nothing_raised do
- assert_equal Category.count, categories.count
- assert_equal Category.count, categories.all.size
- end
- end
-
- def test_cascaded_eager_association_loading_with_twice_includes_edge_cases
- categories = Category.includes(:categorizations => :author).includes(:categorizations => :post)
- assert_nothing_raised do
- assert_equal Category.count, categories.count
- assert_equal Category.count, categories.all.size
- end
- end
-
- def test_cascaded_eager_association_loading_with_duplicated_eager_load
- categories = Category.eager_load(:categorizations).eager_load(:categorizations => :author).where("categorizations.id is not null")
+ categories = Category.includes(:categorizations).includes(:categorizations => :author).where("categorizations.id is not null").references(:categorizations)
assert_nothing_raised do
assert_equal 3, categories.count
assert_equal 3, categories.all.size
end
end
- def test_cascaded_eager_association_loading_with_twice_eager_load_edge_cases
- categories = Category.eager_load(:categorizations => :author).eager_load(:categorizations => :post).where("posts.id is not null")
+ def test_cascaded_eager_association_loading_with_twice_includes_edge_cases
+ categories = Category.includes(:categorizations => :author).includes(:categorizations => :post).where("posts.id is not null").references(:posts)
assert_nothing_raised do
assert_equal 3, categories.count
assert_equal 3, categories.all.size
@@ -143,7 +127,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
silly.parent_id = 1
assert silly.save
- topics = Topic.eager_load(:replies).order('topics.id, replies_topics.id').to_a
+ topics = Topic.find(:all, :include => :replies, :order => ['topics.id', 'replies_topics.id'])
assert_no_queries do
assert_equal 2, topics[0].replies.size
assert_equal 0, topics[1].replies.size
@@ -158,9 +142,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_multiple_stis_and_order
- author = Author.eager_load(:posts => [ :special_comments , :very_special_comment ]).
- order('authors.name', 'comments.body', 'very_special_comments_posts.body').
- where('posts.id = 4').first
+ author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => ['authors.name', 'comments.body', 'very_special_comments_posts.body'], :conditions => 'posts.id = 4')
assert_equal authors(:david), author
assert_no_queries do
author.posts.first.special_comments
@@ -169,9 +151,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_of_stis_with_multiple_references
- authors = Author.eager_load(:posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } }).
- order('comments.body, very_special_comments_posts.body').
- where('posts.id = 4')
+ authors = Author.find(:all, :include => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4')
assert_equal [authors(:david)], authors
assert_no_queries do
authors.first.posts.first.special_comments.first.post.special_comments
diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
index 0964124a81..1e1958410c 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -123,7 +123,7 @@ class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase
assert_nothing_raised do
# @davey_mcdave doesn't have any author_favorites
includes = {:posts => :comments, :categorizations => :category, :author_favorites => :favorite_author }
- Author.all :eager_load => includes, :conditions => {:authors => {:name => @davey_mcdave.name}}, :order => 'categories.name'
+ Author.all :include => includes, :conditions => {:authors => {:name => @davey_mcdave.name}}, :order => 'categories.name'
end
end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index ac9e3f29f9..b79c69bbb5 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -55,13 +55,16 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_loading_with_one_association_with_non_preload
- posts = Post.find(:all, :eager_load => :last_comment, :order => 'comments.id DESC')
+ posts = Post.find(:all, :include => :last_comment, :order => 'comments.id DESC')
post = posts.find { |p| p.id == 1 }
assert_equal Post.find(1).last_comment, post.last_comment
end
def test_loading_conditions_with_or
- posts = authors(:david).posts.find(:all, :eager_load => :comments, :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'")
+ posts = authors(:david).posts.find(
+ :all, :include => :comments, :references => :comments,
+ :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'"
+ )
assert_nil posts.detect { |p| p.author_id != authors(:david).id },
"expected to find only david's posts"
end
@@ -164,8 +167,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
car_post.categories << categories(:technology)
comment = car_post.comments.create!(:body => "hmm")
- categories = Category.find(:all, :conditions => ["posts.id=?", car_post.id],
- :eager_load => {:posts => :comments})
+ categories = Category.find(:all, :conditions => { 'posts.id' => car_post.id },
+ :include => {:posts => :comments})
categories.each do |category|
assert_equal [comment], category.posts[0].comments
end
@@ -263,27 +266,36 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_nested_loading_through_has_one_association_with_order_on_association
- aa = AuthorAddress.find(author_addresses(:david_address).id, :eager_load => {:author => :posts}, :order => 'authors.id')
+ aa = AuthorAddress.find(author_addresses(:david_address).id, :include => {:author => :posts}, :order => 'authors.id')
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_order_on_nested_association
- aa = AuthorAddress.find(author_addresses(:david_address).id, :eager_load => {:author => :posts}, :order => 'posts.id')
+ aa = AuthorAddress.find(author_addresses(:david_address).id, :include => {:author => :posts}, :order => 'posts.id')
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_conditions
- aa = AuthorAddress.find(author_addresses(:david_address).id, :include => {:author => :posts}, :conditions => "author_addresses.id > 0")
+ aa = AuthorAddress.find(
+ author_addresses(:david_address).id, :include => {:author => :posts},
+ :conditions => "author_addresses.id > 0", :references => :author_addresses
+ )
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_conditions_on_association
- aa = AuthorAddress.find(author_addresses(:david_address).id, :eager_load => {:author => :posts}, :conditions => "authors.id > 0")
+ aa = AuthorAddress.find(
+ author_addresses(:david_address).id, :include => {:author => :posts},
+ :conditions => "authors.id > 0", :references => :authors
+ )
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_conditions_on_nested_association
- aa = AuthorAddress.find(author_addresses(:david_address).id, :eager_load => {:author => :posts}, :conditions => "posts.id > 0")
+ aa = AuthorAddress.find(
+ author_addresses(:david_address).id, :include => {:author => :posts},
+ :conditions => "posts.id > 0", :references => :posts
+ )
assert_equal aa.author.posts.count, aa.author.posts.length
end
@@ -332,14 +344,16 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_conditions_string_with_unquoted_table_name
assert_nothing_raised do
- Comment.eager_load(:post).where('posts.id = ?',4).to_a
+ ActiveSupport::Deprecation.silence do
+ Comment.find(:all, :include => :post, :conditions => ['posts.id = ?',4])
+ end
end
end
def test_eager_association_loading_with_belongs_to_and_conditions_hash
comments = []
assert_nothing_raised do
- comments = Comment.eager_load(:post).where(:posts => {:id => 4}).limit(3).order('comments.id').to_a
+ comments = Comment.find(:all, :include => :post, :conditions => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id')
end
assert_equal 3, comments.length
assert_equal [5,6,7], comments.collect { |c| c.id }
@@ -351,20 +365,24 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_conditions_string_with_quoted_table_name
quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id')
assert_nothing_raised do
- Comment.eager_load(:post).where("#{quoted_posts_id} = ?",4).to_a
+ ActiveSupport::Deprecation.silence do
+ Comment.find(:all, :include => :post, :conditions => ["#{quoted_posts_id} = ?",4])
+ end
end
end
def test_eager_association_loading_with_belongs_to_and_order_string_with_unquoted_table_name
assert_nothing_raised do
- Comment.eager_load(:post).order('posts.id').to_a
+ Comment.find(:all, :include => :post, :order => 'posts.id')
end
end
def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name
quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id')
assert_nothing_raised do
- Comment.eager_load(:post).order(quoted_posts_id).to_a
+ ActiveSupport::Deprecation.silence do
+ Comment.find(:all, :include => :post, :order => quoted_posts_id)
+ end
end
end
@@ -528,36 +546,41 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers
- posts = Post.find(:all, :eager_load => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
+ posts = ActiveSupport::Deprecation.silence do
+ Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
+ end
assert_equal 2, posts.size
- count = Post.count(:eager_load => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
+ count = ActiveSupport::Deprecation.silence do
+ Post.count(:include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
+ end
assert_equal count, posts.size
end
def test_eager_with_has_many_and_limit_and_high_offset
- posts = Post.find(:all, :eager_load => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ])
+ posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => { 'authors.name' => 'David' })
assert_equal 0, posts.size
end
def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_array_conditions
assert_queries(1) do
- posts = Post.find(:all, :eager_load => [ :author, :comments ], :limit => 2, :offset => 10,
- :conditions => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ])
+ posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10,
+ :conditions => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ],
+ :references => [:authors, :comments])
assert_equal 0, posts.size
end
end
def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_hash_conditions
assert_queries(1) do
- posts = Post.find(:all, :eager_load => [ :author, :comments ], :limit => 2, :offset => 10,
+ posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10,
:conditions => { 'authors.name' => 'David', 'comments.body' => 'go crazy' })
assert_equal 0, posts.size
end
end
def test_count_eager_with_has_many_and_limit_and_high_offset
- posts = Post.eager_load(:author, :comments).limit(2).offset(10).where("authors.name = ?", 'David').count
+ posts = Post.count(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => { 'authors.name' => 'David' })
assert_equal 0, posts
end
@@ -569,7 +592,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_count_performed_on_a_has_many_association_with_multi_table_conditional
author = authors(:david)
author_posts_without_comments = author.posts.select { |post| post.comments.blank? }
- assert_equal author_posts_without_comments.size, author.posts.eager_load(:comments).where('comments.id is null').count
+ assert_equal author_posts_without_comments.size, author.posts.count(:all, :include => :comments, :conditions => 'comments.id is null', :references => :comments)
end
def test_eager_count_performed_on_a_has_many_through_association_with_multi_table_conditional
@@ -606,15 +629,17 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_many_and_limit_and_conditions_on_the_eagers
posts = authors(:david).posts.find(:all,
- :eager_load => :comments,
+ :include => :comments,
:conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
+ :references => :comments,
:limit => 2
)
assert_equal 2, posts.size
count = Post.count(
- :eager_load => [ :comments, :author ],
+ :include => [ :comments, :author ],
:conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
+ :references => [:authors, :comments],
:limit => 2
)
assert_equal count, posts.size
@@ -623,16 +648,18 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_many_and_limit_and_scoped_conditions_on_the_eagers
posts = nil
Post.send(:with_scope, :find => {
- :eager_load => :comments,
- :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'"
+ :include => :comments,
+ :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
+ :references => :comments
}) do
posts = authors(:david).posts.find(:all, :limit => 2)
assert_equal 2, posts.size
end
Post.send(:with_scope, :find => {
- :eager_load => [ :comments, :author ],
- :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')"
+ :include => [ :comments, :author ],
+ :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
+ :references => [:authors, :comments]
}) do
count = Post.count(:limit => 2)
assert_equal count, posts.size
@@ -642,15 +669,17 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_many_and_limit_and_scoped_and_explicit_conditions_on_the_eagers
Post.send(:with_scope, :find => { :conditions => "1=1" }) do
posts = authors(:david).posts.find(:all,
- :eager_load => :comments,
+ :include => :comments,
:conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
+ :references => :comments,
:limit => 2
)
assert_equal 2, posts.size
count = Post.count(
- :eager_load => [ :comments, :author ],
+ :include => [ :comments, :author ],
:conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
+ :references => [:authors, :comments],
:limit => 2
)
assert_equal count, posts.size
@@ -658,9 +687,15 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_scoped_order_using_association_limiting_without_explicit_scope
- posts_with_explicit_order = Post.find(:all, :conditions => 'comments.id is not null', :eager_load => :comments, :order => 'posts.id DESC', :limit => 2)
+ posts_with_explicit_order = Post.find(
+ :all, :conditions => 'comments.id is not null', :references => :comments,
+ :include => :comments, :order => 'posts.id DESC', :limit => 2
+ )
posts_with_scoped_order = Post.send(:with_scope, :find => {:order => 'posts.id DESC'}) do
- Post.find(:all, :conditions => 'comments.id is not null', :eager_load => :comments, :limit => 2)
+ Post.find(
+ :all, :conditions => 'comments.id is not null',
+ :references => :comments, :include => :comments, :limit => 2
+ )
end
assert_equal posts_with_explicit_order, posts_with_scoped_order
end
@@ -773,17 +808,49 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_limited_eager_with_order
- assert_equal posts(:thinking, :sti_comments), Post.find(:all, :eager_load => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title)', :limit => 2, :offset => 1)
- assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :eager_load => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title) DESC', :limit => 2, :offset => 1)
+ assert_equal(
+ posts(:thinking, :sti_comments),
+ Post.find(
+ :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' },
+ :order => 'UPPER(posts.title)', :limit => 2, :offset => 1
+ )
+ )
+ assert_equal(
+ posts(:sti_post_and_comments, :sti_comments),
+ Post.find(
+ :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' },
+ :order => 'UPPER(posts.title) DESC', :limit => 2, :offset => 1
+ )
+ )
end
def test_limited_eager_with_multiple_order_columns
- assert_equal posts(:thinking, :sti_comments), Post.find(:all, :eager_load => [:author, :comments], :conditions => "authors.name = 'David'", :order => ['UPPER(posts.title)', 'posts.id'], :limit => 2, :offset => 1)
- assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :eager_load => [:author, :comments], :conditions => "authors.name = 'David'", :order => ['UPPER(posts.title) DESC', 'posts.id'], :limit => 2, :offset => 1)
+ assert_equal(
+ posts(:thinking, :sti_comments),
+ Post.find(
+ :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' },
+ :order => ['UPPER(posts.title)', 'posts.id'], :limit => 2, :offset => 1
+ )
+ )
+ assert_equal(
+ posts(:sti_post_and_comments, :sti_comments),
+ Post.find(
+ :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' },
+ :order => ['UPPER(posts.title) DESC', 'posts.id'], :limit => 2, :offset => 1
+ )
+ )
end
def test_limited_eager_with_numeric_in_association
- assert_equal people(:david, :susan), Person.find(:all, :eager_load => [:readers, :primary_contact, :number1_fan], :conditions => "number1_fans_people.first_name like 'M%'", :order => 'people.id', :limit => 2, :offset => 0)
+ assert_equal(
+ people(:david, :susan),
+ Person.find(
+ :all, :include => [:readers, :primary_contact, :number1_fan],
+ :conditions => "number1_fans_people.first_name like 'M%'",
+ :references => :number1_fans_people,
+ :order => 'people.id', :limit => 2, :offset => 0
+ )
+ )
end
def test_preload_with_interpolation
@@ -898,11 +965,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_count_with_include
if current_adapter?(:SybaseAdapter)
- assert_equal 3, assert_deprecated { authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15") }
+ assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15", :references => :comments)
elsif current_adapter?(:OpenBaseAdapter)
- assert_equal 3, assert_deprecated { authors(:david).posts_with_comments.count(:conditions => "length(FETCHBLOB(comments.body)) > 15") }
+ assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(FETCHBLOB(comments.body)) > 15", :references => :comments)
else
- assert_equal 3, assert_deprecated { authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15") }
+ assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15", :references => :comments)
end
end
@@ -913,11 +980,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_conditions_on_join_table_with_include_and_limit
- assert_equal 3, assert_deprecated { Developer.find(:all, :include => 'projects', :conditions => 'developers_projects.access_level = 1', :limit => 5).size }
+ assert_equal 3, Developer.find(:all, :include => 'projects', :conditions => { 'developers_projects.access_level' => 1 }, :limit => 5).size
end
def test_order_on_join_table_with_include_and_limit
- assert_equal 5, assert_deprecated { Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size }
+ assert_equal 5, Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size
end
def test_eager_loading_with_order_on_joined_table_preloads
@@ -1014,9 +1081,9 @@ class EagerAssociationTest < ActiveRecord::TestCase
expected = Firm.find(1).clients_using_primary_key.sort_by(&:name)
# Oracle adapter truncates alias to 30 characters
if current_adapter?(:OracleAdapter)
- firm = Firm.find 1, :eager_load => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name'
+ firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name'
else
- firm = Firm.find 1, :eager_load => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name'
+ firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name'
end
assert_no_queries do
assert_equal expected, firm.clients_using_primary_key
@@ -1033,7 +1100,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_include_has_one_using_primary_key
expected = accounts(:signals37)
- firm = Firm.find(:all, :eager_load => :account_using_primary_key, :order => 'accounts.id').detect {|f| f.id == 1}
+ firm = Firm.find(:all, :include => :account_using_primary_key, :order => 'accounts.id').detect {|f| f.id == 1}
assert_no_queries do
assert_equal expected, firm.account_using_primary_key
end
@@ -1086,13 +1153,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_join_eager_with_empty_order_should_generate_valid_sql
assert_nothing_raised(ActiveRecord::StatementInvalid) do
- Post.eager_load(:comments).order("").where(:comments => {:body => "Thank you for the welcome"}).first
+ Post.includes(:comments).order("").where(:comments => {:body => "Thank you for the welcome"}).first
end
end
def test_join_eager_with_nil_order_should_generate_valid_sql
assert_nothing_raised(ActiveRecord::StatementInvalid) do
- Post.eager_load(:comments).order(nil).where(:comments => {:body => "Thank you for the welcome"}).first
+ Post.includes(:comments).order(nil).where(:comments => {:body => "Thank you for the welcome"}).first
end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index b91d9b5659..f457dfb9b3 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -679,7 +679,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_join_table_alias
- assert_equal 3, Developer.find(:all, :eager_load => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL').size
+ assert_equal(
+ 3,
+ Developer.find(
+ :all, :include => {:projects => :developers}, :references => :developers_projects_join,
+ :conditions => 'developers_projects_join.joined_on IS NOT NULL'
+ ).size
+ )
end
def test_join_with_group
@@ -689,7 +695,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
Project.columns.each { |c| group << "projects.#{c.name}" }
- assert_equal 3, Developer.find(:all, :eager_load => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL', :group => group.join(",")).size
+ assert_equal(
+ 3,
+ Developer.find(
+ :all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL',
+ :references => :developers_projects_join, :group => group.join(",")
+ ).size
+ )
end
def test_find_grouped
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index f1a341437f..3967009c82 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -738,6 +738,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size
end
+ def test_find_or_initialize_returns_the_instantiated_object
+ client = companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client")
+ assert_equal client, companies(:first_firm).clients_of_firm[-1]
+ end
+
+ def test_find_or_initialize_only_instantiates_a_single_object
+ number_of_clients = Client.count
+ companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client").save!
+ companies(:first_firm).save!
+ assert_equal number_of_clients+1, Client.count
+ end
+
def test_find_or_create_with_hash
post = authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
assert_equal post, authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
@@ -1140,16 +1152,42 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_nil companies(:leetsoft).reload.client_of
assert_nil companies(:jadedpixel).reload.client_of
-
assert_equal num_accounts, Account.count
end
def test_restrict
- firm = RestrictedFirm.new(:name => 'restrict')
- firm.save!
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = true
+
+ firm = RestrictedFirm.create!(:name => 'restrict')
firm.companies.create(:name => 'child')
+
assert !firm.companies.empty?
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
+ assert RestrictedFirm.exists?(:name => 'restrict')
+ assert firm.companies.exists?(:name => 'child')
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ end
+
+ def test_restrict_when_dependent_restrict_raises_config_set_to_false
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = false
+
+ firm = RestrictedFirm.create!(:name => 'restrict')
+ firm.companies.create(:name => 'child')
+
+ assert !firm.companies.empty?
+
+ firm.destroy
+
+ assert !firm.errors.empty?
+
+ assert_equal "Cannot delete record because dependent companies exist", firm.errors[:base].first
+ assert RestrictedFirm.exists?(:name => 'restrict')
+ assert firm.companies.exists?(:name => 'child')
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
end
def test_included_in_collection
@@ -1253,6 +1291,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert company.clients_using_sql.loaded?
end
+ def test_get_ids_for_ordered_association
+ assert_equal [companies(:second_client).id, companies(:first_client).id], companies(:first_firm).clients_ordered_by_name_ids
+ end
+
def test_assign_ids_ignoring_blanks
firm = Firm.create!(:name => 'Apple')
firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, '']
@@ -1401,29 +1443,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients.last
end
end
-
+
def test_custom_primary_key_on_new_record_should_fetch_with_query
author = Author.new(:name => "David")
assert !author.essays.loaded?
-
- assert_queries 1 do
+
+ assert_queries 1 do
assert_equal 1, author.essays.size
end
-
+
assert_equal author.essays, Essay.find_all_by_writer_id("David")
-
+
end
-
+
def test_has_many_custom_primary_key
david = authors(:david)
assert_equal david.essays, Essay.find_all_by_writer_id("David")
end
-
+
def test_blank_custom_primary_key_on_new_record_should_not_run_queries
author = Author.new
assert !author.essays.loaded?
-
- assert_queries 0 do
+
+ assert_queries 0 do
assert_equal 0, author.essays.size
end
end
@@ -1649,4 +1691,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [bulb2], car.bulbs
assert_equal [bulb2], car.reload.bulbs
end
+
+ def test_building_has_many_association_with_restrict_dependency
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = true
+
+ klass = Class.new(ActiveRecord::Base)
+
+ assert_deprecated { klass.has_many :companies, :dependent => :restrict }
+ assert_not_deprecated { klass.has_many :companies }
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ end
end
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 efe92c54c6..9cc09194dc 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -738,7 +738,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_select_chosen_fields_only
author = authors(:david)
- assert_equal ['body'], author.comments.select('comments.body').first.attributes.keys
+ assert_equal ['body', 'id'].sort, author.comments.select('comments.body').first.attributes.keys.sort
end
def test_get_has_many_through_belongs_to_ids_with_conditions
@@ -847,7 +847,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_preloading_empty_through_association_via_joins
person = Person.create!(:first_name => "Gaga")
- person = Person.where(:id => person.id).where('readers.id = 1 or 1=1').eager_load(:posts).to_a.first
+ person = Person.where(:id => person.id).where('readers.id = 1 or 1=1').references(:readers).includes(:posts).to_a.first
assert person.posts.loaded?, 'person.posts should be loaded'
assert_equal [], person.posts
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 26931e3e85..246877bbed 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -157,11 +157,62 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
def test_dependence_with_restrict
- firm = RestrictedFirm.new(:name => 'restrict')
- firm.save!
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = true
+
+ firm = RestrictedFirm.create!(:name => 'restrict')
firm.create_account(:credit_limit => 10)
+
assert_not_nil firm.account
+
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
+ assert RestrictedFirm.exists?(:name => 'restrict')
+ assert firm.account.present?
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ end
+
+ def test_dependence_with_restrict_with_dependent_restrict_raises_config_set_to_false
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = false
+
+ firm = RestrictedFirm.create!(:name => 'restrict')
+ firm.create_account(:credit_limit => 10)
+
+ assert_not_nil firm.account
+
+ firm.destroy
+
+ assert !firm.errors.empty?
+ assert_equal "Cannot delete record because a dependent account exists", firm.errors[:base].first
+ assert RestrictedFirm.exists?(:name => 'restrict')
+ assert firm.account.present?
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ end
+
+ def test_dependence_with_restrict_with_dependent_restrict_raises_config_set_to_false_and_attribute_name
+ old_backend = I18n.backend
+ I18n.backend = I18n::Backend::Simple.new
+ I18n.backend.store_translations 'en', :activerecord => {:attributes => {:restricted_firm => {:account => "account model"}}}
+
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = false
+
+ firm = RestrictedFirm.create!(:name => 'restrict')
+ firm.create_account(:credit_limit => 10)
+
+ assert_not_nil firm.account
+
+ firm.destroy
+
+ assert !firm.errors.empty?
+ assert_equal "Cannot delete record because a dependent account model exists", firm.errors[:base].first
+ assert RestrictedFirm.exists?(:name => 'restrict')
+ assert firm.account.present?
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ I18n.backend = old_backend
end
def test_successful_build_association
@@ -456,4 +507,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal car.id, bulb.attributes_after_initialize['car_id']
end
+
+ def test_building_has_one_association_with_dependent_restrict
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = true
+
+ klass = Class.new(ActiveRecord::Base)
+
+ assert_deprecated { klass.has_one :account, :dependent => :restrict }
+ assert_not_deprecated { klass.has_one :account }
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ 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 b04c5c7b54..2503349c08 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -111,7 +111,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_nonpreload_eagerloading
members = assert_queries(1) do
- Member.find(:all, :eager_load => :club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback
+ Member.find(:all, :include => :club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries {members[0].club}
@@ -119,7 +119,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_nonpreload_eager_loading_through_polymorphic
members = assert_queries(1) do
- Member.find(:all, :eager_load => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback
+ Member.find(:all, :include => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries {members[0].sponsor_club}
@@ -128,7 +128,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record
Sponsor.new(:sponsor_club => clubs(:crazy_club), :sponsorable => members(:groucho)).save!
members = assert_queries(1) do
- Member.find(:all, :eager_load => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name DESC') #force fallback
+ Member.find(:all, :include => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name DESC') #force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries { members[0].sponsor_club }
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index 4202d28061..68a1e62328 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -42,7 +42,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
end
def test_join_conditions_allow_nil_associations
- authors = Author.eager_load(:essays).where(:essays => {:id => nil})
+ authors = Author.includes(:essays).where(:essays => {:id => nil})
assert_equal 2, authors.count
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 61b341eaa4..76282213d8 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -104,7 +104,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
f.man.name = 'Mungo'
assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance"
- m = Man.find(:first, :conditions => {:name => 'Gordon'}, :eager_load => :face, :order => 'faces.id')
+ m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :face, :order => 'faces.id')
f = m.face
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
m.name = 'Bongo'
@@ -189,7 +189,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance"
end
- m = Man.find(:first, :conditions => {:name => 'Gordon'}, :eager_load => :interests, :order => 'interests.id')
+ m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :interests, :order => 'interests.id')
is = m.interests
is.each do |i|
assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
@@ -286,7 +286,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase
m.face.description = 'pleasing'
assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance"
- f = Face.find(:first, :eager_load => :man, :order => 'men.id', :conditions => {:description => 'trusting'})
+ f = Face.find(:first, :include => :man, :order => 'men.id', :conditions => {:description => 'trusting'})
m = f.man
assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
f.description = 'gormless'
@@ -369,7 +369,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
m.polymorphic_face.description = 'pleasing'
assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance"
- f = Face.find(:first, :conditions => {:description => 'confused'}, :eager_load => :man, :order => 'men.id')
+ f = Face.find(:first, :conditions => {:description => 'confused'}, :include => :man, :order => 'men.id')
m = f.polymorphic_man
assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance"
f.description = 'gormless'
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 244d01da7d..301755249c 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -362,7 +362,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
assert_raise ActiveRecord::EagerLoadPolymorphicError do
- tags(:general).taggings.eager_load(:taggable).where('bogus_table.column = 1').to_a
+ tags(:general).taggings.find(:all, :include => :taggable, :references => :bogus_table, :conditions => 'bogus_table.column = 1')
end
end
@@ -419,7 +419,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_eager_load_has_many_through_has_many
- author = Author.where('name = ?', 'David').eager_load(:comments).order('comments.id').first
+ author = Author.find :first, :conditions => ['name = ?', 'David'], :include => :comments, :order => 'comments.id'
SpecialComment.new; VerySpecialComment.new
assert_no_queries do
assert_equal [1,2,3,5,6,7,8,9,10,12], author.comments.collect(&:id)
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
index b879ac9fb8..03d99d19f6 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -505,7 +505,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
def test_nested_has_many_through_with_conditions_on_through_associations_preload_via_joins
# Pointless condition to force single-query loading
assert_includes_and_joins_equal(
- Author.where('tags.id = tags.id'),
+ Author.where('tags.id = tags.id').references(:tags),
[authors(:bob)], :misc_post_first_blue_tags
)
end
@@ -515,7 +515,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
end
def test_nested_has_many_through_with_conditions_on_source_associations_preload
- authors = assert_queries(2) { Author.includes(:misc_post_first_blue_tags_2).to_a.sort_by(&:id) }
+ authors = assert_queries(4) { Author.includes(:misc_post_first_blue_tags_2).to_a.sort_by(&:id) }
blue = tags(:blue)
assert_no_queries do
@@ -526,7 +526,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
def test_nested_has_many_through_with_conditions_on_source_associations_preload_via_joins
# Pointless condition to force single-query loading
assert_includes_and_joins_equal(
- Author.where('tags.id = tags.id'),
+ Author.where('tags.id = tags.id').references(:tags),
[authors(:bob)], :misc_post_first_blue_tags_2
)
end
@@ -560,7 +560,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
actual = assert_queries(1) { query.joins(association).to_a.uniq }
assert_equal expected, actual
- actual = assert_queries(1) { query.eager_load(association).to_a.uniq }
+ actual = assert_queries(1) { query.includes(association).to_a.uniq }
assert_equal expected, actual
end
end
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index df657c1eb4..017905e0ac 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -29,7 +29,7 @@ class AssociationsTest < ActiveRecord::TestCase
molecule.electrons.create(:name => 'electron_1')
molecule.electrons.create(:name => 'electron_2')
- liquids = Liquid.eager_load(:molecules => :electrons).where('molecules.id is not null')
+ liquids = Liquid.includes(:molecules => :electrons).references(:molecules).where('molecules.id is not null')
assert_equal 1, liquids[0].molecules.length
end
@@ -129,6 +129,11 @@ class AssociationsTest < ActiveRecord::TestCase
end
end
+ def test_association_with_references
+ firm = companies(:first_firm)
+ assert_equal ['foo'], firm.association_with_references.scoped.references_values
+ end
+
end
class AssociationProxyTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index 0df9ffc0c5..764305459d 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require 'active_support/core_ext/object/inclusion'
+require 'thread'
module ActiveRecord
module AttributeMethods
@@ -20,6 +21,13 @@ module ActiveRecord
include ActiveRecord::AttributeMethods
+ def self.define_attribute_methods
+ # Created in the inherited/included hook for "proper" ARs
+ @attribute_methods_mutex ||= Mutex.new
+
+ super
+ end
+
def self.column_names
%w{ one two three }
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index b6cf26f978..4078b7eb0b 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -121,6 +121,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert keyboard.respond_to?('id')
end
+ def test_id_before_type_cast_with_custom_primary_key
+ keyboard = Keyboard.create
+ keyboard.key_number = '10'
+ assert_equal '10', keyboard.id_before_type_cast
+ assert_equal nil, keyboard.read_attribute_before_type_cast('id')
+ assert_equal '10', keyboard.read_attribute_before_type_cast('key_number')
+ end
+
# Syck calls respond_to? before actually calling initialize
def test_respond_to_with_allocated_object
topic = Topic.allocate
@@ -258,8 +266,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase
topic.send(:write_attribute, :title, "Still another topic")
assert_equal "Still another topic", topic.title
- topic.send(:write_attribute, "title", "Still another topic: part 2")
+ topic[:title] = "Still another topic: part 2"
assert_equal "Still another topic: part 2", topic.title
+
+ topic.send(:write_attribute, "title", "Still another topic: part 3")
+ assert_equal "Still another topic: part 3", topic.title
+
+ topic["title"] = "Still another topic: part 4"
+ assert_equal "Still another topic: part 4", topic.title
end
def test_read_attribute
@@ -312,6 +326,39 @@ class AttributeMethodsTest < ActiveRecord::TestCase
# puts ""
end
+ def test_overridden_write_attribute
+ topic = Topic.new
+ def topic.write_attribute(attr_name, value)
+ super(attr_name, value.downcase)
+ end
+
+ topic.send(:write_attribute, :title, "Yet another topic")
+ assert_equal "yet another topic", topic.title
+
+ topic[:title] = "Yet another topic: part 2"
+ assert_equal "yet another topic: part 2", topic.title
+
+ topic.send(:write_attribute, "title", "Yet another topic: part 3")
+ assert_equal "yet another topic: part 3", topic.title
+
+ topic["title"] = "Yet another topic: part 4"
+ assert_equal "yet another topic: part 4", topic.title
+ end
+
+ def test_overridden_read_attribute
+ topic = Topic.new
+ topic.title = "Stop changing the topic"
+ def topic.read_attribute(attr_name)
+ super(attr_name).upcase
+ end
+
+ assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, "title")
+ assert_equal "STOP CHANGING THE TOPIC", topic["title"]
+
+ assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, :title)
+ assert_equal "STOP CHANGING THE TOPIC", topic[:title]
+ end
+
def test_read_overridden_attribute
topic = Topic.new(:title => 'a')
def topic.title() 'b' end
@@ -733,11 +780,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase
private
def cached_columns
- @cached_columns ||= time_related_columns_on_topic.map(&:name)
+ Topic.columns.find_all { |column|
+ !Topic.serialized_attributes.include? column.name
+ }.map(&:name)
end
def time_related_columns_on_topic
- Topic.columns.select { |c| c.type.in?([:time, :date, :datetime, :timestamp]) }
+ Topic.columns.select { |c| [:time, :date, :datetime, :timestamp].include?(c.type) }
end
def in_time_zone(zone)
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 80a660717e..698c3d0cb1 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -28,6 +28,13 @@ require 'rexml/document'
require 'active_support/core_ext/exception'
require 'bcrypt'
+class FirstAbstractClass < ActiveRecord::Base
+ self.abstract_class = true
+end
+class SecondAbstractClass < FirstAbstractClass
+ self.abstract_class = true
+end
+class Photo < SecondAbstractClass; end
class Category < ActiveRecord::Base; end
class Categorization < ActiveRecord::Base; end
class Smarts < ActiveRecord::Base; end
@@ -180,6 +187,31 @@ class BasicsTest < ActiveRecord::TestCase
end
end
+ def test_previously_changed
+ topic = Topic.find :first
+ topic.title = '<3<3<3'
+ assert_equal({}, topic.previous_changes)
+
+ topic.save!
+ expected = ["The First Topic", "<3<3<3"]
+ assert_equal(expected, topic.previous_changes['title'])
+ end
+
+ def test_previously_changed_dup
+ topic = Topic.find :first
+ topic.title = '<3<3<3'
+ topic.save!
+
+ t2 = topic.dup
+
+ assert_equal(topic.previous_changes, t2.previous_changes)
+
+ topic.title = "lolwut"
+ topic.save!
+
+ assert_not_equal(topic.previous_changes, t2.previous_changes)
+ end
+
def test_preserving_time_objects
assert_kind_of(
Time, Topic.find(1).bonus_time,
@@ -657,7 +689,7 @@ class BasicsTest < ActiveRecord::TestCase
}
topic = Topic.find(1)
topic.attributes = attributes
- assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
+ assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
end
def test_multiparameter_attributes_on_time_with_no_date
@@ -906,7 +938,7 @@ class BasicsTest < ActiveRecord::TestCase
}
topic = Topic.find(1)
topic.attributes = attributes
- assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time
+ assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
end
def test_boolean
@@ -960,10 +992,9 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "b", duped_topic.title
# test if the attribute values have been duped
- topic.title = {"a" => "b"}
duped_topic = topic.dup
- duped_topic.title["a"] = "c"
- assert_equal "b", topic.title["a"]
+ duped_topic.title.replace "c"
+ assert_equal "a", topic.title
# test if attributes set as part of after_initialize are duped correctly
assert_equal topic.author_email_address, duped_topic.author_email_address
@@ -974,8 +1005,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_not_equal duped_topic.id, topic.id
duped_topic.reload
- # FIXME: I think this is poor behavior, and will fix it with #5686
- assert_equal({'a' => 'c'}.to_yaml, duped_topic.title)
+ assert_equal("c", duped_topic.title)
end
def test_dup_with_aggregate_of_same_name_as_attribute
@@ -1074,7 +1104,10 @@ class BasicsTest < ActiveRecord::TestCase
# TODO: extend defaults tests to other databases!
if current_adapter?(:PostgreSQLAdapter)
def test_default
+ tz = Default.default_timezone
+ Default.default_timezone = :local
default = Default.new
+ Default.default_timezone = tz
# fixed dates / times
assert_equal Date.new(2004, 1, 1), default.fixed_date
@@ -1117,7 +1150,8 @@ class BasicsTest < ActiveRecord::TestCase
# use a geometric function to test for an open path
objs = Geometric.find_by_sql ["select isopen(a_path) from geometrics where id = ?", g.id]
- assert_equal objs[0].isopen, 't'
+
+ assert_equal true, objs[0].isopen
# test alternate formats when defining the geometric types
@@ -1145,7 +1179,8 @@ class BasicsTest < ActiveRecord::TestCase
# use a geometric function to test for an closed path
objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id]
- assert_equal objs[0].isclosed, 't'
+
+ assert_equal true, objs[0].isclosed
end
end
@@ -1246,6 +1281,21 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal(hash, important_topic.content)
end
+ # This test was added to fix GH #4004. Obviously the value returned
+ # is not really the value 'before type cast' so we should maybe think
+ # about changing that in the future.
+ def test_serialized_attribute_before_type_cast_returns_unserialized_value
+ klass = Class.new(ActiveRecord::Base)
+ klass.table_name = "topics"
+ klass.serialize :content, Hash
+
+ t = klass.new(:content => { :foo => :bar })
+ assert_equal({ :foo => :bar }, t.content_before_type_cast)
+ t.save!
+ t.reload
+ assert_equal({ :foo => :bar }, t.content_before_type_cast)
+ end
+
def test_serialized_attribute_declared_in_subclass
hash = { 'important1' => 'value1', 'important2' => 'value2' }
important_topic = ImportantTopic.create("important" => hash)
@@ -1268,11 +1318,24 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal(myobj, topic.content)
end
- def test_nil_serialized_attribute_with_class_constraint
+ def test_nil_serialized_attribute_without_class_constraint
topic = Topic.new
assert_nil topic.content
end
+ def test_nil_not_serialized_without_class_constraint
+ assert Topic.new(:content => nil).save
+ assert_equal 1, Topic.where(:content => nil).count
+ end
+
+ def test_nil_not_serialized_with_class_constraint
+ Topic.serialize :content, Hash
+ assert Topic.new(:content => nil).save
+ assert_equal 1, Topic.where(:content => nil).count
+ ensure
+ Topic.serialize(:content)
+ end
+
def test_should_raise_exception_on_serialized_attribute_with_type_mismatch
myobj = MyObject.new('value1', 'value2')
topic = Topic.new(:content => myobj)
@@ -1416,6 +1479,11 @@ class BasicsTest < ActiveRecord::TestCase
end
end
+ def test_set_table_name_symbol_converted_to_string
+ Joke.table_name = :cold_jokes
+ assert_equal 'cold_jokes', Joke.table_name
+ end
+
def test_quoted_table_name_after_set_table_name
klass = Class.new(ActiveRecord::Base)
@@ -1544,7 +1612,7 @@ class BasicsTest < ActiveRecord::TestCase
Developer.find(:all)
end
assert developers.size >= 2
- for i in 1...developers.size
+ (1...developers.size).each do |i|
assert developers[i-1].salary >= developers[i].salary
end
end
@@ -1861,7 +1929,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal [], NonExistentTable.attribute_names
end
- def test_attribtue_names_on_abstract_class
+ def test_attribute_names_on_abstract_class
assert_equal [], AbstractCompany.attribute_names
end
@@ -1904,4 +1972,32 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal Teapot, OtherTeapot.active_record_super
assert_equal ActiveRecord::Model, CoolTeapot.active_record_super
end
+
+ def test_table_name_with_2_abstract_subclasses
+ assert_equal "photos", Photo.table_name
+ end
+
+ def test_column_types_typecast
+ topic = Topic.first
+ refute_equal 't.lo', topic.author_name
+
+ attrs = topic.attributes.dup
+ attrs.delete 'id'
+
+ typecast = Class.new {
+ def type_cast value
+ "t.lo"
+ end
+ }
+
+ types = { 'author_name' => typecast.new }
+ topic = Topic.allocate.init_with 'attributes' => attrs,
+ 'column_types' => types
+
+ assert_equal 't.lo', topic.author_name
+ end
+
+ def test_typecasting_aliases
+ assert_equal 10, Topic.select('10 as tenderlove').first.tenderlove
+ end
end
diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb
index f97aade311..25d2896ab0 100644
--- a/activerecord/test/cases/binary_test.rb
+++ b/activerecord/test/cases/binary_test.rb
@@ -8,7 +8,7 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter)
require 'models/binary'
class BinaryTest < ActiveRecord::TestCase
- FIXTURES = %w(flowers.jpg example.log)
+ FIXTURES = %w(flowers.jpg example.log test.txt)
def test_mixed_encoding
str = "\x80"
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 3f026e310f..0391319a00 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -49,11 +49,11 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_get_maximum_of_field_with_include
- assert_equal 55, Account.eager_load(:firm).where("companies.name != 'Summit'").maximum(:credit_limit)
+ assert_equal 55, Account.maximum(:credit_limit, :include => :firm, :references => :companies, :conditions => "companies.name != 'Summit'")
end
def test_should_get_maximum_of_field_with_scoped_include
- Account.eager_load(:firm).where("companies.name != 'Summit'").scoping do
+ Account.send :with_scope, :find => { :include => :firm, :references => :companies, :conditions => "companies.name != 'Summit'" } do
assert_equal 55, Account.maximum(:credit_limit)
end
end
@@ -270,7 +270,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_not_modify_options_when_using_includes
- options = {:include => :firm}
+ options = {:conditions => 'companies.id > 1', :include => :firm, :references => :companies}
options_copy = options.dup
Account.count(:all, options)
@@ -334,9 +334,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_perform_joined_include_when_referencing_included_tables
- joined_count = assert_deprecated do
- Account.includes(:firm).where(:companies => {:name => '37signals'}).count
- end
+ joined_count = Account.includes(:firm).where(:companies => {:name => '37signals'}).count
assert_equal 1, joined_count
end
@@ -480,4 +478,14 @@ class CalculationsTest < ActiveRecord::TestCase
def test_pluck_with_qualified_column_name
assert_equal [1,2,3,4], Topic.order(:id).pluck("topics.id")
end
+
+ def test_pluck_auto_table_name_prefix
+ c = Company.create!(:name => "test", :contracts => [Contract.new])
+ assert_equal [c.id], Company.joins(:contracts).pluck(:id)
+ end
+
+ def test_pluck_not_auto_table_name_prefix_if_column_joined
+ Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+ assert_equal [7], Company.joins(:contracts).pluck(:developer_id)
+ end
end
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index 14884e42af..a44b49466f 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -126,17 +126,20 @@ module ActiveRecord
if current_adapter?(:PostgreSQLAdapter)
def test_bigint_column_should_map_to_integer
- bigint_column = PostgreSQLColumn.new('number', nil, "bigint")
+ oid = PostgreSQLAdapter::OID::Identity.new
+ bigint_column = PostgreSQLColumn.new('number', nil, oid, "bigint")
assert_equal :integer, bigint_column.type
end
def test_smallint_column_should_map_to_integer
- smallint_column = PostgreSQLColumn.new('number', nil, "smallint")
+ oid = PostgreSQLAdapter::OID::Identity.new
+ smallint_column = PostgreSQLColumn.new('number', nil, oid, "smallint")
assert_equal :integer, smallint_column.type
end
def test_uuid_column_should_map_to_string
- uuid_column = PostgreSQLColumn.new('unique_id', nil, "uuid")
+ oid = PostgreSQLAdapter::OID::Identity.new
+ uuid_column = PostgreSQLColumn.new('unique_id', nil, oid, "uuid")
assert_equal :string, uuid_column.type
end
end
diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb
new file mode 100644
index 0000000000..ccc57cb876
--- /dev/null
+++ b/activerecord/test/cases/column_test.rb
@@ -0,0 +1,29 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class ColumnTest < ActiveRecord::TestCase
+ def test_type_cast_boolean
+ column = Column.new("field", nil, "boolean")
+ assert column.type_cast(true)
+ assert column.type_cast(1)
+ assert column.type_cast('1')
+ assert column.type_cast('t')
+ assert column.type_cast('T')
+ assert column.type_cast('true')
+ assert column.type_cast('TRUE')
+ assert column.type_cast('on')
+ assert column.type_cast('ON')
+ assert !column.type_cast(false)
+ assert !column.type_cast(0)
+ assert !column.type_cast('0')
+ assert !column.type_cast('f')
+ assert !column.type_cast('F')
+ assert !column.type_cast('false')
+ assert !column.type_cast('FALSE')
+ assert !column.type_cast('off')
+ assert !column.type_cast('OFF')
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index a1d1177289..fe1b40d884 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require "rack"
module ActiveRecord
module ConnectionAdapters
@@ -25,6 +26,27 @@ module ActiveRecord
assert ActiveRecord::Base.connection_handler.active_connections?
end
+ def test_connection_pool_per_pid
+ return skip('must support fork') unless Process.respond_to?(:fork)
+
+ object_id = ActiveRecord::Base.connection.object_id
+
+ rd, wr = IO.pipe
+
+ pid = fork {
+ rd.close
+ wr.write Marshal.dump ActiveRecord::Base.connection.object_id
+ wr.close
+ exit!
+ }
+
+ wr.close
+
+ Process.waitpid pid
+ assert_not_equal object_id, Marshal.load(rd.read)
+ rd.close
+ end
+
def test_app_delegation
manager = ConnectionManagement.new(@app)
@@ -80,9 +102,10 @@ module ActiveRecord
test "proxy is polite to it's body and responds to it" do
body = Class.new(String) { def to_path; "/path"; end }.new
- proxy = ConnectionManagement::Proxy.new(body)
- assert proxy.respond_to?(:to_path)
- assert_equal proxy.to_path, "/path"
+ app = lambda { |_| [200, {}, body] }
+ response_body = ConnectionManagement.new(app).call(@env)[2]
+ assert response_body.respond_to?(:to_path)
+ assert_equal response_body.to_path, "/path"
end
end
end
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 3ed96a3ec8..cd3d19e783 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -6,9 +6,11 @@ require 'models/engine'
require 'models/reply'
require 'models/category'
require 'models/categorization'
+require 'models/dog'
+require 'models/dog_lover'
class CounterCacheTest < ActiveRecord::TestCase
- fixtures :topics, :categories, :categorizations, :cars
+ fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers
class ::SpecialTopic < ::Topic
has_many :special_replies, :foreign_key => 'parent_id'
@@ -61,7 +63,7 @@ class CounterCacheTest < ActiveRecord::TestCase
end
end
- test "reset counter should with belongs_to which has class_name" do
+ test "reset counter with belongs_to which has class_name" do
car = cars(:honda)
assert_nothing_raised do
Car.reset_counters(car.id, :engines)
@@ -71,6 +73,20 @@ class CounterCacheTest < ActiveRecord::TestCase
end
end
+ test "reset the right counter if two have the same class_name" do
+ david = dog_lovers(:david)
+
+ DogLover.increment_counter(:bred_dogs_count, david.id)
+ DogLover.increment_counter(:trained_dogs_count, david.id)
+
+ assert_difference 'david.reload.bred_dogs_count', -1 do
+ DogLover.reset_counters(david.id, :bred_dogs)
+ end
+ assert_difference 'david.reload.trained_dogs_count', -1 do
+ DogLover.reset_counters(david.id, :trained_dogs)
+ end
+ end
+
test "update counter with initial null value" do
category = categories(:general)
assert_equal 2, category.categorizations.count
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index b1ce846218..54e0b40b4f 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -497,6 +497,20 @@ class DirtyTest < ActiveRecord::TestCase
assert !pirate.previous_changes.key?('created_on')
end
+ if ActiveRecord::Base.connection.supports_migrations?
+ class Testings < ActiveRecord::Base; end
+ def test_field_named_field
+ ActiveRecord::Base.connection.create_table :testings do |t|
+ t.string :field
+ end
+ assert_nothing_raised do
+ Testings.new.attributes
+ end
+ ensure
+ ActiveRecord::Base.connection.drop_table :testings rescue nil
+ end
+ end
+
private
def with_partial_updates(klass, on = true)
old = klass.partial_updates?
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
new file mode 100644
index 0000000000..e118add44c
--- /dev/null
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -0,0 +1,48 @@
+require 'cases/helper'
+
+if ActiveRecord::Base.connection.supports_explain?
+ class ExplainSubscriberTest < ActiveRecord::TestCase
+ SUBSCRIBER = ActiveRecord::ExplainSubscriber.new
+
+ def test_collects_nothing_if_available_queries_for_explain_is_nil
+ with_queries(nil) do
+ SUBSCRIBER.call
+ assert_nil Thread.current[:available_queries_for_explain]
+ end
+ end
+
+ def test_collects_nothing_if_the_payload_has_an_exception
+ with_queries([]) do |queries|
+ SUBSCRIBER.call(:exception => Exception.new)
+ assert queries.empty?
+ end
+ end
+
+ def test_collects_nothing_for_ignored_payloads
+ with_queries([]) do |queries|
+ ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip|
+ SUBSCRIBER.call(:name => ip)
+ end
+ assert queries.empty?
+ end
+ end
+
+ def test_collects_pairs_of_queries_and_binds
+ sql = 'select 1 from users'
+ binds = [1, 2]
+ with_queries([]) do |queries|
+ SUBSCRIBER.call(:name => 'SQL', :sql => sql, :binds => binds)
+ assert_equal 1, queries.size
+ assert_equal sql, queries[0][0]
+ assert_equal binds, queries[0][1]
+ end
+ end
+
+ def with_queries(queries)
+ Thread.current[:available_queries_for_explain] = queries
+ yield queries
+ ensure
+ Thread.current[:available_queries_for_explain] = nil
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index 6ae6f83446..83c9b6e107 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -14,7 +14,7 @@ if ActiveRecord::Base.connection.supports_explain?
base.connection
end
- def test_logging_query_plan
+ def test_logging_query_plan_with_logger
base.logger.expects(:warn).with do |message|
message.starts_with?('EXPLAIN for:')
end
@@ -24,6 +24,20 @@ if ActiveRecord::Base.connection.supports_explain?
end
end
+ def test_logging_query_plan_without_logger
+ original = base.logger
+ base.logger = nil
+
+ base.logger.expects(:warn).never
+
+ with_threshold(0) do
+ car = Car.where(:name => 'honda').first
+ assert_equal 'honda', car.name
+ end
+ ensure
+ base.logger = original
+ end
+
def test_collect_queries_for_explain
base.auto_explain_threshold_in_seconds = nil
queries = Thread.current[:available_queries_for_explain] = []
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 5b882f966b..76c041397a 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -881,6 +881,17 @@ class FinderTest < ActiveRecord::TestCase
assert_equal 23, sig38.client_of
end
+ def test_find_or_create_from_two_attributes_and_hash
+ number_of_companies = Company.count
+ sig38 = Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert sig38.persisted?
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ assert_equal 23, sig38.client_of
+ end
+
def test_find_or_create_from_one_aggregate_attribute
number_of_customers = Customer.count
created_customer = Customer.find_or_create_by_balance(Money.new(123))
@@ -1119,10 +1130,10 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
- assert_equal 2, Post.find(:all, :eager_load => { :authors => :author_address }, :order => ' author_addresses.id DESC ', :limit => 2).size
+ assert_equal 2, Post.find(:all, :include => { :authors => :author_address }, :order => 'author_addresses.id DESC ', :limit => 2).size
- assert_equal 3, Post.find(:all, :eager_load => { :author => :author_address, :authors => :author_address},
- :order => ' author_addresses_authors.id DESC ', :limit => 3).size
+ assert_equal 3, Post.find(:all, :include => { :author => :author_address, :authors => :author_address},
+ :order => 'author_addresses_authors.id DESC ', :limit => 3).size
end
def test_find_with_nil_inside_set_passed_for_one_attribute
@@ -1149,7 +1160,10 @@ class FinderTest < ActiveRecord::TestCase
end
def test_with_limiting_with_custom_select
- posts = Post.find(:all, :eager_load => :author, :select => ' posts.*, authors.id as "author_id"', :limit => 3, :order => 'posts.id')
+ posts = Post.find(
+ :all, :include => :author, :select => ' posts.*, authors.id as "author_id"',
+ :references => :authors, :limit => 3, :order => 'posts.id'
+ )
assert_equal 3, posts.size
assert_equal [0, 1, 1], posts.map(&:author_id).sort
end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 5c67cbfcce..562b370c97 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -782,7 +782,7 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase
end
def test_table_name_is_defined_in_the_model
- assert_equal :randomly_named_table, ActiveRecord::Fixtures::all_loaded_fixtures["admin/randomly_named_a9"].table_name
- assert_equal :randomly_named_table, Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name
+ assert_equal 'randomly_named_table', ActiveRecord::Fixtures::all_loaded_fixtures["admin/randomly_named_a9"].table_name
+ assert_equal 'randomly_named_table', Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name
end
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index d16cccdaea..9f5f012073 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -6,8 +6,8 @@ require 'minitest/autorun'
require 'stringio'
require 'mocha'
+require 'cases/test_case'
require 'active_record'
-require 'active_record/test_case'
require 'active_support/dependencies'
require 'active_support/logger'
@@ -22,6 +22,9 @@ ActiveSupport::Deprecation.debug = true
# Enable Identity Map only when ENV['IM'] is set to "true"
ActiveRecord::IdentityMap.enabled = (ENV['IM'] == "true")
+# Avoid deprecation warning setting dependent_restrict_raises to false. The default is true
+ActiveRecord::Base.dependent_restrict_raises = false
+
# Connect to the database
ARTest.connect
@@ -58,37 +61,6 @@ ensure
ActiveRecord::Base.default_timezone = old_zone
end
-module ActiveRecord
- class SQLCounter
- cattr_accessor :ignored_sql
- self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/]
-
- # FIXME: this needs to be refactored so specific database can add their own
- # ignored SQL. This ignored SQL is for Oracle.
- ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
-
- cattr_accessor :log
- self.log = []
-
- attr_reader :ignore
-
- def initialize(ignore = Regexp.union(self.class.ignored_sql))
- @ignore = ignore
- end
-
- def call(name, start, finish, message_id, values)
- sql = values[:sql]
-
- # FIXME: this seems bad. we should probably have a better way to indicate
- # the query was cached
- return if 'CACHE' == values[:name] || ignore =~ sql
- self.class.log << sql
- end
- end
-
- ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
-end
-
unless ENV['FIXTURE_DEBUG']
module ActiveRecord::TestFixtures::ClassMethods
def try_to_load_dependency_with_silence(*args)
diff --git a/activerecord/test/cases/identity_map/middleware_test.rb b/activerecord/test/cases/identity_map/middleware_test.rb
index 60dcad4586..5b32a1c6d2 100644
--- a/activerecord/test/cases/identity_map/middleware_test.rb
+++ b/activerecord/test/cases/identity_map/middleware_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require "rack"
module ActiveRecord
module IdentityMap
@@ -19,6 +20,7 @@ module ActiveRecord
called = false
mw = Middleware.new lambda { |env|
called = true
+ [200, {}, nil]
}
mw.call({})
assert called, 'middleware delegated'
@@ -27,6 +29,7 @@ module ActiveRecord
def test_im_enabled_during_delegation
mw = Middleware.new lambda { |env|
assert IdentityMap.enabled?, 'identity map should be enabled'
+ [200, {}, nil]
}
mw.call({})
end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 3ae7b63dff..8f1cdd47ea 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -88,5 +88,17 @@ module ActiveRecord
LegacyMigration.down
assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"
end
+
+ def test_migrate_down_with_table_name_prefix
+ ActiveRecord::Base.table_name_prefix = 'p_'
+ ActiveRecord::Base.table_name_suffix = '_s'
+ migration = InvertibleMigration.new
+ migration.migrate(:up)
+ assert_nothing_raised { migration.migrate(:down) }
+ assert !ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist"
+ ensure
+ ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = ''
+ end
+
end
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 0c458d5318..807274ca67 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -388,6 +388,26 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db?
end
end
+ def test_with_lock_commits_transaction
+ person = Person.find 1
+ person.with_lock do
+ person.first_name = 'fooman'
+ person.save!
+ end
+ assert_equal 'fooman', person.reload.first_name
+ end
+
+ def test_with_lock_rolls_back_transaction
+ person = Person.find 1
+ old = person.first_name
+ person.with_lock do
+ person.first_name = 'fooman'
+ person.save!
+ raise 'oops'
+ end rescue nil
+ assert_equal old, person.reload.first_name
+ end
+
if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
def test_no_locks_no_wait
first, second = duel { Person.find 1 }
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 0f0621f2e0..ebf6e26385 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -103,8 +103,8 @@ class MethodScopingTest < ActiveRecord::TestCase
def test_scoped_find_include
# with the include, will retrieve only developers for the given project
- scoped_developers = Developer.send(:with_scope, :find => { :eager_load => :projects }) do
- Developer.find(:all, :conditions => 'projects.id = 2')
+ scoped_developers = Developer.send(:with_scope, :find => { :include => :projects }) do
+ Developer.find(:all, :conditions => { 'projects.id' => 2 })
end
assert scoped_developers.include?(developers(:david))
assert !scoped_developers.include?(developers(:jamis))
@@ -203,8 +203,8 @@ class MethodScopingTest < ActiveRecord::TestCase
def test_scoped_count_include
# with the include, will retrieve only developers for the given project
- Developer.send(:with_scope, :find => { :eager_load => :projects }) do
- assert_equal 1, Developer.count(:conditions => 'projects.id = 2')
+ Developer.send(:with_scope, :find => { :include => :projects }) do
+ assert_equal 1, Developer.count(:conditions => { 'projects.id' => 2 })
end
end
@@ -268,7 +268,7 @@ class MethodScopingTest < ActiveRecord::TestCase
end
class NestedScopingTest < ActiveRecord::TestCase
- fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
+ fixtures :authors, :developers, :projects, :comments, :posts
def test_merge_options
Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do
@@ -338,8 +338,8 @@ class NestedScopingTest < ActiveRecord::TestCase
end
def test_nested_scoped_find_include
- Developer.send(:with_scope, :find => { :eager_load => :projects }) do
- Developer.send(:with_scope, :find => { :conditions => "projects.id = 2" }) do
+ Developer.send(:with_scope, :find => { :include => :projects }) do
+ Developer.send(:with_scope, :find => { :conditions => { 'projects.id' => 2 } }) do
assert_nothing_raised { Developer.find(1) }
assert_equal('David', Developer.find(:first).name)
end
@@ -348,49 +348,25 @@ class NestedScopingTest < ActiveRecord::TestCase
def test_nested_scoped_find_merged_include
# :include's remain unique and don't "double up" when merging
- Developer.send(:with_scope, :find => { :include => :projects, :conditions => "projects.id = 2" }) do
+ Developer.send(:with_scope, :find => { :include => :projects, :conditions => { 'projects.id' => 2 } }) do
Developer.send(:with_scope, :find => { :include => :projects }) do
- assert_deprecated do
- assert_equal 1, Developer.scoped.includes_values.uniq.length
- assert_equal 'David', Developer.find(:first).name
- end
+ assert_equal 1, Developer.scoped.includes_values.uniq.length
+ assert_equal 'David', Developer.find(:first).name
end
end
# the nested scope doesn't remove the first :include
- Developer.send(:with_scope, :find => { :include => :projects, :conditions => "projects.id = 2" }) do
+ Developer.send(:with_scope, :find => { :include => :projects, :conditions => { 'projects.id' => 2 } }) do
Developer.send(:with_scope, :find => { :include => [] }) do
- assert_deprecated do
- assert_equal 1, Developer.scoped.includes_values.uniq.length
- assert_equal('David', Developer.find(:first).name)
- end
+ assert_equal 1, Developer.scoped.includes_values.uniq.length
+ assert_equal('David', Developer.find(:first).name)
end
end
# mixing array and symbol include's will merge correctly
- Developer.send(:with_scope, :find => { :include => [:projects], :conditions => "projects.id = 2" }) do
+ Developer.send(:with_scope, :find => { :include => [:projects], :conditions => { 'projects.id' => 2 } }) do
Developer.send(:with_scope, :find => { :include => :projects }) do
- assert_deprecated do
- assert_equal 1, Developer.scoped.includes_values.uniq.length
- assert_equal('David', Developer.find(:first).name)
- end
- end
- end
- end
-
- def test_nested_scoped_find_merged_eager_load
- # :include's remain unique and don't "double up" when merging
- Developer.send(:with_scope, :find => { :eager_load => :projects, :conditions => "projects.id = 2" }) do
- Developer.send(:with_scope, :find => { :eager_load => :projects }) do
- assert_equal 1, Developer.scoped.eager_load_values.uniq.length
- assert_equal 'David', Developer.find(:first).name
- end
- end
-
- # mixing array and symbol include's will merge correctly
- Developer.send(:with_scope, :find => { :eager_load => [:projects], :conditions => "projects.id = 2" }) do
- Developer.send(:with_scope, :find => { :eager_load => :projects }) do
- assert_equal 1, Developer.scoped.eager_load_values.uniq.length
+ assert_equal 1, Developer.scoped.includes_values.uniq.length
assert_equal('David', Developer.find(:first).name)
end
end
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
new file mode 100644
index 0000000000..f0b1f74bd3
--- /dev/null
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -0,0 +1,328 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class ChangeSchemaTest < ActiveRecord::TestCase
+ attr_reader :connection, :table_name
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ @table_name = :testings
+ end
+
+ def teardown
+ super
+ connection.drop_table :testings rescue nil
+ ActiveRecord::Base.primary_key_prefix_type = nil
+ end
+
+ def test_create_table_without_id
+ testing_table_with_only_foo_attribute do
+ assert_equal connection.columns(:testings).size, 1
+ end
+ end
+
+ def test_add_column_with_primary_key_attribute
+ testing_table_with_only_foo_attribute do
+ connection.add_column :testings, :id, :primary_key
+ assert_equal connection.columns(:testings).size, 2
+ end
+ end
+
+ def test_create_table_adds_id
+ connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+
+ assert_equal %w(foo id), connection.columns(:testings).map(&:name).sort
+ end
+
+ def test_create_table_with_not_null_column
+ connection.create_table :testings do |t|
+ t.column :foo, :string, :null => false
+ end
+
+ assert_raises(ActiveRecord::StatementInvalid) do
+ connection.execute "insert into testings (foo) values (NULL)"
+ end
+ end
+
+ def test_create_table_with_defaults
+ # MySQL doesn't allow defaults on TEXT or BLOB columns.
+ mysql = current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter)
+
+ connection.create_table :testings do |t|
+ t.column :one, :string, :default => "hello"
+ t.column :two, :boolean, :default => true
+ t.column :three, :boolean, :default => false
+ t.column :four, :integer, :default => 1
+ t.column :five, :text, :default => "hello" unless mysql
+ end
+
+ columns = connection.columns(:testings)
+ one = columns.detect { |c| c.name == "one" }
+ two = columns.detect { |c| c.name == "two" }
+ three = columns.detect { |c| c.name == "three" }
+ four = columns.detect { |c| c.name == "four" }
+ five = columns.detect { |c| c.name == "five" } unless mysql
+
+ assert_equal "hello", one.default
+ assert_equal true, two.default
+ assert_equal false, three.default
+ assert_equal 1, four.default
+ assert_equal "hello", five.default unless mysql
+ end
+
+ def test_create_table_with_limits
+ connection.create_table :testings do |t|
+ t.column :foo, :string, :limit => 255
+
+ t.column :default_int, :integer
+
+ t.column :one_int, :integer, :limit => 1
+ t.column :four_int, :integer, :limit => 4
+ t.column :eight_int, :integer, :limit => 8
+ t.column :eleven_int, :integer, :limit => 11
+ end
+
+ columns = connection.columns(:testings)
+ foo = columns.detect { |c| c.name == "foo" }
+ assert_equal 255, foo.limit
+
+ default = columns.detect { |c| c.name == "default_int" }
+ one = columns.detect { |c| c.name == "one_int" }
+ four = columns.detect { |c| c.name == "four_int" }
+ eight = columns.detect { |c| c.name == "eight_int" }
+ eleven = columns.detect { |c| c.name == "eleven_int" }
+
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_equal 'integer', default.sql_type
+ assert_equal 'smallint', one.sql_type
+ assert_equal 'integer', four.sql_type
+ assert_equal 'bigint', eight.sql_type
+ assert_equal 'integer', eleven.sql_type
+ elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ assert_match 'int(11)', default.sql_type
+ assert_match 'tinyint', one.sql_type
+ assert_match 'int', four.sql_type
+ assert_match 'bigint', eight.sql_type
+ assert_match 'int(11)', eleven.sql_type
+ elsif current_adapter?(:OracleAdapter)
+ assert_equal 'NUMBER(38)', default.sql_type
+ assert_equal 'NUMBER(1)', one.sql_type
+ assert_equal 'NUMBER(4)', four.sql_type
+ assert_equal 'NUMBER(8)', eight.sql_type
+ end
+ end
+
+ def test_create_table_with_primary_key_prefix_as_table_name_with_underscore
+ ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
+
+ connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+
+ assert_equal %w(foo testing_id), connection.columns(:testings).map(&:name).sort
+ end
+
+ def test_create_table_with_primary_key_prefix_as_table_name
+ ActiveRecord::Base.primary_key_prefix_type = :table_name
+
+ connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+
+ assert_equal %w(foo testingid), connection.columns(:testings).map(&:name).sort
+ end
+
+ def test_create_table_with_timestamps_should_create_datetime_columns
+ connection.create_table table_name do |t|
+ t.timestamps
+ end
+ created_columns = connection.columns(table_name)
+
+ created_at_column = created_columns.detect {|c| c.name == 'created_at' }
+ updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
+
+ assert !created_at_column.null
+ assert !updated_at_column.null
+ end
+
+ def test_create_table_with_timestamps_should_create_datetime_columns_with_options
+ connection.create_table table_name do |t|
+ t.timestamps :null => false
+ end
+ created_columns = connection.columns(table_name)
+
+ created_at_column = created_columns.detect {|c| c.name == 'created_at' }
+ updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
+
+ assert !created_at_column.null
+ assert !updated_at_column.null
+ end
+
+ def test_create_table_without_a_block
+ connection.create_table table_name
+ end
+
+ def test_add_column_not_null_without_default
+ # Sybase, and SQLite3 will not allow you to add a NOT NULL
+ # column to a table without a default value.
+ if current_adapter?(:SybaseAdapter, :SQLite3Adapter)
+ skip "not supported on #{connection.class}"
+ end
+
+ connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+ connection.add_column :testings, :bar, :string, :null => false
+
+ assert_raise(ActiveRecord::StatementInvalid) do
+ connection.execute "insert into testings (foo, bar) values ('hello', NULL)"
+ end
+ end
+
+ def test_add_column_not_null_with_default
+ connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+
+ con = connection
+ connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter)
+ connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')"
+ connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter)
+ assert_nothing_raised {connection.add_column :testings, :bar, :string, :null => false, :default => "default" }
+
+ assert_raises(ActiveRecord::StatementInvalid) do
+ unless current_adapter?(:OpenBaseAdapter)
+ connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)"
+ else
+ connection.insert("INSERT INTO testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) VALUES (2, 'hello', NULL)",
+ "Testing Insert","id",2)
+ end
+ end
+ end
+
+ def test_change_column_quotes_column_names
+ connection.create_table :testings do |t|
+ t.column :select, :string
+ end
+
+ connection.change_column :testings, :select, :string, :limit => 10
+
+ # Oracle needs primary key value from sequence
+ if current_adapter?(:OracleAdapter)
+ connection.execute "insert into testings (id, #{connection.quote_column_name('select')}) values (testings_seq.nextval, '7 chars')"
+ else
+ connection.execute "insert into testings (#{connection.quote_column_name('select')}) values ('7 chars')"
+ end
+ end
+
+ def test_keeping_default_and_notnull_constaint_on_change
+ connection.create_table :testings do |t|
+ t.column :title, :string
+ end
+ person_klass = Class.new(ActiveRecord::Base)
+ person_klass.table_name = 'testings'
+
+ person_klass.connection.add_column "testings", "wealth", :integer, :null => false, :default => 99
+ person_klass.reset_column_information
+ assert_equal 99, person_klass.columns_hash["wealth"].default
+ assert_equal false, person_klass.columns_hash["wealth"].null
+ # Oracle needs primary key value from sequence
+ if current_adapter?(:OracleAdapter)
+ assert_nothing_raised {person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')")}
+ else
+ assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")}
+ end
+
+ # change column default to see that column doesn't lose its not null definition
+ person_klass.connection.change_column_default "testings", "wealth", 100
+ person_klass.reset_column_information
+ assert_equal 100, person_klass.columns_hash["wealth"].default
+ assert_equal false, person_klass.columns_hash["wealth"].null
+
+ # rename column to see that column doesn't lose its not null and/or default definition
+ person_klass.connection.rename_column "testings", "wealth", "money"
+ person_klass.reset_column_information
+ assert_nil person_klass.columns_hash["wealth"]
+ assert_equal 100, person_klass.columns_hash["money"].default
+ assert_equal false, person_klass.columns_hash["money"].null
+
+ # change column
+ person_klass.connection.change_column "testings", "money", :integer, :null => false, :default => 1000
+ person_klass.reset_column_information
+ assert_equal 1000, person_klass.columns_hash["money"].default
+ assert_equal false, person_klass.columns_hash["money"].null
+
+ # change column, make it nullable and clear default
+ person_klass.connection.change_column "testings", "money", :integer, :null => true, :default => nil
+ person_klass.reset_column_information
+ assert_nil person_klass.columns_hash["money"].default
+ assert_equal true, person_klass.columns_hash["money"].null
+
+ # change_column_null, make it not nullable and set null values to a default value
+ person_klass.connection.execute('UPDATE testings SET money = NULL')
+ person_klass.connection.change_column_null "testings", "money", false, 2000
+ person_klass.reset_column_information
+ assert_nil person_klass.columns_hash["money"].default
+ assert_equal false, person_klass.columns_hash["money"].null
+ assert_equal 2000, connection.select_values("SELECT money FROM testings").first.to_i
+ end
+
+ def test_column_exists
+ connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+
+ assert connection.column_exists?(:testings, :foo)
+ refute connection.column_exists?(:testings, :bar)
+ end
+
+ def test_column_exists_with_type
+ connection.create_table :testings do |t|
+ t.column :foo, :string
+ t.column :bar, :decimal, :precision => 8, :scale => 2
+ end
+
+ assert connection.column_exists?(:testings, :foo, :string)
+ refute connection.column_exists?(:testings, :foo, :integer)
+
+ assert connection.column_exists?(:testings, :bar, :decimal)
+ refute connection.column_exists?(:testings, :bar, :integer)
+ end
+
+ def test_column_exists_with_definition
+ connection.create_table :testings do |t|
+ t.column :foo, :string, :limit => 100
+ t.column :bar, :decimal, :precision => 8, :scale => 2
+ end
+
+ assert connection.column_exists?(:testings, :foo, :string, :limit => 100)
+ refute connection.column_exists?(:testings, :foo, :string, :limit => 50)
+ assert connection.column_exists?(:testings, :bar, :decimal, :precision => 8, :scale => 2)
+ refute connection.column_exists?(:testings, :bar, :decimal, :precision => 10, :scale => 2)
+ end
+
+ def test_column_exists_on_table_with_no_options_parameter_supplied
+ connection.create_table :testings do |t|
+ t.string :foo
+ end
+ connection.change_table :testings do |t|
+ assert t.column_exists?(:foo)
+ assert !(t.column_exists?(:bar))
+ end
+ end
+
+ private
+ def testing_table_with_only_foo_attribute
+ connection.create_table :testings, :id => false do |t|
+ t.column :foo, :string
+ end
+
+ yield
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
new file mode 100644
index 0000000000..040445ef12
--- /dev/null
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -0,0 +1,188 @@
+require "cases/migration/helper"
+
+module ActiveRecord
+ class Migration
+ class ColumnAttributesTest < ActiveRecord::TestCase
+ include ActiveRecord::Migration::TestHelper
+
+ self.use_transactional_fixtures = false
+
+ def test_add_remove_single_field_using_string_arguments
+ refute TestModel.column_methods_hash.key?(:last_name)
+
+ add_column 'test_models', 'last_name', :string
+
+ TestModel.reset_column_information
+
+ assert TestModel.column_methods_hash.key?(:last_name)
+
+ remove_column 'test_models', 'last_name'
+
+ TestModel.reset_column_information
+ refute TestModel.column_methods_hash.key?(:last_name)
+ end
+
+ def test_add_remove_single_field_using_symbol_arguments
+ refute TestModel.column_methods_hash.key?(:last_name)
+
+ add_column :test_models, :last_name, :string
+
+ TestModel.reset_column_information
+ assert TestModel.column_methods_hash.key?(:last_name)
+
+ remove_column :test_models, :last_name
+
+ TestModel.reset_column_information
+ refute TestModel.column_methods_hash.key?(:last_name)
+ end
+
+ def test_unabstracted_database_dependent_types
+ skip "not supported" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+
+ add_column :test_models, :intelligence_quotient, :tinyint
+ TestModel.reset_column_information
+ assert_match(/tinyint/, TestModel.columns_hash['intelligence_quotient'].sql_type)
+ end
+
+ # We specifically do a manual INSERT here, and then test only the SELECT
+ # functionality. This allows us to more easily catch INSERT being broken,
+ # but SELECT actually working fine.
+ def test_native_decimal_insert_manual_vs_automatic
+ correct_value = '0012345678901234567890.0123456789'.to_d
+
+ connection.add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10'
+
+ # Do a manual insertion
+ if current_adapter?(:OracleAdapter)
+ connection.execute "insert into test_models (id, wealth, created_at, updated_at) values (people_seq.nextval, 12345678901234567890.0123456789, sysdate, sysdate)"
+ elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings
+ connection.execute "insert into test_models (wealth, created_at, updated_at) values ('12345678901234567890.0123456789', 0, 0)"
+ elsif current_adapter?(:PostgreSQLAdapter)
+ connection.execute "insert into test_models (wealth, created_at, updated_at) values (12345678901234567890.0123456789, now(), now())"
+ else
+ connection.execute "insert into test_models (wealth, created_at, updated_at) values (12345678901234567890.0123456789, 0, 0)"
+ end
+
+ # SELECT
+ row = TestModel.find(:first)
+ assert_kind_of BigDecimal, row.wealth
+
+ # If this assert fails, that means the SELECT is broken!
+ unless current_adapter?(:SQLite3Adapter)
+ assert_equal correct_value, row.wealth
+ end
+
+ # Reset to old state
+ TestModel.delete_all
+
+ # Now use the Rails insertion
+ TestModel.create :wealth => BigDecimal.new("12345678901234567890.0123456789")
+
+ # SELECT
+ row = TestModel.find(:first)
+ assert_kind_of BigDecimal, row.wealth
+
+ # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken!
+ unless current_adapter?(:SQLite3Adapter)
+ assert_equal correct_value, row.wealth
+ end
+ end
+
+ def test_add_column_with_precision_and_scale
+ connection.add_column 'test_models', 'wealth', :decimal, :precision => 9, :scale => 7
+
+ wealth_column = TestModel.columns_hash['wealth']
+ assert_equal 9, wealth_column.precision
+ assert_equal 7, wealth_column.scale
+ end
+
+ def test_change_column_preserve_other_column_precision_and_scale
+ skip "only on sqlite3" unless current_adapter?(:SQLite3Adapter)
+
+ connection.add_column 'test_models', 'last_name', :string
+ connection.add_column 'test_models', 'wealth', :decimal, :precision => 9, :scale => 7
+
+ wealth_column = TestModel.columns_hash['wealth']
+ assert_equal 9, wealth_column.precision
+ assert_equal 7, wealth_column.scale
+
+ connection.change_column 'test_models', 'last_name', :string, :null => false
+ TestModel.reset_column_information
+
+ wealth_column = TestModel.columns_hash['wealth']
+ assert_equal 9, wealth_column.precision
+ assert_equal 7, wealth_column.scale
+ end
+
+ def test_native_types
+ add_column "test_models", "first_name", :string
+ add_column "test_models", "last_name", :string
+ add_column "test_models", "bio", :text
+ add_column "test_models", "age", :integer
+ add_column "test_models", "height", :float
+ add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10'
+ add_column "test_models", "birthday", :datetime
+ add_column "test_models", "favorite_day", :date
+ add_column "test_models", "moment_of_truth", :datetime
+ add_column "test_models", "male", :boolean
+
+ TestModel.create :first_name => 'bob', :last_name => 'bobsen',
+ :bio => "I was born ....", :age => 18, :height => 1.78,
+ :wealth => BigDecimal.new("12345678901234567890.0123456789"),
+ :birthday => 18.years.ago, :favorite_day => 10.days.ago,
+ :moment_of_truth => "1782-10-10 21:40:18", :male => true
+
+ bob = TestModel.find(:first)
+ assert_equal 'bob', bob.first_name
+ assert_equal 'bobsen', bob.last_name
+ assert_equal "I was born ....", bob.bio
+ assert_equal 18, bob.age
+
+ # Test for 30 significant digits (beyond the 16 of float), 10 of them
+ # after the decimal place.
+
+ unless current_adapter?(:SQLite3Adapter)
+ assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth
+ end
+
+ assert_equal true, bob.male?
+
+ assert_equal String, bob.first_name.class
+ assert_equal String, bob.last_name.class
+ assert_equal String, bob.bio.class
+ assert_equal Fixnum, bob.age.class
+ assert_equal Time, bob.birthday.class
+
+ if current_adapter?(:OracleAdapter, :SybaseAdapter)
+ # Sybase, and Oracle don't differentiate between date/time
+ assert_equal Time, bob.favorite_day.class
+ else
+ assert_equal Date, bob.favorite_day.class
+ end
+
+ # Oracle adapter stores Time or DateTime with timezone value already in _before_type_cast column
+ # therefore no timezone change is done afterwards when default timezone is changed
+ unless current_adapter?(:OracleAdapter)
+ # Test DateTime column and defaults, including timezone.
+ # FIXME: moment of truth may be Time on 64-bit platforms.
+ if bob.moment_of_truth.is_a?(DateTime)
+
+ with_env_tz 'US/Eastern' do
+ bob.reload
+ assert_equal DateTime.local_offset, bob.moment_of_truth.offset
+ assert_not_equal 0, bob.moment_of_truth.offset
+ assert_not_equal "Z", bob.moment_of_truth.zone
+ # US/Eastern is -5 hours from GMT
+ assert_equal Rational(-5, 24), bob.moment_of_truth.offset
+ assert_match(/\A-05:?00\Z/, bob.moment_of_truth.zone) #ruby 1.8.6 uses HH:MM, prior versions use HHMM
+ assert_equal DateTime::ITALY, bob.moment_of_truth.start
+ end
+ end
+ end
+
+ assert_instance_of TrueClass, bob.male?
+ assert_kind_of BigDecimal, bob.wealth
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb
new file mode 100644
index 0000000000..913d935f7c
--- /dev/null
+++ b/activerecord/test/cases/migration/column_positioning_test.rb
@@ -0,0 +1,60 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class ColumnPositioningTest < ActiveRecord::TestCase
+ attr_reader :connection, :table_name
+ alias :conn :connection
+
+ def setup
+ super
+
+ unless current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ skip "not supported on #{connection.class}"
+ end
+
+ @connection = ActiveRecord::Base.connection
+
+ connection.create_table :testings, :id => false do |t|
+ t.column :first, :integer
+ t.column :second, :integer
+ t.column :third, :integer
+ end
+ end
+
+ def teardown
+ super
+ connection.drop_table :testings rescue nil
+ ActiveRecord::Base.primary_key_prefix_type = nil
+ end
+
+ def test_column_positioning
+ assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name }
+ end
+
+ def test_add_column_with_positioning
+ conn.add_column :testings, :new_col, :integer
+ assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name }
+ end
+
+ def test_add_column_with_positioning_first
+ conn.add_column :testings, :new_col, :integer, :first => true
+ assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name }
+ end
+
+ def test_add_column_with_positioning_after
+ conn.add_column :testings, :new_col, :integer, :after => :first
+ assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name }
+ end
+
+ def test_change_column_with_positioning
+ conn.change_column :testings, :second, :integer, :first => true
+ assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name }
+
+ conn.change_column :testings, :second, :integer, :after => :third
+ assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name }
+ end
+
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 8f136bce2b..7d026961be 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -73,6 +73,18 @@ module ActiveRecord
assert_equal [:drop_table, [:people_reminders]], drop_table
end
+ def test_invert_create_join_table
+ @recorder.record :create_join_table, [:musics, :artists]
+ drop_table = @recorder.inverse.first
+ assert_equal [:drop_table, [:artists_musics]], drop_table
+ end
+
+ def test_invert_create_join_table_with_table_name
+ @recorder.record :create_join_table, [:musics, :artists, {:table_name => :catalog}]
+ drop_table = @recorder.inverse.first
+ assert_equal [:drop_table, [:catalog]], drop_table
+ end
+
def test_invert_rename_table
@recorder.record :rename_table, [:old, :new]
rename = @recorder.inverse.first
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
new file mode 100644
index 0000000000..0428d9ba76
--- /dev/null
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -0,0 +1,70 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class CreateJoinTableTest < ActiveRecord::TestCase
+ attr_reader :connection
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_create_join_table
+ connection.create_join_table :artists, :musics
+
+ assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort
+ ensure
+ connection.drop_table :artists_musics
+ end
+
+ def test_create_join_table_set_not_null_by_default
+ connection.create_join_table :artists, :musics
+
+ assert_equal [false, false], connection.columns(:artists_musics).map(&:null)
+ ensure
+ connection.drop_table :artists_musics
+ end
+
+ def test_create_join_table_with_strings
+ connection.create_join_table 'artists', 'musics'
+
+ assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort
+ ensure
+ connection.drop_table :artists_musics
+ end
+
+ def test_create_join_table_with_the_proper_order
+ connection.create_join_table :videos, :musics
+
+ assert_equal %w(music_id video_id), connection.columns(:musics_videos).map(&:name).sort
+ ensure
+ connection.drop_table :musics_videos
+ end
+
+ def test_create_join_table_with_the_table_name
+ connection.create_join_table :artists, :musics, :table_name => :catalog
+
+ assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort
+ ensure
+ connection.drop_table :catalog
+ end
+
+ def test_create_join_table_with_the_table_name_as_string
+ connection.create_join_table :artists, :musics, :table_name => 'catalog'
+
+ assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort
+ ensure
+ connection.drop_table :catalog
+ end
+
+ def test_create_join_table_with_column_options
+ connection.create_join_table :artists, :musics, :column_options => {:null => true}
+
+ assert_equal [true, true], connection.columns(:artists_musics).map(&:null)
+ ensure
+ connection.drop_table :artists_musics
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb
new file mode 100644
index 0000000000..fe53510ba2
--- /dev/null
+++ b/activerecord/test/cases/migration/helper.rb
@@ -0,0 +1,64 @@
+require "cases/helper"
+
+module ActiveRecord
+ class Migration
+ class << self
+ attr_accessor :message_count
+ end
+
+ def puts(text="")
+ ActiveRecord::Migration.message_count ||= 0
+ ActiveRecord::Migration.message_count += 1
+ end
+
+ module TestHelper
+ attr_reader :connection, :table_name
+
+ class TestModel < ActiveRecord::Base
+ self.table_name = 'test_models'
+ end
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ connection.create_table :test_models do |t|
+ t.timestamps
+ end
+
+ TestModel.reset_column_information
+ end
+
+ def teardown
+ super
+ TestModel.reset_table_name
+ TestModel.reset_sequence_name
+ connection.drop_table :test_models rescue nil
+ end
+
+ private
+ def add_column(*args)
+ connection.add_column(*args)
+ end
+
+ def remove_column(*args)
+ connection.remove_column(*args)
+ end
+
+ def rename_column(*args)
+ connection.rename_column(*args)
+ end
+
+ def add_index(*args)
+ connection.add_index(*args)
+ end
+
+ def change_column(*args)
+ connection.change_column(*args)
+ end
+
+ def rename_table(*args)
+ connection.rename_table(*args)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
new file mode 100644
index 0000000000..dd9492924c
--- /dev/null
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -0,0 +1,185 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class IndexTest < ActiveRecord::TestCase
+ attr_reader :connection, :table_name
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ @table_name = :testings
+
+ connection.create_table table_name do |t|
+ t.column :foo, :string, :limit => 100
+ t.column :bar, :string, :limit => 100
+
+ t.string :first_name
+ t.string :last_name, :limit => 100
+ t.string :key, :limit => 100
+ t.boolean :administrator
+ end
+ end
+
+ def teardown
+ super
+ connection.drop_table :testings rescue nil
+ ActiveRecord::Base.primary_key_prefix_type = nil
+ end
+
+ def test_rename_index
+ skip "not supported on openbase" if current_adapter?(:OpenBaseAdapter)
+
+ # keep the names short to make Oracle and similar behave
+ connection.add_index(table_name, [:foo], :name => 'old_idx')
+ connection.rename_index(table_name, 'old_idx', 'new_idx')
+
+ # if the adapter doesn't support the indexes call, pick defaults that let the test pass
+ refute connection.index_name_exists?(table_name, 'old_idx', false)
+ assert connection.index_name_exists?(table_name, 'new_idx', true)
+ end
+
+ def test_double_add_index
+ skip "not supported on openbase" if current_adapter?(:OpenBaseAdapter)
+
+ connection.add_index(table_name, [:foo], :name => 'some_idx')
+ assert_raises(ArgumentError) {
+ connection.add_index(table_name, [:foo], :name => 'some_idx')
+ }
+ end
+
+ def test_remove_nonexistent_index
+ skip "not supported on openbase" if current_adapter?(:OpenBaseAdapter)
+
+ # we do this by name, so OpenBase is a wash as noted above
+ assert_raise(ArgumentError) { connection.remove_index(table_name, "no_such_index") }
+ end
+
+ def test_add_index_name_length_limit
+ good_index_name = 'x' * connection.index_name_length
+ too_long_index_name = good_index_name + 'x'
+
+ assert_raises(ArgumentError) {
+ connection.add_index(table_name, "foo", :name => too_long_index_name)
+ }
+
+ refute connection.index_name_exists?(table_name, too_long_index_name, false)
+ connection.add_index(table_name, "foo", :name => good_index_name)
+
+ assert connection.index_name_exists?(table_name, good_index_name, false)
+ connection.remove_index(table_name, :name => good_index_name)
+ end
+
+ def test_index_symbol_names
+ connection.add_index table_name, :foo, :name => :symbol_index_name
+ assert connection.index_exists?(table_name, :foo, :name => :symbol_index_name)
+
+ connection.remove_index table_name, :name => :symbol_index_name
+ refute connection.index_exists?(table_name, :foo, :name => :symbol_index_name)
+ end
+
+ def test_index_exists
+ connection.add_index :testings, :foo
+
+ assert connection.index_exists?(:testings, :foo)
+ assert !connection.index_exists?(:testings, :bar)
+ end
+
+ def test_index_exists_on_multiple_columns
+ connection.add_index :testings, [:foo, :bar]
+
+ assert connection.index_exists?(:testings, [:foo, :bar])
+ end
+
+ def test_unique_index_exists
+ connection.add_index :testings, :foo, :unique => true
+
+ assert connection.index_exists?(:testings, :foo, :unique => true)
+ end
+
+ def test_named_index_exists
+ connection.add_index :testings, :foo, :name => "custom_index_name"
+
+ assert connection.index_exists?(:testings, :foo, :name => "custom_index_name")
+ end
+
+ def test_add_index_attribute_length_limit
+ connection.add_index :testings, [:foo, :bar], :length => {:foo => 10, :bar => nil}
+
+ assert connection.index_exists?(:testings, [:foo, :bar])
+ end
+
+ def test_add_index
+ connection.add_index("testings", "last_name")
+ connection.remove_index("testings", "last_name")
+
+ # Orcl nds shrt indx nms. Sybs 2.
+ # OpenBase does not have named indexes. You must specify a single column name
+ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
+ connection.add_index("testings", ["last_name", "first_name"])
+ connection.remove_index("testings", :column => ["last_name", "first_name"])
+
+ # Oracle adapter cannot have specified index name larger than 30 characters
+ # Oracle adapter is shortening index name when just column list is given
+ unless current_adapter?(:OracleAdapter)
+ connection.add_index("testings", ["last_name", "first_name"])
+ connection.remove_index("testings", :name => :index_testings_on_last_name_and_first_name)
+ connection.add_index("testings", ["last_name", "first_name"])
+ connection.remove_index("testings", "last_name_and_first_name")
+ end
+ connection.add_index("testings", ["last_name", "first_name"])
+ connection.remove_index("testings", ["last_name", "first_name"])
+
+ connection.add_index("testings", ["last_name"], :length => 10)
+ connection.remove_index("testings", "last_name")
+
+ connection.add_index("testings", ["last_name"], :length => {:last_name => 10})
+ connection.remove_index("testings", ["last_name"])
+
+ connection.add_index("testings", ["last_name", "first_name"], :length => 10)
+ connection.remove_index("testings", ["last_name", "first_name"])
+
+ connection.add_index("testings", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20})
+ connection.remove_index("testings", ["last_name", "first_name"])
+ end
+
+ # quoting
+ # Note: changed index name from "key" to "key_idx" since "key" is a Firebird reserved word
+ # OpenBase does not have named indexes. You must specify a single column name
+ unless current_adapter?(:OpenBaseAdapter)
+ connection.add_index("testings", ["key"], :name => "key_idx", :unique => true)
+ connection.remove_index("testings", :name => "key_idx", :unique => true)
+ end
+
+ # Sybase adapter does not support indexes on :boolean columns
+ # OpenBase does not have named indexes. You must specify a single column
+ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
+ connection.add_index("testings", %w(last_name first_name administrator), :name => "named_admin")
+ connection.remove_index("testings", :name => "named_admin")
+ end
+
+ # Selected adapters support index sort order
+ if current_adapter?(:SQLite3Adapter, :MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
+ connection.add_index("testings", ["last_name"], :order => {:last_name => :desc})
+ connection.remove_index("testings", ["last_name"])
+ connection.add_index("testings", ["last_name", "first_name"], :order => {:last_name => :desc})
+ connection.remove_index("testings", ["last_name", "first_name"])
+ connection.add_index("testings", ["last_name", "first_name"], :order => {:last_name => :desc, :first_name => :asc})
+ connection.remove_index("testings", ["last_name", "first_name"])
+ connection.add_index("testings", ["last_name", "first_name"], :order => :desc)
+ connection.remove_index("testings", ["last_name", "first_name"])
+ end
+ end
+
+ def test_add_partial_index
+ skip 'only on pg' unless current_adapter?(:PostgreSQLAdapter)
+
+ connection.add_index("testings", "last_name", :where => "first_name = 'john doe'")
+ assert connection.index_exists?("testings", "last_name")
+
+ connection.remove_index("testings", "last_name")
+ assert !connection.index_exists?("testings", "last_name")
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb
new file mode 100644
index 0000000000..ee0c20747e
--- /dev/null
+++ b/activerecord/test/cases/migration/logger_test.rb
@@ -0,0 +1,37 @@
+require "cases/helper"
+
+module ActiveRecord
+ class Migration
+ class LoggerTest < ActiveRecord::TestCase
+ # mysql can't roll back ddl changes
+ self.use_transactional_fixtures = false
+
+ Migration = Struct.new(:name, :version) do
+ def migrate direction
+ # do nothing
+ end
+ end
+
+ def setup
+ super
+ ActiveRecord::SchemaMigration.create_table
+ ActiveRecord::SchemaMigration.delete_all
+ end
+
+ def teardown
+ super
+ ActiveRecord::SchemaMigration.drop_table
+ end
+
+ def test_migration_should_be_run_without_logger
+ previous_logger = ActiveRecord::Base.logger
+ ActiveRecord::Base.logger = nil
+ migrations = [Migration.new('a', 1), Migration.new('b', 2), Migration.new('c', 3)]
+ ActiveRecord::Migrator.new(:up, migrations).migrate
+ ensure
+ ActiveRecord::Base.logger = previous_logger
+ end
+ end
+ end
+end
+
diff --git a/activerecord/test/cases/migration/rename_column_test.rb b/activerecord/test/cases/migration/rename_column_test.rb
new file mode 100644
index 0000000000..16e09fd80e
--- /dev/null
+++ b/activerecord/test/cases/migration/rename_column_test.rb
@@ -0,0 +1,194 @@
+require "cases/migration/helper"
+
+module ActiveRecord
+ class Migration
+ class RenameColumnTest < ActiveRecord::TestCase
+ include ActiveRecord::Migration::TestHelper
+
+ self.use_transactional_fixtures = false
+
+ # FIXME: this is more of an integration test with AR::Base and the
+ # schema modifications. Maybe we should move this?
+ def test_add_rename
+ add_column "test_models", "girlfriend", :string
+ TestModel.reset_column_information
+
+ TestModel.create :girlfriend => 'bobette'
+
+ rename_column "test_models", "girlfriend", "exgirlfriend"
+
+ TestModel.reset_column_information
+ bob = TestModel.find(:first)
+
+ assert_equal "bobette", bob.exgirlfriend
+ end
+
+ # FIXME: another integration test. We should decouple this from the
+ # AR::Base implementation.
+ def test_rename_column_using_symbol_arguments
+ add_column :test_models, :first_name, :string
+
+ TestModel.create :first_name => 'foo'
+
+ rename_column :test_models, :first_name, :nick_name
+ TestModel.reset_column_information
+ assert TestModel.column_names.include?("nick_name")
+ assert_equal ['foo'], TestModel.find(:all).map(&:nick_name)
+ end
+
+ # FIXME: another integration test. We should decouple this from the
+ # AR::Base implementation.
+ def test_rename_column
+ add_column "test_models", "first_name", "string"
+
+ TestModel.create :first_name => 'foo'
+
+ rename_column "test_models", "first_name", "nick_name"
+ TestModel.reset_column_information
+ assert TestModel.column_names.include?("nick_name")
+ assert_equal ['foo'], TestModel.find(:all).map(&:nick_name)
+ end
+
+ def test_rename_column_preserves_default_value_not_null
+ add_column 'test_models', 'salary', :integer, :default => 70000
+
+ default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default
+ assert_equal 70000, default_before
+
+ rename_column "test_models", "salary", "anual_salary"
+
+ assert TestModel.column_names.include?("anual_salary")
+ default_after = connection.columns("test_models").find { |c| c.name == "anual_salary" }.default
+ assert_equal 70000, default_after
+ end
+
+ def test_rename_nonexistent_column
+ exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
+ ActiveRecord::StatementInvalid
+ else
+ ActiveRecord::ActiveRecordError
+ end
+ assert_raise(exception) do
+ rename_column "test_models", "nonexistent", "should_fail"
+ end
+ end
+
+ def test_rename_column_with_sql_reserved_word
+ add_column 'test_models', 'first_name', :string
+ rename_column "test_models", "first_name", "group"
+
+ assert TestModel.column_names.include?("group")
+ end
+
+ def test_rename_column_with_an_index
+ add_column "test_models", :hat_name, :string
+ add_index :test_models, :hat_name
+
+ # FIXME: we should test that the index goes away
+ rename_column "test_models", "hat_name", "name"
+ end
+
+ def test_remove_column_with_index
+ add_column "test_models", :hat_name, :string
+ add_index :test_models, :hat_name
+
+ # FIXME: we should test that the index goes away
+ remove_column("test_models", "hat_name")
+ end
+
+ def test_remove_column_with_multi_column_index
+ add_column "test_models", :hat_size, :integer
+ add_column "test_models", :hat_style, :string, :limit => 100
+ add_index "test_models", ["hat_style", "hat_size"], :unique => true
+
+ # FIXME: we should test that the index goes away
+ remove_column("test_models", "hat_size")
+ end
+
+ # FIXME: we need to test that these calls do something
+ def test_change_type_of_not_null_column
+ change_column "test_models", "updated_at", :datetime, :null => false
+ change_column "test_models", "updated_at", :datetime, :null => false
+ change_column "test_models", "updated_at", :datetime, :null => true
+ end
+
+ def test_change_column_nullability
+ add_column "test_models", "funny", :boolean
+ assert TestModel.columns_hash["funny"].null, "Column 'funny' must initially allow nulls"
+
+ change_column "test_models", "funny", :boolean, :null => false, :default => true
+
+ TestModel.reset_column_information
+ refute TestModel.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point"
+
+ change_column "test_models", "funny", :boolean, :null => true
+ TestModel.reset_column_information
+ assert TestModel.columns_hash["funny"].null, "Column 'funny' must allow nulls again at this point"
+ end
+
+ def test_change_column
+ add_column 'test_models', 'age', :integer
+ add_column 'test_models', 'approved', :boolean, :default => true
+
+ old_columns = connection.columns(TestModel.table_name)
+
+ assert old_columns.find { |c| c.name == 'age' && c.type == :integer }
+
+ change_column "test_models", "age", :string
+
+ new_columns = connection.columns(TestModel.table_name)
+
+ refute new_columns.find { |c| c.name == 'age' and c.type == :integer }
+ assert new_columns.find { |c| c.name == 'age' and c.type == :string }
+
+ old_columns = connection.columns(TestModel.table_name)
+ assert old_columns.find { |c|
+ c.name == 'approved' && c.type == :boolean && c.default == true
+ }
+
+ change_column :test_models, :approved, :boolean, :default => false
+ new_columns = connection.columns(TestModel.table_name)
+
+ refute new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true }
+ assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false }
+ change_column :test_models, :approved, :boolean, :default => true
+ end
+
+ def test_change_column_with_nil_default
+ add_column "test_models", "contributor", :boolean, :default => true
+ assert TestModel.new.contributor?
+
+ change_column "test_models", "contributor", :boolean, :default => nil
+ TestModel.reset_column_information
+ refute TestModel.new.contributor?
+ assert_nil TestModel.new.contributor
+ end
+
+ def test_change_column_with_new_default
+ add_column "test_models", "administrator", :boolean, :default => true
+ assert TestModel.new.administrator?
+
+ change_column "test_models", "administrator", :boolean, :default => false
+ TestModel.reset_column_information
+ refute TestModel.new.administrator?
+ end
+
+ def test_change_column_default
+ add_column "test_models", "first_name", :string
+ connection.change_column_default "test_models", "first_name", "Tester"
+
+ assert_equal "Tester", TestModel.new.first_name
+ end
+
+ def test_change_column_default_to_null
+ add_column "test_models", "first_name", :string
+ connection.change_column_default "test_models", "first_name", nil
+ assert_nil TestModel.new.first_name
+ end
+
+ def test_remove_column_no_second_parameter_raises_exception
+ assert_raise(ArgumentError) { connection.remove_column("funny") }
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
new file mode 100644
index 0000000000..d5ff2c607f
--- /dev/null
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -0,0 +1,72 @@
+require "cases/migration/helper"
+
+module ActiveRecord
+ class Migration
+ class RenameTableTest < ActiveRecord::TestCase
+ include ActiveRecord::Migration::TestHelper
+
+ self.use_transactional_fixtures = false
+
+ def setup
+ super
+ add_column 'test_models', :url, :string
+ remove_column 'test_models', :created_at
+ remove_column 'test_models', :updated_at
+ end
+
+ def test_rename_table_for_sqlite_should_work_with_reserved_words
+ renamed = false
+
+ skip "not supported" unless current_adapter?(:SQLite3Adapter)
+
+ add_column :test_models, :url, :string
+ connection.rename_table :references, :old_references
+ connection.rename_table :test_models, :references
+
+ renamed = true
+
+ # Using explicit id in insert for compatibility across all databases
+ con = connection
+ con.execute "INSERT INTO 'references' (url, created_at, updated_at) VALUES ('http://rubyonrails.com', 0, 0)"
+ assert_equal 'http://rubyonrails.com', connection.select_value("SELECT url FROM 'references' WHERE id=1")
+ ensure
+ return unless renamed
+ connection.rename_table :references, :test_models
+ connection.rename_table :old_references, :references
+ end
+
+ def test_rename_table
+ rename_table :test_models, :octopi
+
+ # Using explicit id in insert for compatibility across all databases
+ con = connection
+ con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
+
+ con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
+
+ con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
+
+ assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")
+
+ rename_table :octopi, :test_models
+ end
+
+ def test_rename_table_with_an_index
+ add_index :test_models, :url
+
+ rename_table :test_models, :octopi
+
+ # Using explicit id in insert for compatibility across all databases
+ con = ActiveRecord::Base.connection
+ con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
+ con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
+ con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
+
+ assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")
+ assert connection.indexes(:octopi).first.columns.include?("url")
+
+ rename_table :octopi, :test_models
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/table_and_index_test.rb b/activerecord/test/cases/migration/table_and_index_test.rb
new file mode 100644
index 0000000000..8fd770abd1
--- /dev/null
+++ b/activerecord/test/cases/migration/table_and_index_test.rb
@@ -0,0 +1,24 @@
+require "cases/helper"
+
+module ActiveRecord
+ class Migration
+ class TableAndIndexTest < 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)
+ # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters
+ ActiveRecord::Base.table_name_prefix = 'p_'
+ ActiveRecord::Base.table_name_suffix = '_s'
+ 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 "p_unique_schema_migrations_s", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name]
+ ensure
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_suffix = ""
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 1e68911ab3..92dc150104 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require "cases/migration/helper"
require 'bigdecimal/util'
require 'models/person'
@@ -10,2285 +11,937 @@ require MIGRATIONS_ROOT + "/rename/1_we_need_things"
require MIGRATIONS_ROOT + "/rename/2_rename_things"
require MIGRATIONS_ROOT + "/decimal/1_give_me_big_numbers"
-if ActiveRecord::Base.connection.supports_migrations?
- class BigNumber < ActiveRecord::Base; end
+class BigNumber < ActiveRecord::Base; end
- class Reminder < ActiveRecord::Base; end
+class Reminder < ActiveRecord::Base; end
- class Thing < ActiveRecord::Base; end
+class Thing < ActiveRecord::Base; end
- class ActiveRecord::Migration
- class << self
- attr_accessor :message_count
- end
-
- def puts(text="")
- ActiveRecord::Migration.message_count ||= 0
- ActiveRecord::Migration.message_count += 1
- end
- end
-
- class MigrationTableAndIndexTest < ActiveRecord::TestCase
- def test_add_schema_info_respects_prefix_and_suffix
- conn = ActiveRecord::Base.connection
+class MigrationTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
- conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
- # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters
- ActiveRecord::Base.table_name_prefix = 'p_'
- ActiveRecord::Base.table_name_suffix = '_s'
- conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+ fixtures :people
- conn.initialize_schema_migrations_table
-
- assert_equal "p_unique_schema_migrations_s", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name]
- ensure
- ActiveRecord::Base.table_name_prefix = ""
- ActiveRecord::Base.table_name_suffix = ""
+ def setup
+ super
+ %w(reminders people_reminders prefix_reminders_suffix).each do |table|
+ Reminder.connection.drop_table(table) rescue nil
end
+ Reminder.reset_column_information
+ ActiveRecord::Migration.verbose = true
+ ActiveRecord::Migration.message_count = 0
end
- class MigrationTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
-
- fixtures :people
-
- def setup
- ActiveRecord::Migration.verbose = true
- ActiveRecord::Migration.message_count = 0
- end
-
- def teardown
- ActiveRecord::Base.connection.initialize_schema_migrations_table
- ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}"
-
- %w(things awesome_things prefix_things_suffix prefix_awesome_things_suffix).each do |table|
- Thing.connection.drop_table(table) rescue nil
- end
- Thing.reset_column_information
-
- %w(reminders people_reminders prefix_reminders_suffix).each do |table|
- Reminder.connection.drop_table(table) rescue nil
- end
- Reminder.reset_column_information
-
- %w(last_name key bio age height wealth birthday favorite_day
- moment_of_truth male administrator funny).each do |column|
- Person.connection.remove_column('people', column) rescue nil
- end
- Person.connection.remove_column("people", "first_name") rescue nil
- Person.connection.remove_column("people", "middle_name") rescue nil
- Person.connection.add_column("people", "first_name", :string, :limit => 40)
- Person.reset_column_information
- end
-
- def test_add_index
- # Limit size of last_name and key columns to support Firebird index limitations
- Person.connection.add_column "people", "last_name", :string, :limit => 100
- Person.connection.add_column "people", "key", :string, :limit => 100
- Person.connection.add_column "people", "administrator", :boolean
-
- assert_nothing_raised { Person.connection.add_index("people", "last_name") }
- assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
-
- # Orcl nds shrt indx nms. Sybs 2.
- # OpenBase does not have named indexes. You must specify a single column name
- unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.remove_index("people", :column => ["last_name", "first_name"]) }
- # Oracle adapter cannot have specified index name larger than 30 characters
- # Oracle adapter is shortening index name when just column list is given
- unless current_adapter?(:OracleAdapter)
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.remove_index("people", :name => :index_people_on_last_name_and_first_name) }
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.remove_index("people", "last_name_and_first_name") }
- end
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => 10) }
- assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
- assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => {:last_name => 10}) }
- assert_nothing_raised { Person.connection.remove_index("people", ["last_name"]) }
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => 10) }
- assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20}) }
- assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
- end
-
- # quoting
- # Note: changed index name from "key" to "key_idx" since "key" is a Firebird reserved word
- # OpenBase does not have named indexes. You must specify a single column name
- unless current_adapter?(:OpenBaseAdapter)
- Person.update_all "#{Person.connection.quote_column_name 'key'}=#{Person.connection.quote_column_name 'id'}" #some databases (including sqlite2 won't add a unique index if existing data non unique)
- assert_nothing_raised { Person.connection.add_index("people", ["key"], :name => "key_idx", :unique => true) }
- assert_nothing_raised { Person.connection.remove_index("people", :name => "key_idx", :unique => true) }
- end
-
- # Sybase adapter does not support indexes on :boolean columns
- # OpenBase does not have named indexes. You must specify a single column
- unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
- assert_nothing_raised { Person.connection.add_index("people", %w(last_name first_name administrator), :name => "named_admin") }
- assert_nothing_raised { Person.connection.remove_index("people", :name => "named_admin") }
- end
+ def teardown
+ ActiveRecord::Base.connection.initialize_schema_migrations_table
+ ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}"
- # Selected adapters support index sort order
- if current_adapter?(:SQLite3Adapter, :MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
- assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :order => {:last_name => :desc}) }
- assert_nothing_raised { Person.connection.remove_index("people", ["last_name"]) }
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :order => {:last_name => :desc}) }
- assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :order => {:last_name => :desc, :first_name => :asc}) }
- assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :order => :desc) }
- assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
- end
+ %w(things awesome_things prefix_things_suffix prefix_awesome_things_suffix).each do |table|
+ Thing.connection.drop_table(table) rescue nil
end
+ Thing.reset_column_information
- def test_index_symbol_names
- assert_nothing_raised { Person.connection.add_index :people, :primary_contact_id, :name => :symbol_index_name }
- assert Person.connection.index_exists?(:people, :primary_contact_id, :name => :symbol_index_name)
- assert_nothing_raised { Person.connection.remove_index :people, :name => :symbol_index_name }
- assert !Person.connection.index_exists?(:people, :primary_contact_id, :name => :symbol_index_name)
+ %w(reminders people_reminders prefix_reminders_suffix).each do |table|
+ Reminder.connection.drop_table(table) rescue nil
end
+ Reminder.reset_column_information
- def test_add_index_length_limit
- good_index_name = 'x' * Person.connection.index_name_length
- too_long_index_name = good_index_name + 'x'
- assert_raise(ArgumentError) { Person.connection.add_index("people", "first_name", :name => too_long_index_name) }
- assert !Person.connection.index_name_exists?("people", too_long_index_name, false)
- assert_nothing_raised { Person.connection.add_index("people", "first_name", :name => good_index_name) }
- assert Person.connection.index_name_exists?("people", good_index_name, false)
- Person.connection.remove_index("people", :name => good_index_name)
- end
-
- def test_remove_nonexistent_index
- # we do this by name, so OpenBase is a wash as noted above
- unless current_adapter?(:OpenBaseAdapter)
- assert_raise(ArgumentError) { Person.connection.remove_index("people", "no_such_index") }
- end
+ %w(last_name key bio age height wealth birthday favorite_day
+ moment_of_truth male administrator funny).each do |column|
+ Person.connection.remove_column('people', column) rescue nil
end
+ Person.connection.remove_column("people", "first_name") rescue nil
+ Person.connection.remove_column("people", "middle_name") rescue nil
+ Person.connection.add_column("people", "first_name", :string, :limit => 40)
+ Person.reset_column_information
+ end
- def test_rename_index
- unless current_adapter?(:OpenBaseAdapter)
- # keep the names short to make Oracle and similar behave
- Person.connection.add_index('people', [:first_name], :name => 'old_idx')
- assert_nothing_raised { Person.connection.rename_index('people', 'old_idx', 'new_idx') }
- # if the adapter doesn't support the indexes call, pick defaults that let the test pass
- assert !Person.connection.index_name_exists?('people', 'old_idx', false)
- assert Person.connection.index_name_exists?('people', 'new_idx', true)
- end
- end
-
- def test_double_add_index
- unless current_adapter?(:OpenBaseAdapter)
- Person.connection.add_index('people', [:first_name], :name => 'some_idx')
- assert_raise(ArgumentError) { Person.connection.add_index('people', [:first_name], :name => 'some_idx') }
- end
- end
-
- def test_index_exists
- Person.connection.create_table :testings do |t|
- t.column :foo, :string, :limit => 100
- t.column :bar, :string, :limit => 100
- end
- Person.connection.add_index :testings, :foo
-
- assert Person.connection.index_exists?(:testings, :foo)
- assert !Person.connection.index_exists?(:testings, :bar)
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def test_index_exists_on_multiple_columns
- Person.connection.create_table :testings do |t|
- t.column :foo, :string, :limit => 100
- t.column :bar, :string, :limit => 100
- end
- Person.connection.add_index :testings, [:foo, :bar]
-
- assert Person.connection.index_exists?(:testings, [:foo, :bar])
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def test_unique_index_exists
- Person.connection.create_table :testings do |t|
- t.column :foo, :string, :limit => 100
- end
- Person.connection.add_index :testings, :foo, :unique => true
-
- assert Person.connection.index_exists?(:testings, :foo, :unique => true)
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def test_named_index_exists
- Person.connection.create_table :testings do |t|
- t.column :foo, :string, :limit => 100
- end
- Person.connection.add_index :testings, :foo, :name => "custom_index_name"
-
- assert Person.connection.index_exists?(:testings, :foo, :name => "custom_index_name")
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def testing_table_with_only_foo_attribute
- Person.connection.create_table :testings, :id => false do |t|
- t.column :foo, :string
- end
-
- yield Person.connection
- ensure
- Person.connection.drop_table :testings rescue nil
- end
- protected :testing_table_with_only_foo_attribute
-
- def test_create_table_without_id
- testing_table_with_only_foo_attribute do |connection|
- assert_equal connection.columns(:testings).size, 1
- end
- end
-
- def test_add_column_with_primary_key_attribute
- testing_table_with_only_foo_attribute do |connection|
- assert_nothing_raised { connection.add_column :testings, :id, :primary_key }
- assert_equal connection.columns(:testings).size, 2
- end
- end
-
- def test_create_table_adds_id
- Person.connection.create_table :testings do |t|
- t.column :foo, :string
- end
-
- assert_equal %w(foo id),
- Person.connection.columns(:testings).map { |c| c.name }.sort
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def test_create_table_with_not_null_column
- assert_nothing_raised do
- Person.connection.create_table :testings do |t|
- t.column :foo, :string, :null => false
- end
- end
-
- assert_raise(ActiveRecord::StatementInvalid) do
- Person.connection.execute "insert into testings (foo) values (NULL)"
- end
- ensure
- Person.connection.drop_table :testings rescue nil
+ def test_create_table_with_force_true_does_not_drop_nonexisting_table
+ if Person.connection.table_exists?(:testings2)
+ Person.connection.drop_table :testings2
end
- def test_create_table_with_defaults
- # MySQL doesn't allow defaults on TEXT or BLOB columns.
- mysql = current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter)
-
- Person.connection.create_table :testings do |t|
- t.column :one, :string, :default => "hello"
- t.column :two, :boolean, :default => true
- t.column :three, :boolean, :default => false
- t.column :four, :integer, :default => 1
- t.column :five, :text, :default => "hello" unless mysql
- end
-
- columns = Person.connection.columns(:testings)
- one = columns.detect { |c| c.name == "one" }
- two = columns.detect { |c| c.name == "two" }
- three = columns.detect { |c| c.name == "three" }
- four = columns.detect { |c| c.name == "four" }
- five = columns.detect { |c| c.name == "five" } unless mysql
+ # using a copy as we need the drop_table method to
+ # continue to work for the ensure block of the test
+ temp_conn = Person.connection.dup
- assert_equal "hello", one.default
- assert_equal true, two.default
- assert_equal false, three.default
- assert_equal 1, four.default
- assert_equal "hello", five.default unless mysql
+ assert_not_equal temp_conn, Person.connection
- ensure
- Person.connection.drop_table :testings rescue nil
+ temp_conn.create_table :testings2, :force => true do |t|
+ t.column :foo, :string
end
+ ensure
+ Person.connection.drop_table :testings2 rescue nil
+ end
- def test_create_table_with_limits
- assert_nothing_raised do
- Person.connection.create_table :testings do |t|
- t.column :foo, :string, :limit => 255
-
- t.column :default_int, :integer
-
- t.column :one_int, :integer, :limit => 1
- t.column :four_int, :integer, :limit => 4
- t.column :eight_int, :integer, :limit => 8
- t.column :eleven_int, :integer, :limit => 11
- end
- end
-
- columns = Person.connection.columns(:testings)
- foo = columns.detect { |c| c.name == "foo" }
- assert_equal 255, foo.limit
-
- default = columns.detect { |c| c.name == "default_int" }
- one = columns.detect { |c| c.name == "one_int" }
- four = columns.detect { |c| c.name == "four_int" }
- eight = columns.detect { |c| c.name == "eight_int" }
- eleven = columns.detect { |c| c.name == "eleven_int" }
-
- if current_adapter?(:PostgreSQLAdapter)
- assert_equal 'integer', default.sql_type
- assert_equal 'smallint', one.sql_type
- assert_equal 'integer', four.sql_type
- assert_equal 'bigint', eight.sql_type
- assert_equal 'integer', eleven.sql_type
- elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
- assert_match 'int(11)', default.sql_type
- assert_match 'tinyint', one.sql_type
- assert_match 'int', four.sql_type
- assert_match 'bigint', eight.sql_type
- assert_match 'int(11)', eleven.sql_type
- elsif current_adapter?(:OracleAdapter)
- assert_equal 'NUMBER(38)', default.sql_type
- assert_equal 'NUMBER(1)', one.sql_type
- assert_equal 'NUMBER(4)', four.sql_type
- assert_equal 'NUMBER(8)', eight.sql_type
- end
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def test_create_table_with_primary_key_prefix_as_table_name_with_underscore
- ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
-
- Person.connection.create_table :testings do |t|
- t.column :foo, :string
- end
-
- 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
- end
-
- def test_create_table_with_primary_key_prefix_as_table_name
- ActiveRecord::Base.primary_key_prefix_type = :table_name
-
- Person.connection.create_table :testings do |t|
- t.column :foo, :string
- end
-
- 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
- end
+ def connection
+ ActiveRecord::Base.connection
+ end
- def test_create_table_with_force_true_does_not_drop_nonexisting_table
- if Person.connection.table_exists?(:testings2)
- Person.connection.drop_table :testings2
- end
+ def test_migration_instance_has_connection
+ migration = Class.new(ActiveRecord::Migration).new
+ assert_equal connection, migration.connection
+ end
- # using a copy as we need the drop_table method to
- # continue to work for the ensure block of the test
- temp_conn = Person.connection.dup
- temp_conn.expects(:drop_table).never
- temp_conn.create_table :testings2, :force => true do |t|
- t.column :foo, :string
+ def test_method_missing_delegates_to_connection
+ migration = Class.new(ActiveRecord::Migration) {
+ def connection
+ Class.new {
+ def create_table; "hi mom!"; end
+ }.new
end
- ensure
- Person.connection.drop_table :testings2 rescue nil
- end
+ }.new
- def test_create_table_with_timestamps_should_create_datetime_columns
- table_name = :testings
+ assert_equal "hi mom!", migration.method_missing(:create_table)
+ end
- Person.connection.create_table table_name do |t|
- t.timestamps
- end
- created_columns = Person.connection.columns(table_name)
+ def test_add_table_with_decimals
+ Person.connection.drop_table :big_numbers rescue nil
+
+ assert !BigNumber.table_exists?
+ GiveMeBigNumbers.up
+
+ assert BigNumber.create(
+ :bank_balance => 1586.43,
+ :big_bank_balance => BigDecimal("1000234000567.95"),
+ :world_population => 6000000000,
+ :my_house_population => 3,
+ :value_of_e => BigDecimal("2.7182818284590452353602875")
+ )
+
+ b = BigNumber.find(:first)
+ assert_not_nil b
+
+ assert_not_nil b.bank_balance
+ assert_not_nil b.big_bank_balance
+ assert_not_nil b.world_population
+ assert_not_nil b.my_house_population
+ assert_not_nil b.value_of_e
+
+ # TODO: set world_population >= 2**62 to cover 64-bit platforms and test
+ # is_a?(Bignum)
+ assert_kind_of Integer, b.world_population
+ assert_equal 6000000000, b.world_population
+ assert_kind_of Fixnum, b.my_house_population
+ assert_equal 3, b.my_house_population
+ assert_kind_of BigDecimal, b.bank_balance
+ assert_equal BigDecimal("1586.43"), b.bank_balance
+ assert_kind_of BigDecimal, b.big_bank_balance
+ assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance
+
+ # This one is fun. The 'value_of_e' field is defined as 'DECIMAL' with
+ # precision/scale explicitly left out. By the SQL standard, numbers
+ # assigned to this field should be truncated but that's seldom respected.
+ if current_adapter?(:PostgreSQLAdapter)
+ # - PostgreSQL changes the SQL spec on columns declared simply as
+ # "decimal" to something more useful: instead of being given a scale
+ # of 0, they take on the compile-time limit for precision and scale,
+ # so the following should succeed unless you have used really wacky
+ # compilation options
+ # - SQLite2 has the default behavior of preserving all data sent in,
+ # so this happens there too
+ assert_kind_of BigDecimal, b.value_of_e
+ assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e
+ elsif current_adapter?(:SQLite3Adapter)
+ # - SQLite3 stores a float, in violation of SQL
+ assert_kind_of BigDecimal, b.value_of_e
+ assert_in_delta BigDecimal("2.71828182845905"), b.value_of_e, 0.00000000000001
+ else
+ # - SQL standard is an integer
+ assert_kind_of Fixnum, b.value_of_e
+ assert_equal 2, b.value_of_e
+ end
+
+ GiveMeBigNumbers.down
+ assert_raise(ActiveRecord::StatementInvalid) { BigNumber.find(:first) }
+ end
- created_at_column = created_columns.detect {|c| c.name == 'created_at' }
- updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
+ def test_filtering_migrations
+ assert !Person.column_methods_hash.include?(:last_name)
+ assert !Reminder.table_exists?
- assert !created_at_column.null
- assert !updated_at_column.null
- ensure
- Person.connection.drop_table table_name rescue nil
- end
+ name_filter = lambda { |migration| migration.name == "ValidPeopleHaveLastNames" }
+ ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", &name_filter)
- def test_create_table_with_timestamps_should_create_datetime_columns_with_options
- table_name = :testings
+ Person.reset_column_information
+ assert Person.column_methods_hash.include?(:last_name)
+ assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
- Person.connection.create_table table_name do |t|
- t.timestamps :null => false
- end
- created_columns = Person.connection.columns(table_name)
+ ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", &name_filter)
- created_at_column = created_columns.detect {|c| c.name == 'created_at' }
- updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
+ Person.reset_column_information
+ assert !Person.column_methods_hash.include?(:last_name)
+ assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
+ end
- assert !created_at_column.null
- assert !updated_at_column.null
- ensure
- Person.connection.drop_table table_name rescue nil
+ class MockMigration < ActiveRecord::Migration
+ attr_reader :went_up, :went_down
+ def initialize
+ @went_up = false
+ @went_down = false
end
- def test_create_table_without_a_block
- table_name = :testings
- Person.connection.create_table table_name
- ensure
- Person.connection.drop_table table_name rescue nil
+ def up
+ @went_up = true
+ super
end
- # Sybase, and SQLite3 will not allow you to add a NOT NULL
- # column to a table without a default value.
- unless current_adapter?(:SybaseAdapter, :SQLite3Adapter)
- def test_add_column_not_null_without_default
- Person.connection.create_table :testings do |t|
- t.column :foo, :string
- end
- Person.connection.add_column :testings, :bar, :string, :null => false
-
- assert_raise(ActiveRecord::StatementInvalid) do
- Person.connection.execute "insert into testings (foo, bar) values ('hello', NULL)"
- end
- ensure
- Person.connection.drop_table :testings rescue nil
- end
+ def down
+ @went_down = true
+ super
end
+ end
- def test_add_column_not_null_with_default
- Person.connection.create_table :testings do |t|
- t.column :foo, :string
- end
-
- con = Person.connection
- Person.connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter)
- Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')"
- Person.connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter)
- assert_nothing_raised {Person.connection.add_column :testings, :bar, :string, :null => false, :default => "default" }
-
- assert_raise(ActiveRecord::StatementInvalid) do
- unless current_adapter?(:OpenBaseAdapter)
- Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)"
- else
- Person.connection.insert("INSERT INTO testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) VALUES (2, 'hello', NULL)",
- "Testing Insert","id",2)
- end
- end
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- # We specifically do a manual INSERT here, and then test only the SELECT
- # functionality. This allows us to more easily catch INSERT being broken,
- # but SELECT actually working fine.
- def test_native_decimal_insert_manual_vs_automatic
- correct_value = '0012345678901234567890.0123456789'.to_d
-
- Person.delete_all
- Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10'
- Person.reset_column_information
-
- # Do a manual insertion
- if current_adapter?(:OracleAdapter)
- Person.connection.execute "insert into people (id, wealth, created_at, updated_at) values (people_seq.nextval, 12345678901234567890.0123456789, sysdate, sysdate)"
- elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings
- Person.connection.execute "insert into people (wealth, created_at, updated_at) values ('12345678901234567890.0123456789', 0, 0)"
- elsif current_adapter?(:PostgreSQLAdapter)
- Person.connection.execute "insert into people (wealth, created_at, updated_at) values (12345678901234567890.0123456789, now(), now())"
- else
- Person.connection.execute "insert into people (wealth, created_at, updated_at) values (12345678901234567890.0123456789, 0, 0)"
- end
-
- # SELECT
- row = Person.find(:first)
- assert_kind_of BigDecimal, row.wealth
-
- # If this assert fails, that means the SELECT is broken!
- unless current_adapter?(:SQLite3Adapter)
- assert_equal correct_value, row.wealth
- end
-
- # Reset to old state
- Person.delete_all
-
- # Now use the Rails insertion
- assert_nothing_raised { Person.create :wealth => BigDecimal.new("12345678901234567890.0123456789") }
-
- # SELECT
- row = Person.find(:first)
- assert_kind_of BigDecimal, row.wealth
+ def test_instance_based_migration_up
+ migration = MockMigration.new
+ assert !migration.went_up, 'have not gone up'
+ assert !migration.went_down, 'have not gone down'
- # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken!
- unless current_adapter?(:SQLite3Adapter)
- assert_equal correct_value, row.wealth
- end
+ migration.migrate :up
+ assert migration.went_up, 'have gone up'
+ assert !migration.went_down, 'have not gone down'
+ end
- # Reset to old state
- Person.connection.del_column "people", "wealth" rescue nil
- Person.reset_column_information
- end
+ def test_instance_based_migration_down
+ migration = MockMigration.new
+ assert !migration.went_up, 'have not gone up'
+ assert !migration.went_down, 'have not gone down'
- def test_add_column_with_precision_and_scale
- Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7
- Person.reset_column_information
+ migration.migrate :down
+ assert !migration.went_up, 'have gone up'
+ assert migration.went_down, 'have not gone down'
+ end
- wealth_column = Person.columns_hash['wealth']
- assert_equal 9, wealth_column.precision
- assert_equal 7, wealth_column.scale
+ def test_migrator_one_up_with_exception_and_rollback
+ unless ActiveRecord::Base.connection.supports_ddl_transactions?
+ skip "not supported on #{ActiveRecord::Base.connection.class}"
end
- # Test SQLite adapter specifically for decimal types with precision and scale
- # attributes, since these need to be maintained in schema but aren't actually
- # used in SQLite itself
- if current_adapter?(:SQLite3Adapter)
- def test_change_column_with_new_precision_and_scale
- Person.delete_all
- Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7
- Person.reset_column_information
-
- Person.connection.change_column 'people', 'wealth', :decimal, :precision => 12, :scale => 8
- Person.reset_column_information
+ refute Person.column_methods_hash.include?(:last_name)
- wealth_column = Person.columns_hash['wealth']
- assert_equal 12, wealth_column.precision
- assert_equal 8, wealth_column.scale
- end
-
- def test_change_column_preserve_other_column_precision_and_scale
- Person.delete_all
- Person.connection.add_column 'people', 'last_name', :string
- Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7
- Person.reset_column_information
-
- wealth_column = Person.columns_hash['wealth']
- assert_equal 9, wealth_column.precision
- assert_equal 7, wealth_column.scale
-
- Person.connection.change_column 'people', 'last_name', :string, :null => false
- Person.reset_column_information
-
- wealth_column = Person.columns_hash['wealth']
- assert_equal 9, wealth_column.precision
- assert_equal 7, wealth_column.scale
- end
- end
-
- def test_native_types
- Person.delete_all
- Person.connection.add_column "people", "last_name", :string
- Person.connection.add_column "people", "bio", :text
- Person.connection.add_column "people", "age", :integer
- Person.connection.add_column "people", "height", :float
- Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10'
- Person.connection.add_column "people", "birthday", :datetime
- Person.connection.add_column "people", "favorite_day", :date
- Person.connection.add_column "people", "moment_of_truth", :datetime
- Person.connection.add_column "people", "male", :boolean
- Person.reset_column_information
-
- assert_nothing_raised do
- Person.create :first_name => 'bob', :last_name => 'bobsen',
- :bio => "I was born ....", :age => 18, :height => 1.78,
- :wealth => BigDecimal.new("12345678901234567890.0123456789"),
- :birthday => 18.years.ago, :favorite_day => 10.days.ago,
- :moment_of_truth => "1782-10-10 21:40:18", :male => true
- end
+ migration = Struct.new(:name, :version) {
+ def migrate(x); raise 'Something broke'; end
+ }.new('zomg', 100)
- bob = Person.find(:first)
- assert_equal 'bob', bob.first_name
- assert_equal 'bobsen', bob.last_name
- assert_equal "I was born ....", bob.bio
- assert_equal 18, bob.age
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
- # Test for 30 significant digits (beyond the 16 of float), 10 of them
- # after the decimal place.
+ e = assert_raise(StandardError) { migrator.migrate }
- unless current_adapter?(:SQLite3Adapter)
- assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth
- end
+ assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message
- assert_equal true, bob.male?
+ Person.reset_column_information
+ refute Person.column_methods_hash.include?(:last_name)
+ end
- assert_equal String, bob.first_name.class
- assert_equal String, bob.last_name.class
- assert_equal String, bob.bio.class
- assert_equal Fixnum, bob.age.class
- assert_equal Time, bob.birthday.class
+ def test_schema_migrations_table_name
+ ActiveRecord::Base.table_name_prefix = "prefix_"
+ ActiveRecord::Base.table_name_suffix = "_suffix"
+ Reminder.reset_table_name
+ assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_suffix = ""
+ Reminder.reset_table_name
+ assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name
+ ensure
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_suffix = ""
+ end
- if current_adapter?(:OracleAdapter, :SybaseAdapter)
- # Sybase, and Oracle don't differentiate between date/time
- assert_equal Time, bob.favorite_day.class
- else
- assert_equal Date, bob.favorite_day.class
- end
+ def test_proper_table_name
+ assert_equal "table", ActiveRecord::Migrator.proper_table_name('table')
+ assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table)
+ assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder)
+ Reminder.reset_table_name
+ assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder)
+
+ # Use the model's own prefix/suffix if a model is given
+ ActiveRecord::Base.table_name_prefix = "ARprefix_"
+ ActiveRecord::Base.table_name_suffix = "_ARsuffix"
+ Reminder.table_name_prefix = 'prefix_'
+ Reminder.table_name_suffix = '_suffix'
+ Reminder.reset_table_name
+ assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder)
+ Reminder.table_name_prefix = ''
+ Reminder.table_name_suffix = ''
+ Reminder.reset_table_name
+
+ # Use AR::Base's prefix/suffix if string or symbol is given
+ ActiveRecord::Base.table_name_prefix = "prefix_"
+ ActiveRecord::Base.table_name_suffix = "_suffix"
+ Reminder.reset_table_name
+ assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table')
+ assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table)
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_suffix = ""
+ Reminder.reset_table_name
+ end
- # Oracle adapter stores Time or DateTime with timezone value already in _before_type_cast column
- # therefore no timezone change is done afterwards when default timezone is changed
- unless current_adapter?(:OracleAdapter)
- # Test DateTime column and defaults, including timezone.
- # FIXME: moment of truth may be Time on 64-bit platforms.
- if bob.moment_of_truth.is_a?(DateTime)
-
- with_env_tz 'US/Eastern' do
- bob.reload
- assert_equal DateTime.local_offset, bob.moment_of_truth.offset
- assert_not_equal 0, bob.moment_of_truth.offset
- assert_not_equal "Z", bob.moment_of_truth.zone
- # US/Eastern is -5 hours from GMT
- assert_equal Rational(-5, 24), bob.moment_of_truth.offset
- assert_match(/\A-05:?00\Z/, bob.moment_of_truth.zone) #ruby 1.8.6 uses HH:MM, prior versions use HHMM
- assert_equal DateTime::ITALY, bob.moment_of_truth.start
- end
- end
- end
+ def test_rename_table_with_prefix_and_suffix
+ assert !Thing.table_exists?
+ ActiveRecord::Base.table_name_prefix = 'prefix_'
+ ActiveRecord::Base.table_name_suffix = '_suffix'
+ Thing.reset_table_name
+ Thing.reset_sequence_name
+ WeNeedThings.up
+
+ assert Thing.create("content" => "hello world")
+ assert_equal "hello world", Thing.find(:first).content
+
+ RenameThings.up
+ Thing.table_name = "prefix_awesome_things_suffix"
+
+ assert_equal "hello world", Thing.find(:first).content
+ ensure
+ ActiveRecord::Base.table_name_prefix = ''
+ ActiveRecord::Base.table_name_suffix = ''
+ Thing.reset_table_name
+ Thing.reset_sequence_name
+ end
- assert_instance_of TrueClass, bob.male?
- assert_kind_of BigDecimal, bob.wealth
- end
+ def test_add_drop_table_with_prefix_and_suffix
+ assert !Reminder.table_exists?
+ ActiveRecord::Base.table_name_prefix = 'prefix_'
+ ActiveRecord::Base.table_name_suffix = '_suffix'
+ Reminder.reset_table_name
+ Reminder.reset_sequence_name
+ WeNeedReminders.up
+ assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
+ assert_equal "hello world", Reminder.find(:first).content
+
+ WeNeedReminders.down
+ assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
+ ensure
+ ActiveRecord::Base.table_name_prefix = ''
+ ActiveRecord::Base.table_name_suffix = ''
+ Reminder.reset_table_name
+ Reminder.reset_sequence_name
+ end
- if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
- def test_unabstracted_database_dependent_types
- Person.delete_all
+ def test_create_table_with_binary_column
+ Person.connection.drop_table :binary_testings rescue nil
- ActiveRecord::Migration.add_column :people, :intelligence_quotient, :tinyint
- Person.reset_column_information
- assert_match(/tinyint/, Person.columns_hash['intelligence_quotient'].sql_type)
- ensure
- ActiveRecord::Migration.remove_column :people, :intelligence_quotient rescue nil
+ assert_nothing_raised {
+ Person.connection.create_table :binary_testings do |t|
+ t.column "data", :binary, :null => false
end
- end
-
- def test_add_remove_single_field_using_string_arguments
- assert !Person.column_methods_hash.include?(:last_name)
-
- ActiveRecord::Migration.add_column 'people', 'last_name', :string
-
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
-
- ActiveRecord::Migration.remove_column 'people', 'last_name'
-
- Person.reset_column_information
- assert !Person.column_methods_hash.include?(:last_name)
- end
+ }
- def test_add_remove_single_field_using_symbol_arguments
- assert !Person.column_methods_hash.include?(:last_name)
-
- ActiveRecord::Migration.add_column :people, :last_name, :string
-
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
-
- ActiveRecord::Migration.remove_column :people, :last_name
-
- Person.reset_column_information
- assert !Person.column_methods_hash.include?(:last_name)
- end
+ columns = Person.connection.columns(:binary_testings)
+ data_column = columns.detect { |c| c.name == "data" }
if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
- def testing_table_for_positioning
- Person.connection.create_table :testings, :id => false do |t|
- t.column :first, :integer
- t.column :second, :integer
- t.column :third, :integer
- end
-
- yield Person.connection
- ensure
- Person.connection.drop_table :testings rescue nil
- end
- protected :testing_table_for_positioning
-
- def test_column_positioning
- testing_table_for_positioning do |conn|
- assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name }
- end
- end
-
- def test_add_column_with_positioning
- testing_table_for_positioning do |conn|
- conn.add_column :testings, :new_col, :integer
- assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name }
- end
- testing_table_for_positioning do |conn|
- conn.add_column :testings, :new_col, :integer, :first => true
- assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name }
- end
- testing_table_for_positioning do |conn|
- conn.add_column :testings, :new_col, :integer, :after => :first
- assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name }
- end
- end
-
- def test_change_column_with_positioning
- testing_table_for_positioning do |conn|
- conn.change_column :testings, :second, :integer, :first => true
- assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name }
- end
- testing_table_for_positioning do |conn|
- conn.change_column :testings, :second, :integer, :after => :third
- assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name }
- end
- end
+ assert_equal '', data_column.default
+ else
+ assert_nil data_column.default
end
- def test_add_rename
- Person.delete_all
-
- begin
- Person.connection.add_column "people", "girlfriend", :string
- Person.reset_column_information
- Person.create :girlfriend => 'bobette'
-
- Person.connection.rename_column "people", "girlfriend", "exgirlfriend"
-
- Person.reset_column_information
- bob = Person.find(:first)
-
- assert_equal "bobette", bob.exgirlfriend
- ensure
- Person.connection.remove_column("people", "girlfriend") rescue nil
- Person.connection.remove_column("people", "exgirlfriend") rescue nil
- end
-
- end
-
- def test_rename_column_using_symbol_arguments
- begin
- names_before = Person.find(:all).map(&:first_name)
- Person.connection.rename_column :people, :first_name, :nick_name
- Person.reset_column_information
- assert Person.column_names.include?("nick_name")
- assert_equal names_before, Person.find(:all).map(&:nick_name)
- ensure
- Person.connection.remove_column("people","nick_name")
- Person.connection.add_column("people","first_name", :string)
- end
- end
-
- def test_rename_column
- begin
- names_before = Person.find(:all).map(&:first_name)
- Person.connection.rename_column "people", "first_name", "nick_name"
- Person.reset_column_information
- assert Person.column_names.include?("nick_name")
- assert_equal names_before, Person.find(:all).map(&:nick_name)
- ensure
- Person.connection.remove_column("people","nick_name")
- Person.connection.add_column("people","first_name", :string)
- end
- end
-
- def test_rename_column_preserves_default_value_not_null
- begin
- default_before = Developer.connection.columns("developers").find { |c| c.name == "salary" }.default
- assert_equal 70000, default_before
- Developer.connection.rename_column "developers", "salary", "anual_salary"
- Developer.reset_column_information
- assert Developer.column_names.include?("anual_salary")
- default_after = Developer.connection.columns("developers").find { |c| c.name == "anual_salary" }.default
- assert_equal 70000, default_after
- ensure
- Developer.connection.rename_column "developers", "anual_salary", "salary"
- Developer.reset_column_information
- end
- end
-
- def test_rename_nonexistent_column
- ActiveRecord::Base.connection.create_table(:hats) do |table|
- table.column :hat_name, :string, :default => nil
- end
- exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
- ActiveRecord::StatementInvalid
- else
- ActiveRecord::ActiveRecordError
- end
- assert_raise(exception) do
- Person.connection.rename_column "hats", "nonexistent", "should_fail"
- end
- ensure
- ActiveRecord::Base.connection.drop_table(:hats)
- end
-
- def test_rename_column_with_sql_reserved_word
- begin
- assert_nothing_raised { Person.connection.rename_column "people", "first_name", "group" }
- Person.reset_column_information
- assert Person.column_names.include?("group")
- ensure
- Person.connection.remove_column("people", "group") rescue nil
- Person.connection.add_column("people", "first_name", :string) rescue nil
- end
- end
-
- def test_rename_column_with_an_index
- ActiveRecord::Base.connection.create_table(:hats) do |table|
- table.column :hat_name, :string, :limit => 100
- table.column :hat_size, :integer
- end
- Person.connection.add_index :hats, :hat_name
- assert_nothing_raised do
- Person.connection.rename_column "hats", "hat_name", "name"
- end
- ensure
- ActiveRecord::Base.connection.drop_table(:hats)
- end
-
- def test_remove_column_with_index
- ActiveRecord::Base.connection.create_table(:hats) do |table|
- table.column :hat_name, :string, :limit => 100
- table.column :hat_size, :integer
- end
- ActiveRecord::Base.connection.add_index "hats", "hat_size"
-
- assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") }
- ensure
- ActiveRecord::Base.connection.drop_table(:hats)
- end
-
- def test_remove_column_with_multi_column_index
- ActiveRecord::Base.connection.create_table(:hats) do |table|
- table.column :hat_name, :string, :limit => 100
- table.column :hat_size, :integer
- table.column :hat_style, :string, :limit => 100
- end
- ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true
-
- assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") }
- ensure
- ActiveRecord::Base.connection.drop_table(:hats)
- end
-
- def test_remove_column_no_second_parameter_raises_exception
- assert_raise(ArgumentError) { Person.connection.remove_column("funny") }
- end
-
- def test_change_type_of_not_null_column
- assert_nothing_raised do
- Topic.connection.change_column "topics", "written_on", :datetime, :null => false
- Topic.reset_column_information
-
- Topic.connection.change_column "topics", "written_on", :datetime, :null => false
- Topic.reset_column_information
+ Person.connection.drop_table :binary_testings rescue nil
+ end
- Topic.connection.change_column "topics", "written_on", :datetime, :null => true
- Topic.reset_column_information
- end
- end
+ def test_create_table_with_custom_sequence_name
+ skip "not supported" unless current_adapter? :OracleAdapter
- if current_adapter?(:SQLite3Adapter)
- def test_rename_table_for_sqlite_should_work_with_reserved_words
- begin
- assert_nothing_raised do
- ActiveRecord::Base.connection.rename_table :references, :old_references
- ActiveRecord::Base.connection.create_table :octopuses do |t|
- t.column :url, :string
- end
- end
-
- assert_nothing_raised { ActiveRecord::Base.connection.rename_table :octopuses, :references }
-
- # Using explicit id in insert for compatibility across all databases
- con = ActiveRecord::Base.connection
- assert_nothing_raised do
- con.execute "INSERT INTO 'references' (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://rubyonrails.com')"
- end
- assert_equal 'http://rubyonrails.com', ActiveRecord::Base.connection.select_value("SELECT url FROM 'references' WHERE id=1")
-
- ensure
- ActiveRecord::Base.connection.drop_table :references
- ActiveRecord::Base.connection.rename_table :old_references, :references
- end
- end
- end
-
- def test_rename_table
+ # table name is 29 chars, the standard sequence name will
+ # be 33 chars and should be shortened
+ assert_nothing_raised do
begin
- ActiveRecord::Base.connection.create_table :octopuses do |t|
- t.column :url, :string
+ Person.connection.create_table :table_with_name_thats_just_ok do |t|
+ t.column :foo, :string, :null => false
end
- ActiveRecord::Base.connection.rename_table :octopuses, :octopi
-
- # Using explicit id in insert for compatibility across all databases
- con = ActiveRecord::Base.connection
- con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
- assert_nothing_raised { con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" }
- con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
-
- assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1")
-
ensure
- ActiveRecord::Base.connection.drop_table :octopuses rescue nil
- ActiveRecord::Base.connection.drop_table :octopi rescue nil
+ Person.connection.drop_table :table_with_name_thats_just_ok rescue nil
end
end
- def test_change_column_nullability
- Person.delete_all
- Person.connection.add_column "people", "funny", :boolean
- Person.reset_column_information
- assert Person.columns_hash["funny"].null, "Column 'funny' must initially allow nulls"
- Person.connection.change_column "people", "funny", :boolean, :null => false, :default => true
- Person.reset_column_information
- assert !Person.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point"
- Person.connection.change_column "people", "funny", :boolean, :null => true
- Person.reset_column_information
- assert Person.columns_hash["funny"].null, "Column 'funny' must allow nulls again at this point"
- end
-
- def test_rename_table_with_an_index
+ # should be all good w/ a custom sequence name
+ assert_nothing_raised do
begin
- ActiveRecord::Base.connection.create_table :octopuses do |t|
- t.column :url, :string
+ Person.connection.create_table :table_with_name_thats_just_ok,
+ :sequence_name => 'suitably_short_seq' do |t|
+ t.column :foo, :string, :null => false
end
- ActiveRecord::Base.connection.add_index :octopuses, :url
- ActiveRecord::Base.connection.rename_table :octopuses, :octopi
-
- # Using explicit id in insert for compatibility across all databases
- con = ActiveRecord::Base.connection
- con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
- assert_nothing_raised { con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" }
- con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
+ Person.connection.execute("select suitably_short_seq.nextval from dual")
- assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1")
- assert ActiveRecord::Base.connection.indexes(:octopi).first.columns.include?("url")
ensure
- ActiveRecord::Base.connection.drop_table :octopuses rescue nil
- ActiveRecord::Base.connection.drop_table :octopi rescue nil
- end
- end
-
- def test_change_column
- Person.connection.add_column 'people', 'age', :integer
- label = "test_change_column Columns"
- old_columns = Person.connection.columns(Person.table_name, label)
- assert old_columns.find { |c| c.name == 'age' and c.type == :integer }
-
- assert_nothing_raised { Person.connection.change_column "people", "age", :string }
-
- new_columns = Person.connection.columns(Person.table_name, label)
- assert_nil new_columns.find { |c| c.name == 'age' and c.type == :integer }
- assert new_columns.find { |c| c.name == 'age' and c.type == :string }
-
- old_columns = Topic.connection.columns(Topic.table_name, label)
- assert old_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true }
- assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => false }
- new_columns = Topic.connection.columns(Topic.table_name, label)
- assert_nil new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true }
- assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false }
- assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => true }
- end
-
- def test_change_column_with_nil_default
- Person.connection.add_column "people", "contributor", :boolean, :default => true
- Person.reset_column_information
- assert Person.new.contributor?
-
- assert_nothing_raised { Person.connection.change_column "people", "contributor", :boolean, :default => nil }
- Person.reset_column_information
- assert !Person.new.contributor?
- assert_nil Person.new.contributor
- ensure
- Person.connection.remove_column("people", "contributor") rescue nil
- end
-
- def test_change_column_with_new_default
- Person.connection.add_column "people", "administrator", :boolean, :default => true
- Person.reset_column_information
- assert Person.new.administrator?
-
- assert_nothing_raised { Person.connection.change_column "people", "administrator", :boolean, :default => false }
- Person.reset_column_information
- assert !Person.new.administrator?
- ensure
- Person.connection.remove_column("people", "administrator") rescue nil
- end
-
- def test_change_column_default
- Person.connection.change_column_default "people", "first_name", "Tester"
- Person.reset_column_information
- assert_equal "Tester", Person.new.first_name
- end
-
- def test_change_column_quotes_column_names
- Person.connection.create_table :testings do |t|
- t.column :select, :string
- end
-
- assert_nothing_raised { Person.connection.change_column :testings, :select, :string, :limit => 10 }
-
- # Oracle needs primary key value from sequence
- if current_adapter?(:OracleAdapter)
- assert_nothing_raised { Person.connection.execute "insert into testings (id, #{Person.connection.quote_column_name('select')}) values (testings_seq.nextval, '7 chars')" }
- else
- assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" }
+ Person.connection.drop_table :table_with_name_thats_just_ok,
+ :sequence_name => 'suitably_short_seq' rescue nil
end
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def test_keeping_default_and_notnull_constaint_on_change
- Person.connection.create_table :testings do |t|
- t.column :title, :string
- end
- person_klass = Class.new(Person)
- person_klass.table_name = 'testings'
-
- person_klass.connection.add_column "testings", "wealth", :integer, :null => false, :default => 99
- person_klass.reset_column_information
- assert_equal 99, person_klass.columns_hash["wealth"].default
- assert_equal false, person_klass.columns_hash["wealth"].null
- # Oracle needs primary key value from sequence
- if current_adapter?(:OracleAdapter)
- assert_nothing_raised {person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')")}
- else
- assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")}
- end
-
- # change column default to see that column doesn't lose its not null definition
- person_klass.connection.change_column_default "testings", "wealth", 100
- person_klass.reset_column_information
- assert_equal 100, person_klass.columns_hash["wealth"].default
- assert_equal false, person_klass.columns_hash["wealth"].null
-
- # rename column to see that column doesn't lose its not null and/or default definition
- person_klass.connection.rename_column "testings", "wealth", "money"
- person_klass.reset_column_information
- assert_nil person_klass.columns_hash["wealth"]
- assert_equal 100, person_klass.columns_hash["money"].default
- assert_equal false, person_klass.columns_hash["money"].null
-
- # change column
- person_klass.connection.change_column "testings", "money", :integer, :null => false, :default => 1000
- person_klass.reset_column_information
- assert_equal 1000, person_klass.columns_hash["money"].default
- assert_equal false, person_klass.columns_hash["money"].null
-
- # change column, make it nullable and clear default
- person_klass.connection.change_column "testings", "money", :integer, :null => true, :default => nil
- person_klass.reset_column_information
- assert_nil person_klass.columns_hash["money"].default
- assert_equal true, person_klass.columns_hash["money"].null
-
- # change_column_null, make it not nullable and set null values to a default value
- person_klass.connection.execute('UPDATE testings SET money = NULL')
- person_klass.connection.change_column_null "testings", "money", false, 2000
- person_klass.reset_column_information
- assert_nil person_klass.columns_hash["money"].default
- assert_equal false, person_klass.columns_hash["money"].null
- assert_equal [2000], Person.connection.select_values("SELECT money FROM testings").map { |s| s.to_i }.sort
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def test_change_column_default_to_null
- Person.connection.change_column_default "people", "first_name", nil
- Person.reset_column_information
- assert_nil Person.new.first_name
end
- def test_column_exists
- Person.connection.create_table :testings do |t|
- t.column :foo, :string
- end
-
- assert Person.connection.column_exists?(:testings, :foo)
- assert !Person.connection.column_exists?(:testings, :bar)
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def test_column_exists_with_type
- Person.connection.create_table :testings do |t|
- t.column :foo, :string
- t.column :bar, :decimal, :precision => 8, :scale => 2
- end
-
- assert Person.connection.column_exists?(:testings, :foo, :string)
- assert !Person.connection.column_exists?(:testings, :foo, :integer)
- assert Person.connection.column_exists?(:testings, :bar, :decimal)
- assert !Person.connection.column_exists?(:testings, :bar, :integer)
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def test_column_exists_with_definition
- Person.connection.create_table :testings do |t|
- t.column :foo, :string, :limit => 100
- t.column :bar, :decimal, :precision => 8, :scale => 2
- end
-
- assert Person.connection.column_exists?(:testings, :foo, :string, :limit => 100)
- assert !Person.connection.column_exists?(:testings, :foo, :string, :limit => 50)
- assert Person.connection.column_exists?(:testings, :bar, :decimal, :precision => 8, :scale => 2)
- assert !Person.connection.column_exists?(:testings, :bar, :decimal, :precision => 10, :scale => 2)
- ensure
- Person.connection.drop_table :testings rescue nil
+ # confirm the custom sequence got dropped
+ assert_raise(ActiveRecord::StatementInvalid) do
+ Person.connection.execute("select suitably_short_seq.nextval from dual")
end
+ end
- def test_column_exists_on_table_with_no_options_parameter_supplied
- Person.connection.create_table :testings do |t|
- t.string :foo
- end
- Person.connection.change_table :testings do |t|
- assert t.column_exists?(:foo)
- assert !(t.column_exists?(:bar))
- end
+ protected
+ def with_env_tz(new_tz = 'US/Eastern')
+ old_tz, ENV['TZ'] = ENV['TZ'], new_tz
+ yield
ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def test_add_table
- assert !Reminder.table_exists?
-
- WeNeedReminders.up
-
- assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
- assert_equal "hello world", Reminder.find(:first).content
-
- WeNeedReminders.down
- assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
- end
-
- def test_add_table_with_decimals
- Person.connection.drop_table :big_numbers rescue nil
-
- assert !BigNumber.table_exists?
- GiveMeBigNumbers.up
-
- assert BigNumber.create(
- :bank_balance => 1586.43,
- :big_bank_balance => BigDecimal("1000234000567.95"),
- :world_population => 6000000000,
- :my_house_population => 3,
- :value_of_e => BigDecimal("2.7182818284590452353602875")
- )
-
- b = BigNumber.find(:first)
- assert_not_nil b
-
- assert_not_nil b.bank_balance
- assert_not_nil b.big_bank_balance
- assert_not_nil b.world_population
- assert_not_nil b.my_house_population
- assert_not_nil b.value_of_e
-
- # TODO: set world_population >= 2**62 to cover 64-bit platforms and test
- # is_a?(Bignum)
- assert_kind_of Integer, b.world_population
- assert_equal 6000000000, b.world_population
- assert_kind_of Fixnum, b.my_house_population
- assert_equal 3, b.my_house_population
- assert_kind_of BigDecimal, b.bank_balance
- assert_equal BigDecimal("1586.43"), b.bank_balance
- assert_kind_of BigDecimal, b.big_bank_balance
- assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance
-
- # This one is fun. The 'value_of_e' field is defined as 'DECIMAL' with
- # precision/scale explicitly left out. By the SQL standard, numbers
- # assigned to this field should be truncated but that's seldom respected.
- if current_adapter?(:PostgreSQLAdapter)
- # - PostgreSQL changes the SQL spec on columns declared simply as
- # "decimal" to something more useful: instead of being given a scale
- # of 0, they take on the compile-time limit for precision and scale,
- # so the following should succeed unless you have used really wacky
- # compilation options
- # - SQLite2 has the default behavior of preserving all data sent in,
- # so this happens there too
- assert_kind_of BigDecimal, b.value_of_e
- assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e
- elsif current_adapter?(:SQLite3Adapter)
- # - SQLite3 stores a float, in violation of SQL
- assert_kind_of BigDecimal, b.value_of_e
- assert_in_delta BigDecimal("2.71828182845905"), b.value_of_e, 0.00000000000001
- else
- # - SQL standard is an integer
- assert_kind_of Fixnum, b.value_of_e
- assert_equal 2, b.value_of_e
- end
-
- GiveMeBigNumbers.down
- assert_raise(ActiveRecord::StatementInvalid) { BigNumber.find(:first) }
- end
-
- def test_migrator
- assert !Person.column_methods_hash.include?(:last_name)
- assert !Reminder.table_exists?
-
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
-
- assert_equal 3, ActiveRecord::Migrator.current_version
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
- assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
- assert_equal "hello world", Reminder.find(:first).content
-
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid")
-
- assert_equal 0, ActiveRecord::Migrator.current_version
- Person.reset_column_information
- assert !Person.column_methods_hash.include?(:last_name)
- assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
- end
-
- def test_filtering_migrations
- assert !Person.column_methods_hash.include?(:last_name)
- assert !Reminder.table_exists?
-
- name_filter = lambda { |migration| migration.name == "ValidPeopleHaveLastNames" }
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", &name_filter)
-
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
- assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
-
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", &name_filter)
-
- Person.reset_column_information
- assert !Person.column_methods_hash.include?(:last_name)
- assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
- end
-
- class MockMigration < ActiveRecord::Migration
- attr_reader :went_up, :went_down
- def initialize
- @went_up = false
- @went_down = false
- end
-
- def up
- @went_up = true
- super
- end
-
- def down
- @went_down = true
- super
- end
+ old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
+end
- def test_instance_based_migration_up
- migration = MockMigration.new
- assert !migration.went_up, 'have not gone up'
- assert !migration.went_down, 'have not gone down'
-
- migration.migrate :up
- assert migration.went_up, 'have gone up'
- assert !migration.went_down, 'have not gone down'
+class ReservedWordsMigrationTest < ActiveRecord::TestCase
+ def test_drop_index_from_table_named_values
+ connection = Person.connection
+ connection.create_table :values, :force => true do |t|
+ t.integer :value
end
- def test_instance_based_migration_down
- migration = MockMigration.new
- assert !migration.went_up, 'have not gone up'
- assert !migration.went_down, 'have not gone down'
-
- migration.migrate :down
- assert !migration.went_up, 'have gone up'
- assert migration.went_down, 'have not gone down'
+ assert_nothing_raised do
+ connection.add_index :values, :value
+ connection.remove_index :values, :column => :value
end
- def test_migrator_one_up
- assert !Person.column_methods_hash.include?(:last_name)
- assert !Reminder.table_exists?
-
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
-
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
- assert !Reminder.table_exists?
-
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 2)
-
- assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
- assert_equal "hello world", Reminder.find(:first).content
- end
-
- def test_migrator_one_down
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
+ connection.drop_table :values rescue nil
+ end
+end
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1)
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
- assert !Reminder.table_exists?
+class ChangeTableMigrationsTest < ActiveRecord::TestCase
+ def setup
+ @connection = Person.connection
+ @connection.create_table :delete_me, :force => true do |t|
end
+ end
- def test_migrator_one_up_one_down
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
+ def teardown
+ Person.connection.drop_table :delete_me rescue nil
+ end
- assert !Person.column_methods_hash.include?(:last_name)
- assert !Reminder.table_exists?
+ def test_references_column_type_adds_id
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {})
+ t.references :customer
end
+ end
- def test_migrator_double_up
- assert_equal(0, ActiveRecord::Migrator.current_version)
- ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1)
- assert_nothing_raised { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1) }
- assert_equal(1, ActiveRecord::Migrator.current_version)
+ def test_remove_references_column_type_removes_id
+ with_change_table do |t|
+ @connection.expects(:remove_column).with(:delete_me, 'customer_id')
+ t.remove_references :customer
end
+ end
- def test_migrator_double_down
- assert_equal(0, ActiveRecord::Migrator.current_version)
- ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1)
- ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1)
- assert_nothing_raised { ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1) }
- assert_equal(0, ActiveRecord::Migrator.current_version)
+ def test_add_belongs_to_works_like_add_references
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {})
+ t.belongs_to :customer
end
+ end
- if ActiveRecord::Base.connection.supports_ddl_transactions?
- def test_migrator_one_up_with_exception_and_rollback
- assert !Person.column_methods_hash.include?(:last_name)
-
- e = assert_raise(StandardError) do
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/broken", 100)
- end
-
- assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message
-
- Person.reset_column_information
- assert !Person.column_methods_hash.include?(:last_name)
- end
+ def test_remove_belongs_to_works_like_remove_references
+ with_change_table do |t|
+ @connection.expects(:remove_column).with(:delete_me, 'customer_id')
+ t.remove_belongs_to :customer
end
+ end
- def test_finds_migrations
- migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid").migrations
-
- [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i|
- assert_equal migrations[i].version, pair.first
- assert_equal migrations[i].name, pair.last
- end
+ def test_references_column_type_with_polymorphic_adds_type
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {})
+ @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {})
+ t.references :taggable, :polymorphic => true
end
+ end
- def test_finds_migrations_in_subdirectories
- migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid_with_subdirectories").migrations
-
- [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i|
- assert_equal migrations[i].version, pair.first
- assert_equal migrations[i].name, pair.last
- end
+ def test_remove_references_column_type_with_polymorphic_removes_type
+ with_change_table do |t|
+ @connection.expects(:remove_column).with(:delete_me, 'taggable_type')
+ @connection.expects(:remove_column).with(:delete_me, 'taggable_id')
+ t.remove_references :taggable, :polymorphic => true
end
+ end
- def test_finds_migrations_from_two_directories
- directories = [MIGRATIONS_ROOT + '/valid_with_timestamps', MIGRATIONS_ROOT + '/to_copy_with_timestamps']
- migrations = ActiveRecord::Migrator.new(:up, directories).migrations
-
- [[20090101010101, "PeopleHaveHobbies"],
- [20090101010202, "PeopleHaveDescriptions"],
- [20100101010101, "ValidWithTimestampsPeopleHaveLastNames"],
- [20100201010101, "ValidWithTimestampsWeNeedReminders"],
- [20100301010101, "ValidWithTimestampsInnocentJointable"]].each_with_index do |pair, i|
- assert_equal pair.first, migrations[i].version
- assert_equal pair.last, migrations[i].name
- end
+ def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {:null => false})
+ @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {:null => false})
+ t.references :taggable, :polymorphic => true, :null => false
end
+ end
- def test_dump_schema_information_outputs_lexically_ordered_versions
- migration_path = MIGRATIONS_ROOT + '/valid_with_timestamps'
- ActiveRecord::Migrator.run(:up, migration_path, 20100301010101)
- ActiveRecord::Migrator.run(:up, migration_path, 20100201010101)
-
- schema_info = ActiveRecord::Base.connection.dump_schema_information
- assert_match(/20100201010101.*20100301010101/m, schema_info)
+ def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag
+ with_change_table do |t|
+ @connection.expects(:remove_column).with(:delete_me, 'taggable_type')
+ @connection.expects(:remove_column).with(:delete_me, 'taggable_id')
+ t.remove_references :taggable, :polymorphic => true, :null => false
end
+ end
- def test_finds_pending_migrations
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2", 1)
- migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/interleaved/pass_2").pending_migrations
-
- assert_equal 1, migrations.size
- assert_equal migrations[0].version, 3
- assert_equal migrations[0].name, 'InterleavedInnocentJointable'
+ def test_timestamps_creates_updated_at_and_created_at
+ with_change_table do |t|
+ @connection.expects(:add_timestamps).with(:delete_me)
+ t.timestamps
end
+ end
- def test_relative_migrations
- list = Dir.chdir(MIGRATIONS_ROOT) do
- ActiveRecord::Migrator.up("valid/", 1)
- end
-
- migration_proxy = list.find { |item|
- item.name == 'ValidPeopleHaveLastNames'
- }
- assert migration_proxy, 'should find pending migration'
+ def test_remove_timestamps_creates_updated_at_and_created_at
+ with_change_table do |t|
+ @connection.expects(:remove_timestamps).with(:delete_me)
+ t.remove_timestamps
end
+ end
- def test_only_loads_pending_migrations
- # migrate up to 1
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
-
- proxies = ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", nil)
-
- names = proxies.map(&:name)
- assert !names.include?('ValidPeopleHaveLastNames')
- assert names.include?('WeNeedReminders')
- assert names.include?('InnocentJointable')
+ def string_column
+ if current_adapter?(:PostgreSQLAdapter)
+ "character varying(255)"
+ elsif current_adapter?(:OracleAdapter)
+ 'VARCHAR2(255)'
+ else
+ 'varchar(255)'
end
+ end
- def test_target_version_zero_should_run_only_once
- # migrate up to 1
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1)
-
- # migrate down to 0
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0)
-
- # migrate down to 0 again
- proxies = ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0)
- assert_equal [], proxies
+ def integer_column
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ 'int(11)'
+ elsif current_adapter?(:OracleAdapter)
+ 'NUMBER(38)'
+ else
+ 'integer'
end
+ end
- def test_migrator_db_has_no_schema_migrations_table
- # Oracle adapter raises error if semicolon is present as last character
- if current_adapter?(:OracleAdapter)
- ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations")
- else
- ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations;")
- end
- assert_nothing_raised do
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1)
- end
+ def test_integer_creates_integer_column
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, :foo, integer_column, {})
+ @connection.expects(:add_column).with(:delete_me, :bar, integer_column, {})
+ t.integer :foo, :bar
end
+ end
- def test_migrator_verbosity
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
- assert_not_equal 0, ActiveRecord::Migration.message_count
- ActiveRecord::Migration.message_count = 0
-
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
- assert_not_equal 0, ActiveRecord::Migration.message_count
- ActiveRecord::Migration.message_count = 0
+ def test_string_creates_string_column
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, :foo, string_column, {})
+ @connection.expects(:add_column).with(:delete_me, :bar, string_column, {})
+ t.string :foo, :bar
end
+ end
- def test_migrator_verbosity_off
- ActiveRecord::Migration.verbose = false
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
- assert_equal 0, ActiveRecord::Migration.message_count
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
- assert_equal 0, ActiveRecord::Migration.message_count
+ def test_column_creates_column
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, :bar, :integer, {})
+ t.column :bar, :integer
end
+ end
- def test_migrator_going_down_due_to_version_target
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0)
-
- assert !Person.column_methods_hash.include?(:last_name)
- assert !Reminder.table_exists?
-
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
-
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
- assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
- assert_equal "hello world", Reminder.find(:first).content
+ def test_column_creates_column_with_options
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, :bar, :integer, {:null => false})
+ t.column :bar, :integer, :null => false
end
+ 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)
+ def test_index_creates_index
+ with_change_table do |t|
+ @connection.expects(:add_index).with(:delete_me, :bar, {})
+ t.index :bar
end
+ end
- def test_migrator_forward
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1)
- assert_equal(1, ActiveRecord::Migrator.current_version)
-
- ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid", 2)
- assert_equal(3, ActiveRecord::Migrator.current_version)
-
- ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid")
- assert_equal(3, ActiveRecord::Migrator.current_version)
+ def test_index_creates_index_with_options
+ with_change_table do |t|
+ @connection.expects(:add_index).with(:delete_me, :bar, {:unique => true})
+ t.index :bar, :unique => true
end
+ end
- def test_get_all_versions
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
- assert_equal([1,2,3], ActiveRecord::Migrator.get_all_versions)
-
- ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
- assert_equal([1,2], ActiveRecord::Migrator.get_all_versions)
-
- ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
- assert_equal([1], ActiveRecord::Migrator.get_all_versions)
-
- ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
- assert_equal([], ActiveRecord::Migrator.get_all_versions)
+ def test_index_exists
+ with_change_table do |t|
+ @connection.expects(:index_exists?).with(:delete_me, :bar, {})
+ t.index_exists?(:bar)
end
+ end
- def test_schema_migrations_table_name
- ActiveRecord::Base.table_name_prefix = "prefix_"
- ActiveRecord::Base.table_name_suffix = "_suffix"
- Reminder.reset_table_name
- assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name
- ActiveRecord::Base.table_name_prefix = ""
- ActiveRecord::Base.table_name_suffix = ""
- Reminder.reset_table_name
- assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name
- ensure
- ActiveRecord::Base.table_name_prefix = ""
- ActiveRecord::Base.table_name_suffix = ""
- end
-
- def test_proper_table_name
- assert_equal "table", ActiveRecord::Migrator.proper_table_name('table')
- assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table)
- assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder)
- Reminder.reset_table_name
- assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder)
-
- # Use the model's own prefix/suffix if a model is given
- ActiveRecord::Base.table_name_prefix = "ARprefix_"
- ActiveRecord::Base.table_name_suffix = "_ARsuffix"
- Reminder.table_name_prefix = 'prefix_'
- Reminder.table_name_suffix = '_suffix'
- Reminder.reset_table_name
- assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder)
- Reminder.table_name_prefix = ''
- Reminder.table_name_suffix = ''
- Reminder.reset_table_name
-
- # Use AR::Base's prefix/suffix if string or symbol is given
- ActiveRecord::Base.table_name_prefix = "prefix_"
- ActiveRecord::Base.table_name_suffix = "_suffix"
- Reminder.reset_table_name
- assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table')
- assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table)
- ActiveRecord::Base.table_name_prefix = ""
- ActiveRecord::Base.table_name_suffix = ""
- Reminder.reset_table_name
- end
-
- def test_rename_table_with_prefix_and_suffix
- assert !Thing.table_exists?
- ActiveRecord::Base.table_name_prefix = 'prefix_'
- ActiveRecord::Base.table_name_suffix = '_suffix'
- Thing.reset_table_name
- Thing.reset_sequence_name
- WeNeedThings.up
-
- assert Thing.create("content" => "hello world")
- assert_equal "hello world", Thing.find(:first).content
-
- RenameThings.up
- Thing.table_name = "prefix_awesome_things_suffix"
-
- assert_equal "hello world", Thing.find(:first).content
- ensure
- ActiveRecord::Base.table_name_prefix = ''
- ActiveRecord::Base.table_name_suffix = ''
- Thing.reset_table_name
- Thing.reset_sequence_name
- end
-
- def test_add_drop_table_with_prefix_and_suffix
- assert !Reminder.table_exists?
- ActiveRecord::Base.table_name_prefix = 'prefix_'
- ActiveRecord::Base.table_name_suffix = '_suffix'
- Reminder.reset_table_name
- Reminder.reset_sequence_name
- WeNeedReminders.up
- assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
- assert_equal "hello world", Reminder.find(:first).content
-
- WeNeedReminders.down
- assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
- ensure
- ActiveRecord::Base.table_name_prefix = ''
- ActiveRecord::Base.table_name_suffix = ''
- Reminder.reset_table_name
- Reminder.reset_sequence_name
+ def test_index_exists_with_options
+ with_change_table do |t|
+ @connection.expects(:index_exists?).with(:delete_me, :bar, {:unique => true})
+ t.index_exists?(:bar, :unique => true)
end
+ end
- def test_create_table_with_binary_column
- Person.connection.drop_table :binary_testings rescue nil
-
- assert_nothing_raised {
- Person.connection.create_table :binary_testings do |t|
- t.column "data", :binary, :null => false
- end
- }
-
- columns = Person.connection.columns(:binary_testings)
- data_column = columns.detect { |c| c.name == "data" }
-
- if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
- assert_equal '', data_column.default
- else
- assert_nil data_column.default
- end
-
- Person.connection.drop_table :binary_testings rescue nil
+ def test_change_changes_column
+ with_change_table do |t|
+ @connection.expects(:change_column).with(:delete_me, :bar, :string, {})
+ t.change :bar, :string
end
+ end
- def test_migrator_with_duplicates
- assert_raise(ActiveRecord::DuplicateMigrationVersionError) do
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate", nil)
- end
+ def test_change_changes_column_with_options
+ with_change_table do |t|
+ @connection.expects(:change_column).with(:delete_me, :bar, :string, {:null => true})
+ t.change :bar, :string, :null => true
end
+ end
- def test_migrator_with_duplicate_names
- assert_raise(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate_names", nil)
- end
+ def test_change_default_changes_column
+ with_change_table do |t|
+ @connection.expects(:change_column_default).with(:delete_me, :bar, :string)
+ t.change_default :bar, :string
end
+ end
- def test_migrator_with_missing_version_numbers
- assert_raise(ActiveRecord::UnknownMigrationVersionError) do
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 500)
- end
+ def test_remove_drops_single_column
+ with_change_table do |t|
+ @connection.expects(:remove_column).with(:delete_me, [:bar])
+ t.remove :bar
end
+ end
- def test_create_table_with_custom_sequence_name
- return unless current_adapter? :OracleAdapter
-
- # table name is 29 chars, the standard sequence name will
- # be 33 chars and should be shortened
- assert_nothing_raised do
- begin
- Person.connection.create_table :table_with_name_thats_just_ok do |t|
- t.column :foo, :string, :null => false
- end
- ensure
- Person.connection.drop_table :table_with_name_thats_just_ok rescue nil
- end
- end
-
- # should be all good w/ a custom sequence name
- assert_nothing_raised do
- begin
- Person.connection.create_table :table_with_name_thats_just_ok,
- :sequence_name => 'suitably_short_seq' do |t|
- t.column :foo, :string, :null => false
- end
-
- Person.connection.execute("select suitably_short_seq.nextval from dual")
-
- ensure
- Person.connection.drop_table :table_with_name_thats_just_ok,
- :sequence_name => 'suitably_short_seq' rescue nil
- end
- end
-
- # confirm the custom sequence got dropped
- assert_raise(ActiveRecord::StatementInvalid) do
- Person.connection.execute("select suitably_short_seq.nextval from dual")
- end
+ def test_remove_drops_multiple_columns
+ with_change_table do |t|
+ @connection.expects(:remove_column).with(:delete_me, [:bar, :baz])
+ t.remove :bar, :baz
end
-
- protected
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
- end
-
end
- class MigrationLoggerTest < ActiveRecord::TestCase
- def test_migration_should_be_run_without_logger
- previous_logger = ActiveRecord::Base.logger
- ActiveRecord::Base.logger = nil
- assert_nothing_raised do
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
- end
- ensure
- ActiveRecord::Base.logger = previous_logger
+ def test_remove_index_removes_index_with_options
+ with_change_table do |t|
+ @connection.expects(:remove_index).with(:delete_me, {:unique => true})
+ t.remove_index :unique => true
end
end
- class InterleavedMigrationsTest < ActiveRecord::TestCase
- def test_migrator_interleaved_migrations
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_1")
-
- assert_nothing_raised do
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2")
- end
-
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
-
- assert_nothing_raised do
- proxies = ActiveRecord::Migrator.down(
- MIGRATIONS_ROOT + "/interleaved/pass_3")
- names = proxies.map(&:name)
- assert names.include?('InterleavedPeopleHaveLastNames')
- assert names.include?('InterleavedInnocentJointable')
- end
+ def test_rename_renames_column
+ with_change_table do |t|
+ @connection.expects(:rename_column).with(:delete_me, :bar, :baz)
+ t.rename :bar, :baz
end
end
- class ReservedWordsMigrationTest < ActiveRecord::TestCase
- def test_drop_index_from_table_named_values
- connection = Person.connection
- connection.create_table :values, :force => true do |t|
- t.integer :value
- end
-
- assert_nothing_raised do
- connection.add_index :values, :value
- connection.remove_index :values, :column => :value
- end
-
- connection.drop_table :values rescue nil
+ protected
+ def with_change_table
+ Person.connection.change_table :delete_me do |t|
+ yield t
end
end
+end
-
- class ChangeTableMigrationsTest < ActiveRecord::TestCase
+if ActiveRecord::Base.connection.supports_bulk_alter?
+ class BulkAlterTableMigrationsTest < ActiveRecord::TestCase
def setup
@connection = Person.connection
- @connection.create_table :delete_me, :force => true do |t|
- end
+ @connection.create_table(:delete_me, :force => true) {|t| }
end
def teardown
- Person.connection.drop_table :delete_me rescue nil
+ Person.connection.drop_table(:delete_me) rescue nil
end
- def test_references_column_type_adds_id
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {})
- t.references :customer
+ def test_adding_multiple_columns
+ assert_queries(1) do
+ with_bulk_change_table do |t|
+ t.column :name, :string
+ t.string :qualification, :experience
+ t.integer :age, :default => 0
+ t.date :birthdate
+ t.timestamps
+ end
end
- end
- def test_remove_references_column_type_removes_id
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'customer_id')
- t.remove_references :customer
- end
+ assert_equal 8, columns.size
+ [:name, :qualification, :experience].each {|s| assert_equal :string, column(s).type }
+ assert_equal 0, column(:age).default
end
- def test_add_belongs_to_works_like_add_references
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {})
- t.belongs_to :customer
+ def test_removing_columns
+ with_bulk_change_table do |t|
+ t.string :qualification, :experience
end
- end
- def test_remove_belongs_to_works_like_remove_references
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'customer_id')
- t.remove_belongs_to :customer
- end
- end
+ [:qualification, :experience].each {|c| assert column(c) }
- def test_references_column_type_with_polymorphic_adds_type
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {})
- @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {})
- t.references :taggable, :polymorphic => true
+ assert_queries(1) do
+ with_bulk_change_table do |t|
+ t.remove :qualification, :experience
+ t.string :qualification_experience
+ end
end
- end
- def test_remove_references_column_type_with_polymorphic_removes_type
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'taggable_type')
- @connection.expects(:remove_column).with(:delete_me, 'taggable_id')
- t.remove_references :taggable, :polymorphic => true
- end
+ [:qualification, :experience].each {|c| assert ! column(c) }
+ assert column(:qualification_experience)
end
- def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {:null => false})
- @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {:null => false})
- t.references :taggable, :polymorphic => true, :null => false
+ def test_adding_indexes
+ with_bulk_change_table do |t|
+ t.string :username
+ t.string :name
+ t.integer :age
end
- end
- def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'taggable_type')
- @connection.expects(:remove_column).with(:delete_me, 'taggable_id')
- t.remove_references :taggable, :polymorphic => true, :null => false
+ # Adding an index fires a query every time to check if an index already exists or not
+ assert_queries(3) do
+ with_bulk_change_table do |t|
+ t.index :username, :unique => true, :name => :awesome_username_index
+ t.index [:name, :age]
+ end
end
- end
- def test_timestamps_creates_updated_at_and_created_at
- with_change_table do |t|
- @connection.expects(:add_timestamps).with(:delete_me)
- t.timestamps
- end
- end
+ assert_equal 2, indexes.size
- def test_remove_timestamps_creates_updated_at_and_created_at
- with_change_table do |t|
- @connection.expects(:remove_timestamps).with(:delete_me)
- t.remove_timestamps
- end
- end
+ name_age_index = index(:index_delete_me_on_name_and_age)
+ assert_equal ['name', 'age'].sort, name_age_index.columns.sort
+ assert ! name_age_index.unique
- def string_column
- if current_adapter?(:PostgreSQLAdapter)
- "character varying(255)"
- elsif current_adapter?(:OracleAdapter)
- 'VARCHAR2(255)'
- else
- 'varchar(255)'
- end
+ assert index(:awesome_username_index).unique
end
- def integer_column
- if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
- 'int(11)'
- elsif current_adapter?(:OracleAdapter)
- 'NUMBER(38)'
- else
- 'integer'
+ def test_removing_index
+ with_bulk_change_table do |t|
+ t.string :name
+ t.index :name
end
- end
- def test_integer_creates_integer_column
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :foo, integer_column, {})
- @connection.expects(:add_column).with(:delete_me, :bar, integer_column, {})
- t.integer :foo, :bar
- end
- end
+ assert index(:index_delete_me_on_name)
- def test_string_creates_string_column
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :foo, string_column, {})
- @connection.expects(:add_column).with(:delete_me, :bar, string_column, {})
- t.string :foo, :bar
- end
- end
-
- def test_column_creates_column
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :bar, :integer, {})
- t.column :bar, :integer
+ assert_queries(3) do
+ with_bulk_change_table do |t|
+ t.remove_index :name
+ t.index :name, :name => :new_name_index, :unique => true
+ end
end
- end
- def test_column_creates_column_with_options
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :bar, :integer, {:null => false})
- t.column :bar, :integer, :null => false
- end
- end
+ assert ! index(:index_delete_me_on_name)
- def test_index_creates_index
- with_change_table do |t|
- @connection.expects(:add_index).with(:delete_me, :bar, {})
- t.index :bar
- end
+ new_name_index = index(:new_name_index)
+ assert new_name_index.unique
end
- def test_index_creates_index_with_options
- with_change_table do |t|
- @connection.expects(:add_index).with(:delete_me, :bar, {:unique => true})
- t.index :bar, :unique => true
+ def test_changing_columns
+ with_bulk_change_table do |t|
+ t.string :name
+ t.date :birthdate
end
- end
- def test_index_exists
- with_change_table do |t|
- @connection.expects(:index_exists?).with(:delete_me, :bar, {})
- t.index_exists?(:bar)
- end
- end
+ assert ! column(:name).default
+ assert_equal :date, column(:birthdate).type
- def test_index_exists_with_options
- with_change_table do |t|
- @connection.expects(:index_exists?).with(:delete_me, :bar, {:unique => true})
- t.index_exists?(:bar, :unique => true)
+ # One query for columns (delete_me table)
+ # One query for primary key (delete_me table)
+ # One query to do the bulk change
+ assert_queries(3) do
+ with_bulk_change_table do |t|
+ t.change :name, :string, :default => 'NONAME'
+ t.change :birthdate, :datetime
+ end
end
- end
- def test_change_changes_column
- with_change_table do |t|
- @connection.expects(:change_column).with(:delete_me, :bar, :string, {})
- t.change :bar, :string
- end
+ assert_equal 'NONAME', column(:name).default
+ assert_equal :datetime, column(:birthdate).type
end
- def test_change_changes_column_with_options
- with_change_table do |t|
- @connection.expects(:change_column).with(:delete_me, :bar, :string, {:null => true})
- t.change :bar, :string, :null => true
- end
- end
+ protected
- def test_change_default_changes_column
- with_change_table do |t|
- @connection.expects(:change_column_default).with(:delete_me, :bar, :string)
- t.change_default :bar, :string
- end
- end
+ def with_bulk_change_table
+ # Reset columns/indexes cache as we're changing the table
+ @columns = @indexes = nil
- def test_remove_drops_single_column
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, [:bar])
- t.remove :bar
+ Person.connection.change_table(:delete_me, :bulk => true) do |t|
+ yield t
end
end
- def test_remove_drops_multiple_columns
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, [:bar, :baz])
- t.remove :bar, :baz
- end
+ def column(name)
+ columns.detect {|c| c.name == name.to_s }
end
- def test_remove_index_removes_index_with_options
- with_change_table do |t|
- @connection.expects(:remove_index).with(:delete_me, {:unique => true})
- t.remove_index :unique => true
- end
+ def columns
+ @columns ||= Person.connection.columns('delete_me')
end
- def test_rename_renames_column
- with_change_table do |t|
- @connection.expects(:rename_column).with(:delete_me, :bar, :baz)
- t.rename :bar, :baz
- end
+ def index(name)
+ indexes.detect {|i| i.name == name.to_s }
end
- protected
- def with_change_table
- Person.connection.change_table :delete_me do |t|
- yield t
- end
+ def indexes
+ @indexes ||= Person.connection.indexes('delete_me')
end
- end
-
- if ActiveRecord::Base.connection.supports_bulk_alter?
- class BulkAlterTableMigrationsTest < ActiveRecord::TestCase
- def setup
- @connection = Person.connection
- @connection.create_table(:delete_me, :force => true) {|t| }
- end
-
- def teardown
- Person.connection.drop_table(:delete_me) rescue nil
- end
-
- def test_adding_multiple_columns
- assert_queries(1) do
- with_bulk_change_table do |t|
- t.column :name, :string
- t.string :qualification, :experience
- t.integer :age, :default => 0
- t.date :birthdate
- t.timestamps
- end
- end
-
- assert_equal 8, columns.size
- [:name, :qualification, :experience].each {|s| assert_equal :string, column(s).type }
- assert_equal 0, column(:age).default
- end
-
- def test_removing_columns
- with_bulk_change_table do |t|
- t.string :qualification, :experience
- end
-
- [:qualification, :experience].each {|c| assert column(c) }
-
- assert_queries(1) do
- with_bulk_change_table do |t|
- t.remove :qualification, :experience
- t.string :qualification_experience
- end
- end
-
- [:qualification, :experience].each {|c| assert ! column(c) }
- assert column(:qualification_experience)
- end
-
- def test_adding_indexes
- with_bulk_change_table do |t|
- t.string :username
- t.string :name
- t.integer :age
- end
-
- # Adding an index fires a query every time to check if an index already exists or not
- assert_queries(3) do
- with_bulk_change_table do |t|
- t.index :username, :unique => true, :name => :awesome_username_index
- t.index [:name, :age]
- end
- end
-
- assert_equal 2, indexes.size
-
- name_age_index = index(:index_delete_me_on_name_and_age)
- assert_equal ['name', 'age'].sort, name_age_index.columns.sort
- assert ! name_age_index.unique
-
- assert index(:awesome_username_index).unique
- end
-
- def test_removing_index
- with_bulk_change_table do |t|
- t.string :name
- t.index :name
- end
-
- assert index(:index_delete_me_on_name)
-
- assert_queries(3) do
- with_bulk_change_table do |t|
- t.remove_index :name
- t.index :name, :name => :new_name_index, :unique => true
- end
- end
+ end # AlterTableMigrationsTest
- assert ! index(:index_delete_me_on_name)
-
- new_name_index = index(:new_name_index)
- assert new_name_index.unique
- end
-
- def test_changing_columns
- with_bulk_change_table do |t|
- t.string :name
- t.date :birthdate
- end
-
- assert ! column(:name).default
- assert_equal :date, column(:birthdate).type
-
- # One query for columns (delete_me table)
- # One query for primary key (delete_me table)
- # One query to do the bulk change
- assert_queries(3) do
- with_bulk_change_table do |t|
- t.change :name, :string, :default => 'NONAME'
- t.change :birthdate, :datetime
- end
- end
-
- assert_equal 'NONAME', column(:name).default
- assert_equal :datetime, column(:birthdate).type
- end
-
- protected
-
- def with_bulk_change_table
- # Reset columns/indexes cache as we're changing the table
- @columns = @indexes = nil
-
- Person.connection.change_table(:delete_me, :bulk => true) do |t|
- yield t
- end
- end
-
- def column(name)
- columns.detect {|c| c.name == name.to_s }
- end
-
- def columns
- @columns ||= Person.connection.columns('delete_me')
- end
-
- def index(name)
- indexes.detect {|i| i.name == name.to_s }
- end
-
- def indexes
- @indexes ||= Person.connection.indexes('delete_me')
- end
- end # AlterTableMigrationsTest
+end
+class CopyMigrationsTest < ActiveRecord::TestCase
+ def setup
end
- class CopyMigrationsTest < ActiveRecord::TestCase
- def setup
- end
+ def clear
+ ActiveRecord::Base.timestamped_migrations = true
+ to_delete = Dir[@migrations_path + "/*.rb"] - @existing_migrations
+ File.delete(*to_delete)
+ end
- def clear
- ActiveRecord::Base.timestamped_migrations = true
- to_delete = Dir[@migrations_path + "/*.rb"] - @existing_migrations
- File.delete(*to_delete)
- end
+ def test_copying_migrations_without_timestamps
+ ActiveRecord::Base.timestamped_migrations = false
+ @migrations_path = MIGRATIONS_ROOT + "/valid"
+ @existing_migrations = Dir[@migrations_path + "/*.rb"]
+
+ copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"})
+ assert File.exists?(@migrations_path + "/4_people_have_hobbies.bukkits.rb")
+ assert File.exists?(@migrations_path + "/5_people_have_descriptions.bukkits.rb")
+ assert_equal [@migrations_path + "/4_people_have_hobbies.bukkits.rb", @migrations_path + "/5_people_have_descriptions.bukkits.rb"], copied.map(&:filename)
+
+ expected = "# This migration comes from bukkits (originally 1)"
+ assert_equal expected, IO.readlines(@migrations_path + "/4_people_have_hobbies.bukkits.rb")[0].chomp
+
+ files_count = Dir[@migrations_path + "/*.rb"].length
+ copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"})
+ assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
+ assert copied.empty?
+ ensure
+ clear
+ end
- def test_copying_migrations_without_timestamps
- ActiveRecord::Base.timestamped_migrations = false
- @migrations_path = MIGRATIONS_ROOT + "/valid"
- @existing_migrations = Dir[@migrations_path + "/*.rb"]
+ def test_copying_migrations_without_timestamps_from_2_sources
+ ActiveRecord::Base.timestamped_migrations = false
+ @migrations_path = MIGRATIONS_ROOT + "/valid"
+ @existing_migrations = Dir[@migrations_path + "/*.rb"]
+
+ sources = {}
+ sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy"
+ sources[:omg] = MIGRATIONS_ROOT + "/to_copy2"
+ ActiveRecord::Migration.copy(@migrations_path, sources)
+ assert File.exists?(@migrations_path + "/4_people_have_hobbies.bukkits.rb")
+ assert File.exists?(@migrations_path + "/5_people_have_descriptions.bukkits.rb")
+ assert File.exists?(@migrations_path + "/6_create_articles.omg.rb")
+ assert File.exists?(@migrations_path + "/7_create_comments.omg.rb")
+
+ files_count = Dir[@migrations_path + "/*.rb"].length
+ ActiveRecord::Migration.copy(@migrations_path, sources)
+ assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
+ ensure
+ clear
+ end
- copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"})
- assert File.exists?(@migrations_path + "/4_people_have_hobbies.bukkits.rb")
- assert File.exists?(@migrations_path + "/5_people_have_descriptions.bukkits.rb")
- assert_equal [@migrations_path + "/4_people_have_hobbies.bukkits.rb", @migrations_path + "/5_people_have_descriptions.bukkits.rb"], copied.map(&:filename)
+ def test_copying_migrations_with_timestamps
+ @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
+ @existing_migrations = Dir[@migrations_path + "/*.rb"]
- expected = "# This migration comes from bukkits (originally 1)"
- assert_equal expected, IO.readlines(@migrations_path + "/4_people_have_hobbies.bukkits.rb")[0].chomp
+ Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
+ copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
+ assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
+ assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
+ expected = [@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb",
+ @migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb"]
+ assert_equal expected, copied.map(&:filename)
files_count = Dir[@migrations_path + "/*.rb"].length
- copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"})
+ copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
assert copied.empty?
- ensure
- clear
end
+ ensure
+ clear
+ end
- def test_copying_migrations_without_timestamps_from_2_sources
- ActiveRecord::Base.timestamped_migrations = false
- @migrations_path = MIGRATIONS_ROOT + "/valid"
- @existing_migrations = Dir[@migrations_path + "/*.rb"]
+ def test_copying_migrations_with_timestamps_from_2_sources
+ @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
+ @existing_migrations = Dir[@migrations_path + "/*.rb"]
- sources = ActiveSupport::OrderedHash.new
- sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy"
- sources[:omg] = MIGRATIONS_ROOT + "/to_copy2"
- ActiveRecord::Migration.copy(@migrations_path, sources)
- assert File.exists?(@migrations_path + "/4_people_have_hobbies.bukkits.rb")
- assert File.exists?(@migrations_path + "/5_people_have_descriptions.bukkits.rb")
- assert File.exists?(@migrations_path + "/6_create_articles.omg.rb")
- assert File.exists?(@migrations_path + "/7_create_comments.omg.rb")
+ sources = {}
+ sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps"
+ sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps2"
+
+ Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
+ copied = ActiveRecord::Migration.copy(@migrations_path, sources)
+ assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
+ assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
+ assert File.exists?(@migrations_path + "/20100726101012_create_articles.omg.rb")
+ assert File.exists?(@migrations_path + "/20100726101013_create_comments.omg.rb")
+ assert_equal 4, copied.length
files_count = Dir[@migrations_path + "/*.rb"].length
ActiveRecord::Migration.copy(@migrations_path, sources)
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- ensure
- clear
end
+ ensure
+ clear
+ end
- def test_copying_migrations_with_timestamps
- @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
- @existing_migrations = Dir[@migrations_path + "/*.rb"]
+ def test_copying_migrations_with_timestamps_to_destination_with_timestamps_in_future
+ @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
+ @existing_migrations = Dir[@migrations_path + "/*.rb"]
- Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
- copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
- assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
- assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
- expected = [@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb",
- @migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb"]
- assert_equal expected, copied.map(&:filename)
+ Time.travel_to(Time.utc(2010, 2, 20, 10, 10, 10)) do
+ ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
+ assert File.exists?(@migrations_path + "/20100301010102_people_have_hobbies.bukkits.rb")
+ assert File.exists?(@migrations_path + "/20100301010103_people_have_descriptions.bukkits.rb")
- files_count = Dir[@migrations_path + "/*.rb"].length
- copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
- assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- assert copied.empty?
- end
- ensure
- clear
+ files_count = Dir[@migrations_path + "/*.rb"].length
+ copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
+ assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
+ assert copied.empty?
end
+ ensure
+ clear
+ end
- def test_copying_migrations_with_timestamps_from_2_sources
- @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
- @existing_migrations = Dir[@migrations_path + "/*.rb"]
-
- sources = ActiveSupport::OrderedHash.new
- sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps"
- sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps2"
+ def test_skipping_migrations
+ @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
+ @existing_migrations = Dir[@migrations_path + "/*.rb"]
+
+ sources = {}
+ sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps"
+ sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_name_collision"
+
+ skipped = []
+ on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" }
+ copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip)
+ assert_equal 2, copied.length
+
+ assert_equal 1, skipped.length
+ assert_equal ["omg PeopleHaveHobbies"], skipped
+ ensure
+ clear
+ end
- Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
- copied = ActiveRecord::Migration.copy(@migrations_path, sources)
- assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
- assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
- assert File.exists?(@migrations_path + "/20100726101012_create_articles.omg.rb")
- assert File.exists?(@migrations_path + "/20100726101013_create_comments.omg.rb")
- assert_equal 4, copied.length
+ def test_skip_is_not_called_if_migrations_are_from_the_same_plugin
+ @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
+ @existing_migrations = Dir[@migrations_path + "/*.rb"]
- files_count = Dir[@migrations_path + "/*.rb"].length
- ActiveRecord::Migration.copy(@migrations_path, sources)
- assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- end
- ensure
- clear
- end
+ sources = {}
+ sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps"
- def test_copying_migrations_with_timestamps_to_destination_with_timestamps_in_future
- @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
- @existing_migrations = Dir[@migrations_path + "/*.rb"]
+ skipped = []
+ on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" }
+ copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip)
+ ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip)
- Time.travel_to(Time.utc(2010, 2, 20, 10, 10, 10)) do
- ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
- assert File.exists?(@migrations_path + "/20100301010102_people_have_hobbies.bukkits.rb")
- assert File.exists?(@migrations_path + "/20100301010103_people_have_descriptions.bukkits.rb")
-
- files_count = Dir[@migrations_path + "/*.rb"].length
- copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
- assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- assert copied.empty?
- end
- ensure
- clear
- end
-
- def test_skipping_migrations
- @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
- @existing_migrations = Dir[@migrations_path + "/*.rb"]
+ assert_equal 2, copied.length
+ assert_equal 0, skipped.length
+ ensure
+ clear
+ end
- sources = ActiveSupport::OrderedHash.new
- sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps"
- sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_name_collision"
+ def test_copying_migrations_to_non_existing_directory
+ @migrations_path = MIGRATIONS_ROOT + "/non_existing"
+ @existing_migrations = []
- skipped = []
- on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" }
- copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip)
+ Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
+ copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
+ assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
+ assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
assert_equal 2, copied.length
-
- assert_equal 1, skipped.length
- assert_equal ["omg PeopleHaveHobbies"], skipped
- ensure
- clear
end
+ ensure
+ clear
+ Dir.delete(@migrations_path)
+ end
- def test_skip_is_not_called_if_migrations_are_from_the_same_plugin
- @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
- @existing_migrations = Dir[@migrations_path + "/*.rb"]
-
- sources = ActiveSupport::OrderedHash.new
- sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps"
-
- skipped = []
- on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" }
- copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip)
- ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip)
+ def test_copying_migrations_to_empty_directory
+ @migrations_path = MIGRATIONS_ROOT + "/empty"
+ @existing_migrations = []
+ Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
+ copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
+ assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
+ assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
assert_equal 2, copied.length
- assert_equal 0, skipped.length
- ensure
- clear
- end
-
- def test_copying_migrations_to_non_existing_directory
- @migrations_path = MIGRATIONS_ROOT + "/non_existing"
- @existing_migrations = []
-
- Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
- copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
- assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
- assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
- assert_equal 2, copied.length
- end
- ensure
- clear
- Dir.delete(@migrations_path)
- end
-
- def test_copying_migrations_to_empty_directory
- @migrations_path = MIGRATIONS_ROOT + "/empty"
- @existing_migrations = []
-
- Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
- copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
- assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
- assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
- assert_equal 2, copied.length
- end
- ensure
- clear
end
+ ensure
+ clear
end
end
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
new file mode 100644
index 0000000000..1e16addcf3
--- /dev/null
+++ b/activerecord/test/cases/migrator_test.rb
@@ -0,0 +1,377 @@
+require "cases/helper"
+require "cases/migration/helper"
+
+module ActiveRecord
+ class MigratorTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ # Use this class to sense if migrations have gone
+ # up or down.
+ class Sensor < ActiveRecord::Migration
+ attr_reader :went_up, :went_down
+
+ def initialize name = self.class.name, version = nil
+ super
+ @went_up = false
+ @went_down = false
+ end
+
+ def up; @went_up = true; end
+ def down; @went_down = true; end
+ end
+
+ def setup
+ super
+ ActiveRecord::SchemaMigration.create_table
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ end
+
+ def teardown
+ super
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ end
+
+ def test_migrator_with_duplicate_names
+ assert_raises(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do
+ list = [Migration.new('Chunky'), Migration.new('Chunky')]
+ ActiveRecord::Migrator.new(:up, list)
+ end
+ end
+
+ def test_migrator_with_duplicate_versions
+ assert_raises(ActiveRecord::DuplicateMigrationVersionError) do
+ list = [Migration.new('Foo', 1), Migration.new('Bar', 1)]
+ ActiveRecord::Migrator.new(:up, list)
+ end
+ end
+
+ def test_migrator_with_missing_version_numbers
+ assert_raises(ActiveRecord::UnknownMigrationVersionError) do
+ list = [Migration.new('Foo', 1), Migration.new('Bar', 2)]
+ ActiveRecord::Migrator.new(:up, list, 3).run
+ end
+ end
+
+ def test_finds_migrations
+ migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid")
+
+ [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i|
+ assert_equal migrations[i].version, pair.first
+ assert_equal migrations[i].name, pair.last
+ end
+ end
+
+ def test_finds_migrations_in_subdirectories
+ migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid_with_subdirectories")
+
+ [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i|
+ assert_equal migrations[i].version, pair.first
+ assert_equal migrations[i].name, pair.last
+ end
+ end
+
+ def test_finds_migrations_from_two_directories
+ directories = [MIGRATIONS_ROOT + '/valid_with_timestamps', MIGRATIONS_ROOT + '/to_copy_with_timestamps']
+ migrations = ActiveRecord::Migrator.migrations directories
+
+ [[20090101010101, "PeopleHaveHobbies"],
+ [20090101010202, "PeopleHaveDescriptions"],
+ [20100101010101, "ValidWithTimestampsPeopleHaveLastNames"],
+ [20100201010101, "ValidWithTimestampsWeNeedReminders"],
+ [20100301010101, "ValidWithTimestampsInnocentJointable"]].each_with_index do |pair, i|
+ assert_equal pair.first, migrations[i].version
+ assert_equal pair.last, migrations[i].name
+ end
+ end
+
+ def test_deprecated_constructor
+ assert_deprecated do
+ ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid")
+ end
+ end
+
+ def test_relative_migrations
+ list = Dir.chdir(MIGRATIONS_ROOT) do
+ ActiveRecord::Migrator.migrations("valid/")
+ end
+
+ migration_proxy = list.find { |item|
+ item.name == 'ValidPeopleHaveLastNames'
+ }
+ assert migration_proxy, 'should find pending migration'
+ end
+
+ def test_finds_pending_migrations
+ ActiveRecord::SchemaMigration.create!(:version => '1')
+ migration_list = [ Migration.new('foo', 1), Migration.new('bar', 3) ]
+ migrations = ActiveRecord::Migrator.new(:up, migration_list).pending_migrations
+
+ assert_equal 1, migrations.size
+ assert_equal migration_list.last, migrations.first
+ end
+
+ def test_migrator_interleaved_migrations
+ pass_one = [Sensor.new('One', 1)]
+
+ ActiveRecord::Migrator.new(:up, pass_one).migrate
+ assert pass_one.first.went_up
+ refute pass_one.first.went_down
+
+ pass_two = [Sensor.new('One', 1), Sensor.new('Three', 3)]
+ ActiveRecord::Migrator.new(:up, pass_two).migrate
+ refute pass_two[0].went_up
+ assert pass_two[1].went_up
+ assert pass_two.all? { |x| !x.went_down }
+
+ pass_three = [Sensor.new('One', 1),
+ Sensor.new('Two', 2),
+ Sensor.new('Three', 3)]
+
+ ActiveRecord::Migrator.new(:down, pass_three).migrate
+ assert pass_three[0].went_down
+ refute pass_three[1].went_down
+ assert pass_three[2].went_down
+ end
+
+ def test_up_calls_up
+ migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
+ ActiveRecord::Migrator.new(:up, migrations).migrate
+ assert migrations.all? { |m| m.went_up }
+ assert migrations.all? { |m| !m.went_down }
+ assert_equal 2, ActiveRecord::Migrator.current_version
+ end
+
+ def test_down_calls_down
+ test_up_calls_up
+
+ migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
+ ActiveRecord::Migrator.new(:down, migrations).migrate
+ assert migrations.all? { |m| !m.went_up }
+ assert migrations.all? { |m| m.went_down }
+ assert_equal 0, ActiveRecord::Migrator.current_version
+ end
+
+ def test_current_version
+ ActiveRecord::SchemaMigration.create!(:version => '1000')
+ assert_equal 1000, ActiveRecord::Migrator.current_version
+ end
+
+ def test_migrator_one_up
+ calls, migrations = sensors(3)
+
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ ActiveRecord::Migrator.new(:up, migrations, 2).migrate
+ assert_equal [[:up, 2]], calls
+ end
+
+ def test_migrator_one_down
+ calls, migrations = sensors(3)
+
+ ActiveRecord::Migrator.new(:up, migrations).migrate
+ assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls
+ calls.clear
+
+ ActiveRecord::Migrator.new(:down, migrations, 1).migrate
+
+ assert_equal [[:down, 3], [:down, 2]], calls
+ end
+
+ def test_migrator_one_up_one_down
+ calls, migrations = sensors(3)
+
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ assert_equal [[:down, 1]], calls
+ end
+
+ def test_migrator_double_up
+ calls, migrations = sensors(3)
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal [], calls
+ end
+
+ def test_migrator_double_down
+ calls, migrations = sensors(3)
+
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+
+ ActiveRecord::Migrator.new(:up, migrations, 1).run
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ ActiveRecord::Migrator.new(:down, migrations, 1).run
+ assert_equal [[:down, 1]], calls
+ calls.clear
+
+ ActiveRecord::Migrator.new(:down, migrations, 1).run
+ assert_equal [], calls
+
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+ end
+
+ def test_migrator_verbosity
+ _, migrations = sensors(3)
+
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_not_equal 0, ActiveRecord::Migration.message_count
+
+ ActiveRecord::Migration.message_count = 0
+
+ ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ assert_not_equal 0, ActiveRecord::Migration.message_count
+ ActiveRecord::Migration.message_count = 0
+ end
+
+ def test_migrator_verbosity_off
+ _, migrations = sensors(3)
+
+ ActiveRecord::Migration.message_count = 0
+ ActiveRecord::Migration.verbose = false
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal 0, ActiveRecord::Migration.message_count
+ ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ assert_equal 0, ActiveRecord::Migration.message_count
+ end
+
+ def test_target_version_zero_should_run_only_once
+ calls, migrations = sensors(3)
+
+ # migrate up to 1
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ # migrate down to 0
+ ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ assert_equal [[:down, 1]], calls
+ calls.clear
+
+ # migrate down to 0 again
+ ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ assert_equal [], calls
+ end
+
+ def test_migrator_going_down_due_to_version_target
+ calls, migrator = migrator_class(3)
+
+ migrator.up("valid", 1)
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ migrator.migrate("valid", 0)
+ assert_equal [[:down, 1]], calls
+ calls.clear
+
+ migrator.migrate("valid")
+ assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls
+ end
+
+ def test_migrator_rollback
+ _, migrator = migrator_class(3)
+
+ migrator.migrate("valid")
+ assert_equal(3, ActiveRecord::Migrator.current_version)
+
+ migrator.rollback("valid")
+ assert_equal(2, ActiveRecord::Migrator.current_version)
+
+ migrator.rollback("valid")
+ assert_equal(1, ActiveRecord::Migrator.current_version)
+
+ migrator.rollback("valid")
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+
+ migrator.rollback("valid")
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+ end
+
+ def test_migrator_db_has_no_schema_migrations_table
+ _, migrator = migrator_class(3)
+
+ ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations")
+ refute ActiveRecord::Base.connection.table_exists?('schema_migrations')
+ migrator.migrate("valid", 1)
+ assert ActiveRecord::Base.connection.table_exists?('schema_migrations')
+ end
+
+ def test_migrator_forward
+ _, migrator = migrator_class(3)
+ migrator.migrate("/valid", 1)
+ assert_equal(1, ActiveRecord::Migrator.current_version)
+
+ migrator.forward("/valid", 2)
+ assert_equal(3, ActiveRecord::Migrator.current_version)
+
+ migrator.forward("/valid")
+ assert_equal(3, ActiveRecord::Migrator.current_version)
+ end
+
+ def test_only_loads_pending_migrations
+ # migrate up to 1
+ ActiveRecord::SchemaMigration.create!(:version => '1')
+
+ calls, migrator = migrator_class(3)
+ migrator.migrate("valid", nil)
+
+ assert_equal [[:up, 2], [:up, 3]], calls
+ end
+
+ def test_get_all_versions
+ _, migrator = migrator_class(3)
+
+ migrator.migrate("valid")
+ assert_equal([1,2,3], ActiveRecord::Migrator.get_all_versions)
+
+ migrator.rollback("valid")
+ assert_equal([1,2], ActiveRecord::Migrator.get_all_versions)
+
+ migrator.rollback("valid")
+ assert_equal([1], ActiveRecord::Migrator.get_all_versions)
+
+ migrator.rollback("valid")
+ assert_equal([], ActiveRecord::Migrator.get_all_versions)
+ end
+
+ private
+ def m(name, version, &block)
+ x = Sensor.new name, version
+ x.extend(Module.new {
+ define_method(:up) { block.call(:up, x); super() }
+ define_method(:down) { block.call(:down, x); super() }
+ }) if block_given?
+ end
+
+ def sensors(count)
+ calls = []
+ migrations = count.times.map { |i|
+ m(nil, i + 1) { |c,migration|
+ calls << [c, migration.version]
+ }
+ }
+ [calls, migrations]
+ end
+
+ def migrator_class(count)
+ calls, migrations = sensors(count)
+
+ migrator = Class.new(Migrator).extend(Module.new {
+ define_method(:migrations) { |paths|
+ migrations
+ }
+ })
+ [calls, migrator]
+ end
+ end
+end
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index 72d96f2bab..f7a5d05582 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -72,7 +72,7 @@ class ModulesTest < ActiveRecord::TestCase
clients = []
assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do
- clients << MyApplication::Business::Client.find(3, :eager_load => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL')
+ clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL', :references => :accounts)
clients << MyApplication::Business::Client.find(3, :include => {:firm => :account})
end
diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb
index e704322b5d..a802cfbf31 100644
--- a/activerecord/test/cases/multiple_db_test.rb
+++ b/activerecord/test/cases/multiple_db_test.rb
@@ -10,6 +10,7 @@ class MultipleDbTest < ActiveRecord::TestCase
def setup
@courses = create_fixtures("courses") { Course.retrieve_connection }
+ @colleges = create_fixtures("colleges") { College.retrieve_connection }
@entrants = create_fixtures("entrants")
end
@@ -87,4 +88,15 @@ class MultipleDbTest < ActiveRecord::TestCase
def test_arel_table_engines
assert_equal Entrant.arel_engine, Bird.arel_engine
end
+
+ def test_associations_should_work_when_model_has_no_connection
+ begin
+ ActiveRecord::Model.remove_connection
+ assert_nothing_raised ActiveRecord::ConnectionNotEstablished do
+ College.first.courses.first
+ end
+ ensure
+ ActiveRecord::Model.establish_connection 'arunit'
+ end
+ end
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index b4d1a631fa..e6e50a4cd4 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -200,3 +200,19 @@ class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase
assert_equal 'foo', model.primary_key
end
end
+
+if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ def test_primaery_key_method_with_ansi_quotes
+ con = ActiveRecord::Base.connection
+ con.execute("SET SESSION sql_mode='ANSI_QUOTES'")
+ assert_equal "id", con.primary_key("topics")
+ ensure
+ con.reconnect!
+ end
+
+ end
+end
+
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 9554386dcf..f36f4237b1 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -3,7 +3,7 @@ require 'models/topic'
require 'models/task'
require 'models/category'
require 'models/post'
-
+require 'rack'
class QueryCacheTest < ActiveRecord::TestCase
fixtures :tasks, :topics, :categories, :posts, :categories_posts
@@ -43,6 +43,7 @@ class QueryCacheTest < ActiveRecord::TestCase
called = false
mw = ActiveRecord::QueryCache.new lambda { |env|
called = true
+ [200, {}, nil]
}
mw.call({})
assert called, 'middleware should delegate'
@@ -53,6 +54,7 @@ class QueryCacheTest < ActiveRecord::TestCase
Task.find 1
Task.find 1
assert_equal 1, ActiveRecord::Base.connection.query_cache.length
+ [200, {}, nil]
}
mw.call({})
end
@@ -62,6 +64,7 @@ class QueryCacheTest < ActiveRecord::TestCase
mw = ActiveRecord::QueryCache.new lambda { |env|
assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on'
+ [200, {}, nil]
}
mw.call({})
end
@@ -83,7 +86,7 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_cache_off_after_close
- mw = ActiveRecord::QueryCache.new lambda { |env| }
+ mw = ActiveRecord::QueryCache.new lambda { |env| [200, {}, nil] }
body = mw.call({}).last
assert ActiveRecord::Base.connection.query_cache_enabled, 'cache enabled'
@@ -94,6 +97,7 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_cache_clear_after_close
mw = ActiveRecord::QueryCache.new lambda { |env|
Post.find(:first)
+ [200, {}, nil]
}
body = mw.call({}).last
@@ -244,14 +248,3 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
end
end
end
-
-class QueryCacheBodyProxyTest < ActiveRecord::TestCase
-
- test "is polite to it's body and responds to it" do
- body = Class.new(String) { def to_path; "/path"; end }.new
- proxy = ActiveRecord::QueryCache::BodyProxy.new(nil, body, ActiveRecord::Base.connection_id)
- assert proxy.respond_to?(:to_path)
- assert_equal proxy.to_path, "/path"
- end
-
-end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 7fd15027eb..16f05f2198 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -189,8 +189,8 @@ class ReflectionTest < ActiveRecord::TestCase
def test_reflection_of_all_associations
# FIXME these assertions bust a lot
- assert_equal 37, Firm.reflect_on_all_associations.size
- assert_equal 27, Firm.reflect_on_all_associations(:has_many).size
+ assert_equal 39, Firm.reflect_on_all_associations.size
+ assert_equal 29, Firm.reflect_on_all_associations(:has_many).size
assert_equal 10, 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/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 52dfea8fac..edf38cb7a3 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -105,8 +105,8 @@ class RelationScopingTest < ActiveRecord::TestCase
def test_scoped_find_include
# with the include, will retrieve only developers for the given project
- scoped_developers = Developer.eager_load(:projects).scoping do
- Developer.where('projects.id = 2').all
+ scoped_developers = Developer.includes(:projects).scoping do
+ Developer.where('projects.id' => 2).all
end
assert scoped_developers.include?(developers(:david))
assert !scoped_developers.include?(developers(:jamis))
@@ -528,7 +528,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
d = DeveloperWithIncludes.create!
d.audit_logs.create! :message => 'foo'
- assert_equal 1, DeveloperWithIncludes.eager_load(:audit_logs).where(:audit_logs => { :message => 'foo' }).count
+ assert_equal 1, DeveloperWithIncludes.where(:audit_logs => { :message => 'foo' }).count
end
def test_default_scope_is_threadsafe
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index e9a3b0419c..ac6dee3c6a 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -44,7 +44,7 @@ module ActiveRecord
end
def test_multi_value_methods
- assert_equal [:select, :group, :order, :joins, :where, :having, :bind].map(&:to_s).sort,
+ assert_equal [:select, :group, :order, :joins, :where, :having, :bind, :references].map(&:to_s).sort,
Relation::MULTI_VALUE_METHODS.map(&:to_s).sort
end
@@ -136,10 +136,23 @@ module ActiveRecord
assert relation.eager_loading?
end
- def test_apply_finder_options_supports_eager_load
+ def test_references_values
relation = Relation.new :a, :b
- relation = relation.apply_finder_options(:eager_load => :b)
- assert_equal [:b], relation.eager_load_values
+ assert_equal [], relation.references_values
+ relation = relation.references(:foo).references(:omg, :lol)
+ assert_equal ['foo', 'omg', 'lol'], relation.references_values
+ end
+
+ def test_references_values_dont_duplicate
+ relation = Relation.new :a, :b
+ relation = relation.references(:foo).references(:foo)
+ assert_equal ['foo'], relation.references_values
+ end
+
+ def test_apply_finder_options_takes_references
+ relation = Relation.new :a, :b
+ relation = relation.apply_finder_options(:references => :foo)
+ assert_equal ['foo'], relation.references_values
end
end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index f1a9f3e0a1..7b1d65c6db 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -4,11 +4,11 @@ require 'models/tagging'
require 'models/post'
require 'models/topic'
require 'models/comment'
-require 'models/reply'
require 'models/author'
require 'models/comment'
require 'models/entrant'
require 'models/developer'
+require 'models/reply'
require 'models/company'
require 'models/bird'
require 'models/car'
@@ -16,6 +16,7 @@ require 'models/engine'
require 'models/tyre'
require 'models/minivan'
+
class RelationTest < ActiveRecord::TestCase
fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
:tags, :taggings, :cars, :minivans
@@ -176,19 +177,19 @@ class RelationTest < ActiveRecord::TestCase
end
def test_finding_with_cross_table_order_and_limit
- tags = Tag.eager_load(:taggings).
+ tags = Tag.includes(:taggings).
order("tags.name asc", "taggings.taggable_id asc", "REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").
limit(1).to_a
assert_equal 1, tags.length
end
def test_finding_with_complex_order_and_limit
- tags = Tag.eager_load(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a
+ tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a
assert_equal 1, tags.length
end
def test_finding_with_complex_order
- tags = Tag.eager_load(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a
+ tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a
assert_equal 3, tags.length
end
@@ -214,6 +215,19 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [2, 4, 6, 8, 10], even_ids.sort
end
+ def test_none
+ assert_no_queries do
+ assert_equal [], Developer.none
+ assert_equal [], Developer.scoped.none
+ end
+ end
+
+ def test_none_chainable
+ assert_no_queries do
+ assert_equal [], Developer.none.where(:name => 'David')
+ end
+ end
+
def test_joins_with_nil_argument
assert_nothing_raised { DependentFirm.joins(nil).first }
end
@@ -1166,18 +1180,48 @@ class RelationTest < ActiveRecord::TestCase
assert_equal ['Foo', 'Foo'], query.uniq(true).uniq(false).map(&:name)
end
- def test_deprecated_references_eager_loaded_tables
- expected = tags(:general)
- tagging = taggings(:welcome_general)
- tag = assert_queries 1 do
- assert_deprecated do
- Tag.includes(:taggings).where(:taggings => { :id => tagging.id }).to_a.first
- end
- end
+ def test_references_triggers_eager_loading
+ scope = Post.includes(:comments)
+ assert !scope.eager_loading?
+ assert scope.references(:comments).eager_loading?
+ end
- assert_equal expected, tag
- assert_no_queries do
- tag.taggings.to_a
- end
+ def test_references_doesnt_trigger_eager_loading_if_reference_not_included
+ scope = Post.references(:comments)
+ assert !scope.eager_loading?
+ end
+
+ def test_automatically_added_where_references
+ scope = Post.where(:comments => { :body => "Bla" })
+ assert_equal ['comments'], scope.references_values
+
+ scope = Post.where('comments.body' => 'Bla')
+ assert_equal ['comments'], scope.references_values
+ end
+
+ def test_automatically_added_having_references
+ scope = Post.having(:comments => { :body => "Bla" })
+ assert_equal ['comments'], scope.references_values
+
+ scope = Post.having('comments.body' => 'Bla')
+ assert_equal ['comments'], scope.references_values
+ end
+
+ def test_automatically_added_order_references
+ scope = Post.order('comments.body')
+ assert_equal ['comments'], scope.references_values
+
+ scope = Post.order('comments.body', 'yaks.body')
+ assert_equal ['comments', 'yaks'], scope.references_values
+
+ # Don't infer yaks, let's not go down that road again...
+ scope = Post.order('comments.body, yaks.body')
+ assert_equal ['comments'], scope.references_values
+
+ scope = Post.order('comments.body asc')
+ assert_equal ['comments'], scope.references_values
+
+ scope = Post.order('foo(comments.body)')
+ assert_equal [], scope.references_values
end
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 724632489b..3314013cd4 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -2,6 +2,11 @@ require "cases/helper"
class SchemaDumperTest < ActiveRecord::TestCase
+ def initialize(*)
+ super
+ ActiveRecord::SchemaMigration.create_table
+ end
+
def setup
super
@stream = StringIO.new
@@ -14,6 +19,16 @@ class SchemaDumperTest < ActiveRecord::TestCase
@stream.string
end
+ def test_dump_schema_information_outputs_lexically_ordered_versions
+ versions = %w{ 20100101010101 20100201010101 20100301010101 }
+ versions.reverse.each do |v|
+ ActiveRecord::SchemaMigration.create!(:version => v)
+ end
+
+ schema_info = ActiveRecord::Base.connection.dump_schema_information
+ assert_match(/20100201010101.*20100301010101/m, schema_info)
+ end
+
def test_magic_comment
assert_match "# encoding: #{@stream.external_encoding.name}", standard_dump
end
@@ -170,6 +185,15 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_equal 'add_index "companies", ["firm_id", "type", "rating", "ruby_type"], :name => "company_index"', index_definition
end
+ def test_schema_dumps_partial_indices
+ index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_equal 'add_index "companies", ["firm_id", "type"], :name => "company_partial_index", :where => "(rating > 10)"', index_definition
+ else
+ assert_equal 'add_index "companies", ["firm_id", "type"], :name => "company_partial_index"', index_definition
+ end
+ end
+
def test_schema_dump_should_honor_nonstandard_primary_keys
output = standard_dump
match = output.match(%r{create_table "movies"(.*)do})
@@ -212,6 +236,13 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
end
+ def test_schema_dump_includes_hstores_shorthand_definition
+ output = standard_dump
+ if %r{create_table "postgresql_hstores"} =~ output
+ assert_match %r{t.hstore "hash_store", default => ""}, output
+ end
+ end
+
def test_schema_dump_includes_tsvector_shorthand_definition
output = standard_dump
if %r{create_table "postgresql_tsvectors"} =~ output
diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb
index 61b04b3e37..a4c065e667 100644
--- a/activerecord/test/cases/serialization_test.rb
+++ b/activerecord/test/cases/serialization_test.rb
@@ -13,7 +13,8 @@ class SerializationTest < ActiveRecord::TestCase
:created_at => Time.utc(2006, 8, 1),
:awesome => false,
:preferences => { :gem => '<strong>ruby</strong>' },
- :alternative_id => nil
+ :alternative_id => nil,
+ :id => nil
}
end
@@ -24,7 +25,7 @@ class SerializationTest < ActiveRecord::TestCase
end
def test_serialize_should_be_reversible
- for format in FORMATS
+ FORMATS.each do |format|
@serialized = Contact.new.send("to_#{format}")
contact = Contact.new.send("from_#{format}", @serialized)
@@ -33,7 +34,7 @@ class SerializationTest < ActiveRecord::TestCase
end
def test_serialize_should_allow_attribute_only_filtering
- for format in FORMATS
+ FORMATS.each do |format|
@serialized = Contact.new(@contact_attributes).send("to_#{format}", :only => [ :age, :name ])
contact = Contact.new.send("from_#{format}", @serialized)
assert_equal @contact_attributes[:name], contact.name, "For #{format}"
@@ -42,7 +43,7 @@ class SerializationTest < ActiveRecord::TestCase
end
def test_serialize_should_allow_attribute_except_filtering
- for format in FORMATS
+ FORMATS.each do |format|
@serialized = Contact.new(@contact_attributes).send("to_#{format}", :except => [ :age, :name ])
contact = Contact.new.send("from_#{format}", @serialized)
assert_nil contact.name, "For #{format}"
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index 5a3f9a9711..40520d6da2 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -4,14 +4,14 @@ require 'models/admin/user'
class StoreTest < ActiveRecord::TestCase
setup do
- @john = Admin::User.create(:name => 'John Doe', :color => 'black')
+ @john = Admin::User.create(:name => 'John Doe', :color => 'black', :remember_login => true)
end
test "reading store attributes through accessors" do
assert_equal 'black', @john.color
assert_nil @john.homepage
end
-
+
test "writing store attributes through accessors" do
@john.color = 'red'
@john.homepage = '37signals.com'
@@ -31,4 +31,13 @@ class StoreTest < ActiveRecord::TestCase
@john.color = 'red'
assert @john.settings_changed?
end
+
+ test "object initialization with not nullable column" do
+ assert_equal true, @john.remember_login
+ end
+
+ test "writing with not nullable column" do
+ @john.remember_login = false
+ assert_equal false, @john.remember_login
+ end
end
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
new file mode 100644
index 0000000000..94a13d386c
--- /dev/null
+++ b/activerecord/test/cases/test_case.rb
@@ -0,0 +1,10 @@
+require 'active_support/deprecation'
+ActiveSupport::Deprecation.silence do
+ require 'active_record/test_case'
+end
+
+ActiveRecord::TestCase.class_eval do
+ def sqlite3? connection
+ connection.class.name.split('::').last == "SQLite3Adapter"
+ end
+end
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index f155b9bc40..c72b7b35cd 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -118,4 +118,21 @@ class AssociationValidationTest < ActiveRecord::TestCase
end
end
+ def test_validates_associated_models_in_the_same_context
+ Topic.validates_presence_of :title, :on => :custom_context
+ Topic.validates_associated :replies
+ Reply.validates_presence_of :title, :on => :custom_context
+
+ t = Topic.new('title' => '')
+ r = t.replies.new('title' => '')
+
+ assert t.valid?
+ assert !t.valid?(:custom_context)
+
+ t.title = "Longer"
+ assert !t.valid?(:custom_context), "Should NOT be valid if the associated object is not valid in the same context."
+
+ r.title = "Longer"
+ assert t.valid?(:custom_context), "Should be valid if the associated object is not valid in the same context."
+ end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 327830096b..79442d68b0 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -92,6 +92,30 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert r3.valid?, "Saving r3"
end
+ def test_validate_uniqueness_with_object_scope
+ Reply.validates_uniqueness_of(:content, :scope => :topic)
+
+ t = Topic.create("title" => "I'm unique!")
+
+ r1 = t.replies.create "title" => "r1", "content" => "hello world"
+ assert r1.valid?, "Saving r1"
+
+ r2 = t.replies.create "title" => "r2", "content" => "hello world"
+ assert !r2.valid?, "Saving r2 first time"
+ end
+
+ def test_validate_uniqueness_with_object_arg
+ Reply.validates_uniqueness_of(:topic)
+
+ t = Topic.create("title" => "I'm unique!")
+
+ r1 = t.replies.create "title" => "r1", "content" => "hello world"
+ assert r1.valid?, "Saving r1"
+
+ r2 = t.replies.create "title" => "r2", "content" => "hello world"
+ assert !r2.valid?, "Saving r2 first time"
+ end
+
def test_validate_uniqueness_scoped_to_defining_class
t = Topic.create("title" => "What, me worry?")
diff --git a/activerecord/test/fixtures/colleges.yml b/activerecord/test/fixtures/colleges.yml
new file mode 100644
index 0000000000..27591e0c2c
--- /dev/null
+++ b/activerecord/test/fixtures/colleges.yml
@@ -0,0 +1,3 @@
+FIU:
+ id: 1
+ name: Florida International University
diff --git a/activerecord/test/fixtures/courses.yml b/activerecord/test/fixtures/courses.yml
index 5ee1916003..de3a4a97e5 100644
--- a/activerecord/test/fixtures/courses.yml
+++ b/activerecord/test/fixtures/courses.yml
@@ -1,6 +1,7 @@
ruby:
id: 1
name: Ruby Development
+ college: FIU
java:
id: 2
diff --git a/activerecord/test/fixtures/developers.yml b/activerecord/test/fixtures/developers.yml
index 308bf75de2..3656564f63 100644
--- a/activerecord/test/fixtures/developers.yml
+++ b/activerecord/test/fixtures/developers.yml
@@ -8,7 +8,7 @@ jamis:
name: Jamis
salary: 150000
-<% for digit in 3..10 %>
+<% (3..10).each do |digit| %>
dev_<%= digit %>:
id: <%= digit %>
name: fixture_<%= digit %>
diff --git a/activerecord/test/fixtures/dog_lovers.yml b/activerecord/test/fixtures/dog_lovers.yml
new file mode 100644
index 0000000000..d3e5e4a1aa
--- /dev/null
+++ b/activerecord/test/fixtures/dog_lovers.yml
@@ -0,0 +1,4 @@
+david:
+ id: 1
+ bred_dogs_count: 0
+ trained_dogs_count: 1
diff --git a/activerecord/test/fixtures/dogs.yml b/activerecord/test/fixtures/dogs.yml
new file mode 100644
index 0000000000..16d19be2c5
--- /dev/null
+++ b/activerecord/test/fixtures/dogs.yml
@@ -0,0 +1,3 @@
+sophie:
+ id: 1
+ trainer_id: 1
diff --git a/activerecord/test/migrations/broken/100_migration_that_raises_exception.rb b/activerecord/test/migrations/broken/100_migration_that_raises_exception.rb
deleted file mode 100644
index ffb224dad9..0000000000
--- a/activerecord/test/migrations/broken/100_migration_that_raises_exception.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class MigrationThatRaisesException < ActiveRecord::Migration
- def self.up
- add_column "people", "last_name", :string
- raise 'Something broke'
- end
-
- def self.down
- remove_column "people", "last_name"
- end
-end
diff --git a/activerecord/test/migrations/duplicate/1_people_have_last_names.rb b/activerecord/test/migrations/duplicate/1_people_have_last_names.rb
deleted file mode 100644
index 81af5fef5e..0000000000
--- a/activerecord/test/migrations/duplicate/1_people_have_last_names.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class PeopleHaveLastNames < ActiveRecord::Migration
- def self.up
- add_column "people", "last_name", :string
- end
-
- def self.down
- remove_column "people", "last_name"
- end
-end \ No newline at end of file
diff --git a/activerecord/test/migrations/duplicate/2_we_need_reminders.rb b/activerecord/test/migrations/duplicate/2_we_need_reminders.rb
deleted file mode 100644
index d5e71ce8ef..0000000000
--- a/activerecord/test/migrations/duplicate/2_we_need_reminders.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class WeNeedReminders < ActiveRecord::Migration
- def self.up
- create_table("reminders") do |t|
- t.column :content, :text
- t.column :remind_at, :datetime
- end
- end
-
- def self.down
- drop_table "reminders"
- end
-end \ No newline at end of file
diff --git a/activerecord/test/migrations/duplicate/3_foo.rb b/activerecord/test/migrations/duplicate/3_foo.rb
deleted file mode 100644
index 916fe580fa..0000000000
--- a/activerecord/test/migrations/duplicate/3_foo.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class Foo < ActiveRecord::Migration
- def self.up
- end
-
- def self.down
- end
-end \ No newline at end of file
diff --git a/activerecord/test/migrations/duplicate/3_innocent_jointable.rb b/activerecord/test/migrations/duplicate/3_innocent_jointable.rb
deleted file mode 100644
index 21c9ca5328..0000000000
--- a/activerecord/test/migrations/duplicate/3_innocent_jointable.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class InnocentJointable < ActiveRecord::Migration
- def self.up
- create_table("people_reminders", :id => false) do |t|
- t.column :reminder_id, :integer
- t.column :person_id, :integer
- end
- end
-
- def self.down
- drop_table "people_reminders"
- end
-end \ No newline at end of file
diff --git a/activerecord/test/migrations/duplicate_names/20080507052938_chunky.rb b/activerecord/test/migrations/duplicate_names/20080507052938_chunky.rb
deleted file mode 100644
index 5fe5089e18..0000000000
--- a/activerecord/test/migrations/duplicate_names/20080507052938_chunky.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class Chunky < ActiveRecord::Migration
- def self.up
- end
-
- def self.down
- end
-end
diff --git a/activerecord/test/migrations/duplicate_names/20080507053028_chunky.rb b/activerecord/test/migrations/duplicate_names/20080507053028_chunky.rb
deleted file mode 100644
index 5fe5089e18..0000000000
--- a/activerecord/test/migrations/duplicate_names/20080507053028_chunky.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class Chunky < ActiveRecord::Migration
- def self.up
- end
-
- def self.down
- end
-end
diff --git a/activerecord/test/migrations/interleaved/pass_1/3_interleaved_innocent_jointable.rb b/activerecord/test/migrations/interleaved/pass_1/3_interleaved_innocent_jointable.rb
deleted file mode 100644
index bf912fbfc8..0000000000
--- a/activerecord/test/migrations/interleaved/pass_1/3_interleaved_innocent_jointable.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class InterleavedInnocentJointable < ActiveRecord::Migration
- def self.up
- create_table("people_reminders", :id => false) do |t|
- t.column :reminder_id, :integer
- t.column :person_id, :integer
- end
- end
-
- def self.down
- drop_table "people_reminders"
- end
-end
diff --git a/activerecord/test/migrations/interleaved/pass_2/1_interleaved_people_have_last_names.rb b/activerecord/test/migrations/interleaved/pass_2/1_interleaved_people_have_last_names.rb
deleted file mode 100644
index c6c94213a0..0000000000
--- a/activerecord/test/migrations/interleaved/pass_2/1_interleaved_people_have_last_names.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class InterleavedPeopleHaveLastNames < ActiveRecord::Migration
- def self.up
- add_column "people", "last_name", :string
- end
-
- def self.down
- remove_column "people", "last_name"
- end
-end
diff --git a/activerecord/test/migrations/interleaved/pass_2/3_interleaved_innocent_jointable.rb b/activerecord/test/migrations/interleaved/pass_2/3_interleaved_innocent_jointable.rb
deleted file mode 100644
index bf912fbfc8..0000000000
--- a/activerecord/test/migrations/interleaved/pass_2/3_interleaved_innocent_jointable.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class InterleavedInnocentJointable < ActiveRecord::Migration
- def self.up
- create_table("people_reminders", :id => false) do |t|
- t.column :reminder_id, :integer
- t.column :person_id, :integer
- end
- end
-
- def self.down
- drop_table "people_reminders"
- end
-end
diff --git a/activerecord/test/migrations/interleaved/pass_3/1_interleaved_people_have_last_names.rb b/activerecord/test/migrations/interleaved/pass_3/1_interleaved_people_have_last_names.rb
deleted file mode 100644
index c6c94213a0..0000000000
--- a/activerecord/test/migrations/interleaved/pass_3/1_interleaved_people_have_last_names.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class InterleavedPeopleHaveLastNames < ActiveRecord::Migration
- def self.up
- add_column "people", "last_name", :string
- end
-
- def self.down
- remove_column "people", "last_name"
- end
-end
diff --git a/activerecord/test/migrations/interleaved/pass_3/2_interleaved_i_raise_on_down.rb b/activerecord/test/migrations/interleaved/pass_3/2_interleaved_i_raise_on_down.rb
deleted file mode 100644
index 6849995f5e..0000000000
--- a/activerecord/test/migrations/interleaved/pass_3/2_interleaved_i_raise_on_down.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-class InterleavedIRaiseOnDown < ActiveRecord::Migration
- def self.up
- end
-
- def self.down
- raise
- end
-end
diff --git a/activerecord/test/migrations/interleaved/pass_3/3_interleaved_innocent_jointable.rb b/activerecord/test/migrations/interleaved/pass_3/3_interleaved_innocent_jointable.rb
deleted file mode 100644
index bf912fbfc8..0000000000
--- a/activerecord/test/migrations/interleaved/pass_3/3_interleaved_innocent_jointable.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class InterleavedInnocentJointable < ActiveRecord::Migration
- def self.up
- create_table("people_reminders", :id => false) do |t|
- t.column :reminder_id, :integer
- t.column :person_id, :integer
- end
- end
-
- def self.down
- drop_table "people_reminders"
- end
-end
diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb
index c12c88e195..d0e628bd50 100644
--- a/activerecord/test/models/admin/user.rb
+++ b/activerecord/test/models/admin/user.rb
@@ -1,4 +1,5 @@
class Admin::User < ActiveRecord::Base
belongs_to :account
store :settings, :accessors => [ :color, :homepage ]
+ store :preferences, :accessors => [ :remember_login ]
end
diff --git a/activerecord/test/models/arunit2_model.rb b/activerecord/test/models/arunit2_model.rb
new file mode 100644
index 0000000000..04b8b15d3d
--- /dev/null
+++ b/activerecord/test/models/arunit2_model.rb
@@ -0,0 +1,3 @@
+class ARUnit2Model < ActiveRecord::Base
+ self.abstract_class = true
+end
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 881e387c8f..d50e11d6c9 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -3,7 +3,7 @@ class Author < ActiveRecord::Base
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, :eager_load => :comments, :class_name => "Post", :order => 'comments.id'
+ has_many :posts_with_comments_sorted_by_comment_id, :include => :comments, :class_name => "Post", :order => 'comments.id'
has_many :posts_sorted_by_id_limited, :class_name => "Post", :order => 'posts.id', :limit => 1
has_many :posts_with_categories, :include => :categories, :class_name => "Post"
has_many :posts_with_comments_and_categories, :include => [ :comments, :categories ], :order => "posts.id", :class_name => "Post"
@@ -49,12 +49,12 @@ class Author < ActiveRecord::Base
has_many :sti_post_comments, :through => :sti_posts, :source => :comments
has_many :special_nonexistant_posts, :class_name => "SpecialPost", :conditions => "posts.body = 'nonexistant'"
- has_many :special_nonexistant_post_comments, :through => :special_nonexistant_posts, :source => :comments, :conditions => "comments.post_id = 0"
+ has_many :special_nonexistant_post_comments, :through => :special_nonexistant_posts, :source => :comments, :conditions => { 'comments.post_id' => 0 }
has_many :nonexistant_comments, :through => :posts
has_many :hello_posts, :class_name => "Post", :conditions => "posts.body = 'hello'"
has_many :hello_post_comments, :through => :hello_posts, :source => :comments
- has_many :posts_with_no_comments, :class_name => 'Post', :conditions => 'comments.id is null', :eager_load => :comments
+ has_many :posts_with_no_comments, :class_name => 'Post', :conditions => { 'comments.id' => nil }, :include => :comments
has_many :hello_posts_with_hash_conditions, :class_name => "Post",
:conditions => {:body => 'hello'}
diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb
index 3cd63f0660..02b85fd38a 100644
--- a/activerecord/test/models/category.rb
+++ b/activerecord/test/models/category.rb
@@ -2,7 +2,7 @@ class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
has_and_belongs_to_many :special_posts, :class_name => "Post"
has_and_belongs_to_many :other_posts, :class_name => "Post"
- has_and_belongs_to_many :posts_with_authors_sorted_by_author_id, :class_name => "Post", :eager_load => :authors, :order => "authors.id"
+ has_and_belongs_to_many :posts_with_authors_sorted_by_author_id, :class_name => "Post", :include => :authors, :order => "authors.id"
has_and_belongs_to_many(:select_testing_posts,
:class_name => 'Post',
diff --git a/activerecord/test/models/college.rb b/activerecord/test/models/college.rb
new file mode 100644
index 0000000000..c7495d7deb
--- /dev/null
+++ b/activerecord/test/models/college.rb
@@ -0,0 +1,5 @@
+require_dependency 'models/arunit2_model'
+
+class College < ARUnit2Model
+ has_many :courses
+end
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index fe9c465c81..fbdfaa2c29 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -45,6 +45,7 @@ class Firm < Company
has_many :unsorted_clients_with_symbol, :class_name => :Client
has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC"
has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id"
+ has_many :clients_ordered_by_name, :order => "name", :class_name => "Client"
has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false
has_many :dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :destroy
has_many :exclusively_dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all
@@ -88,6 +89,8 @@ class Firm < Company
has_many :accounts
has_many :unautosaved_accounts, :foreign_key => "firm_id", :class_name => 'Account', :autosave => false
+ has_many :association_with_references, :class_name => 'Client', :references => :foo
+
def log
@log ||= []
end
diff --git a/activerecord/test/models/course.rb b/activerecord/test/models/course.rb
index 8a40fa740d..f3d0e05ff7 100644
--- a/activerecord/test/models/course.rb
+++ b/activerecord/test/models/course.rb
@@ -1,3 +1,6 @@
-class Course < ActiveRecord::Base
+require_dependency 'models/arunit2_model'
+
+class Course < ARUnit2Model
+ belongs_to :college
has_many :entrants
end
diff --git a/activerecord/test/models/dog.rb b/activerecord/test/models/dog.rb
new file mode 100644
index 0000000000..72b7d33a86
--- /dev/null
+++ b/activerecord/test/models/dog.rb
@@ -0,0 +1,4 @@
+class Dog < ActiveRecord::Base
+ belongs_to :breeder, :class_name => "DogLover", :counter_cache => :bred_dogs_count
+ belongs_to :trainer, :class_name => "DogLover", :counter_cache => :trained_dogs_count
+end
diff --git a/activerecord/test/models/dog_lover.rb b/activerecord/test/models/dog_lover.rb
new file mode 100644
index 0000000000..a33dc575c5
--- /dev/null
+++ b/activerecord/test/models/dog_lover.rb
@@ -0,0 +1,4 @@
+class DogLover < ActiveRecord::Base
+ has_many :trained_dogs, :class_name => "Dog", :foreign_key => :trainer_id
+ has_many :bred_dogs, :class_name => "Dog", :foreign_key => :breeder_id
+end
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 5792b4705b..d2a0c6b40c 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -3,13 +3,14 @@ class Person < ActiveRecord::Base
has_one :reader
has_many :posts, :through => :readers
- has_many :posts_with_no_comments, :through => :readers, :source => :post, :eager_load => :comments, :conditions => 'comments.id is null'
+ has_many :posts_with_no_comments, :through => :readers, :source => :post, :include => :comments,
+ :conditions => 'comments.id is null', :references => :comments
has_many :references
has_many :bad_references
has_many :fixed_bad_references, :conditions => { :favourite => true }, :class_name => 'BadReference'
has_one :favourite_reference, :class_name => 'Reference', :conditions => ['favourite=?', true]
- has_many :posts_with_comments_sorted_by_comment_id, :through => :readers, :source => :post, :eager_load => :comments, :order => 'comments.id'
+ has_many :posts_with_comments_sorted_by_comment_id, :through => :readers, :source => :post, :include => :comments, :order => 'comments.id'
has_many :jobs, :through => :references
has_many :jobs_with_dependent_destroy, :source => :job, :through => :references, :dependent => :destroy
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index ab2c7ccc10..65b6f9f227 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -1,5 +1,5 @@
ActiveRecord::Schema.define do
- create_table :binary_fields, :force => true, :options => 'CHARACTER SET latin1' do |t|
+ create_table :binary_fields, :force => true do |t|
t.binary :tiny_blob, :limit => 255
t.binary :normal_blob, :limit => 65535
t.binary :medium_blob, :limit => 16777215
@@ -32,4 +32,4 @@ CREATE TABLE collation_tests (
) CHARACTER SET utf8 COLLATE utf8_general_ci
SQL
-end \ No newline at end of file
+end
diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb
index a0adfe3752..7d324f98c4 100644
--- a/activerecord/test/schema/mysql_specific_schema.rb
+++ b/activerecord/test/schema/mysql_specific_schema.rb
@@ -1,5 +1,5 @@
ActiveRecord::Schema.define do
- create_table :binary_fields, :force => true, :options => 'CHARACTER SET latin1' do |t|
+ create_table :binary_fields, :force => true do |t|
t.binary :tiny_blob, :limit => 255
t.binary :normal_blob, :limit => 65535
t.binary :medium_blob, :limit => 16777215
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 5cf9a207f3..25b416a906 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,6 +1,6 @@
ActiveRecord::Schema.define do
- %w(postgresql_tsvectors postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings
+ %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings
postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones).each do |table_name|
execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
end
@@ -63,6 +63,15 @@ _SQL
);
_SQL
+ if 't' == select_value("select 'hstore'=ANY(select typname from pg_type)")
+ execute <<_SQL
+ CREATE TABLE postgresql_hstores (
+ id SERIAL PRIMARY KEY,
+ hash_store hstore default ''::hstore
+ );
+_SQL
+ end
+
execute <<_SQL
CREATE TABLE postgresql_moneys (
id SERIAL PRIMARY KEY,
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 11378c12e5..428a85ab4e 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -37,7 +37,8 @@ ActiveRecord::Schema.define do
create_table :admin_users, :force => true do |t|
t.string :name
- t.text :settings
+ t.text :settings, :null => true
+ t.text :preferences, :null => false, :default => ""
t.references :account
end
@@ -174,6 +175,7 @@ ActiveRecord::Schema.define do
end
add_index :companies, [:firm_id, :type, :rating, :ruby_type], :name => "company_index"
+ add_index :companies, [:firm_id, :type], :name => "company_partial_index", :where => "rating > 10"
create_table :computers, :force => true do |t|
t.integer :developer, :null => false
@@ -213,6 +215,16 @@ ActiveRecord::Schema.define do
t.integer :access_level, :default => 1
end
+ create_table :dog_lovers, :force => true do |t|
+ t.integer :trained_dogs_count, :default => 0
+ t.integer :bred_dogs_count, :default => 0
+ end
+
+ create_table :dogs, :force => true do |t|
+ t.integer :trainer_id
+ t.integer :breeder_id
+ end
+
create_table :edges, :force => true, :id => false do |t|
t.column :source_id, :integer, :null => false
t.column :sink_id, :integer, :null => false
@@ -714,8 +726,6 @@ ActiveRecord::Schema.define do
create_table :countries_treaties, :force => true, :id => false do |t|
t.string :country_id, :null => false
t.string :treaty_id, :null => false
- t.datetime :created_at
- t.datetime :updated_at
end
create_table :liquid, :force => true do |t|
@@ -750,4 +760,9 @@ end
Course.connection.create_table :courses, :force => true do |t|
t.column :name, :string, :null => false
+ t.column :college_id, :integer
+end
+
+College.connection.create_table :colleges, :force => true do |t|
+ t.column :name, :string, :null => false
end
diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb
index 60fea46fd3..11154c3797 100644
--- a/activerecord/test/support/connection.rb
+++ b/activerecord/test/support/connection.rb
@@ -1,4 +1,5 @@
require 'active_support/logger'
+require_dependency 'models/college'
require_dependency 'models/course'
module ARTest
@@ -15,6 +16,6 @@ module ARTest
ActiveRecord::Model.logger = ActiveSupport::Logger.new("debug.log")
ActiveRecord::Model.configurations = connection_config
ActiveRecord::Model.establish_connection 'arunit'
- Course.establish_connection 'arunit2'
+ ARUnit2Model.establish_connection 'arunit2'
end
end
diff --git a/activeresource/CHANGELOG.md b/activeresource/CHANGELOG.md
index 09d1eeb1c8..e092621a33 100644
--- a/activeresource/CHANGELOG.md
+++ b/activeresource/CHANGELOG.md
@@ -1,4 +1,14 @@
-## Rails 3.2.0 (unreleased) ##
+## Rails 4.0.0 (unreleased) ##
+
+* Adds support for PATCH requests. *dlee*
+
+
+## Rails 3.2.1 (January 26, 2012) ##
+
+* Documentation fixes.
+
+
+## Rails 3.2.0 (January 20, 2012) ##
* Redirect responses: 303 See Other and 307 Temporary Redirect now behave like
301 Moved Permanently and 302 Found. GH #3302.
@@ -6,6 +16,21 @@
*Jim Herz*
+## Rails 3.1.4 (unreleased) ##
+
+* No changes
+
+
+## Rails 3.1.3 (November 20, 2011) ##
+
+* No changes
+
+
+## Rails 3.1.2 (November 18, 2011) ##
+
+* No changes
+
+
## Rails 3.1.1 (October 7, 2011) ##
* No changes.
@@ -18,12 +43,38 @@
class User < ActiveResource::Base self.format = :xml
end
+
+## Rails 3.0.12 (unreleased) ##
+
+* No changes.
+
+
+## Rails 3.0.11 (November 18, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.10 (August 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.9 (June 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.8 (June 7, 2011) ##
+
+* No Changes
+
+
## Rails 3.0.7 (April 18, 2011) ##
* No changes.
-* Rails 3.0.6 (April 5, 2011)
+## Rails 3.0.6 (April 5, 2011) ##
* No changes.
diff --git a/activeresource/Rakefile b/activeresource/Rakefile
index b1c18ff189..b85d8c7eb5 100755
--- a/activeresource/Rakefile
+++ b/activeresource/Rakefile
@@ -12,6 +12,7 @@ Rake::TestTask.new { |t|
t.libs << "test"
t.pattern = 'test/**/*_test.rb'
t.warning = true
+ t.verbose = true
}
namespace :test do
@@ -33,7 +34,7 @@ end
task :lines do
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
- for file_name in FileList["lib/active_resource/**/*.rb"]
+ FileList["lib/active_resource/**/*.rb"].each do |file_name|
next if file_name =~ /vendor/
f = File.open(file_name)
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index 548990cb70..0c2d070aef 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -25,29 +25,29 @@ module ActiveResource
#
# == Automated mapping
#
- # Active Resource objects represent your RESTful resources as manipulatable Ruby objects. To map resources
+ # Active Resource objects represent your RESTful resources as manipulatable Ruby objects. To map resources
# to Ruby objects, Active Resource only needs a class name that corresponds to the resource name (e.g., the class
# Person maps to the resources people, very similarly to Active Record) and a +site+ value, which holds the
# URI of the resources.
#
# class Person < ActiveResource::Base
- # self.site = "http://api.people.com:3000/"
+ # self.site = "https://api.people.com"
# end
#
- # Now the Person class is mapped to RESTful resources located at <tt>http://api.people.com:3000/people/</tt>, and
+ # Now the Person class is mapped to RESTful resources located at <tt>https://api.people.com/people/</tt>, and
# you can now use Active Resource's life cycle methods to manipulate resources. In the case where you already have
# an existing model with the same name as the desired RESTful resource you can set the +element_name+ value.
#
# class PersonResource < ActiveResource::Base
- # self.site = "http://api.people.com:3000/"
+ # self.site = "https://api.people.com"
# self.element_name = "person"
# end
#
# If your Active Resource object is required to use an HTTP proxy you can set the +proxy+ value which holds a URI.
#
# class PersonResource < ActiveResource::Base
- # self.site = "http://api.people.com:3000/"
- # self.proxy = "http://user:password@proxy.people.com:8080"
+ # self.site = "https://api.people.com"
+ # self.proxy = "https://user:password@proxy.people.com:8080"
# end
#
#
@@ -103,7 +103,7 @@ module ActiveResource
# You can validate resources client side by overriding validation methods in the base class.
#
# class Person < ActiveResource::Base
- # self.site = "http://api.people.com:3000/"
+ # self.site = "https://api.people.com"
# protected
# def validate
# errors.add("last", "has invalid characters") unless last =~ /[a-zA-Z]*/
@@ -114,47 +114,64 @@ module ActiveResource
#
# == Authentication
#
- # Many REST APIs will require authentication, usually in the form of basic
- # HTTP authentication. Authentication can be specified by:
+ # Many REST APIs require authentication. The HTTP spec describes two ways to
+ # make requests with a username and password (see RFC 2617).
#
- # === HTTP Basic Authentication
- # * putting the credentials in the URL for the +site+ variable.
+ # Basic authentication simply sends a username and password along with HTTP
+ # requests. These sensitive credentials are sent unencrypted, visible to
+ # any onlooker, so this scheme should only be used with SSL.
+ #
+ # Digest authentication sends a crytographic hash of the username, password,
+ # HTTP method, URI, and a single-use secret key provided by the server.
+ # Sensitive credentials aren't visible to onlookers, so digest authentication
+ # doesn't require SSL. However, this doesn't mean the connection is secure!
+ # Just the username and password.
+ #
+ # (You really, really want to use SSL. There's little reason not to.)
+ #
+ # === Picking an authentication scheme
+ #
+ # Basic authentication is the default. To switch to digest authentication,
+ # set +auth_type+ to +:digest+:
#
# class Person < ActiveResource::Base
- # self.site = "http://ryan:password@api.people.com:3000/"
+ # self.auth_type = :digest
# end
#
- # * defining +user+ and/or +password+ variables
+ # === Setting the username and password
+ #
+ # Set +user+ and +password+ on the class, or include them in the +site+ URL.
#
# class Person < ActiveResource::Base
- # self.site = "http://api.people.com:3000/"
+ # # Set user and password directly:
# self.user = "ryan"
# self.password = "password"
- # end
- #
- # For obvious security reasons, it is probably best if such services are available
- # over HTTPS.
#
- # 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.
+ # # Or include them in the site:
+ # self.site = "https://ryan:password@api.people.com"
+ # end
#
# === Certificate Authentication
#
- # * End point uses an X509 certificate for authentication. <tt>See ssl_options=</tt> for all options.
+ # You can also authenticate using an X509 certificate. <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}
+ #
+ # File.open(pem_file_path, 'rb') do |pem_file|
+ # self.ssl_options = {
+ # cert: OpenSSL::X509::Certificate.new(pem_file),
+ # key: OpenSSL::PKey::RSA.new(pem_file),
+ # ca_path: "/path/to/OpenSSL/formatted/CA_Certs",
+ # verify_mode: OpenSSL::SSL::VERIFY_PEER }
+ # end
# end
#
#
# == Errors & Validation
#
# Error handling and validation is handled in much the same manner as you're used to seeing in
- # Active Record. Both the response code in the HTTP response and the body of the response are used to
+ # Active Record. Both the response code in the HTTP response and the body of the response are used to
# indicate that an error occurred.
#
# === Resource errors
@@ -163,7 +180,7 @@ module ActiveResource
# response code will be returned from the server which will raise an ActiveResource::ResourceNotFound
# exception.
#
- # # GET http://api.people.com:3000/people/999.json
+ # # GET https://api.people.com/people/999.json
# ryan = Person.find(999) # 404, raises ActiveResource::ResourceNotFound
#
#
@@ -185,7 +202,7 @@ module ActiveResource
# * Other - ActiveResource::ConnectionError
#
# These custom exceptions allow you to deal with resource errors more naturally and with more precision
- # rather than returning a general HTTP error. For example:
+ # rather than returning a general HTTP error. For example:
#
# begin
# ryan = Person.find(my_id)
@@ -199,7 +216,7 @@ module ActiveResource
# an ActiveResource::MissingPrefixParam will be raised.
#
# class Comment < ActiveResource::Base
- # self.site = "http://someip.com/posts/:post_id/"
+ # self.site = "https://someip.com/posts/:post_id"
# end
#
# Comment.find(1)
@@ -208,8 +225,8 @@ module ActiveResource
# === Validation errors
#
# 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 or JSON representation of the validation errors. The save operation will
+ # (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 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)
@@ -217,20 +234,29 @@ module ActiveResource
# ryan.save # => false
#
# # When
- # # PUT http://api.people.com:3000/people/1.json
+ # # PUT https://api.people.com/people/1.xml
# # or
- # # PUT http://api.people.com:3000/people/1.json
+ # # PUT https://api.people.com/people/1.json
# # is requested with invalid values, the response is:
# #
# # Response (422):
# # <errors><error>First cannot be empty</error></errors>
# # or
- # # {"errors":["First cannot be empty"]}
+ # # {"errors":{"first":["cannot be empty"]}}
# #
#
# ryan.errors.invalid?(:first) # => true
# ryan.errors.full_messages # => ['First cannot be empty']
#
+ # For backwards-compatibility with older endpoints, the following formats are also supported in JSON responses:
+ #
+ # # {"errors":['First cannot be empty']}
+ # # This was the required format for previous versions of ActiveResource
+ # # {"first":["cannot be empty"]}
+ # # This was the default format produced by respond_with in ActionController <3.2.1
+ #
+ # Parsing either of these formats will result in a deprecation warning.
+ #
# Learn more about Active Resource's validation features in the ActiveResource::Validations documentation.
#
# === Timeouts
@@ -240,7 +266,7 @@ module ActiveResource
# amount of time before Active Resource times out with the +timeout+ variable.
#
# class Person < ActiveResource::Base
- # self.site = "http://api.people.com:3000/"
+ # self.site = "https://api.people.com"
# self.timeout = 5
# end
#
@@ -383,22 +409,22 @@ module ActiveResource
@known_attributes ||= []
end
- # Gets the URI of the REST resources to map for this class. The site variable is required for
+ # Gets the URI of the REST resources to map for this class. The site variable is required for
# Active Resource's mapping to work.
def site
# Not using superclass_delegating_reader because don't want subclasses to modify superclass instance
#
# With superclass_delegating_reader
#
- # Parent.site = 'http://anonymous@test.com'
- # Subclass.site # => 'http://anonymous@test.com'
+ # Parent.site = 'https://anonymous@test.com'
+ # Subclass.site # => 'https://anonymous@test.com'
# Subclass.site.user = 'david'
- # Parent.site # => 'http://david@test.com'
+ # Parent.site # => 'https://david@test.com'
#
# Without superclass_delegating_reader (expected behavior)
#
- # Parent.site = 'http://anonymous@test.com'
- # Subclass.site # => 'http://anonymous@test.com'
+ # Parent.site = 'https://anonymous@test.com'
+ # Subclass.site # => 'https://anonymous@test.com'
# Subclass.site.user = 'david' # => TypeError: can't modify frozen object
#
if defined?(@site)
@@ -562,6 +588,12 @@ module ActiveResource
def headers
@headers ||= {}
+
+ if superclass != Object && superclass.headers
+ @headers = superclass.headers.merge(@headers)
+ else
+ @headers
+ end
end
attr_writer :element_name
@@ -592,7 +624,7 @@ module ActiveResource
prefix(options)
end
- # An attribute reader for the source string for the resource path \prefix. This
+ # An attribute reader for the source string for the resource path \prefix. This
# method is regenerated at runtime based on what the \prefix is set to.
def prefix_source
prefix # generate #prefix and #prefix_source methods first
@@ -625,7 +657,7 @@ module ActiveResource
alias_method :set_element_name, :element_name= #:nodoc:
alias_method :set_collection_name, :collection_name= #:nodoc:
- # Gets the element path for the given ID in +id+. If the +query_options+ parameter is omitted, Rails
+ # Gets the element path for the given ID in +id+. If the +query_options+ parameter is omitted, Rails
# will split from the \prefix options.
#
# ==== Options
@@ -638,7 +670,7 @@ module ActiveResource
# # => /posts/1.json
#
# class Comment < ActiveResource::Base
- # self.site = "http://37s.sunrise.i/posts/:post_id/"
+ # self.site = "https://37s.sunrise.com/posts/:post_id"
# end
#
# Comment.element_path(1, :post_id => 5)
@@ -668,7 +700,7 @@ module ActiveResource
# # => /posts/new.json
#
# class Comment < ActiveResource::Base
- # self.site = "http://37s.sunrise.i/posts/:post_id/"
+ # self.site = "https://37s.sunrise.com/posts/:post_id"
# end
#
# Comment.collection_path(:post_id => 5)
@@ -677,7 +709,7 @@ module ActiveResource
"#{prefix(prefix_options)}#{collection_name}/new.#{format.extension}"
end
- # Gets the collection path for the REST resources. If the +query_options+ parameter is omitted, Rails
+ # Gets the collection path for the REST resources. If the +query_options+ parameter is omitted, Rails
# will split from the +prefix_options+.
#
# ==== Options
@@ -725,8 +757,8 @@ module ActiveResource
# ryan = Person.new(:first => 'ryan')
# ryan.save
#
- # Returns the newly created resource. If a failure has occurred an
- # exception will be raised (see <tt>save</tt>). If the resource is invalid and
+ # Returns the newly created resource. If a failure has occurred an
+ # exception will be raised (see <tt>save</tt>). If the resource is invalid and
# has not been saved then <tt>valid?</tt> will return <tt>false</tt>,
# while <tt>new?</tt> will still return <tt>true</tt>.
#
@@ -747,11 +779,11 @@ module ActiveResource
self.new(attributes).tap { |resource| resource.save }
end
- # Core method for finding resources. Used similarly to Active Record's +find+ method.
+ # Core method for finding resources. Used similarly to Active Record's +find+ method.
#
# ==== Arguments
- # The first argument is considered to be the scope of the query. That is, how many
- # resources are returned from the request. It can be one of the following.
+ # The first argument is considered to be the scope of the query. That is, how many
+ # resources are returned from the request. It can be one of the following.
#
# * <tt>:one</tt> - Returns a single resource.
# * <tt>:first</tt> - Returns the first resource found.
@@ -834,7 +866,7 @@ module ActiveResource
find(:last, *args)
end
- # This is an alias for find(:all). You can pass in all the same
+ # 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)
@@ -939,12 +971,12 @@ module ActiveResource
# Accepts a URI and creates the site URI from that.
def create_site_uri_from(site)
- site.is_a?(URI) ? site.dup : URI.parser.parse(site)
+ site.is_a?(URI) ? site.dup : URI.parse(site)
end
# Accepts a URI and creates the proxy URI from that.
def create_proxy_uri_from(proxy)
- proxy.is_a?(URI) ? proxy.dup : URI.parser.parse(proxy)
+ proxy.is_a?(URI) ? proxy.dup : URI.parse(proxy)
end
# contains a set of the current prefix parameters.
@@ -1015,7 +1047,7 @@ module ActiveResource
# not_ryan.new? # => true
#
# Any active resource member attributes will NOT be cloned, though all other
- # attributes are. This is to prevent the conflict between any +prefix_options+
+ # attributes are. This is to prevent the conflict between any +prefix_options+
# that refer to the original parent resource and the newly cloned parent
# resource that does not exist.
#
@@ -1031,7 +1063,7 @@ module ActiveResource
# Clone all attributes except the pk and any nested ARes
cloned = Hash[attributes.reject {|k,v| k == self.class.primary_key || v.is_a?(ActiveResource::Base)}.map { |k, v| [k, v.clone] }]
# Form the new resource - bypass initialize of resource with 'new' as that will call 'load' which
- # attempts to convert hashes into member objects and arrays into collections of objects. We want
+ # attempts to convert hashes into member objects and arrays into collections of objects. We want
# the raw objects to be cloned so we bypass load by directly setting the attributes hash.
resource = self.class.new({})
resource.prefix_options = self.prefix_options
@@ -1083,7 +1115,7 @@ module ActiveResource
attributes[self.class.primary_key] = id
end
- # Test for equality. Resource are equal if and only if +other+ is the same object or
+ # Test for equality. Resource are equal if and only if +other+ is the same object or
# is an instance of the same class, is not <tt>new?</tt>, and has the same +id+.
#
# ==== Examples
@@ -1139,7 +1171,7 @@ module ActiveResource
end
end
- # Saves (+POST+) or \updates (+PUT+) a resource. Delegates to +create+ if the object is \new,
+ # Saves (+POST+) or \updates (+PUT+) a resource. Delegates to +create+ if the object is \new,
# +update+ if it exists. If the response to the \save includes a body, it will be assumed that this body
# is Json for the final object as it looked after the \save (which would include attributes like +created_at+
# that weren't part of the original submit).
@@ -1190,7 +1222,7 @@ module ActiveResource
end
# Evaluates to <tt>true</tt> if this resource is not <tt>new?</tt> and is
- # found on the remote service. Using this method, you can check for
+ # found on the remote service. Using this method, you can check for
# resources that may have been deleted between the object's instantiation
# and actions on it.
#
@@ -1232,7 +1264,7 @@ module ActiveResource
end
# A method to manually load attributes from a \hash. Recursively loads collections of
- # resources. This method is called in +initialize+ and +create+ when a \hash of attributes
+ # resources. This method is called in +initialize+ and +create+ when a \hash of attributes
# is provided.
#
# ==== Examples
@@ -1289,12 +1321,12 @@ module ActiveResource
#
# Note: Unlike ActiveRecord::Base.update_attribute, this method <b>is</b>
# subject to normal validation routines as an update sends the whole body
- # of the resource in the request. (See Validations).
+ # of the resource in the request. (See Validations).
#
# As such, this method is equivalent to calling update_attributes with a single attribute/value pair.
#
# If the saving fails because of a connection or remote service error, an
- # exception will be raised. If saving fails because the resource is
+ # exception will be raised. If saving fails because the resource is
# invalid then <tt>false</tt> will be returned.
def update_attribute(name, value)
self.send("#{name}=".to_sym, value)
@@ -1305,7 +1337,7 @@ module ActiveResource
# and requests that the record be saved.
#
# If the saving fails because of a connection or remote service error, an
- # exception will be raised. If saving fails because the resource is
+ # exception will be raised. If saving fails because the resource is
# invalid then <tt>false</tt> will be returned.
#
# Note: Though this request can be made with a partial set of the
diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb
index 94839c8c25..8a8dc3146d 100644
--- a/activeresource/lib/active_resource/connection.rb
+++ b/activeresource/lib/active_resource/connection.rb
@@ -15,6 +15,7 @@ module ActiveResource
HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
:put => 'Content-Type',
:post => 'Content-Type',
+ :patch => 'Content-Type',
:delete => 'Accept',
:head => 'Accept'
}
@@ -39,14 +40,14 @@ module ActiveResource
# Set URI for remote service.
def site=(site)
- @site = site.is_a?(URI) ? site : URI.parser.parse(site)
+ @site = site.is_a?(URI) ? site : URI.parse(site)
@user = URI.parser.unescape(@site.user) if @site.user
@password = URI.parser.unescape(@site.password) if @site.password
end
# Set the proxy for remote service.
def proxy=(proxy)
- @proxy = proxy.is_a?(URI) ? proxy : URI.parser.parse(proxy)
+ @proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy)
end
# Sets the user for remote service.
@@ -86,6 +87,12 @@ module ActiveResource
with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) }
end
+ # Executes a PATCH request (see HTTP protocol documentation if unfamiliar).
+ # Used to update resources.
+ def patch(path, body = '', headers = {})
+ with_auth { request(:patch, path, body.to_s, build_request_headers(headers, :patch, self.site.merge(path))) }
+ end
+
# Executes a PUT request (see HTTP protocol documentation if unfamiliar).
# Used to update resources.
def put(path, body = '', headers = {})
@@ -166,38 +173,28 @@ module ActiveResource
end
def configure_http(http)
- http = apply_ssl_options(http)
-
- # Net::HTTP timeouts default to 60 seconds.
- if @timeout
- http.open_timeout = @timeout
- http.read_timeout = @timeout
+ apply_ssl_options(http).tap do |https|
+ # Net::HTTP timeouts default to 60 seconds.
+ if defined? @timeout
+ https.open_timeout = @timeout
+ https.read_timeout = @timeout
+ end
end
-
- http
end
def apply_ssl_options(http)
- return http unless @site.is_a?(URI::HTTPS)
+ http.tap do |https|
+ # Skip config if site is already a https:// URI.
+ if defined? @ssl_options
+ http.use_ssl = true
- http.use_ssl = true
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
- return http unless defined?(@ssl_options)
+ # Default to no cert verification (WTF? FIXME)
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
- 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
+ # All the SSL options have corresponding http settings.
+ @ssl_options.each { |key, value| http.send "#{key}=", value }
+ end
+ end
end
def default_header
diff --git a/activeresource/lib/active_resource/custom_methods.rb b/activeresource/lib/active_resource/custom_methods.rb
index 2a651dd48e..839dc79d50 100644
--- a/activeresource/lib/active_resource/custom_methods.rb
+++ b/activeresource/lib/active_resource/custom_methods.rb
@@ -2,7 +2,7 @@ require 'active_support/core_ext/object/blank'
module ActiveResource
# A module to support custom REST methods and sub-resources, allowing you to break out
- # of the "default" REST methods with your own custom resource requests. For example,
+ # of the "default" REST methods with your own custom resource requests. For example,
# say you use Rails to expose a REST service and configure your routes with:
#
# map.resources :people, :new => { :register => :post },
@@ -11,16 +11,16 @@ module ActiveResource
#
# This route set creates routes for the following HTTP requests:
#
- # POST /people/new/register.json # PeopleController.register
- # PUT /people/1/promote.json # PeopleController.promote with :id => 1
- # DELETE /people/1/deactivate.json # PeopleController.deactivate with :id => 1
- # GET /people/active.json # PeopleController.active
+ # POST /people/new/register.json # PeopleController.register
+ # PATCH/PUT /people/1/promote.json # PeopleController.promote with :id => 1
+ # DELETE /people/1/deactivate.json # PeopleController.deactivate with :id => 1
+ # GET /people/active.json # PeopleController.active
#
# Using this module, Active Resource can use these custom REST methods just like the
# standard methods.
#
# class Person < ActiveResource::Base
- # self.site = "http://37s.sunrise.i:3000"
+ # self.site = "https://37s.sunrise.com"
# end
#
# Person.new(:name => 'Ryan').post(:register) # POST /people/new/register.json
@@ -63,6 +63,10 @@ module ActiveResource
connection.post(custom_method_collection_url(custom_method_name, options), body, headers)
end
+ def patch(custom_method_name, options = {}, body = '')
+ connection.patch(custom_method_collection_url(custom_method_name, options), body, headers)
+ end
+
def put(custom_method_name, options = {}, body = '')
connection.put(custom_method_collection_url(custom_method_name, options), body, headers)
end
@@ -98,6 +102,10 @@ module ActiveResource
end
end
+ def patch(method_name, options = {}, body = '')
+ connection.patch(custom_method_element_url(method_name, options), body, self.class.headers)
+ end
+
def put(method_name, options = {}, body = '')
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
end
diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb
index 36f52d61d3..9856f5872f 100644
--- a/activeresource/lib/active_resource/http_mock.rb
+++ b/activeresource/lib/active_resource/http_mock.rb
@@ -4,7 +4,7 @@ require 'active_support/core_ext/object/inclusion'
module ActiveResource
class InvalidRequestError < StandardError; end #:nodoc:
- # One thing that has always been a pain with remote web services is testing. The HttpMock
+ # One thing that has always been a pain with remote web services is testing. The HttpMock
# class makes it easy to test your Active Resource models by creating a set of mock responses to specific
# requests.
#
@@ -15,17 +15,17 @@ module ActiveResource
#
# mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
#
- # * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +put+, +delete+ or
+ # * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +patch+, +put+, +delete+ or
# +head+.
# * <tt>path</tt> - A string, starting with a "/", defining the URI that is expected to be
# called.
- # * <tt>request_headers</tt> - Headers that are expected along with the request. This argument uses a
- # hash format, such as <tt>{ "Content-Type" => "application/json" }</tt>. This mock will only trigger
+ # * <tt>request_headers</tt> - Headers that are expected along with the request. This argument uses a
+ # hash format, such as <tt>{ "Content-Type" => "application/json" }</tt>. This mock will only trigger
# if your tests sends a request with identical headers.
- # * <tt>body</tt> - The data to be returned. This should be a string of Active Resource parseable content,
+ # * <tt>body</tt> - The data to be returned. This should be a string of Active Resource parseable content,
# such as Json.
# * <tt>status</tt> - The HTTP response code, as an integer, to return with the response.
- # * <tt>response_headers</tt> - Headers to be returned with the response. Uses the same hash format as
+ # * <tt>response_headers</tt> - Headers to be returned with the response. Uses the same hash format as
# <tt>request_headers</tt> listed above.
#
# In order for a mock to deliver its content, the incoming request must match by the <tt>http_method</tt>,
@@ -55,7 +55,7 @@ module ActiveResource
@responses = responses
end
- [ :post, :put, :get, :delete, :head ].each do |method|
+ [ :post, :patch, :put, :get, :delete, :head ].each do |method|
# def post(path, request_headers = {}, body = nil, status = 200, response_headers = {})
# @responses[Request.new(:post, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
# end
@@ -217,7 +217,7 @@ module ActiveResource
end
# body? methods
- { true => %w(post put),
+ { true => %w(post patch put),
false => %w(get delete head) }.each do |has_body, methods|
methods.each do |method|
# def post(path, body, headers)
@@ -292,11 +292,8 @@ module ActiveResource
@body = nil
end
- if @body.nil?
- self['Content-Length'] = "0"
- else
- self['Content-Length'] = body.size.to_s
- end
+ self['Content-Length'] = @body.nil? ? "0" : body.size.to_s
+
end
# Returns true if code is 2xx,
diff --git a/activeresource/lib/active_resource/validations.rb b/activeresource/lib/active_resource/validations.rb
index ca265d053e..028acb8bce 100644
--- a/activeresource/lib/active_resource/validations.rb
+++ b/activeresource/lib/active_resource/validations.rb
@@ -25,10 +25,48 @@ module ActiveResource
end
end
+ # Grabs errors from a hash of attribute => array of errors elements
+ # The second parameter directs the errors cache to be cleared (default)
+ # or not (by passing true)
+ #
+ # Unrecognized attribute names will be humanized and added to the record's
+ # base errors.
+ def from_hash(messages, save_cache = false)
+ clear unless save_cache
+
+ messages.each do |(key,errors)|
+ errors.each do |error|
+ if @base.attributes.keys.include?(key)
+ add key, error
+ elsif key == 'base'
+ self[:base] << error
+ else
+ # reporting an error on an attribute not in attributes
+ # format and add them to base
+ self[:base] << "#{key.humanize} #{error}"
+ end
+ end
+ end
+ end
+
# Grabs errors from a json response.
def from_json(json, save_cache = false)
- array = Array.wrap(ActiveSupport::JSON.decode(json)['errors']) rescue []
- from_array array, save_cache
+ decoded = ActiveSupport::JSON.decode(json) || {} rescue {}
+ if decoded.kind_of?(Hash) && (decoded.has_key?('errors') || decoded.empty?)
+ errors = decoded['errors'] || {}
+ if errors.kind_of?(Array)
+ # 3.2.1-style with array of strings
+ ActiveSupport::Deprecation.warn('Returning errors as an array of strings is deprecated.')
+ from_array errors, save_cache
+ else
+ # 3.2.2+ style
+ from_hash errors, save_cache
+ end
+ else
+ # <3.2-style respond_with - lacks 'errors' key
+ ActiveSupport::Deprecation.warn('Returning errors as a hash without a root "errors" key is deprecated.')
+ from_hash decoded, save_cache
+ end
end
# Grabs errors from an XML response.
@@ -41,7 +79,7 @@ module ActiveResource
# Module to support validation and errors with Active Resource objects. The module overrides
# Base#save to rescue ActiveResource::ResourceInvalid exceptions and parse the errors returned
# in the web service response. The module also adds an +errors+ collection that mimics the interface
- # of the errors provided by ActiveRecord::Errors.
+ # of the errors provided by ActiveModel::Errors.
#
# ==== Example
#
diff --git a/activeresource/test/cases/authorization_test.rb b/activeresource/test/cases/authorization_test.rb
index 0185e5432d..fbfe086599 100644
--- a/activeresource/test/cases/authorization_test.rb
+++ b/activeresource/test/cases/authorization_test.rb
@@ -9,8 +9,18 @@ class AuthorizationTest < ActiveSupport::TestCase
@david = { :person => { :id => 2, :name => 'David' } }.to_json
@authenticated_conn = ActiveResource::Connection.new("http://david:test123@localhost")
@basic_authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' }
+ end
- @nonce = "MTI0OTUxMzc4NzpjYWI3NDM3NDNmY2JmODU4ZjQ2ZjcwNGZkMTJiMjE0NA=="
+ private
+ def decode(response)
+ @authenticated_conn.format.decode(response.body)
+ end
+end
+
+class BasicAuthorizationTest < AuthorizationTest
+ def setup
+ super
+ @authenticated_conn.auth_type = :basic
ActiveResource::HttpMock.respond_to do |mock|
mock.get "/people/2.json", @basic_authorization_request_header, @david
@@ -19,34 +29,48 @@ class AuthorizationTest < ActiveSupport::TestCase
mock.delete "/people/2.json", @basic_authorization_request_header, nil, 200
mock.post "/people/2/addresses.json", @basic_authorization_request_header, nil, 201, 'Location' => '/people/1/addresses/5'
mock.head "/people/2.json", @basic_authorization_request_header, nil, 200
+ end
+ end
- mock.get "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "fad396f6a34aeba28e28b9b96ddbb671") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
- mock.get "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "c064d5ba8891a25290c76c8c7d31fb7b") }, @david, 200
- mock.get "/people/1.json", { 'Authorization' => request_digest_auth_header("/people/1.json", "f9c0b594257bb8422af4abd429c5bb70") }, @matz, 200
+ def test_get
+ david = decode(@authenticated_conn.get("/people/2.json"))
+ assert_equal "David", david["name"]
+ end
- mock.put "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "50a685d814f94665b9d160fbbaa3958a") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
- mock.put "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "5a75cde841122d8e0f20f8fd1f98a743") }, nil, 204
+ def test_post
+ response = @authenticated_conn.post("/people/2/addresses.json")
+ assert_equal "/people/1/addresses/5", response["Location"]
+ end
- mock.delete "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "846f799107eab5ca4285b909ee299a33") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
- mock.delete "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "9f5b155224edbbb69fd99d8ce094681e") }, nil, 200
+ def test_put
+ response = @authenticated_conn.put("/people/2.json")
+ assert_equal 204, response.code
+ end
- mock.post "/people/2/addresses.json", { 'Authorization' => blank_digest_auth_header("/people/2/addresses.json", "6984d405ff3d9ed07bbf747dcf16afb0") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
- mock.post "/people/2/addresses.json", { 'Authorization' => request_digest_auth_header("/people/2/addresses.json", "4bda6a28dbf930b5af9244073623bd04") }, nil, 201, 'Location' => '/people/1/addresses/5'
+ def test_delete
+ response = @authenticated_conn.delete("/people/2.json")
+ assert_equal 200, response.code
+ end
- mock.head "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "15e5ed84ba5c4cfcd5c98a36c2e4f421") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
- mock.head "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "d4c6d2bcc8717abb2e2ccb8c49ee6a91") }, nil, 200
- end
+ def test_head
+ response = @authenticated_conn.head("/people/2.json")
+ assert_equal 200, response.code
+ end
- # Make client nonce deterministic
- class << @authenticated_conn
- private
+ def test_retry_on_401_doesnt_happen_with_basic_auth
+ assert_raise(ActiveResource::UnauthorizedAccess) { @authenticated_conn.get("/people/1.json") }
+ assert_equal "", @authenticated_conn.send(:response_auth_header)
+ end
- def client_nonce
- 'i-am-a-client-nonce'
- end
- end
+ def test_raises_invalid_request_on_unauthorized_requests
+ assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.json") }
+ assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.json") }
+ assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.json") }
+ assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.json") }
+ assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.json") }
end
+
def test_authorization_header
authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization']
@@ -116,7 +140,6 @@ class AuthorizationTest < ActiveSupport::TestCase
end
def test_authorization_header_if_credentials_supplied_and_auth_type_is_basic
- @authenticated_conn.auth_type = :basic
authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization']
authorization = authorization_header["Authorization"].to_s.split
@@ -125,76 +148,77 @@ class AuthorizationTest < ActiveSupport::TestCase
assert_equal ["david", "test123"], ::Base64.decode64(authorization[1]).split(":")[0..1]
end
- def test_authorization_header_if_credentials_supplied_and_auth_type_is_digest
- @authenticated_conn.auth_type = :digest
- authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
- assert_equal blank_digest_auth_header("/people/2.json", "fad396f6a34aeba28e28b9b96ddbb671"), authorization_header['Authorization']
+ def test_client_nonce_is_not_nil
+ assert_not_nil ActiveResource::Connection.new("http://david:test123@localhost").send(:client_nonce)
end
+end
- def test_authorization_header_with_query_string_if_auth_type_is_digest
+class DigestAuthorizationTest < AuthorizationTest
+ def setup
+ super
@authenticated_conn.auth_type = :digest
- authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json?only=name'))
- assert_equal blank_digest_auth_header("/people/2.json?only=name", "f8457b0b5d21b6b80737a386217afb24"), authorization_header['Authorization']
- end
- def test_get
- david = decode(@authenticated_conn.get("/people/2.json"))
- assert_equal "David", david["name"]
- end
+ # Make client nonce deterministic
+ def @authenticated_conn.client_nonce; 'i-am-a-client-nonce' end
- def test_post
- response = @authenticated_conn.post("/people/2/addresses.json")
- assert_equal "/people/1/addresses/5", response["Location"]
- end
+ @nonce = "MTI0OTUxMzc4NzpjYWI3NDM3NDNmY2JmODU4ZjQ2ZjcwNGZkMTJiMjE0NA=="
- def test_put
- response = @authenticated_conn.put("/people/2.json")
- assert_equal 204, response.code
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.get "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "fad396f6a34aeba28e28b9b96ddbb671") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
+ mock.get "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "c064d5ba8891a25290c76c8c7d31fb7b") }, @david, 200
+ mock.get "/people/1.json", { 'Authorization' => request_digest_auth_header("/people/1.json", "f9c0b594257bb8422af4abd429c5bb70") }, @matz, 200
+
+ mock.put "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "50a685d814f94665b9d160fbbaa3958a") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
+ mock.put "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "5a75cde841122d8e0f20f8fd1f98a743") }, nil, 204
+
+ mock.delete "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "846f799107eab5ca4285b909ee299a33") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
+ mock.delete "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "9f5b155224edbbb69fd99d8ce094681e") }, nil, 200
+
+ mock.post "/people/2/addresses.json", { 'Authorization' => blank_digest_auth_header("/people/2/addresses.json", "6984d405ff3d9ed07bbf747dcf16afb0") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
+ mock.post "/people/2/addresses.json", { 'Authorization' => request_digest_auth_header("/people/2/addresses.json", "4bda6a28dbf930b5af9244073623bd04") }, nil, 201, 'Location' => '/people/1/addresses/5'
+
+ mock.head "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "15e5ed84ba5c4cfcd5c98a36c2e4f421") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
+ mock.head "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "d4c6d2bcc8717abb2e2ccb8c49ee6a91") }, nil, 200
+ end
end
- def test_delete
- response = @authenticated_conn.delete("/people/2.json")
- assert_equal 200, response.code
+ def test_authorization_header_if_credentials_supplied_and_auth_type_is_digest
+ authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
+ assert_equal blank_digest_auth_header("/people/2.json", "fad396f6a34aeba28e28b9b96ddbb671"), authorization_header['Authorization']
end
- def test_head
- response = @authenticated_conn.head("/people/2.json")
- assert_equal 200, response.code
+ def test_authorization_header_with_query_string_if_auth_type_is_digest
+ authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json?only=name'))
+ assert_equal blank_digest_auth_header("/people/2.json?only=name", "f8457b0b5d21b6b80737a386217afb24"), authorization_header['Authorization']
end
def test_get_with_digest_auth_handles_initial_401_response_and_retries
- @authenticated_conn.auth_type = :digest
response = @authenticated_conn.get("/people/2.json")
assert_equal "David", decode(response)["name"]
end
def test_post_with_digest_auth_handles_initial_401_response_and_retries
- @authenticated_conn.auth_type = :digest
response = @authenticated_conn.post("/people/2/addresses.json")
assert_equal "/people/1/addresses/5", response["Location"]
assert_equal 201, response.code
end
def test_put_with_digest_auth_handles_initial_401_response_and_retries
- @authenticated_conn.auth_type = :digest
- response = @authenticated_conn.put("/people/2.json")
- assert_equal 204, response.code
+ response = @authenticated_conn.put("/people/2.json")
+ assert_equal 204, response.code
end
def test_delete_with_digest_auth_handles_initial_401_response_and_retries
- @authenticated_conn.auth_type = :digest
response = @authenticated_conn.delete("/people/2.json")
assert_equal 200, response.code
end
def test_head_with_digest_auth_handles_initial_401_response_and_retries
- @authenticated_conn.auth_type = :digest
response = @authenticated_conn.head("/people/2.json")
assert_equal 200, response.code
end
def test_get_with_digest_auth_caches_nonce
- @authenticated_conn.auth_type = :digest
response = @authenticated_conn.get("/people/2.json")
assert_equal "David", decode(response)["name"]
@@ -203,19 +227,6 @@ class AuthorizationTest < ActiveSupport::TestCase
assert_equal "Matz", decode(response)["name"]
end
- def test_retry_on_401_only_happens_with_digest_auth
- assert_raise(ActiveResource::UnauthorizedAccess) { @authenticated_conn.get("/people/1.json") }
- assert_equal "", @authenticated_conn.send(:response_auth_header)
- end
-
- def test_raises_invalid_request_on_unauthorized_requests
- assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.json") }
- assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.json") }
- assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.json") }
- assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.json") }
- assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.json") }
- end
-
def test_raises_invalid_request_on_unauthorized_requests_with_digest_auth
@conn.auth_type = :digest
assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.json") }
@@ -225,17 +236,7 @@ class AuthorizationTest < ActiveSupport::TestCase
assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.json") }
end
- def test_client_nonce_is_not_nil
- assert_not_nil ActiveResource::Connection.new("http://david:test123@localhost").send(:client_nonce)
- end
-
- protected
- def assert_response_raises(klass, code)
- assert_raise(klass, "Expected response code #{code} to raise #{klass}") do
- @conn.__send__(:handle_response, Response.new(code))
- end
- end
-
+ private
def blank_digest_auth_header(uri, response)
%Q(Digest username="david", realm="", qop="", uri="#{uri}", nonce="", nc="0", cnonce="i-am-a-client-nonce", opaque="", response="#{response}")
end
@@ -247,8 +248,4 @@ class AuthorizationTest < ActiveSupport::TestCase
def response_digest_auth_header
%Q(Digest realm="RailsTestApp", qop="auth", algorithm=MD5, nonce="#{@nonce}", opaque="ef6dfb078ba22298d366f99567814ffb")
end
-
- def decode(response)
- @authenticated_conn.format.decode(response.body)
- end
end
diff --git a/activeresource/test/cases/base_errors_test.rb b/activeresource/test/cases/base_errors_test.rb
index aacbeeb83c..88ac2de96e 100644
--- a/activeresource/test/cases/base_errors_test.rb
+++ b/activeresource/test/cases/base_errors_test.rb
@@ -5,7 +5,7 @@ class BaseErrorsTest < ActiveSupport::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; charset=utf-8'}
- 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; charset=utf-8'}
+ mock.post "/people.json", {}, %q({"errors":{"age":["can't be blank"],"name":["can't be blank", "must start with a letter"],"person":["quota full for today."]}}), 422, {'Content-Type' => 'application/json; charset=utf-8'}
end
end
@@ -83,7 +83,7 @@ class BaseErrorsTest < ActiveSupport::TestCase
def test_should_mark_as_invalid_when_content_type_is_unavailable_in_response_header
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, {}
- 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, {}
+ mock.post "/people.json", {}, %q({"errors":{"age":["can't be blank"],"name":["can't be blank", "must start with a letter"],"person":["quota full for today."]}}), 422, {}
end
[ :json, :xml ].each do |format|
@@ -93,6 +93,36 @@ class BaseErrorsTest < ActiveSupport::TestCase
end
end
+ def test_should_parse_json_string_errors_with_an_errors_key
+ ActiveResource::HttpMock.respond_to do |mock|
+ 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; charset=utf-8'}
+ end
+
+ assert_deprecated(/as an array/) do
+ invalid_user_using_format(:json) 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_parse_3_1_style_json_errors
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.post "/people.json", {}, %q({"age":["can't be blank"],"name":["can't be blank", "must start with a letter"],"person":["quota full for today."]}), 422, {'Content-Type' => 'application/json; charset=utf-8'}
+ end
+
+ assert_deprecated(/without a root/) do
+ invalid_user_using_format(:json) 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
+
private
def invalid_user_using_format(mime_type_reference)
previous_format = Person.format
diff --git a/activeresource/test/cases/base_test.rb b/activeresource/test/cases/base_test.rb
index c3b963844c..33a6596602 100644
--- a/activeresource/test/cases/base_test.rb
+++ b/activeresource/test/cases/base_test.rb
@@ -8,7 +8,6 @@ require "fixtures/proxy"
require "fixtures/address"
require "fixtures/subscription_plan"
require 'active_support/json'
-require 'active_support/ordered_hash'
require 'active_support/core_ext/hash/conversions'
require 'mocha'
@@ -438,6 +437,41 @@ class BaseTest < ActiveSupport::TestCase
assert_not_equal(first_connection, second_connection, 'Connection should be re-created')
end
+ def test_header_inheritance
+ fruit = Class.new(ActiveResource::Base)
+ apple = Class.new(fruit)
+ fruit.site = 'http://market'
+
+ fruit.headers['key'] = 'value'
+ assert_equal 'value', apple.headers['key']
+ end
+
+ def test_header_inheritance_set_at_multiple_points
+ fruit = Class.new(ActiveResource::Base)
+ apple = Class.new(fruit)
+ fruit.site = 'http://market'
+
+ fruit.headers['key'] = 'value'
+ assert_equal 'value', apple.headers['key']
+
+ apple.headers['key2'] = 'value2'
+ fruit.headers['key3'] = 'value3'
+
+ assert_equal 'value', apple.headers['key']
+ assert_equal 'value2', apple.headers['key2']
+ assert_equal 'value3', apple.headers['key3']
+ end
+
+ def test_header_inheritance_should_not_leak_upstream
+ fruit = Class.new(ActiveResource::Base)
+ apple = Class.new(fruit)
+ fruit.site = 'http://market'
+
+ fruit.headers['key'] = 'value'
+
+ apple.headers['key2'] = 'value2'
+ assert_equal nil, fruit.headers['key2']
+ end
########################################################################
# Tests for setting up remote URLs for a given model (including adding
@@ -464,8 +498,7 @@ class BaseTest < ActiveSupport::TestCase
assert Person.collection_path(:gender => 'male', :student => true).include?('student=true')
assert_equal '/people.json?name%5B%5D=bob&name%5B%5D=your+uncle%2Bme&name%5B%5D=&name%5B%5D=false', Person.collection_path(:name => ['bob', 'your uncle+me', nil, false])
-
- assert_equal '/people.json?struct%5Ba%5D%5B%5D=2&struct%5Ba%5D%5B%5D=1&struct%5Bb%5D=fred', Person.collection_path(:struct => ActiveSupport::OrderedHash[:a, [2,1], 'b', 'fred'])
+ assert_equal '/people.json?struct%5Ba%5D%5B%5D=2&struct%5Ba%5D%5B%5D=1&struct%5Bb%5D=fred', Person.collection_path(:struct => {:a => [2,1], 'b' => 'fred'})
end
def test_custom_element_path
diff --git a/activeresource/test/cases/connection_test.rb b/activeresource/test/cases/connection_test.rb
index 653912f000..0a07ead15e 100644
--- a/activeresource/test/cases/connection_test.rb
+++ b/activeresource/test/cases/connection_test.rb
@@ -224,7 +224,6 @@ class ConnectionTest < ActiveSupport::TestCase
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?
diff --git a/activeresource/test/cases/format_test.rb b/activeresource/test/cases/format_test.rb
index 21fdc24832..315f9db1eb 100644
--- a/activeresource/test/cases/format_test.rb
+++ b/activeresource/test/cases/format_test.rb
@@ -11,15 +11,19 @@ class FormatTest < ActiveSupport::TestCase
end
def test_http_format_header_name
- header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:get]
- assert_equal 'Accept', header_name
+ [:get, :head].each do |verb|
+ header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[verb]
+ assert_equal 'Accept', header_name
+ end
- headers_names = [ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:put], ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:post]]
- headers_names.each{ |name| assert_equal 'Content-Type', name }
+ [:patch, :put, :post].each do |verb|
+ header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[verb]
+ assert_equal 'Content-Type', header_name
+ end
end
def test_formats_on_single_element
- for format in [ :json, :xml ]
+ [ :json, :xml ].each do |format|
using_format(Person, format) do
ActiveResource::HttpMock.respond_to.get "/people/1.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@david)
assert_equal @david[:name], Person.find(1).name
@@ -28,7 +32,7 @@ class FormatTest < ActiveSupport::TestCase
end
def test_formats_on_collection
- for format in [ :json, :xml ]
+ [ :json, :xml ].each do |format|
using_format(Person, format) do
ActiveResource::HttpMock.respond_to.get "/people.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@programmers)
remote_programmers = Person.find(:all)
@@ -39,7 +43,7 @@ class FormatTest < ActiveSupport::TestCase
end
def test_formats_on_custom_collection_method
- for format in [ :json, :xml ]
+ [ :json, :xml ].each do |format|
using_format(Person, format) do
ActiveResource::HttpMock.respond_to.get "/people/retrieve.#{format}?name=David", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode([@david])
remote_programmers = Person.get(:retrieve, :name => 'David')
diff --git a/activeresource/test/cases/http_mock_test.rb b/activeresource/test/cases/http_mock_test.rb
index d2fd911314..d13d9258ce 100644
--- a/activeresource/test/cases/http_mock_test.rb
+++ b/activeresource/test/cases/http_mock_test.rb
@@ -8,7 +8,7 @@ class HttpMockTest < ActiveSupport::TestCase
FORMAT_HEADER = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES
- [:post, :put, :get, :delete, :head].each do |method|
+ [:post, :patch, :put, :get, :delete, :head].each do |method|
test "responds to simple #{method} request" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(method, "/people/1", { FORMAT_HEADER[method] => "application/json" }, "Response")
@@ -193,7 +193,7 @@ class HttpMockTest < ActiveSupport::TestCase
end
def request(method, path, headers = {}, body = nil)
- if method.in?([:put, :post])
+ if method.in?([:patch, :put, :post])
@http.send(method, path, body, headers)
else
@http.send(method, path, headers)
diff --git a/activeresource/test/fixtures/street_address.rb b/activeresource/test/fixtures/street_address.rb
index 94a86702b0..6a8adb98b5 100644
--- a/activeresource/test/fixtures/street_address.rb
+++ b/activeresource/test/fixtures/street_address.rb
@@ -1,4 +1,4 @@
class StreetAddress < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000/people/:person_id/"
+ self.site = "http://37s.sunrise.i:3000/people/:person_id"
self.element_name = 'address'
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index c19d4c7b32..3e40e08ca2 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,5 +1,20 @@
## Rails 4.0.0 (unreleased) ##
+* AS::Callbacks: deprecate `:rescuable` option. *Bogdan Gusiev*
+
+* Adds Integer#ordinal to get the ordinal suffix string of an integer. *Tim Gildea*
+
+* AS::Callbacks: `:per_key` option is no longer supported
+
+* `AS::Callbacks#define_callbacks`: add `:skip_after_callbacks_if_terminated` option.
+
+* Add html_escape_once to ERB::Util, and delegate escape_once tag helper to it. *Carlos Antonio da Silva*
+
+* Remove ActiveSupport::TestCase#pending method, use `skip` instead. *Carlos Antonio da Silva*
+
+* Deprecates the compatibility method Module#local_constant_names,
+ use Module#local_constants instead (which returns symbols). *fxn*
+
* Deletes the compatibility method Module#method_names,
use Module#methods from now on (which returns symbols). *fxn*
@@ -9,9 +24,24 @@
* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger
from Ruby stdlib.
-## Rails 3.2.0 (unreleased) ##
+* Unicode database updated to 6.1.0.
+
+
+## Rails 3.2.1 (January 26, 2012) ##
+
+* Documentation fixes and improvements.
+
+* Update time zone offset information. *Ravil Bayramgalin*
+
+* The deprecated `ActiveSupport::Base64.decode64` calls `::Base64.decode64`
+ now. *Jonathan Viney*
+
+* Fixes uninitialized constant `ActiveSupport::TaggedLogging::ERROR`. *kennyj*
-* Add ActiveSupport::Cache::NullStore for use in development and testing. *Brian Durand*
+
+## Rails 3.2.0 (January 20, 2012) ##
+
+* ActiveSupport::Base64 is deprecated in favor of ::Base64. *Sergey Nartimov*
* Module#synchronize is deprecated with no replacement. Please use `monitor`
from ruby's standard library.
@@ -80,6 +110,37 @@
* ActiveSupport::BufferedLogger#flush is deprecated. Set sync on your
filehandle, or tune your filesystem.
+
+## Rails 3.1.4 (unreleased) ##
+
+* No changes
+
+
+## Rails 3.1.3 (November 20, 2011) ##
+
+* No changes
+
+
+## Rails 3.1.2 (November 18, 2011) ##
+
+* No changes
+
+
+## Rails 3.1.1 (October 7, 2011) ##
+
+* ruby193: String#prepend is also unsafe *Akira Matsuda*
+
+* Fix obviously breakage of Time.=== for Time subclasses *jeremyevans*
+
+* Added fix so that file store does not raise an exception when cache dir does
+ not exist yet. This can happen if a delete_matched is called before anything
+ is saved in the cache. *Philippe Huibonhoa*
+
+* Fixed performance issue where TimeZone lookups would require tzinfo each time *Tim Lucas*
+
+* ActiveSupport::OrderedHash is now marked as extractable when using Array#extract_options! *Prem Sichanugrist*
+
+
## Rails 3.1.0 (August 30, 2011) ##
* ActiveSupport::Dependencies#load and ActiveSupport::Dependencies#require now
@@ -122,12 +183,38 @@
* JSON decoding now uses the multi_json gem which also vendors a json engine called OkJson. The yaml backend has been removed in favor of OkJson as a default engine for 1.8.x, while the built in 1.9.x json implementation will be used by default. *Josh Kalderimis*
+## Rails 3.0.12 (unreleased) ##
+
+* No changes.
+
+
+## Rails 3.0.11 (November 18, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.10 (August 16, 2011) ##
+
+* Delayed backtrace scrubbing in `load_missing_constant` until we actually
+ raise the exception
+
+
+## Rails 3.0.9 (June 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.8 (June 7, 2011) ##
+
+* No changes.
+
+
## Rails 3.0.7 (April 18, 2011) ##
* Hash.from_xml no longer loses attributes on tags containing only whitespace *André Arko*
-* Rails 3.0.6 (April 5, 2011)
+## Rails 3.0.6 (April 5, 2011) ##
* No changes.
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 4db22a0dbd..d26d71b615 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -16,6 +16,9 @@ Gem::Specification.new do |s|
s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
+ s.rdoc_options.concat ['--encoding', 'UTF-8']
+
s.add_dependency('i18n', '~> 0.6')
s.add_dependency('multi_json', '~> 1.0')
+ s.add_dependency('tzinfo', '~> 0.3.31')
end
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index 5fefa429df..5fefa429df 100644..100755
--- a/activesupport/bin/generate_tables
+++ b/activesupport/bin/generate_tables
diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb
index 36e29644c6..0595446189 100644
--- a/activesupport/lib/active_support/buffered_logger.rb
+++ b/activesupport/lib/active_support/buffered_logger.rb
@@ -1,8 +1,5 @@
-require 'thread'
-require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/deprecation'
require 'active_support/logger'
-require 'fileutils'
module ActiveSupport
BufferedLogger = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 7d032ca984..d7408eff9f 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -77,8 +77,7 @@ module ActiveSupport
def expand_cache_key(key, namespace = nil)
expanded_cache_key = namespace ? "#{namespace}/" : ""
- prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
- if prefix
+ if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
expanded_cache_key << "#{prefix}/"
end
@@ -91,7 +90,7 @@ module ActiveSupport
def retrieve_cache_key(key)
case
when key.respond_to?(:cache_key) then key.cache_key
- when key.is_a?(Array) then ['Array', *key.map { |element| retrieve_cache_key(element) }].to_param
+ when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
else key.to_param
end.to_s
end
@@ -383,11 +382,7 @@ module ActiveSupport
options = merged_options(options)
instrument(:exist?, name) do |payload|
entry = read_entry(namespaced_key(name, options), options)
- if entry && !entry.expired?
- true
- else
- false
- end
+ entry && !entry.expired?
end
end
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 9460532af0..b7712a4a15 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -143,7 +143,7 @@ module ActiveSupport
# Translate a file path into a key.
def file_path_key(path)
- fname = path[cache_path.size, path.size].split(File::SEPARATOR, 4).last
+ fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last
Rack::Utils.unescape(fname)
end
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index e38a8387b4..2e1ccb72d8 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -6,7 +6,6 @@ rescue LoadError => e
end
require 'digest/md5'
-require 'active_support/core_ext/string/encoding'
module ActiveSupport
module Cache
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index f1d00aab6d..6e36edee4f 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -66,8 +66,6 @@ module ActiveSupport
#
# Calls the before and around callbacks in the order they were set, yields
# the block (if given one), and then runs the after callbacks in reverse order.
- # Optionally accepts a key, which will be used to compile an optimized callback
- # method for each key. See +ClassMethods.define_callbacks+ for more information.
#
# If the callback chain was halted, returns +false+. Otherwise returns the result
# of the block, or +true+ if no block is given.
@@ -76,8 +74,9 @@ module ActiveSupport
# save
# end
#
- def run_callbacks(kind, *args, &block)
- send("_run_#{kind}_callbacks", *args, &block)
+ def run_callbacks(kind, key = nil, &block)
+ #TODO: deprecate key argument
+ self.class.__run_callbacks(kind, self, &block)
end
private
@@ -91,29 +90,29 @@ module ActiveSupport
class Callback #:nodoc:#
@@_callback_sequence = 0
- attr_accessor :chain, :filter, :kind, :options, :per_key, :klass, :raw_filter
+ attr_accessor :chain, :filter, :kind, :options, :klass, :raw_filter
def initialize(chain, filter, kind, options, klass)
@chain, @kind, @klass = chain, kind, klass
+ deprecate_per_key_option(options)
normalize_options!(options)
- @per_key = options.delete(:per_key)
@raw_filter, @options = filter, options
@filter = _compile_filter(filter)
- @compiled_options = _compile_options(options)
- @callback_id = next_id
+ recompile_options!
+ end
- _compile_per_key_options
+ def deprecate_per_key_option(options)
+ if options[:per_key]
+ raise NotImplementedError, ":per_key option is no longer supported. Use generic :if and :unless options instead."
+ end
end
def clone(chain, klass)
obj = super()
obj.chain = chain
obj.klass = klass
- obj.per_key = @per_key.dup
obj.options = @options.dup
- obj.per_key[:if] = @per_key[:if].dup
- obj.per_key[:unless] = @per_key[:unless].dup
obj.options[:if] = @options[:if].dup
obj.options[:unless] = @options[:unless].dup
obj
@@ -122,10 +121,6 @@ module ActiveSupport
def normalize_options!(options)
options[:if] = Array(options[:if])
options[:unless] = Array(options[:unless])
-
- options[:per_key] ||= {}
- options[:per_key][:if] = Array(options[:per_key][:if])
- options[:per_key][:unless] = Array(options[:per_key][:unless])
end
def name
@@ -141,32 +136,19 @@ module ActiveSupport
end
def _update_filter(filter_options, new_options)
- filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
- filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
+ filter_options[:if].concat(Array(new_options[:unless])) if new_options.key?(:unless)
+ filter_options[:unless].concat(Array(new_options[:if])) if new_options.key?(:if)
end
- def recompile!(_options, _per_key)
+ def recompile!(_options)
+ deprecate_per_key_option(_options)
_update_filter(self.options, _options)
- _update_filter(self.per_key, _per_key)
- @callback_id = next_id
- @filter = _compile_filter(@raw_filter)
- @compiled_options = _compile_options(@options)
- _compile_per_key_options
- end
-
- def _compile_per_key_options
- key_options = _compile_options(@per_key)
-
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def _one_time_conditions_valid_#{@callback_id}?
- true if #{key_options}
- end
- RUBY_EVAL
+ recompile_options!
end
# Wraps code with filter
- def apply(code, key=nil, object=nil)
+ def apply(code)
case @kind
when :before
<<-RUBY_EVAL
@@ -185,7 +167,7 @@ module ActiveSupport
when :after
<<-RUBY_EVAL
#{code}
- if #{@compiled_options}
+ if #{!chain.config[:skip_after_callbacks_if_terminated] || "!halted"} && #{@compiled_options}
#{@filter}
end
RUBY_EVAL
@@ -200,11 +182,6 @@ module ActiveSupport
end
end
-
- def one_time_conditions_valid?(object)
- object.send("_one_time_conditions_valid_#{@callback_id}?")
- end
-
private
# Compile around filters with conditions into proxy methods
@@ -241,7 +218,7 @@ module ActiveSupport
# Options support the same options as filters themselves (and support
# symbols, string, procs, and objects), so compile a conditional
# expression based on the options
- def _compile_options(options)
+ def recompile_options!
conditions = ["true"]
unless options[:if].empty?
@@ -252,7 +229,7 @@ module ActiveSupport
conditions << Array(_compile_filter(options[:unless])).map {|f| "!#{f}"}
end
- conditions.flatten.join(" && ")
+ @compiled_options = conditions.flatten.join(" && ")
end
# Filters support:
@@ -330,76 +307,37 @@ module ActiveSupport
@name = name
@config = {
:terminator => "false",
- :rescuable => false,
:scope => [ :kind ]
}.merge(config)
end
- def compile(key=nil, object=nil)
+ def compile
method = []
method << "value = nil"
method << "halted = false"
- callbacks = yielding
- applicable_callbacks_for(key, object).reverse_each do |callback|
- callbacks = callback.apply(callbacks, key, object)
+ callbacks = "value = yield if block_given? && !halted"
+ reverse_each do |callback|
+ callbacks = callback.apply(callbacks)
end
method << callbacks
- method << "raise rescued_error if rescued_error" if config[:rescuable]
method << "halted ? false : (block_given? ? value : true)"
method.flatten.compact.join("\n")
end
- # Returns part of method that evaluates the callback block
- def yielding
- method = []
- if config[:rescuable]
- method << "rescued_error = nil"
- method << "begin"
- end
-
- method << "value = yield if block_given? && !halted"
-
- if config[:rescuable]
- method << "rescue Exception => e"
- method << "rescued_error = e"
- method << "end"
- end
- method.join("\n")
- end
-
- # Selects callbacks that have valid <tt>:per_key</tt> condition
- def applicable_callbacks_for(key, object)
- return self unless key
- select do |callback|
- callback.one_time_conditions_valid?(object)
- end
- end
end
module ClassMethods
- # Generate the internal runner method called by +run_callbacks+.
- def __define_runner(symbol) #:nodoc:
- runner_method = "_run_#{symbol}_callbacks"
- unless private_method_defined?(runner_method)
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{runner_method}(key = nil, &blk)
- self.class.__run_callback(key, :#{symbol}, self, &blk)
- end
- private :#{runner_method}
- RUBY_EVAL
- end
- end
- # This method calls the callback method for the given key.
- # If this called first time it creates a new callback method for the key,
- # calculating which callbacks can be omitted because of per_key conditions.
+ # This method runs callback chain for the given kind.
+ # If this called first time it creates a new callback method for the kind.
+ # This generated method plays caching role.
#
- def __run_callback(key, kind, object, &blk) #:nodoc:
- name = __callback_runner_name(key, kind)
- unless object.respond_to?(name)
- str = send("_#{kind}_callbacks").compile(key, object)
+ def __run_callbacks(kind, object, &blk) #:nodoc:
+ name = __callback_runner_name(kind)
+ unless object.respond_to?(name, true)
+ str = object.send("_#{kind}_callbacks").compile
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{name}() #{str} end
protected :#{name}
@@ -409,12 +347,12 @@ module ActiveSupport
end
def __reset_runner(symbol)
- name = __callback_runner_name(nil, symbol)
+ name = __callback_runner_name(symbol)
undef_method(name) if method_defined?(name)
end
- def __callback_runner_name(key, kind)
- "_run__#{self.name.hash.abs}__#{kind}__#{key.hash.abs}__callbacks"
+ def __callback_runner_name(kind)
+ "_run__#{self.name.hash.abs}__#{kind}__callbacks"
end
# This is used internally to append, prepend and skip callbacks to the
@@ -467,29 +405,6 @@ module ActiveSupport
# will be called only when it returns a false value.
# * <tt>:prepend</tt> - If true, the callback will be prepended to the existing
# chain rather than appended.
- # * <tt>:per_key</tt> - A hash with <tt>:if</tt> and <tt>:unless</tt> options;
- # see "Per-key conditions" below.
- #
- # ===== Per-key conditions
- #
- # When creating or skipping callbacks, you can specify conditions that
- # are always the same for a given key. For instance, in Action Pack,
- # we convert :only and :except conditions into per-key conditions.
- #
- # before_filter :authenticate, :except => "index"
- #
- # becomes
- #
- # set_callback :process_action, :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}}
- #
- # Per-key conditions are evaluated only once per use of a given key.
- # In the case of the above example, you would do:
- #
- # run_callbacks(:process_action, action_name) { ... dispatch stuff ... }
- #
- # In that case, each action_name would get its own compiled callback
- # method that took into consideration the per_key conditions. This
- # is a speed improvement for ActionPack.
#
def set_callback(name, *filter_list, &block)
mapped = nil
@@ -524,7 +439,7 @@ module ActiveSupport
if filter && options.any?
new_filter = filter.clone(chain, self)
chain.insert(chain.index(filter), new_filter)
- new_filter.recompile!(options, options[:per_key] || {})
+ new_filter.recompile!(options)
end
chain.delete(filter)
@@ -568,10 +483,10 @@ module ActiveSupport
# other callbacks are not executed. Defaults to "false", meaning no value
# halts the chain.
#
- # * <tt>:rescuable</tt> - By default, after filters are not executed if
- # the given block or a before filter raises an error. By setting this option
- # to <tt>true</tt> exception raised by given block is stored and after
- # executing all the after callbacks the stored exception is raised.
+ # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after callbacks should be terminated
+ # by the <tt>:terminator</tt> option. By default after callbacks executed no matter
+ # if callback chain was terminated or not.
+ # Option makes sence only when <tt>:terminator</tt> option is specified.
#
# * <tt>:scope</tt> - Indicates which methods should be executed when an object
# is used as a callback.
@@ -621,7 +536,6 @@ module ActiveSupport
callbacks.each do |callback|
class_attribute "_#{callback}_callbacks"
send("_#{callback}_callbacks=", CallbackChain.new(callback, config))
- __define_runner(callback)
end
end
end
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index a2d2719de7..a8aa53a80f 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -1,7 +1,5 @@
require 'active_support/concern'
require 'active_support/ordered_options'
-require 'active_support/core_ext/kernel/singleton_class'
-require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/array/extract_options'
module ActiveSupport
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 6dd321ecf9..6f730e4b6f 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -1,8 +1,12 @@
+require 'active_support/deprecation'
+
class DateTime
class << self
- # DateTimes aren't aware of DST rules, so use a consistent non-DST offset when creating a DateTime with an offset in the local zone
+ # *DEPRECATED*: Use +DateTime.civil_from_format+ directly.
def local_offset
- ::Time.local(2007).utc_offset.to_r / 86400
+ ActiveSupport::Deprecation.warn 'DateTime.local_offset is deprecated. ' \
+ 'Use DateTime.civil_from_format directly.', caller
+ ::Time.local(2012).utc_offset.to_r / 86400
end
# Returns <tt>Time.zone.now.to_datetime</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise returns <tt>Time.now.to_datetime</tt>.
diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
index d7b3ad7d8d..dc55e9c33c 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -58,32 +58,27 @@ class DateTime
alias_method :default_inspect, :inspect
alias_method :inspect, :readable_inspect
- # Converts self to a Ruby Date object; time portion is discarded.
- def to_date
- ::Date.new(year, month, day)
- end unless instance_methods(false).include?(:to_date)
-
# Attempts to convert self to a Ruby Time object; returns self if out of range of Ruby Time class.
# If self has an offset other than 0, self will just be returned unaltered, since there's no clean way to map it to a Time.
def to_time
self.offset == 0 ? ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * 1000000) : self
end
- # To be able to keep Times, Dates and DateTimes interchangeable on conversions.
- def to_datetime
- self
- end unless instance_methods(false).include?(:to_datetime)
-
+ # Returns DateTime with local offset for given year if format is local else offset is zero
+ #
+ # DateTime.civil_from_format :local, 2012
+ # # => Sun, 01 Jan 2012 00:00:00 +0300
+ # DateTime.civil_from_format :local, 2012, 12, 17
+ # # => Mon, 17 Dec 2012 00:00:00 +0000
def self.civil_from_format(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0)
- offset = utc_or_local.to_sym == :local ? local_offset : 0
+ if utc_or_local.to_sym == :local
+ offset = ::Time.local(year, month, day).utc_offset.to_r / 86400
+ else
+ offset = 0
+ end
civil(year, month, day, hour, min, sec, offset)
end
- # Converts datetime to an appropriate format for use in XML.
- def xmlschema
- strftime("%Y-%m-%dT%H:%M:%S%Z")
- end unless instance_methods(false).include?(:xmlschema)
-
# Converts self to a floating-point number of seconds since the Unix epoch.
def to_f
seconds_since_unix_epoch.to_f
diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb
index 3645597301..fc3277f4d2 100644
--- a/activesupport/lib/active_support/core_ext/file/atomic.rb
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -17,6 +17,7 @@ class File
require 'fileutils' unless defined?(FileUtils)
temp_file = Tempfile.new(basename(file_name), temp_dir)
+ temp_file.binmode
yield temp_file
temp_file.close
diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
index f4cb445444..7d54c9fae6 100644
--- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
+++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
@@ -14,7 +14,7 @@ class Hash
# #with_indifferent_access. This method will be called on the current object
# by the enclosing object and is aliased to #with_indifferent_access by
# default. Subclasses of Hash may overwrite this method to return +self+ if
- # converting to an +ActiveSupport::HashWithIndifferentAccess+ would not be
+ # converting to an <tt>ActiveSupport::HashWithIndifferentAccess</tt> would not be
# desirable.
#
# b = {:b => 1}
diff --git a/activesupport/lib/active_support/core_ext/integer/inflections.rb b/activesupport/lib/active_support/core_ext/integer/inflections.rb
index 0e606056c0..1e30687166 100644
--- a/activesupport/lib/active_support/core_ext/integer/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/integer/inflections.rb
@@ -14,4 +14,18 @@ class Integer
def ordinalize
ActiveSupport::Inflector.ordinalize(self)
end
+
+ # Ordinal returns the suffix used to denote the position
+ # in an ordered sequence such as 1st, 2nd, 3rd, 4th.
+ #
+ # 1.ordinal # => "st"
+ # 2.ordinal # => "nd"
+ # 1002.ordinal # => "nd"
+ # 1003.ordinal # => "rd"
+ # -11.ordinal # => "th"
+ # -1001.ordinal # => "st"
+ #
+ def ordinal
+ ActiveSupport::Inflector.ordinal(self)
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 7de824a77f..ac2a63d3a1 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -81,25 +81,25 @@ class Module
# no matter whether +nil+ responds to the delegated method. You can get a
# +nil+ instead with the +:allow_nil+ option.
#
- # class Foo
- # attr_accessor :bar
- # def initialize(bar = nil)
- # @bar = bar
- # end
- # delegate :zoo, :to => :bar
- # end
- #
- # Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
- #
- # class Foo
- # attr_accessor :bar
- # def initialize(bar = nil)
- # @bar = bar
- # end
- # delegate :zoo, :to => :bar, :allow_nil => true
- # end
- #
- # Foo.new.zoo # returns nil
+ # class Foo
+ # attr_accessor :bar
+ # def initialize(bar = nil)
+ # @bar = bar
+ # end
+ # delegate :zoo, :to => :bar
+ # end
+ #
+ # Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
+ #
+ # class Foo
+ # attr_accessor :bar
+ # def initialize(bar = nil)
+ # @bar = bar
+ # end
+ # delegate :zoo, :to => :bar, :allow_nil => true
+ # end
+ #
+ # Foo.new.zoo # returns nil
#
def delegate(*methods)
options = methods.pop
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index 1893a9cfa6..743db47bac 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -61,9 +61,19 @@ class Module
constants(false)
end
- # Returns the names of the constants defined locally rather than the
- # constants themselves. See <tt>local_constants</tt>.
+ # *DEPRECATED*: Use +local_constants+ instead.
+ #
+ # Returns the names of the constants defined locally as strings.
+ #
+ # module M
+ # X = 1
+ # end
+ # M.local_constant_names # => ["X"]
+ #
+ # This method is useful for forward compatibility, since Ruby 1.8 returns
+ # constant names as strings, whereas 1.9 returns them as symbols.
def local_constant_names
+ ActiveSupport::Deprecation.warn('Module#local_constant_names is deprecated, use Module#local_constants instead', caller)
local_constants.map { |c| c.to_s }
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index 7271671908..d67711f3b8 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -1,5 +1,4 @@
# encoding: utf-8
-require 'active_support/core_ext/string/encoding'
class Object
# An object is blank if it's false, empty, or a whitespace string.
diff --git a/activesupport/lib/active_support/core_ext/range/overlaps.rb b/activesupport/lib/active_support/core_ext/range/overlaps.rb
index 7df653b53f..603657c180 100644
--- a/activesupport/lib/active_support/core_ext/range/overlaps.rb
+++ b/activesupport/lib/active_support/core_ext/range/overlaps.rb
@@ -3,6 +3,6 @@ class Range
# (1..5).overlaps?(4..6) # => true
# (1..5).overlaps?(7..9) # => false
def overlaps?(other)
- include?(other.first) || other.include?(first)
+ cover?(other.first) || other.cover?(first)
end
end
diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb
index 72522d395c..ab49af55bf 100644
--- a/activesupport/lib/active_support/core_ext/string.rb
+++ b/activesupport/lib/active_support/core_ext/string.rb
@@ -9,6 +9,5 @@ require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/string/interpolation'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/string/exclude'
-require 'active_support/core_ext/string/encoding'
require 'active_support/core_ext/string/strip'
require 'active_support/core_ext/string/inquiry'
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 1e57b586d9..2194dafe4d 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -100,8 +100,8 @@ class String
#
# +underscore+ will also change '::' to '/' to convert namespaces to paths.
#
- # "ActiveRecord".underscore # => "active_record"
- # "ActiveRecord::Errors".underscore # => active_record/errors
+ # "ActiveModel".underscore # => "active_model"
+ # "ActiveModel::Errors".underscore # => "active_model/errors"
def underscore
ActiveSupport::Inflector.underscore(self)
end
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index 73aa7dd89a..dd780da157 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -5,6 +5,8 @@ class ERB
module Util
HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;' }
JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' }
+ HTML_ESCAPE_ONCE_REGEXP = /[\"><]|&(?!([a-zA-Z]+|(#\d+));)/
+ JSON_ESCAPE_REGEXP = /[&"><]/
# A utility method for escaping HTML tag characters.
# This method is also aliased as <tt>h</tt>.
@@ -33,6 +35,21 @@ class ERB
singleton_class.send(:remove_method, :html_escape)
module_function :html_escape
+ # A utility method for escaping HTML without affecting existing escaped entities.
+ #
+ # ==== Examples
+ # html_escape_once("1 < 2 &amp; 3")
+ # # => "1 &lt; 2 &amp; 3"
+ #
+ # html_escape_once("&lt;&lt; Accept & Checkout")
+ # # => "&lt;&lt; Accept &amp; Checkout"
+ def html_escape_once(s)
+ result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP) { |special| HTML_ESCAPE[special] }
+ s.html_safe? ? result.html_safe : result
+ end
+
+ module_function :html_escape_once
+
# A utility method for escaping HTML entities in JSON strings
# using \uXXXX JavaScript escape sequences for string literals:
#
@@ -51,7 +68,7 @@ class ERB
# <%=j @person.to_json %>
#
def json_escape(s)
- result = s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] }
+ result = s.to_s.gsub(JSON_ESCAPE_REGEXP) { |special| JSON_ESCAPE[special] }
s.html_safe? ? result.html_safe : result
end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 5cf2715753..5076697c04 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -67,7 +67,7 @@ class Time
end
# Returns a new Time where one or more of the elements have been changed according to the +options+ parameter. The time options
- # (hour, minute, sec, usec) reset cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0. If the hour and
+ # (hour, min, sec, usec) reset cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0. If the hour and
# minute is passed, then sec and usec is set to 0.
def change(options)
::Time.send(
@@ -276,9 +276,9 @@ class Time
beginning_of_day..end_of_day
end
- # Returns a Range representing the whole week of the current time.
- def all_week
- beginning_of_week..end_of_week
+ # Returns a Range representing the whole week of the current time. Week starts on start_day (default is :monday, i.e. end of Sunday).
+ def all_week(start_day = :monday)
+ beginning_of_week(start_day)..end_of_week(start_day)
end
# Returns a Range representing the whole month of the current time.
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index 5a17fc1afa..0fdcd383f0 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -53,32 +53,4 @@ class Time
def formatted_offset(colon = true, alternate_utc_string = nil)
utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)
end
-
- # Converts a Time object to a Date, dropping hour, minute, and second precision.
- #
- # my_time = Time.now # => Mon Nov 12 22:59:51 -0500 2007
- # my_time.to_date # => Mon, 12 Nov 2007
- #
- # your_time = Time.parse("1/13/2009 1:13:03 P.M.") # => Tue Jan 13 13:13:03 -0500 2009
- # your_time.to_date # => Tue, 13 Jan 2009
- def to_date
- ::Date.new(year, month, day)
- end unless method_defined?(:to_date)
-
- # A method to keep Time, Date and DateTime instances interchangeable on conversions.
- # In this case, it simply returns +self+.
- def to_time
- self
- end unless method_defined?(:to_time)
-
- # Converts a Time instance to a Ruby DateTime instance, preserving UTC offset.
- #
- # my_time = Time.now # => Mon Nov 12 23:04:21 -0500 2007
- # my_time.to_datetime # => Mon, 12 Nov 2007 23:04:21 -0500
- #
- # your_time = Time.parse("1/13/2009 1:13:03 P.M.") # => Tue Jan 13 13:13:03 -0500 2009
- # your_time.to_datetime # => Tue, 13 Jan 2009 13:13:03 -0500
- def to_datetime
- ::DateTime.civil(year, month, day, hour, min, sec, Rational(utc_offset, 86400))
- end unless method_defined?(:to_datetime)
end
diff --git a/activesupport/lib/active_support/core_ext/uri.rb b/activesupport/lib/active_support/core_ext/uri.rb
index 0b219ce44a..bfe0832b37 100644
--- a/activesupport/lib/active_support/core_ext/uri.rb
+++ b/activesupport/lib/active_support/core_ext/uri.rb
@@ -20,7 +20,7 @@ end
module URI
class << self
def parser
- @parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
+ @parser ||= URI::Parser.new
end
end
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index e121e452a3..2c5950edf5 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -105,7 +105,7 @@ module ActiveSupport #:nodoc:
next unless mod.is_a?(Module)
# Get a list of the constants that were added
- new_constants = mod.local_constant_names - original_constants
+ new_constants = mod.local_constants - original_constants
# self[namespace] returns an Array of the constants that are being evaluated
# for that namespace. For instance, if parent.rb requires child.rb, the first
@@ -133,7 +133,7 @@ module ActiveSupport #:nodoc:
namespaces.map do |namespace|
module_name = Dependencies.to_constant_name(namespace)
original_constants = Dependencies.qualified_const_defined?(module_name) ?
- Inflector.constantize(module_name).local_constant_names : []
+ Inflector.constantize(module_name).local_constants : []
watching << module_name
@stack[module_name] << original_constants
diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb
index f7036315d6..420b965c87 100644
--- a/activesupport/lib/active_support/gzip.rb
+++ b/activesupport/lib/active_support/gzip.rb
@@ -1,6 +1,5 @@
require 'zlib'
require 'stringio'
-require 'active_support/core_ext/string/encoding'
module ActiveSupport
# A convenient wrapper for the zlib standard library that allows compression/decompression of strings with gzip.
diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb
index 527cce2594..b3eb1333ca 100644
--- a/activesupport/lib/active_support/inflections.rb
+++ b/activesupport/lib/active_support/inflections.rb
@@ -2,7 +2,7 @@ module ActiveSupport
Inflector.inflections do |inflect|
inflect.plural(/$/, 's')
inflect.plural(/s$/i, 's')
- inflect.plural(/(ax|test)is$/i, '\1es')
+ inflect.plural(/^(ax|test)is$/i, '\1es')
inflect.plural(/(octop|vir)us$/i, '\1i')
inflect.plural(/(octop|vir)i$/i, '\1i')
inflect.plural(/(alias|status)$/i, '\1es')
@@ -23,10 +23,11 @@ module ActiveSupport
inflect.plural(/(quiz)$/i, '\1zes')
inflect.singular(/s$/i, '')
+ inflect.singular(/(ss)$/i, '\1')
inflect.singular(/(n)ews$/i, '\1ews')
inflect.singular(/([ti])a$/i, '\1um')
- inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
- inflect.singular(/(^analy)ses$/i, '\1sis')
+ inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1\2sis')
+ inflect.singular(/(^analy)(sis|ses)$/i, '\1sis')
inflect.singular(/([^f])ves$/i, '\1fe')
inflect.singular(/(hive)s$/i, '\1')
inflect.singular(/(tive)s$/i, '\1')
@@ -36,12 +37,13 @@ module ActiveSupport
inflect.singular(/(m)ovies$/i, '\1ovie')
inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
inflect.singular(/(m|l)ice$/i, '\1ouse')
- inflect.singular(/(bus)es$/i, '\1')
+ inflect.singular(/(bus)(es)?$/i, '\1')
inflect.singular(/(o)es$/i, '\1')
inflect.singular(/(shoe)s$/i, '\1')
- inflect.singular(/(cris|ax|test)es$/i, '\1is')
- inflect.singular(/(octop|vir)i$/i, '\1us')
- inflect.singular(/(alias|status)es$/i, '\1')
+ inflect.singular(/(cris|test)(is|es)$/i, '\1is')
+ inflect.singular(/^(a)x[ie]s$/i, '\1xis')
+ inflect.singular(/(octop|vir)(us|i)$/i, '\1us')
+ inflect.singular(/(alias|status)(es)?$/i, '\1')
inflect.singular(/^(ox)en/i, '\1')
inflect.singular(/(vert|ind)ices$/i, '\1ex')
inflect.singular(/(matr)ices$/i, '\1ix')
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index 90bb62f57b..13b23d627a 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/array/prepend_and_append'
+
module ActiveSupport
module Inflector
# A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
@@ -26,6 +28,13 @@ module ActiveSupport
@plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
end
+ # Private, for the test suite.
+ def initialize_dup(orig)
+ %w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope|
+ instance_variable_set("@#{scope}", orig.send(scope).dup)
+ end
+ end
+
# Specifies a new acronym. An acronym must be specified as it will appear in a camelized string. An underscore
# string that contains the acronym will retain the acronym when passed to `camelize`, `humanize`, or `titleize`.
# A camelized string that contains the acronym will maintain the acronym when titleized or humanized, and will
@@ -82,7 +91,7 @@ module ActiveSupport
def plural(rule, replacement)
@uncountables.delete(rule) if rule.is_a?(String)
@uncountables.delete(replacement)
- @plurals.insert(0, [rule, replacement])
+ @plurals.prepend([rule, replacement])
end
# Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
@@ -90,7 +99,7 @@ module ActiveSupport
def singular(rule, replacement)
@uncountables.delete(rule) if rule.is_a?(String)
@uncountables.delete(replacement)
- @singulars.insert(0, [rule, replacement])
+ @singulars.prepend([rule, replacement])
end
# Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
@@ -134,7 +143,7 @@ module ActiveSupport
# human /_cnt$/i, '\1_count'
# human "legacy_col_person_name", "Name"
def human(rule, replacement)
- @humans.insert(0, [rule, replacement])
+ @humans.prepend([rule, replacement])
end
# Clears the loaded inflections within a given scope (default is <tt>:all</tt>).
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 7f325aee94..c630bd21b1 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -42,10 +42,10 @@ module ActiveSupport
# +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
#
# Examples:
- # "active_record".camelize # => "ActiveRecord"
- # "active_record".camelize(:lower) # => "activeRecord"
- # "active_record/errors".camelize # => "ActiveRecord::Errors"
- # "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
+ # "active_model".camelize # => "ActiveModel"
+ # "active_model".camelize(:lower) # => "activeModel"
+ # "active_model/errors".camelize # => "ActiveModel::Errors"
+ # "active_model/errors".camelize(:lower) # => "activeModel::Errors"
#
# As a rule of thumb you can think of +camelize+ as the inverse of +underscore+,
# though there are cases where that does not hold:
@@ -66,8 +66,8 @@ module ActiveSupport
# Changes '::' to '/' to convert namespaces to paths.
#
# Examples:
- # "ActiveRecord".underscore # => "active_record"
- # "ActiveRecord::Errors".underscore # => active_record/errors
+ # "ActiveModel".underscore # => "active_model"
+ # "ActiveModel::Errors".underscore # => "active_model/errors"
#
# As a rule of thumb you can think of +underscore+ as the inverse of +camelize+,
# though there are cases where that does not hold:
@@ -92,9 +92,12 @@ module ActiveSupport
# "author_id" # => "Author"
def humanize(lower_case_and_underscored_word)
result = lower_case_and_underscored_word.to_s.dup
- inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
+ inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
result.gsub!(/_id$/, "")
- result.gsub(/(_)?([a-z\d]*)/i) { "#{$1 && ' '}#{inflections.acronyms[$2] || $2.downcase}" }.gsub(/^\w/) { $&.upcase }
+ result.tr!('_', ' ')
+ result.gsub(/([a-z\d]*)/i) { |match|
+ "#{inflections.acronyms[match] || match.downcase}"
+ }.gsub(/^\w/) { $&.upcase }
end
# Capitalizes all the words and replaces some characters in the string to create
@@ -143,7 +146,7 @@ module ActiveSupport
# Example:
# "puni_puni" # => "puni-puni"
def dasherize(underscored_word)
- underscored_word.gsub(/_/, '-')
+ underscored_word.tr('_', '-')
end
# Removes the module part from the expression in the string:
@@ -209,7 +212,7 @@ module ActiveSupport
constant = Object
names.each do |name|
- constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name)
+ constant = constant.const_get(name, false)
end
constant
end
@@ -240,13 +243,36 @@ module ActiveSupport
begin
constantize(camel_cased_word)
rescue NameError => e
- raise unless e.message =~ /uninitialized constant #{const_regexp(camel_cased_word)}$/ ||
+ raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ ||
e.name.to_s == camel_cased_word.to_s
rescue ArgumentError => e
raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
end
end
+ # Returns the suffix that should be added to a number to denote the position
+ # in an ordered sequence such as 1st, 2nd, 3rd, 4th.
+ #
+ # Examples:
+ # ordinal(1) # => "st"
+ # ordinal(2) # => "nd"
+ # ordinal(1002) # => "nd"
+ # ordinal(1003) # => "rd"
+ # ordinal(-11) # => "th"
+ # ordinal(-1021) # => "st"
+ def ordinal(number)
+ if (11..13).include?(number.to_i.abs % 100)
+ "th"
+ else
+ case number.to_i.abs % 10
+ when 1; "st"
+ when 2; "nd"
+ when 3; "rd"
+ else "th"
+ end
+ end
+ end
+
# Turns a number into an ordinal string used to denote the position in an
# ordered sequence such as 1st, 2nd, 3rd, 4th.
#
@@ -258,16 +284,7 @@ module ActiveSupport
# ordinalize(-11) # => "-11th"
# ordinalize(-1021) # => "-1021st"
def ordinalize(number)
- if (11..13).include?(number.to_i.abs % 100)
- "#{number}th"
- else
- case number.to_i.abs % 10
- when 1; "#{number}st"
- when 2; "#{number}nd"
- when 3; "#{number}rd"
- else "#{number}th"
- end
- end
+ "#{number}#{ordinal(number)}"
end
private
@@ -291,10 +308,10 @@ module ActiveSupport
def apply_inflections(word, rules)
result = word.to_s.dup
- if word.empty? || inflections.uncountables.any? { |inflection| result =~ /\b#{inflection}\Z/i }
+ if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/])
result
else
- rules.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
+ rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
result
end
end
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 0e1bf4c40b..b2adfea273 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,7 +1,6 @@
require 'active_support/core_ext/object/to_json'
require 'active_support/core_ext/module/delegation'
require 'active_support/json/variable'
-require 'active_support/ordered_hash'
require 'bigdecimal'
require 'active_support/core_ext/big_decimal/conversions' # for #to_s
@@ -148,8 +147,8 @@ class Object
end
end
-class Struct
- def as_json(options = nil) #:nodoc:
+class Struct #:nodoc:
+ def as_json(options = nil)
Hash[members.zip(values)]
end
end
@@ -239,8 +238,7 @@ class Hash
# use encoder as a proxy to call as_json on all values in the subset, to protect from circular references
encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
- result = self.is_a?(ActiveSupport::OrderedHash) ? ActiveSupport::OrderedHash : Hash
- result[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }]
+ Hash[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }]
end
def encode_json(encoder)
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index 6296c1d4b8..58938cdc3d 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -92,7 +92,7 @@ module ActiveSupport
begin
send(method, ActiveSupport::Notifications::Event.new(message, *args))
rescue Exception => e
- logger.error "Could not log #{message.inspect} event. #{e.class}: #{e.message}"
+ logger.error "Could not log #{message.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
end
end
@@ -100,9 +100,9 @@ module ActiveSupport
%w(info debug warn error fatal unknown).each do |level|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
- def #{level}(*args, &block)
+ def #{level}(progname = nil, &block)
return unless logger
- logger.#{level}(*args, &block)
+ logger.#{level}(progname, &block)
end
METHOD
end
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index 66e8fcadb4..d055767eab 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -2,6 +2,41 @@ require 'logger'
module ActiveSupport
class Logger < ::Logger
+ # Broadcasts logs to multiple loggers
+ def self.broadcast(logger) # :nodoc:
+ Module.new do
+ define_method(:add) do |*args, &block|
+ logger.add(*args, &block)
+ super(*args, &block)
+ end
+
+ define_method(:<<) do |x|
+ logger << x
+ super(x)
+ end
+
+ define_method(:close) do
+ logger.close
+ super()
+ end
+
+ define_method(:progname=) do |name|
+ logger.progname = name
+ super(name)
+ end
+
+ define_method(:formatter=) do |formatter|
+ logger.formatter = formatter
+ super(formatter)
+ end
+
+ define_method(:level=) do |level|
+ logger.level = level
+ super(level)
+ end
+ end
+ end
+
def initialize(*args)
super
@formatter = SimpleFormatter.new
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 6ec5a04933..ada2e79ccb 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -9,6 +9,11 @@ module ActiveSupport
#
# This can be used in situations similar to the <tt>MessageVerifier</tt>, but where you don't
# want users to be able to determine the value of the payload.
+ #
+ # key = OpenSSL::Digest::SHA256.new('password').digest # => "\x89\xE0\x156\xAC..."
+ # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
+ # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
+ # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
class MessageEncryptor
module NullSerializer #:nodoc:
def self.load(value)
@@ -23,6 +28,15 @@ module ActiveSupport
class InvalidMessage < StandardError; end
OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError
+ # Initialize a new MessageEncryptor.
+ # +secret+ must be at least as long as the cipher key size. For the default 'aes-256-cbc' cipher,
+ # this is 256 bits. If you are using a user-entered secret, you can generate a suitable key with
+ # <tt>OpenSSL::Digest::SHA256.new(user_secret).digest</tt> or similar.
+ #
+ # Options:
+ # * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'
+ # * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
+ #
def initialize(secret, options = {})
@secret = secret
@cipher = options[:cipher] || 'aes-256-cbc'
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index ac61870871..9a748dfa60 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -1,4 +1,5 @@
# encoding: utf-8
+require 'active_support/json'
require 'active_support/core_ext/string/access'
require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/module/delegation'
@@ -188,6 +189,10 @@ module ActiveSupport #:nodoc:
chars(Unicode.tidy_bytes(@wrapped_string, force))
end
+ def as_json(options = nil) #:nodoc:
+ to_s.as_json(options)
+ end
+
%w(capitalize downcase reverse tidy_bytes upcase).each do |method|
define_method("#{method}!") do |*args|
@wrapped_string = send(method, *args).to_s
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index a0a8f3c97e..cb89d45c92 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -10,7 +10,7 @@ module ActiveSupport
NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
# The Unicode version that is supported by the implementation
- UNICODE_VERSION = '6.0.0'
+ UNICODE_VERSION = '6.1.0'
# The default normalization used for operations that require normalization. It can be set to any of the
# normalizations in NORMALIZATION_FORMS.
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index f549d2fff3..13f675c654 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -68,6 +68,10 @@ module ActiveSupport
# Sometimes you do not want to subscribe to an event for the entire life of
# the application. There are two ways to unsubscribe.
#
+ # WARNING: The instrumentation framework is designed for long-running subscribers,
+ # use this feature sparingly because it wipes some internal caches and that has
+ # a negative impact on performance.
+ #
# === Subscribe While a Block Runs
#
# You can subscribe to some event temporarily while some block runs. For
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index bf81567d22..538e41e0eb 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -1,5 +1,3 @@
-require 'active_support/ordered_hash'
-
# Usually key value pairs are handled something like this:
#
# h = {}
@@ -17,7 +15,7 @@ require 'active_support/ordered_hash'
# h.girl # => 'Mary'
#
module ActiveSupport #:nodoc:
- class OrderedOptions < OrderedHash
+ class OrderedOptions < Hash
alias_method :_get, :[] # preserve the original #[] method
protected :_get # make it protected
@@ -30,8 +28,9 @@ module ActiveSupport #:nodoc:
end
def method_missing(name, *args)
- if name.to_s =~ /(.*)=$/
- self[$1] = args.first
+ name_string = name.to_s
+ if name_string.chomp!('=')
+ self[name_string] = args.first
else
self[name]
end
diff --git a/activesupport/lib/active_support/ruby/shim.rb b/activesupport/lib/active_support/ruby/shim.rb
index 41fd866481..13e96b3596 100644
--- a/activesupport/lib/active_support/ruby/shim.rb
+++ b/activesupport/lib/active_support/ruby/shim.rb
@@ -12,5 +12,4 @@ require 'active_support/core_ext/date_time/conversions'
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/string/conversions'
require 'active_support/core_ext/string/interpolation'
-require 'active_support/core_ext/string/encoding'
require 'active_support/core_ext/time/conversions'
diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb
index dc3ca25938..f6ad861353 100644
--- a/activesupport/lib/active_support/tagged_logging.rb
+++ b/activesupport/lib/active_support/tagged_logging.rb
@@ -1,63 +1,58 @@
require 'active_support/core_ext/object/blank'
require 'logger'
+require 'active_support/logger'
module ActiveSupport
- # Wraps any standard Logger class to provide tagging capabilities. Examples:
+ # Wraps any standard Logger object to provide tagging capabilities. Examples:
#
- # Logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
- # Logger.tagged("BCX") { Logger.info "Stuff" } # Logs "[BCX] Stuff"
- # Logger.tagged("BCX", "Jason") { Logger.info "Stuff" } # Logs "[BCX] [Jason] Stuff"
- # Logger.tagged("BCX") { Logger.tagged("Jason") { Logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff"
+ # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
+ # logger.tagged("BCX") { logger.info "Stuff" } # Logs "[BCX] Stuff"
+ # logger.tagged("BCX", "Jason") { logger.info "Stuff" } # Logs "[BCX] [Jason] Stuff"
+ # logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff"
#
# This is used by the default Rails.logger as configured by Railties to make it easy to stamp log lines
# with subdomains, request ids, and anything else to aid debugging of multi-user production applications.
- class TaggedLogging
- def initialize(logger)
- @logger = logger
- @tags = Hash.new { |h,k| h[k] = [] }
- end
-
- def tagged(*new_tags)
- tags = current_tags
- new_tags = Array(new_tags).flatten.reject(&:blank?)
- tags.concat new_tags
- yield
- ensure
- new_tags.size.times { tags.pop }
- end
+ module TaggedLogging
+ class Formatter < ActiveSupport::Logger::SimpleFormatter # :nodoc:
+ # This method is invoked when a log event occurs
+ def call(severity, timestamp, progname, msg)
+ super(severity, timestamp, progname, "#{tags_text}#{msg}")
+ end
- def add(severity, message = nil, progname = nil, &block)
- @logger.add(severity, "#{tags_text}#{message}", progname, &block)
- end
+ def clear!
+ current_tags.clear
+ end
- %w( fatal error warn info debug unknown ).each do |severity|
- eval <<-EOM, nil, __FILE__, __LINE__ + 1
- def #{severity}(progname = nil, &block) # def warn(progname = nil, &block)
- add(Logger::#{severity.upcase}, progname, &block) # add(Logger::WARN, progname, &block)
- end # end
- EOM
- end
+ def current_tags
+ Thread.current[:activesupport_tagged_logging_tags] ||= []
+ end
- def flush
- @tags.delete(Thread.current)
- @logger.flush if @logger.respond_to?(:flush)
+ private
+ def tags_text
+ tags = current_tags
+ if tags.any?
+ tags.collect { |tag| "[#{tag}] " }.join
+ end
+ end
end
- def method_missing(method, *args)
- @logger.send(method, *args)
+ def self.new(logger)
+ logger.formatter = Formatter.new
+ logger.extend(self)
end
- protected
-
- def tags_text
- tags = current_tags
- if tags.any?
- tags.collect { |tag| "[#{tag}]" }.join(" ") + " "
- end
+ def tagged(*new_tags)
+ tags = formatter.current_tags
+ new_tags = new_tags.flatten.reject(&:blank?)
+ tags.concat new_tags
+ yield
+ ensure
+ tags.pop(new_tags.size)
end
- def current_tags
- @tags[Thread.current]
+ def flush
+ formatter.clear!
+ super if defined?(super)
end
end
end
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 4169557286..9a52c916ec 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -3,7 +3,6 @@ require 'active_support/testing/setup_and_teardown'
require 'active_support/testing/assertions'
require 'active_support/testing/deprecation'
require 'active_support/testing/declarative'
-require 'active_support/testing/pending'
require 'active_support/testing/isolation'
require 'active_support/testing/mochaing'
require 'active_support/core_ext/kernel/reporting'
@@ -40,7 +39,6 @@ module ActiveSupport
include ActiveSupport::Testing::SetupAndTeardown
include ActiveSupport::Testing::Assertions
include ActiveSupport::Testing::Deprecation
- include ActiveSupport::Testing::Pending
extend ActiveSupport::Testing::Declarative
# test/unit backwards compatibility methods
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index c896b955fb..1a0681e850 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -37,10 +37,6 @@ module ActiveSupport
!ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/))
end
- def self.included(base)
- base.send :include, MiniTest
- end
-
def _run_class_setup # class setup method should only happen in parent
unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST']
self.class.setup if self.class.respond_to?(:setup)
@@ -48,18 +44,16 @@ module ActiveSupport
end
end
- module MiniTest
- def run(runner)
- _run_class_setup
+ def run(runner)
+ _run_class_setup
- serialized = run_in_isolation do |isolated_runner|
- super(isolated_runner)
- end
-
- retval, proxy = Marshal.load(serialized)
- proxy.__replay__(runner)
- retval
+ serialized = run_in_isolation do |isolated_runner|
+ super(isolated_runner)
end
+
+ retval, proxy = Marshal.load(serialized)
+ proxy.__replay__(runner)
+ retval
end
module Forking
@@ -117,13 +111,3 @@ module ActiveSupport
end
end
end
-
-# Only in subprocess for windows / jruby.
-if ENV['ISOLATION_TEST']
- require "test/unit/collector/objectspace"
- class Test::Unit::Collector::ObjectSpace
- def include?(test)
- super && test.method_name == ENV['ISOLATION_TEST']
- end
- end
-end
diff --git a/activesupport/lib/active_support/testing/pending.rb b/activesupport/lib/active_support/testing/pending.rb
deleted file mode 100644
index 510f80f32c..0000000000
--- a/activesupport/lib/active_support/testing/pending.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# Some code from jeremymcanally's "pending"
-# https://github.com/jeremymcanally/pending/tree/master
-
-module ActiveSupport
- module Testing
- module Pending
-
- unless defined?(Spec)
-
- @@pending_cases = []
- @@at_exit = false
-
- def pending(description = "", &block)
- skip(description.blank? ? nil : description)
- end
- end
-
- end
- end
-end
diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb
index 209bfac19f..244ee1a224 100644
--- a/activesupport/lib/active_support/testing/performance.rb
+++ b/activesupport/lib/active_support/testing/performance.rb
@@ -13,7 +13,6 @@ module ActiveSupport
included do
superclass_delegating_accessor :profile_options
self.profile_options = {}
- include ForMiniTest
end
# each implementation should define metrics and freeze the defaults
@@ -36,40 +35,38 @@ module ActiveSupport
"#{self.class.name}##{method_name}"
end
- module ForMiniTest
- def run(runner)
- @runner = runner
+ def run(runner)
+ @runner = runner
- run_warmup
- if full_profile_options && metrics = full_profile_options[:metrics]
- metrics.each do |metric_name|
- if klass = Metrics[metric_name.to_sym]
- run_profile(klass.new)
- end
+ run_warmup
+ if full_profile_options && metrics = full_profile_options[:metrics]
+ metrics.each do |metric_name|
+ if klass = Metrics[metric_name.to_sym]
+ run_profile(klass.new)
end
end
-
- return
end
- def run_test(metric, mode)
- result = '.'
+ return
+ end
+
+ def run_test(metric, mode)
+ result = '.'
+ begin
+ run_callbacks :setup
+ setup
+ metric.send(mode) { __send__ method_name }
+ rescue Exception => e
+ result = @runner.puke(self.class, method_name, e)
+ ensure
begin
- run_callbacks :setup
- setup
- metric.send(mode) { __send__ method_name }
+ teardown
+ run_callbacks :teardown, :enumerator => :reverse_each
rescue Exception => e
result = @runner.puke(self.class, method_name, e)
- ensure
- begin
- teardown
- run_callbacks :teardown, :enumerator => :reverse_each
- rescue Exception => e
- result = @runner.puke(self.class, method_name, e)
- end
end
- result
end
+ result
end
protected
diff --git a/activesupport/lib/active_support/testing/performance/ruby.rb b/activesupport/lib/active_support/testing/performance/ruby.rb
index 26731c6bd7..b7a34ea279 100644
--- a/activesupport/lib/active_support/testing/performance/ruby.rb
+++ b/activesupport/lib/active_support/testing/performance/ruby.rb
@@ -86,9 +86,12 @@ module ActiveSupport
end
protected
- # overridden by each implementation
def with_gc_stats
+ GC::Profiler.enable
+ GC.start
yield
+ ensure
+ GC::Profiler.disable
end
end
@@ -124,27 +127,42 @@ module ActiveSupport
class Memory < DigitalInformationUnit
Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY)
+
+ # Ruby 1.9 + GCdata patch
+ if GC.respond_to?(:malloc_allocated_size)
+ def measure
+ GC.malloc_allocated_size
+ end
+ end
end
class Objects < Amount
Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS)
+
+ # Ruby 1.9 + GCdata patch
+ if GC.respond_to?(:malloc_allocations)
+ def measure
+ GC.malloc_allocations
+ end
+ end
end
class GcRuns < Amount
Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS)
+
+ def measure
+ GC.count
+ end
end
class GcTime < Time
Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME)
+
+ def measure
+ GC::Profiler.total_time
+ end
end
end
end
end
end
-
-if RUBY_VERSION.between?('1.9.2', '2.0')
- require 'active_support/testing/performance/ruby/yarv'
-else
- $stderr.puts 'Update your ruby interpreter to be able to run benchmarks.'
- exit
-end
diff --git a/activesupport/lib/active_support/testing/performance/ruby/yarv.rb b/activesupport/lib/active_support/testing/performance/ruby/yarv.rb
deleted file mode 100644
index c34d31bf10..0000000000
--- a/activesupport/lib/active_support/testing/performance/ruby/yarv.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-module ActiveSupport
- module Testing
- module Performance
- module Metrics
- class Base
- protected
- def with_gc_stats
- GC::Profiler.enable
- GC.start
- yield
- ensure
- GC::Profiler.disable
- end
- end
-
- class Memory < DigitalInformationUnit
- # Ruby 1.9 + GCdata patch
- if GC.respond_to?(:malloc_allocated_size)
- def measure
- GC.malloc_allocated_size
- end
- end
- end
-
- class Objects < Amount
- # Ruby 1.9 + GCdata patch
- if GC.respond_to?(:malloc_allocations)
- def measure
- GC.malloc_allocations
- end
- end
- end
-
- class GcRuns < Amount
- def measure
- GC.count
- end
- end
-
- class GcTime < Time
- def measure
- GC::Profiler.total_time
- end
- end
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat
index 7edc4663e8..df17a8cccf 100644
--- a/activesupport/lib/active_support/values/unicode_tables.dat
+++ b/activesupport/lib/active_support/values/unicode_tables.dat
Binary files differ
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index 6c222b83ba..dbb6c71907 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -71,7 +71,7 @@ module ActiveSupport
child_nodes = element.child_nodes
if child_nodes.length > 0
- for i in 0...child_nodes.length
+ (0...child_nodes.length).each do |i|
child = child_nodes.item(i)
merge_element!(hash, child) unless child.node_type == Node.TEXT_NODE
end
@@ -133,7 +133,7 @@ module ActiveSupport
def get_attributes(element)
attribute_hash = {}
attributes = element.attributes
- for i in 0...attributes.length
+ (0...attributes.length).each do |i|
attribute_hash[CONTENT_KEY] ||= ''
attribute_hash[attributes.item(i).name] = attributes.item(i).value
end
@@ -147,7 +147,7 @@ module ActiveSupport
def texts(element)
texts = []
child_nodes = element.child_nodes
- for i in 0...child_nodes.length
+ (0...child_nodes.length).each do |i|
item = child_nodes.item(i)
if item.node_type == Node.TEXT_NODE
texts << item.get_data
@@ -163,7 +163,7 @@ module ActiveSupport
def empty_content?(element)
text = ''
child_nodes = element.child_nodes
- for i in 0...child_nodes.length
+ (0...child_nodes.length).each do |i|
item = child_nodes.item(i)
if item.node_type == Node.TEXT_NODE
text << item.get_data.strip
diff --git a/activesupport/test/broadcast_logger_test.rb b/activesupport/test/broadcast_logger_test.rb
new file mode 100644
index 0000000000..6d4e3b74f7
--- /dev/null
+++ b/activesupport/test/broadcast_logger_test.rb
@@ -0,0 +1,82 @@
+require 'abstract_unit'
+
+module ActiveSupport
+ class BroadcastLoggerTest < TestCase
+ attr_reader :logger, :log1, :log2
+ def setup
+ @log1 = FakeLogger.new
+ @log2 = FakeLogger.new
+ @log1.extend Logger.broadcast @log2
+ @logger = @log1
+ end
+
+ def test_debug
+ logger.debug "foo"
+ assert_equal 'foo', log1.adds.first[2]
+ assert_equal 'foo', log2.adds.first[2]
+ end
+
+ def test_close
+ logger.close
+ assert log1.closed, 'should be closed'
+ assert log2.closed, 'should be closed'
+ end
+
+ def test_chevrons
+ logger << "foo"
+ assert_equal %w{ foo }, log1.chevrons
+ assert_equal %w{ foo }, log2.chevrons
+ end
+
+ def test_level
+ assert_nil logger.level
+ logger.level = 10
+ assert_equal 10, log1.level
+ assert_equal 10, log2.level
+ end
+
+ def test_progname
+ assert_nil logger.progname
+ logger.progname = 10
+ assert_equal 10, log1.progname
+ assert_equal 10, log2.progname
+ end
+
+ def test_formatter
+ assert_nil logger.formatter
+ logger.formatter = 10
+ assert_equal 10, log1.formatter
+ assert_equal 10, log2.formatter
+ end
+
+ class FakeLogger
+ attr_reader :adds, :closed, :chevrons
+ attr_accessor :level, :progname, :formatter
+
+ def initialize
+ @adds = []
+ @closed = false
+ @chevrons = []
+ @level = nil
+ @progname = nil
+ @formatter = nil
+ end
+
+ def debug msg, &block
+ add(:omg, nil, msg, &block)
+ end
+
+ def << x
+ @chevrons << x
+ end
+
+ def add(*args)
+ @adds << args
+ end
+
+ def close
+ @closed = true
+ end
+ end
+ end
+end
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 3454c378d3..b03865da93 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -4,19 +4,19 @@ require 'active_support/cache'
class CacheKeyTest < ActiveSupport::TestCase
def test_expand_cache_key
- assert_equal 'Array/1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true])
- assert_equal 'name/Array/1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true], :name)
+ assert_equal '1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true])
+ assert_equal 'name/1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true], :name)
end
def test_expand_cache_key_with_rails_cache_id
begin
ENV['RAILS_CACHE_ID'] = 'c99'
assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key(:foo)
- assert_equal 'c99/Array/foo', ActiveSupport::Cache.expand_cache_key([:foo])
- assert_equal 'c99/Array/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar])
+ assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key([:foo])
+ assert_equal 'c99/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar])
assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key(:foo, :nm)
- assert_equal 'nm/c99/Array/foo', ActiveSupport::Cache.expand_cache_key([:foo], :nm)
- assert_equal 'nm/c99/Array/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm)
+ assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key([:foo], :nm)
+ assert_equal 'nm/c99/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm)
ensure
ENV['RAILS_CACHE_ID'] = nil
end
@@ -55,7 +55,7 @@ class CacheKeyTest < ActiveSupport::TestCase
def key.cache_key
:foo_key
end
- assert_equal 'Array/foo_key', ActiveSupport::Cache.expand_cache_key([key])
+ assert_equal 'foo_key', ActiveSupport::Cache.expand_cache_key([key])
end
def test_expand_cache_key_of_nil
@@ -69,14 +69,6 @@ class CacheKeyTest < ActiveSupport::TestCase
def test_expand_cache_key_of_true
assert_equal 'true', ActiveSupport::Cache.expand_cache_key(true)
end
-
- def test_expand_cache_key_of_one_element_array_different_than_key_of_element
- element = 'foo'
- array = [element]
- element_cache_key = ActiveSupport::Cache.expand_cache_key(element)
- array_cache_key = ActiveSupport::Cache.expand_cache_key(array)
- assert_not_equal element_cache_key, array_cache_key
- end
end
class CacheStoreSettingTest < ActiveSupport::TestCase
diff --git a/activesupport/test/callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb
index b5ad34c204..e5ac9511df 100644
--- a/activesupport/test/callback_inheritance_test.rb
+++ b/activesupport/test/callback_inheritance_test.rb
@@ -9,8 +9,8 @@ class GrandParent
end
define_callbacks :dispatch
- set_callback :dispatch, :before, :before1, :before2, :per_key => {:if => proc {|c| c.action_name == "index" || c.action_name == "update" }}
- set_callback :dispatch, :after, :after1, :after2, :per_key => {:if => proc {|c| c.action_name == "update" || c.action_name == "delete" }}
+ set_callback :dispatch, :before, :before1, :before2, :if => proc {|c| c.action_name == "index" || c.action_name == "update" }
+ set_callback :dispatch, :after, :after1, :after2, :if => proc {|c| c.action_name == "update" || c.action_name == "delete" }
def before1
@log << "before1"
@@ -37,12 +37,12 @@ class GrandParent
end
class Parent < GrandParent
- skip_callback :dispatch, :before, :before2, :per_key => {:unless => proc {|c| c.action_name == "update" }}
- skip_callback :dispatch, :after, :after2, :per_key => {:unless => proc {|c| c.action_name == "delete" }}
+ skip_callback :dispatch, :before, :before2, :unless => proc {|c| c.action_name == "update" }
+ skip_callback :dispatch, :after, :after2, :unless => proc {|c| c.action_name == "delete" }
end
class Child < GrandParent
- skip_callback :dispatch, :before, :before2, :per_key => {:unless => proc {|c| c.action_name == "update" }}, :if => :state_open?
+ skip_callback :dispatch, :before, :before2, :unless => proc {|c| c.action_name == "update" }, :if => :state_open?
def state_open?
@state == :open
@@ -112,15 +112,15 @@ class BasicCallbacksTest < ActiveSupport::TestCase
@unknown = GrandParent.new("unknown").dispatch
end
- def test_basic_per_key1
+ def test_basic_conditional_callback1
assert_equal %w(before1 before2 index), @index.log
end
- def test_basic_per_key2
+ def test_basic_conditional_callback2
assert_equal %w(before1 before2 update after2 after1), @update.log
end
- def test_basic_per_key3
+ def test_basic_conditional_callback3
assert_equal %w(delete after2 after1), @delete.log
end
end
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 2c2a420619..25688a9da5 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
module CallbacksTest
class Phone
include ActiveSupport::Callbacks
- define_callbacks :save, :rescuable => true
+ define_callbacks :save
set_callback :save, :before, :before_save1
set_callback :save, :after, :after_save1
@@ -95,7 +95,7 @@ module CallbacksTest
define_callbacks :dispatch
- set_callback :dispatch, :before, :log, :per_key => {:unless => proc {|c| c.action_name == :index || c.action_name == :show }}
+ set_callback :dispatch, :before, :log, :unless => proc {|c| c.action_name == :index || c.action_name == :show }
set_callback :dispatch, :after, :log2
attr_reader :action_name, :logger
@@ -120,7 +120,7 @@ module CallbacksTest
end
class Child < ParentController
- skip_callback :dispatch, :before, :log, :per_key => {:if => proc {|c| c.action_name == :update} }
+ skip_callback :dispatch, :before, :log, :if => proc {|c| c.action_name == :update}
skip_callback :dispatch, :after, :log2
end
@@ -131,10 +131,10 @@ module CallbacksTest
super
end
- before_save Proc.new {|r| r.history << [:before_save, :starts_true, :if] }, :per_key => {:if => :starts_true}
- before_save Proc.new {|r| r.history << [:before_save, :starts_false, :if] }, :per_key => {:if => :starts_false}
- before_save Proc.new {|r| r.history << [:before_save, :starts_true, :unless] }, :per_key => {:unless => :starts_true}
- before_save Proc.new {|r| r.history << [:before_save, :starts_false, :unless] }, :per_key => {:unless => :starts_false}
+ before_save Proc.new {|r| r.history << [:before_save, :starts_true, :if] }, :if => :starts_true
+ before_save Proc.new {|r| r.history << [:before_save, :starts_false, :if] }, :if => :starts_false
+ before_save Proc.new {|r| r.history << [:before_save, :starts_true, :unless] }, :unless => :starts_true
+ before_save Proc.new {|r| r.history << [:before_save, :starts_false, :unless] }, :unless => :starts_false
def starts_true
if @@starts_true
@@ -329,7 +329,7 @@ module CallbacksTest
define_callbacks :save
attr_reader :stuff
- set_callback :save, :before, :action, :per_key => {:if => :yes}
+ set_callback :save, :before, :action, :if => :yes
def yes() true end
@@ -344,6 +344,54 @@ module CallbacksTest
end
end
+ module ExtendModule
+ def self.extended(base)
+ base.class_eval do
+ set_callback :save, :before, :record3
+ end
+ end
+ def record3
+ @recorder << 3
+ end
+ end
+
+ module IncludeModule
+ def self.included(base)
+ base.class_eval do
+ set_callback :save, :before, :record2
+ end
+ end
+ def record2
+ @recorder << 2
+ end
+ end
+
+ class ExtendCallbacks
+
+ include ActiveSupport::Callbacks
+
+ define_callbacks :save
+ set_callback :save, :before, :record1
+
+ include IncludeModule
+
+ def save
+ run_callbacks :save
+ end
+
+ attr_reader :recorder
+
+ def initialize
+ @recorder = []
+ end
+
+ private
+
+ def record1
+ @recorder << 1
+ end
+ end
+
class AroundCallbacksTest < ActiveSupport::TestCase
def test_save_around
around = AroundPerson.new
@@ -391,13 +439,6 @@ module CallbacksTest
end
class CallbacksTest < ActiveSupport::TestCase
- def test_save_phone
- phone = Phone.new
- assert_raise RuntimeError do
- phone.save
- end
- assert_equal [:before, :after], phone.history
- end
def test_save_person
person = Person.new
@@ -645,4 +686,28 @@ module CallbacksTest
end
end
+ class ExtendCallbacksTest < ActiveSupport::TestCase
+ def test_save
+ model = ExtendCallbacks.new.extend ExtendModule
+ model.save
+ assert_equal [1, 2, 3], model.recorder
+ end
+ end
+
+ class PerKeyOptionDeprecationTest < ActiveSupport::TestCase
+
+ def test_per_key_option_deprecaton
+ assert_raise NotImplementedError do
+ Phone.class_eval do
+ set_callback :save, :before, :before_save1, :per_key => {:if => "true"}
+ end
+ end
+ assert_raise NotImplementedError do
+ Phone.class_eval do
+ skip_callback :save, :before, :before_save1, :per_key => {:if => "true"}
+ end
+ end
+ end
+ end
+
end
diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb
index 81d200a0c8..135f894056 100644
--- a/activesupport/test/constantize_test_cases.rb
+++ b/activesupport/test/constantize_test_cases.rb
@@ -19,7 +19,7 @@ module ConstantizeTestCases
assert_raise(NameError) { yield("Ace::ConstantizeTestCases") }
assert_raise(NameError) { yield("Ace::Base::ConstantizeTestCases") }
end
-
+
def run_safe_constantize_tests_on
assert_nothing_raised { assert_equal Ace::Base::Case, yield("Ace::Base::Case") }
assert_nothing_raised { assert_equal Ace::Base::Case, yield("::Ace::Base::Case") }
@@ -33,5 +33,6 @@ module ConstantizeTestCases
assert_nothing_raised { assert_equal nil, yield("blargle") }
assert_nothing_raised { assert_equal nil, yield("Ace::ConstantizeTestCases") }
assert_nothing_raised { assert_equal nil, yield("Ace::Base::ConstantizeTestCases") }
+ assert_nothing_raised { assert_equal nil, yield("#<Class:0x7b8b718b>::Nested_1") }
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index e1740d8c7e..cd8cb5d18b 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -43,8 +43,8 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_civil_from_format
- assert_equal DateTime.civil(2010, 5, 4, 0, 0, 0, DateTime.local_offset), DateTime.civil_from_format(:local, 2010, 5, 4)
- assert_equal DateTime.civil(2010, 5, 4, 0, 0, 0, 0), DateTime.civil_from_format(:utc, 2010, 5, 4)
+ assert_equal Time.local(2010, 5, 4, 0, 0, 0), DateTime.civil_from_format(:local, 2010, 5, 4)
+ assert_equal Time.utc(2010, 5, 4, 0, 0, 0), DateTime.civil_from_format(:utc, 2010, 5, 4)
end
def test_seconds_since_midnight
@@ -347,15 +347,6 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert DateTime.new.acts_like_time?
end
- def test_local_offset
- with_env_tz 'US/Eastern' do
- assert_equal Rational(-5, 24), DateTime.local_offset
- end
- with_env_tz 'US/Central' do
- assert_equal Rational(-6, 24), DateTime.local_offset
- end
- end
-
def test_utc?
assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12).utc?
assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc?
diff --git a/activesupport/test/core_ext/integer_ext_test.rb b/activesupport/test/core_ext/integer_ext_test.rb
index bfbb2260c6..41736fb672 100644
--- a/activesupport/test/core_ext/integer_ext_test.rb
+++ b/activesupport/test/core_ext/integer_ext_test.rb
@@ -21,6 +21,10 @@ class IntegerExtTest < ActiveSupport::TestCase
# Its results are tested comprehensively in the inflector test cases.
assert_equal '1st', 1.ordinalize
assert_equal '8th', 8.ordinalize
- 1000000000000000000000000000000000000000000000000000000000000000000000.ordinalize
+ end
+
+ def test_ordinal
+ assert_equal 'st', 1.ordinal
+ assert_equal 'th', 8.ordinal
end
end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 950ef82a3c..09ca4e7296 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -212,6 +212,12 @@ class ModuleTest < ActiveSupport::TestCase
def test_local_constants
assert_equal %w(Constant1 Constant3), Ab.local_constants.sort.map(&:to_s)
end
+
+ def test_local_constant_names
+ ActiveSupport::Deprecation.silence do
+ assert_equal %w(Constant1 Constant3), Ab.local_constant_names
+ end
+ end
end
module BarMethodAliaser
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index 8a91f6d69c..cf1ec448c2 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -71,4 +71,16 @@ class RangeTest < ActiveSupport::TestCase
range = (1..3)
assert range.method(:include?) != range.method(:cover?)
end
+
+ def test_overlaps_on_time
+ time_range_1 = Time.utc(2005, 12, 10, 15, 30)..Time.utc(2005, 12, 10, 17, 30)
+ time_range_2 = Time.utc(2005, 12, 10, 17, 00)..Time.utc(2005, 12, 10, 18, 00)
+ assert time_range_1.overlaps?(time_range_2)
+ end
+
+ def test_no_overlaps_on_time
+ time_range_1 = Time.utc(2005, 12, 10, 15, 30)..Time.utc(2005, 12, 10, 17, 30)
+ time_range_2 = Time.utc(2005, 12, 10, 17, 31)..Time.utc(2005, 12, 10, 18, 00)
+ assert !time_range_1.overlaps?(time_range_2)
+ end
end
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 076373cbf0..c542acca68 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -632,14 +632,14 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30), Time.utc(2005, 2, 21, 17, 44, 30)
assert_equal Time.time_with_datetime_fallback(:local, 2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30)
assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0)
- assert_equal Time.time_with_datetime_fallback(:local, 2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, DateTime.local_offset)
+ assert_equal Time.time_with_datetime_fallback(:local, 2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30)
assert_equal Time.time_with_datetime_fallback(:utc, 1900, 2, 21, 17, 44, 30), DateTime.civil(1900, 2, 21, 17, 44, 30, 0)
assert_equal Time.time_with_datetime_fallback(:utc, 2005), Time.utc(2005)
assert_equal Time.time_with_datetime_fallback(:utc, 2039), DateTime.civil(2039, 1, 1, 0, 0, 0, 0)
assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30, 1), Time.utc(2005, 2, 21, 17, 44, 30, 1) #with usec
# This won't overflow on 64bit linux
unless time_is_64bits?
- assert_equal Time.time_with_datetime_fallback(:local, 1900, 2, 21, 17, 44, 30), DateTime.civil(1900, 2, 21, 17, 44, 30, DateTime.local_offset, 0)
+ assert_equal Time.time_with_datetime_fallback(:local, 1900, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1900, 2, 21, 17, 44, 30)
assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1),
DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0)
assert_equal ::Date::ITALY, Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1).start # use Ruby's default start value
@@ -661,10 +661,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
def test_local_time
assert_equal Time.local_time(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30)
- assert_equal Time.local_time(2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, DateTime.local_offset)
+ assert_equal Time.local_time(2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30)
unless time_is_64bits?
- assert_equal Time.local_time(1901, 2, 21, 17, 44, 30), DateTime.civil(1901, 2, 21, 17, 44, 30, DateTime.local_offset)
+ assert_equal Time.local_time(1901, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1901, 2, 21, 17, 44, 30)
end
end
@@ -825,6 +825,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
def test_all_week
assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_week
+ assert_equal Time.local(2011,6,5,0,0,0)..Time.local(2011,6,11,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_week(:sunday)
end
def test_all_month
diff --git a/activesupport/test/core_ext/uri_ext_test.rb b/activesupport/test/core_ext/uri_ext_test.rb
index 4a6cbb8801..03e388dd7a 100644
--- a/activesupport/test/core_ext/uri_ext_test.rb
+++ b/activesupport/test/core_ext/uri_ext_test.rb
@@ -7,11 +7,7 @@ class URIExtTest < ActiveSupport::TestCase
def test_uri_decode_handle_multibyte
str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese.
- if URI.const_defined?(:Parser)
- parser = URI::Parser.new
- assert_equal str, parser.unescape(parser.escape(str))
- else
- assert_equal str, URI.unescape(URI.escape(str))
- end
+ parser = URI::Parser.new
+ assert_equal str, parser.unescape(parser.escape(str))
end
end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 3311d58254..91ae6bc189 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -26,23 +26,20 @@ class InflectorTest < ActiveSupport::TestCase
end
def test_uncountable_word_is_not_greedy
- uncountable_word = "ors"
- countable_word = "sponsor"
+ with_dup do
+ uncountable_word = "ors"
+ countable_word = "sponsor"
- cached_uncountables = ActiveSupport::Inflector.inflections.uncountables
+ ActiveSupport::Inflector.inflections.uncountable << uncountable_word
- ActiveSupport::Inflector.inflections.uncountable << uncountable_word
+ assert_equal uncountable_word, ActiveSupport::Inflector.singularize(uncountable_word)
+ assert_equal uncountable_word, ActiveSupport::Inflector.pluralize(uncountable_word)
+ assert_equal ActiveSupport::Inflector.pluralize(uncountable_word), ActiveSupport::Inflector.singularize(uncountable_word)
- assert_equal uncountable_word, ActiveSupport::Inflector.singularize(uncountable_word)
- assert_equal uncountable_word, ActiveSupport::Inflector.pluralize(uncountable_word)
- assert_equal ActiveSupport::Inflector.pluralize(uncountable_word), ActiveSupport::Inflector.singularize(uncountable_word)
-
- assert_equal "sponsor", ActiveSupport::Inflector.singularize(countable_word)
- assert_equal "sponsors", ActiveSupport::Inflector.pluralize(countable_word)
- assert_equal "sponsor", ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.pluralize(countable_word))
-
- ensure
- ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_uncountables
+ assert_equal "sponsor", ActiveSupport::Inflector.singularize(countable_word)
+ assert_equal "sponsors", ActiveSupport::Inflector.pluralize(countable_word)
+ assert_equal "sponsor", ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.pluralize(countable_word))
+ end
end
SingularToPlural.each do |singular, plural|
@@ -66,6 +63,14 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+ SingularToPlural.each do |singular, plural|
+ define_method "test_singularize_singular_#{singular}" do
+ assert_equal(singular, ActiveSupport::Inflector.singularize(singular))
+ assert_equal(singular.capitalize, ActiveSupport::Inflector.singularize(singular.capitalize))
+ end
+ end
+
+
def test_overwrite_previous_inflectors
assert_equal("series", ActiveSupport::Inflector.singularize("series"))
ActiveSupport::Inflector.inflections.singular "series", "serie"
@@ -295,7 +300,7 @@ class InflectorTest < ActiveSupport::TestCase
ActiveSupport::Inflector.constantize(string)
end
end
-
+
def test_safe_constantize
run_safe_constantize_tests_on do |string|
ActiveSupport::Inflector.safe_constantize(string)
@@ -304,6 +309,12 @@ class InflectorTest < ActiveSupport::TestCase
def test_ordinal
OrdinalNumbers.each do |number, ordinalized|
+ assert_equal(ordinalized, number + ActiveSupport::Inflector.ordinal(number))
+ end
+ end
+
+ def test_ordinalize
+ OrdinalNumbers.each do |number, ordinalized|
assert_equal(ordinalized, ActiveSupport::Inflector.ordinalize(number))
end
end
@@ -335,56 +346,50 @@ class InflectorTest < ActiveSupport::TestCase
%w{plurals singulars uncountables humans}.each do |inflection_type|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def test_clear_#{inflection_type}
- cached_values = ActiveSupport::Inflector.inflections.#{inflection_type}
- ActiveSupport::Inflector.inflections.clear :#{inflection_type}
- assert ActiveSupport::Inflector.inflections.#{inflection_type}.empty?, \"#{inflection_type} inflections should be empty after clear :#{inflection_type}\"
- ActiveSupport::Inflector.inflections.instance_variable_set :@#{inflection_type}, cached_values
+ with_dup do
+ ActiveSupport::Inflector.inflections.clear :#{inflection_type}
+ assert ActiveSupport::Inflector.inflections.#{inflection_type}.empty?, \"#{inflection_type} inflections should be empty after clear :#{inflection_type}\"
+ end
end
RUBY
end
def test_clear_all
- cached_values = ActiveSupport::Inflector.inflections.plurals.dup, ActiveSupport::Inflector.inflections.singulars.dup, ActiveSupport::Inflector.inflections.uncountables.dup, ActiveSupport::Inflector.inflections.humans.dup
- ActiveSupport::Inflector.inflections do |inflect|
- # ensure any data is present
- inflect.plural(/(quiz)$/i, '\1zes')
- inflect.singular(/(database)s$/i, '\1')
- inflect.uncountable('series')
- inflect.human("col_rpted_bugs", "Reported bugs")
-
- inflect.clear :all
-
- assert inflect.plurals.empty?
- assert inflect.singulars.empty?
- assert inflect.uncountables.empty?
- assert inflect.humans.empty?
+ with_dup do
+ ActiveSupport::Inflector.inflections do |inflect|
+ # ensure any data is present
+ inflect.plural(/(quiz)$/i, '\1zes')
+ inflect.singular(/(database)s$/i, '\1')
+ inflect.uncountable('series')
+ inflect.human("col_rpted_bugs", "Reported bugs")
+
+ inflect.clear :all
+
+ assert inflect.plurals.empty?
+ assert inflect.singulars.empty?
+ assert inflect.uncountables.empty?
+ assert inflect.humans.empty?
+ end
end
- ActiveSupport::Inflector.inflections.instance_variable_set :@plurals, cached_values[0]
- ActiveSupport::Inflector.inflections.instance_variable_set :@singulars, cached_values[1]
- ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_values[2]
- ActiveSupport::Inflector.inflections.instance_variable_set :@humans, cached_values[3]
end
def test_clear_with_default
- cached_values = ActiveSupport::Inflector.inflections.plurals.dup, ActiveSupport::Inflector.inflections.singulars.dup, ActiveSupport::Inflector.inflections.uncountables.dup, ActiveSupport::Inflector.inflections.humans.dup
- ActiveSupport::Inflector.inflections do |inflect|
- # ensure any data is present
- inflect.plural(/(quiz)$/i, '\1zes')
- inflect.singular(/(database)s$/i, '\1')
- inflect.uncountable('series')
- inflect.human("col_rpted_bugs", "Reported bugs")
-
- inflect.clear
-
- assert inflect.plurals.empty?
- assert inflect.singulars.empty?
- assert inflect.uncountables.empty?
- assert inflect.humans.empty?
+ with_dup do
+ ActiveSupport::Inflector.inflections do |inflect|
+ # ensure any data is present
+ inflect.plural(/(quiz)$/i, '\1zes')
+ inflect.singular(/(database)s$/i, '\1')
+ inflect.uncountable('series')
+ inflect.human("col_rpted_bugs", "Reported bugs")
+
+ inflect.clear
+
+ assert inflect.plurals.empty?
+ assert inflect.singulars.empty?
+ assert inflect.uncountables.empty?
+ assert inflect.humans.empty?
+ end
end
- ActiveSupport::Inflector.inflections.instance_variable_set :@plurals, cached_values[0]
- ActiveSupport::Inflector.inflections.instance_variable_set :@singulars, cached_values[1]
- ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_values[2]
- ActiveSupport::Inflector.inflections.instance_variable_set :@humans, cached_values[3]
end
Irregularities.each do |irregularity|
@@ -433,26 +438,28 @@ class InflectorTest < ActiveSupport::TestCase
end
end
- { :singulars => :singular, :plurals => :plural, :uncountables => :uncountable, :humans => :human }.each do |scope, method|
+ %w(plurals singulars uncountables humans acronyms).each do |scope|
ActiveSupport::Inflector.inflections do |inflect|
define_method("test_clear_inflections_with_#{scope}") do
- # save the inflections
- values = inflect.send(scope)
-
- # clear the inflections
- inflect.clear(scope)
-
- assert_equal [], inflect.send(scope)
-
- # restore the inflections
- if scope == :uncountables
- inflect.send(method, values)
- else
- values.reverse.each { |value| inflect.send(method, *value) }
+ with_dup do
+ # clear the inflections
+ inflect.clear(scope)
+ assert_equal [], inflect.send(scope)
end
-
- assert_equal values, inflect.send(scope)
end
end
end
+
+ # Dups the singleton and yields, restoring the original inflections later.
+ # Use this in tests what modify the state of the singleton.
+ #
+ # This helper is implemented by setting @__instance__ because in some tests
+ # there are module functions that access ActiveSupport::Inflector.inflections,
+ # so we need to replace the singleton itself.
+ def with_dup
+ original = ActiveSupport::Inflector.inflections
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original.dup)
+ ensure
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original)
+ end
end
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index eb2915c286..809b8b46c9 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -93,6 +93,7 @@ module InflectorTestCases
"matrix_fu" => "matrix_fus",
"axis" => "axes",
+ "taxi" => "taxis", # prevents regression
"testis" => "testes",
"crisis" => "crises",
diff --git a/activesupport/test/log_subscriber_test.rb b/activesupport/test/log_subscriber_test.rb
index 0c1f3c51ed..8e160714b1 100644
--- a/activesupport/test/log_subscriber_test.rb
+++ b/activesupport/test/log_subscriber_test.rb
@@ -118,6 +118,6 @@ class SyncLogSubscriberTest < ActiveSupport::TestCase
assert_equal 'some_event.my_log_subscriber', @logger.logged(:info).last
assert_equal 1, @logger.logged(:error).size
- assert_equal 'Could not log "puke.my_log_subscriber" event. RuntimeError: puke', @logger.logged(:error).last
+ assert_match 'Could not log "puke.my_log_subscriber" event. RuntimeError: puke', @logger.logged(:error).last
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 63e7a35c01..90aa13b3e6 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -88,6 +88,9 @@ class MultibyteCharsTest < ActiveSupport::TestCase
assert(('a'.mb_chars << 'b'.mb_chars).kind_of?(@proxy_class))
end
+ def test_should_return_string_as_json
+ assert_equal UNICODE_STRING, @chars.as_json
+ end
end
class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb
index 2fde07995b..bdde5141a9 100644
--- a/activesupport/test/safe_buffer_test.rb
+++ b/activesupport/test/safe_buffer_test.rb
@@ -7,6 +7,10 @@ class SafeBufferTest < ActiveSupport::TestCase
@buffer = ActiveSupport::SafeBuffer.new
end
+ def test_titleize
+ assert_equal 'Foo', "foo".html_safe.titleize
+ end
+
test "Should look like a string" do
assert @buffer.is_a?(String)
assert_equal "", @buffer
diff --git a/install.rb b/install.rb
index 05bba27a14..abc02249c2 100644
--- a/install.rb
+++ b/install.rb
@@ -8,4 +8,4 @@ end
puts "Installing Rails..."
`gem build rails.gemspec`
`gem install rails-#{version}.gem --no-ri --no-rdoc `
-`rm rails-#{version}.gem` \ No newline at end of file
+`rm rails-#{version}.gem`
diff --git a/load_paths.rb b/load_paths.rb
index 17f5ce180d..6b224d4ad5 100644
--- a/load_paths.rb
+++ b/load_paths.rb
@@ -1,4 +1,4 @@
# bust gem prelude
require 'rubygems' unless defined? Gem
require 'bundler'
-Bundler.setup \ No newline at end of file
+Bundler.setup
diff --git a/rails.gemspec b/rails.gemspec
index 1d4c6c36c8..cd7c5d1ee9 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
s.description = 'Ruby on Rails is a full-stack web framework optimized for programmer happiness and sustainable productivity. It encourages beautiful code by favoring convention over configuration.'
s.required_ruby_version = '>= 1.9.3'
- s.required_rubygems_version = ">= 1.3.6"
+ s.required_rubygems_version = ">= 1.8.11"
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 31ff241230..f556ee210d 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,8 +1,36 @@
## Rails 4.0.0 (unreleased) ##
+* Allow to set class that will be used to run as a console, other than IRB, with `Rails.application.config.console=`. It's best to add it to `console` block. *Piotr Sarnacki*
+
+ Example:
+
+ # it can be added to config/application.rb
+ console do
+ # this block is called only when running console,
+ # so we can safely require pry here
+ require "pry"
+ config.console = Pry
+ end
+
+* Add convenience `hide!` method to Rails generators to hide current generator
+ namespace from showing when running `rails generate`. *Carlos Antonio da Silva*
+
+* Scaffold now uses `content_tag_for` in index.html.erb *José Valim*
+
* Rails::Plugin has gone. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies. *Santiago Pastorino*
-## Rails 3.2.0 (unreleased) ##
+
+## Rails 3.2.1 (January 26, 2012) ##
+
+* Documentation fixes.
+
+* Migration generation understands decimal{1.2} and decimal{1-2}, in
+ addition to decimal{1,2}. *José Valim*
+
+
+## Rails 3.2.0 (January 20, 2012) ##
+
+* Turn gem has been removed from default Gemfile. We still looking for a best presentation for tests output. *Guillermo Iguaran*
* Rails::Plugin is deprecated and will be removed in Rails 4.0. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies. *Santiago Pastorino*
@@ -29,7 +57,8 @@
* Update Rails::Rack::Logger middleware to apply any tags set in config.log_tags to the newly ActiveSupport::TaggedLogging Rails.logger. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications *DHH*
-* Default options to `rails new` can be set in ~/.railsrc *Guillermo Iguaran*
+* Default options to `rails new` can be set in ~/.railsrc. You can specify extra command-line arguments to be used every time
+ 'rails new' runs in the .railsrc configuration file in your home directory. *Guillermo Iguaran*
* Add destroy alias to Rails engines *Guillermo Iguaran*
@@ -42,7 +71,23 @@
* Remove old 'config.paths.app.controller' API in favor of 'config.paths["app/controller"]' API *Guillermo Iguaran*
-## Rails 3.1.2 (unreleased) ##
+## Rails 3.1.4 (unreleased) ##
+
+* Setting config.force_ssl also marks the session cookie as secure.
+
+ *José Valim*
+
+* Add therubyrhino to Gemfile in new applications when running under JRuby.
+
+ *Guillermo Iguaran*
+
+
+## Rails 3.1.3 (November 20, 2011) ##
+
+* New apps should be generated with a sass-rails dependency of 3.1.5, not 3.1.5.rc.2
+
+
+## Rails 3.1.2 (November 18, 2011) ##
* Engines: don't blow up if db/seeds.rb is missing.
@@ -52,7 +97,7 @@
*GH 2564*
*José Valim*
-
+
## Rails 3.1.1 (October 07, 2011) ##
* Add jquery-rails to Gemfile of plugins, test/dummy app needs it. Closes #3091. *Santiago Pastorino*
@@ -64,7 +109,7 @@
config/initializers/* will not be executed.
Plugins developers need to special case their initializers that are
- meant to be run in the assets group by adding :group => :assets.
+ meant to be run in the assets group by adding :group => :assets.
## Rails 3.1.0 (August 30, 2011) ##
@@ -158,12 +203,37 @@
* Include all helpers from plugins and shared engines in application *Piotr Sarnacki*
+## Rails 3.0.12 (unreleased) ##
+
+* No changes.
+
+
+## Rails 3.0.11 (November 18, 2011) ##
+
+* Updated Prototype UJS to lastest version fixing multiples errors in IE [Guillermo Iguaran]
+
+
+## Rails 3.0.10 (August 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.9 (June 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.8 (June 7, 2011) ##
+
+* Fix Rake 0.9.0 support.
+
+
## Rails 3.0.7 (April 18, 2011) ##
* No changes.
-* Rails 3.0.6 (April 5, 2011)
+## Rails 3.0.6 (April 5, 2011) ##
* No changes.
diff --git a/railties/guides/assets/stylesheets/fixes.css b/railties/guides/assets/stylesheets/fixes.css
index 54efa5b9b7..bf86b29efa 100644
--- a/railties/guides/assets/stylesheets/fixes.css
+++ b/railties/guides/assets/stylesheets/fixes.css
@@ -12,5 +12,5 @@
.syntaxhighlighter table thead,
.syntaxhighlighter table caption,
.syntaxhighlighter textarea {
- line-height: 1.2em !important;
+ line-height: 1.25em !important;
}
diff --git a/railties/guides/assets/stylesheets/main.css b/railties/guides/assets/stylesheets/main.css
index 4a775f632c..90723cc8e1 100644
--- a/railties/guides/assets/stylesheets/main.css
+++ b/railties/guides/assets/stylesheets/main.css
@@ -81,6 +81,7 @@ body {
font-size: 87.5%;
line-height: 1.5em;
background: #222;
+ min-width: 69em;
color: #999;
}
diff --git a/railties/guides/code/getting_started/Gemfile b/railties/guides/code/getting_started/Gemfile
index 898510dcaa..768985070c 100644
--- a/railties/guides/code/getting_started/Gemfile
+++ b/railties/guides/code/getting_started/Gemfile
@@ -1,8 +1,9 @@
-source 'http://rubygems.org'
+source 'https://rubygems.org'
+
+gem 'rails', '3.2.0'
-gem 'rails', '3.1.0'
# Bundle edge Rails instead:
-# gem 'rails', :git => 'git://github.com/rails/rails.git'
+# gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'sqlite3'
@@ -10,13 +11,23 @@ gem 'sqlite3'
# Gems used only for assets and not required
# in production environments by default.
group :assets do
- gem 'sass-rails', " ~> 3.1.0"
- gem 'coffee-rails', "~> 3.1.0"
- gem 'uglifier'
+ gem 'sass-rails', '~> 3.2.3'
+ gem 'coffee-rails', '~> 3.2.1'
+
+ # See https://github.com/sstephenson/execjs#readme for more supported runtimes
+ # gem 'therubyracer'
+
+ gem 'uglifier', '>= 1.0.3'
end
gem 'jquery-rails'
+# To use ActiveModel has_secure_password
+# gem 'bcrypt-ruby', '~> 3.0.0'
+
+# To use Jbuilder templates for JSON
+# gem 'jbuilder'
+
# Use unicorn as the web server
# gem 'unicorn'
diff --git a/railties/guides/code/getting_started/app/assets/javascripts/application.js b/railties/guides/code/getting_started/app/assets/javascripts/application.js
index 37c7bfcdb5..9097d830e2 100644
--- a/railties/guides/code/getting_started/app/assets/javascripts/application.js
+++ b/railties/guides/code/getting_started/app/assets/javascripts/application.js
@@ -1,9 +1,15 @@
-// This is a manifest file that'll be compiled into including all the files listed below.
-// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
-// be included in the compiled file accessible from http://example.com/assets/application.js
+// This is a manifest file that'll be compiled into application.js, which will include all the files
+// listed below.
+//
+// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
+// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
+//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
+// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
+// GO AFTER THE REQUIRES BELOW.
+//
//= require jquery
//= require jquery_ujs
//= require_tree .
diff --git a/railties/guides/code/getting_started/app/assets/stylesheets/application.css b/railties/guides/code/getting_started/app/assets/stylesheets/application.css
index fc25b5723f..3b5cc6648e 100644
--- a/railties/guides/code/getting_started/app/assets/stylesheets/application.css
+++ b/railties/guides/code/getting_started/app/assets/stylesheets/application.css
@@ -1,7 +1,13 @@
/*
- * This is a manifest file that'll automatically include all the stylesheets available in this directory
- * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
- * the top of the compiled file, but it's generally better to create a new file per style scope.
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
+ * listed below.
+ *
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
+ *
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
+ * compiled file, but it's generally better to create a new file per style scope.
+ *
*= require_self
- *= require_tree .
-*/ \ No newline at end of file
+ *= require_tree .
+*/
diff --git a/railties/guides/code/getting_started/app/views/layouts/application.html.erb b/railties/guides/code/getting_started/app/views/layouts/application.html.erb
index 7fd6b4f516..6578a41da2 100644
--- a/railties/guides/code/getting_started/app/views/layouts/application.html.erb
+++ b/railties/guides/code/getting_started/app/views/layouts/application.html.erb
@@ -6,7 +6,7 @@
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
</head>
-<body style="background: #EEEEEE;">
+<body>
<%= yield %>
diff --git a/railties/guides/code/getting_started/config/application.rb b/railties/guides/code/getting_started/config/application.rb
index 5f9010fced..d2cd5c028b 100644
--- a/railties/guides/code/getting_started/config/application.rb
+++ b/railties/guides/code/getting_started/config/application.rb
@@ -4,7 +4,7 @@ require 'rails/all'
if defined?(Bundler)
# If you precompile assets before deploying to production, use this line
- Bundler.require *Rails.groups(:assets => %w(development test))
+ Bundler.require(*Rails.groups(:assets => %w(development test)))
# If you want your assets lazily compiled in production, use this line
# Bundler.require(:default, :assets, Rails.env)
end
@@ -37,13 +37,19 @@ module Blog
# Use SQL instead of Active Record's schema dumper when creating the database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
- # like if you have constraints or database-specific column types
+ # like if you have constraints or database-specific column types.
# config.active_record.schema_format = :sql
- # Enable the asset pipeline
+ # Enforce whitelist mode for mass assignment.
+ # This will create an empty whitelist of attributes available for mass-assignment for all models
+ # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
+ # parameters by using an attr_accessible or attr_protected declaration.
+ # config.active_record.whitelist_attributes = true
+
+ # Enable the asset pipeline.
config.assets.enabled = true
- # Version of your assets, change this if you want to expire all your assets
+ # Version of your assets, change this if you want to expire all your assets.
config.assets.version = '1.0'
end
end
diff --git a/railties/guides/code/getting_started/config/database.yml b/railties/guides/code/getting_started/config/database.yml
index 51a4dd459d..32a998ad72 100644
--- a/railties/guides/code/getting_started/config/database.yml
+++ b/railties/guides/code/getting_started/config/database.yml
@@ -6,7 +6,9 @@
development:
adapter: sqlite3
database: db/development.sqlite3
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
timeout: 5000
# Warning: The database defined as "test" will be erased and
@@ -15,11 +17,15 @@ development:
test:
adapter: sqlite3
database: db/test.sqlite3
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
timeout: 5000
production:
adapter: sqlite3
database: db/production.sqlite3
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
timeout: 5000
diff --git a/railties/guides/code/getting_started/config/environments/development.rb b/railties/guides/code/getting_started/config/environments/development.rb
index aefd25c6b6..cec2b20c0b 100644
--- a/railties/guides/code/getting_started/config/environments/development.rb
+++ b/railties/guides/code/getting_started/config/environments/development.rb
@@ -1,34 +1,34 @@
Blog::Application.configure do
- # Settings specified here will take precedence over those in config/application.rb
+ # Settings specified here will take precedence over those in config/application.rb.
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
- # Show full error reports and disable caching
+ # Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
- # Don't care if the mailer can't send
+ # Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
- # Print deprecation notices to the Rails logger
+ # Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
- # Only use best-standards-support built into browsers
+ # Only use best-standards-support built into browsers.
config.action_dispatch.best_standards_support = :builtin
- # Raise exception on mass assignment protection for ActiveRecord models
+ # Raise exception on mass assignment protection for ActiveRecord models.
config.active_record.mass_assignment_sanitizer = :strict
# Log the query plan for queries taking more than this (works
- # with SQLite, MySQL, and PostgreSQL)
+ # with SQLite, MySQL, and PostgreSQL).
config.active_record.auto_explain_threshold_in_seconds = 0.5
- # Do not compress assets
+ # Do not compress assets.
config.assets.compress = false
- # Expands the lines which load the assets
+ # Expands the lines which load the assets.
config.assets.debug = true
end
diff --git a/railties/guides/code/getting_started/config/environments/production.rb b/railties/guides/code/getting_started/config/environments/production.rb
index c9b2f41c39..cfb8c960d6 100644
--- a/railties/guides/code/getting_started/config/environments/production.rb
+++ b/railties/guides/code/getting_started/config/environments/production.rb
@@ -1,67 +1,67 @@
Blog::Application.configure do
- # Settings specified here will take precedence over those in config/application.rb
+ # Settings specified here will take precedence over those in config/application.rb.
- # Code is not reloaded between requests
+ # Code is not reloaded between requests.
config.cache_classes = true
- # Full error reports are disabled and caching is turned on
+ # Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
- # Disable Rails's static asset server (Apache or nginx will already do this)
+ # Disable Rails's static asset server (Apache or nginx will already do this).
config.serve_static_assets = false
- # Compress JavaScripts and CSS
+ # Compress JavaScripts and CSS.
config.assets.compress = true
- # Don't fallback to assets pipeline if a precompiled asset is missed
+ # Don't fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
- # Generate digests for assets URLs
+ # Generate digests for assets URLs.
config.assets.digest = true
- # Defaults to Rails.root.join("public/assets")
+ # Defaults to Rails.root.join("public/assets").
# config.assets.manifest = YOUR_PATH
- # Specifies the header that your server uses for sending files
+ # Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
- # See everything in the log (default is :info)
+ # See everything in the log (default is :info).
# config.log_level = :debug
- # Prepend all log lines with the following tags
+ # Prepend all log lines with the following tags.
# config.log_tags = [ :subdomain, :uuid ]
- # Use a different logger for distributed setups
+ # Use a different logger for distributed setups.
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
- # Use a different cache store in production
+ # Use a different cache store in production.
# config.cache_store = :mem_cache_store
- # Enable serving of images, stylesheets, and JavaScripts from an asset server
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = "http://assets.example.com"
- # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
+ # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added).
# config.assets.precompile += %w( search.js )
- # Disable delivery errors, bad email addresses will be ignored
+ # Disable delivery errors, bad email addresses will be ignored.
# config.action_mailer.raise_delivery_errors = false
- # Enable threaded mode
+ # Enable threaded mode.
# config.threadsafe!
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
- # the I18n.default_locale when a translation can not be found)
+ # the I18n.default_locale when a translation can not be found).
config.i18n.fallbacks = true
- # Send deprecation notices to registered listeners
+ # Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
# Log the query plan for queries taking more than this (works
- # with SQLite, MySQL, and PostgreSQL)
+ # with SQLite, MySQL, and PostgreSQL).
# config.active_record.auto_explain_threshold_in_seconds = 0.5
end
diff --git a/railties/guides/code/getting_started/config/environments/test.rb b/railties/guides/code/getting_started/config/environments/test.rb
index 1d45541d5c..f2bc932fb3 100644
--- a/railties/guides/code/getting_started/config/environments/test.rb
+++ b/railties/guides/code/getting_started/config/environments/test.rb
@@ -1,5 +1,5 @@
Blog::Application.configure do
- # Settings specified here will take precedence over those in config/application.rb
+ # Settings specified here will take precedence over those in config/application.rb.
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
@@ -7,28 +7,28 @@ Blog::Application.configure do
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
- # Configure static asset server for tests with Cache-Control for performance
+ # Configure static asset server for tests with Cache-Control for performance.
config.serve_static_assets = true
config.static_cache_control = "public, max-age=3600"
- # Show full error reports and disable caching
+ # Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
- # Raise exceptions instead of rendering exception templates
+ # Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false
- # Disable request forgery protection in test environment
- config.action_controller.allow_forgery_protection = false
+ # Disable request forgery protection in test environment.
+ config.action_controller.allow_forgery_protection = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
- # Print deprecation notices to the stderr
- config.active_support.deprecation = :stderr
+ # Raise exception on mass assignment protection for Active Record models.
+ config.active_record.mass_assignment_sanitizer = :strict
- # Allow pass debug_assets=true as a query parameter to load pages with unpackaged assets
- config.assets.allow_debugging = true
+ # Print deprecation notices to the stderr.
+ config.active_support.deprecation = :stderr
end
diff --git a/railties/guides/code/getting_started/config/initializers/inflections.rb b/railties/guides/code/getting_started/config/initializers/inflections.rb
index 9e8b0131f8..5d8d9be237 100644
--- a/railties/guides/code/getting_started/config/initializers/inflections.rb
+++ b/railties/guides/code/getting_started/config/initializers/inflections.rb
@@ -8,3 +8,8 @@
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end
+#
+# These inflection rules are supported but not enabled by default:
+# ActiveSupport::Inflector.inflections do |inflect|
+# inflect.acronym 'RESTful'
+# end
diff --git a/railties/guides/code/getting_started/config/routes.rb b/railties/guides/code/getting_started/config/routes.rb
index 31f0d210db..b048ac68f1 100644
--- a/railties/guides/code/getting_started/config/routes.rb
+++ b/railties/guides/code/getting_started/config/routes.rb
@@ -1,7 +1,7 @@
Blog::Application.routes.draw do
- resources :posts do
- resources :comments
- end
+ resources :posts do
+ resources :comments
+ end
get "home/index"
@@ -60,5 +60,5 @@ Blog::Application.routes.draw do
# This is a legacy wild controller route that's not recommended for RESTful applications.
# Note: This route will make all actions in every controller accessible via GET requests.
- # match ':controller(/:action(/:id(.:format)))'
+ # match ':controller(/:action(/:id))(.:format)'
end
diff --git a/railties/guides/code/getting_started/public/500.html b/railties/guides/code/getting_started/public/500.html
index b80307fc16..f3648a0dbc 100644
--- a/railties/guides/code/getting_started/public/500.html
+++ b/railties/guides/code/getting_started/public/500.html
@@ -20,7 +20,6 @@
<!-- This file lives in public/500.html -->
<div class="dialog">
<h1>We're sorry, but something went wrong.</h1>
- <p>We've been notified about this issue and we'll take a look at it shortly.</p>
</div>
</body>
</html>
diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb
index 49ad8f7769..d6a98f9ac4 100644
--- a/railties/guides/rails_guides/generator.rb
+++ b/railties/guides/rails_guides/generator.rb
@@ -167,7 +167,7 @@ module RailsGuides
def select_only(guides)
prefixes = ENV['ONLY'].split(",").map(&:strip)
guides.select do |guide|
- prefixes.any? {|p| guide.start_with?(p)}
+ prefixes.any? { |p| guide.start_with?(p) || guide.start_with?("kindle") }
end
end
diff --git a/railties/guides/rails_guides/indexer.rb b/railties/guides/rails_guides/indexer.rb
index fb46491817..89fbccbb1d 100644
--- a/railties/guides/rails_guides/indexer.rb
+++ b/railties/guides/rails_guides/indexer.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/object/blank'
-require 'active_support/ordered_hash'
require 'active_support/core_ext/string/inflections'
module RailsGuides
@@ -21,7 +20,7 @@ module RailsGuides
def process(string, current_level=3, counters=[1])
s = StringScanner.new(string)
- level_hash = ActiveSupport::OrderedHash.new
+ level_hash = {}
while !s.eos?
re = %r{^h(\d)(?:\((#.*?)\))?\s*\.\s*(.*)$}
diff --git a/railties/guides/source/3_1_release_notes.textile b/railties/guides/source/3_1_release_notes.textile
index c4da87dc34..f88d8624ba 100644
--- a/railties/guides/source/3_1_release_notes.textile
+++ b/railties/guides/source/3_1_release_notes.textile
@@ -21,6 +21,113 @@ Rails 3.1 requires Ruby 1.8.7 or higher. Support for all of the previous Ruby ve
TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02 though. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x jump on 1.9.2 for smooth sailing.
+h4. What to update in your apps
+
+The following changes are meant for upgrading your application to Rails 3.1.3, the latest 3.1.x version of Rails.
+
+h5. Gemfile
+
+Make the following changes to your +Gemfile+.
+
+<ruby>
+gem 'rails', '= 3.1.3'
+gem 'mysql2'
+
+# Needed for the new asset pipeline
+group :assets do
+ gem 'sass-rails', "~> 3.1.5"
+ gem 'coffee-rails', "~> 3.1.1"
+ gem 'uglifier', ">= 1.0.3"
+end
+
+# jQuery is the default JavaScript library in Rails 3.1
+gem 'jquery-rails'
+</ruby>
+
+h5. config/application.rb
+
+* The asset pipeline requires the following additions:
+
+<ruby>
+config.assets.enabled = true
+config.assets.version = '1.0'
+</ruby>
+
+* If your application is using the "/assets" route for a resource you may want change the prefix used for assets to avoid conflicts:
+
+<ruby>
+# Defaults to '/assets'
+config.assets.prefix = '/asset-files'
+</ruby>
+
+h5. config/environments/development.rb
+
+* Remove the RJS setting <tt>config.action_view.debug_rjs = true</tt>.
+
+* Add the following, if you enable the asset pipeline.
+
+<ruby>
+# Do not compress assets
+config.assets.compress = false
+
+# Expands the lines which load the assets
+config.assets.debug = true
+</ruby>
+
+h5. config/environments/production.rb
+
+* Again, most of the changes below are for the asset pipeline. You can read more about these in the "Asset Pipeline":asset_pipeline.html guide.
+
+<ruby>
+# Compress JavaScripts and CSS
+config.assets.compress = true
+
+# Don't fallback to assets pipeline if a precompiled asset is missed
+config.assets.compile = false
+
+# Generate digests for assets URLs
+config.assets.digest = true
+
+# Defaults to Rails.root.join("public/assets")
+# config.assets.manifest = YOUR_PATH
+
+# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
+# config.assets.precompile += %w( search.js )
+
+
+# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
+# config.force_ssl = true
+
+</ruby>
+
+h5. config/environments/test.rb
+
+<ruby>
+# Configure static asset server for tests with Cache-Control for performance
+config.serve_static_assets = true
+config.static_cache_control = "public, max-age=3600"
+</ruby>
+
+h5. config/initializers/wrap_parameters.rb
+
+* Add this file with the following contents, if you wish to wrap parameters into a nested hash. This is on by default in new applications.
+
+<ruby>
+# Be sure to restart your server when you modify this file.
+# This file contains settings for ActionController::ParamsWrapper which
+# is enabled by default.
+
+# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
+ActiveSupport.on_load(:action_controller) do
+ wrap_parameters :format => [:json]
+end
+
+# Disable root element in JSON by default.
+ActiveSupport.on_load(:active_record) do
+ self.include_root_in_json = false
+end
+</ruby>
+
h3. Creating a Rails 3.1 application
<shell>
@@ -46,7 +153,7 @@ $ rails new myapp --edge
If you have a local checkout of the Rails repository and want to generate an application using that, you can pass the +--dev+ flag:
<shell>
-$ ruby /path/to/rails/bin/rails new myapp --dev
+$ ruby /path/to/rails/railties/bin/rails new myapp --dev
</shell>
h3. Rails Architectural Changes
diff --git a/railties/guides/source/3_2_release_notes.textile b/railties/guides/source/3_2_release_notes.textile
index 927abc7944..d669a7fdfa 100644
--- a/railties/guides/source/3_2_release_notes.textile
+++ b/railties/guides/source/3_2_release_notes.textile
@@ -7,19 +7,47 @@ Highlights in Rails 3.2:
* Automatic Query Explains
* Tagged Logging
-These release notes cover the major changes, but don't include every little bug fix and change. If you want to see everything, check out the "list of commits":https://github.com/rails/rails/commits/3-2-stable in the main Rails repository on GitHub.
+These release notes cover the major changes, but do not include each bug-fix and changes. If you want to see everything, check out the "list of commits":https://github.com/rails/rails/commits/3-2-stable in the main Rails repository on GitHub.
endprologue.
h3. Upgrading to Rails 3.2
-If you're upgrading an existing application, it's a great idea to have good test coverage before going in. You should also first upgrade to Rails 3.1 in case you haven't and make sure your application still runs as expected before attempting to update to Rails 3.2. Then take heed of the following changes:
+If you're upgrading an existing application, it's a great idea to have good test coverage before going in. You should also first upgrade to Rails 3.1 in case you haven't and make sure your application still runs as expected before attempting an update to Rails 3.2. Then take heed of the following changes:
h4. Rails 3.2 requires at least Ruby 1.8.7
Rails 3.2 requires Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially and you should upgrade as early as possible. Rails 3.2 is also compatible with Ruby 1.9.2.
-TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump on 1.9.2 or 1.9.3 for smooth sailing.
+TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump on to 1.9.2 or 1.9.3 for smooth sailing.
+
+h4. What to update in your apps
+
+* Update your Gemfile to depend on
+** <tt>rails = 3.2.0</tt>
+** <tt>sass-rails ~> 3.2.3</tt>
+** <tt>coffee-rails ~> 3.2.1</tt>
+** <tt>uglifier >= 1.0.3</tt>
+
+* Rails 3.2 deprecates <tt>vendor/plugins</tt> and Rails 4.0 will remove them completely. You can start replacing these plugins by extracting them as gems and adding them in your Gemfile. If you choose not to make them gems, you can move them into, say, <tt>lib/my_plugin/*</tt> and add an appropriate initializer in <tt>config/initializers/my_plugin.rb</tt>.
+
+* There are a couple of new configuration changes you'd want to add in <tt>config/environments/development.rb</tt>:
+
+<ruby>
+# Raise exception on mass assignment protection for Active Record models
+config.active_record.mass_assignment_sanitizer = :strict
+
+# Log the query plan for queries taking more than this (works
+# with SQLite, MySQL, and PostgreSQL)
+config.active_record.auto_explain_threshold_in_seconds = 0.5
+</ruby>
+
+The <tt>mass_assignment_sanitizer</tt> config also needs to be added in <tt>config/environments/test.rb</tt>:
+
+<ruby>
+# Raise exception on mass assignment protection for Active Record models
+config.active_record.mass_assignment_sanitizer = :strict
+</ruby>
h3. Creating a Rails 3.2 application
@@ -33,7 +61,7 @@ h4. Vendoring Gems
Rails now uses a +Gemfile+ in the application root to determine the gems you require for your application to start. This +Gemfile+ is processed by the "Bundler":https://github.com/carlhuda/bundler gem, which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems.
-More information: - "Bundler homepage":http://gembundler.com
+More information: "Bundler homepage":http://gembundler.com
h4. Living on the Edge
@@ -46,7 +74,7 @@ $ rails new myapp --edge
If you have a local checkout of the Rails repository and want to generate an application using that, you can pass the +--dev+ flag:
<shell>
-$ ruby /path/to/rails/bin/rails new myapp --dev
+$ ruby /path/to/rails/railties/bin/rails new myapp --dev
</shell>
h3. Major Features
@@ -63,10 +91,13 @@ Queries that take more than half a second to run are *automatically* explained i
h4. Tagged Logging
-When running a multi-user, multi-account application, it's a great help to be able to filter the log by who did what. TaggedLogging in Active Support helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging of such applications.
+When running a multi-user, multi-account application, it's a great help to be able to filter the log by who did what. TaggedLogging in Active Support helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications.
-h3. Railties
+h3. Documentation
+
+From Rails 3.2, the Rails guides are available for the Kindle and free Kindle Reading Apps for the iPad, iPhone, Mac, Android, etc.
+h3. Railties
* Speed up development by only reloading classes if dependencies files changed. This can be turned off by setting <tt>config.reload_classes_only_on_change</tt> to false.
@@ -86,9 +117,9 @@ config.railties_order = [Blog::Engine, :main_app, :all]
* Scaffold returns 204 No Content for API requests without content. This makes scaffold work with jQuery out of the box.
-* Update Rails::Rack::Logger middleware to apply any tags set in config.log_tags to the newly ActiveSupport::TaggedLogging Rails.logger. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications
+* Update <tt>Rails::Rack::Logger</tt> middleware to apply any tags set in <tt>config.log_tags</tt> to <tt>ActiveSupport::TaggedLogging</tt>. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications.
-* Default options to +rails new+ can be set in <tt>~/.railsrc</tt>.
+* Default options to +rails new+ can be set in <tt>~/.railsrc</tt>. You can specify extra command-line arguments to be used every time 'rails new' runs in the <tt>.railsrc</tt> configuration file in your home directory.
* Add an alias +d+ for +destroy+. This works for engines too.
@@ -102,14 +133,22 @@ rails g scaffold Post title:string:index author:uniq price:decimal{7,2}
will create indexes for +title+ and +author+ with the latter being an unique index. Some types such as decimal accept custom options. In the example, +price+ will be a decimal column with precision and scale set to 7 and 2 respectively.
+* Turn gem has been removed from default Gemfile.
+
* Remove old plugin generator +rails generate plugin+ in favor of +rails plugin new+ command.
* Remove old <tt>config.paths.app.controller</tt> API in favor of <tt>config.paths["app/controller"]</tt>.
-h4. Deprecations
+h4(#railties_deprecations). Deprecations
* +Rails::Plugin+ is deprecated and will be removed in Rails 4.0. Instead of adding plugins to +vendor/plugins+ use gems or bundler with path or git dependencies.
+h3. Action Mailer
+
+* Upgraded <tt>mail</tt> version to 2.4.0.
+
+* Removed the old Action Mailer API which was deprecated since Rails 3.0.
+
h3. Action Pack
h4. Action Controller
@@ -166,7 +205,7 @@ We now no longer write out HTTP_COOKIE and the cookie jar is persistent between
* Assets should use the request protocol by default or default to relative if no request is available.
-h5. Deprecations
+h5(#actioncontroller_deprecations). Deprecations
* Deprecated implied layout lookup in controllers whose parent had a explicit layout set:
@@ -181,14 +220,28 @@ end
In the example above, Posts controller will no longer automatically look up for a posts layout. If you need this functionality you could either remove <tt>layout "application"</tt> from +ApplicationController+ or explicitly set it to +nil+ in +PostsController+.
+* Deprecated <tt>ActionController::UnknownAction</tt> in favour of <tt>AbstractController::ActionNotFound</tt>.
+
+* Deprecated <tt>ActionController::DoubleRenderError</tt> in favour of <tt>AbstractController::DoubleRenderError</tt>.
+
+* Deprecated <tt>method_missing</tt> in favour of +action_missing+ for missing actions.
+
+* Deprecated <tt>ActionController#rescue_action</tt>, <tt>ActionController#initialize_template_class</tt> and <tt>ActionController#assign_shortcuts</tt>.
+
h4. Action Dispatch
+* Add <tt>config.action_dispatch.default_charset</tt> to configure default charset for <tt>ActionDispatch::Response</tt>.
+
* Added <tt>ActionDispatch::RequestId</tt> middleware that'll make a unique X-Request-Id header available to the response and enables the <tt>ActionDispatch::Request#uuid</tt> method. This makes it easy to trace requests from end-to-end in the stack and to identify individual requests in mixed logs like Syslog.
* The <tt>ShowExceptions</tt> middleware now accepts a exceptions application that is responsible to render an exception when the application fails. The application is invoked with a copy of the exception in +env["action_dispatch.exception"]+ and with the <tt>PATH_INFO</tt> rewritten to the status code.
* Allow rescue responses to be configured through a railtie as in <tt>config.action_dispatch.rescue_responses</tt>.
+h5(#actiondispatch_deprecations). Deprecations
+
+* Deprecated the ability to set a default charset at the controller level, use the new <tt>config.action_dispatch.default_charset</tt> instead.
+
h4. Action View
* Add +button_tag+ support to <tt>ActionView::Helpers::FormBuilder</tt>. This support mimics the default behavior of +submit_tag+.
@@ -199,7 +252,7 @@ h4. Action View
<% end %>
</ruby>
-* Date helpers accept a new option <tt>:use_two_digit_numbers => true</tt>, that renders select boxes for months and days with a leading zero without changing the respective values. For example, this is useful for displaying ISO8601-style dates such as '2011-08-01'.
+* Date helpers accept a new option <tt>:use_two_digit_numbers => true</tt>, that renders select boxes for months and days with a leading zero without changing the respective values. For example, this is useful for displaying ISO 8601-style dates such as '2011-08-01'.
* You can provide a namespace for your form to ensure uniqueness of id attributes on form elements. The namespace attribute will be prefixed with underscore on the generated HTML id.
@@ -232,17 +285,25 @@ end
* Added +font_path+ helper method that computes the path to a font asset in <tt>public/fonts</tt>.
-h5. Deprecations
+h5(#actionview_deprecations). Deprecations
* Passing formats or handlers to render :template and friends like <tt>render :template => "foo.html.erb"</tt> is deprecated. Instead, you can provide :handlers and :formats directly as an options: <tt> render :template => "foo", :formats => [:html, :js], :handlers => :erb</tt>.
+h4. Sprockets
+
+* Adds a configuration option <tt>config.assets.logger</tt> to control Sprockets logging. Set it to +false+ to turn off logging and to +nil+ to default to +Rails.logger+.
+
h3. Active Record
+* Boolean columns with 'on' and 'ON' values are type cast to true.
+
+* When the +timestamps+ method creates the +created_at+ and +updated_at+ columns, it makes them non-nullable by default.
+
* Implemented <tt>ActiveRecord::Relation#explain</tt>.
* Implements <tt>AR::Base.silence_auto_explain</tt> which allows the user to selectively disable automatic EXPLAINs within a block.
-* Implements automatic EXPLAIN logging for slow queries. A new configuration parameter +config.active_record.auto_explain_threshold_in_seconds+ determines what's to be considered a slow query. Setting that to nil disables this feature. Defaults are 0.5 in development mode, and nil in test and production modes. As of this writing there's support for SQLite, MySQL (mysql2 adapter), and PostgreSQL.
+* Implements automatic EXPLAIN logging for slow queries. A new configuration parameter +config.active_record.auto_explain_threshold_in_seconds+ determines what's to be considered a slow query. Setting that to nil disables this feature. Defaults are 0.5 in development mode, and nil in test and production modes. Rails 3.2 supports this feature in SQLite, MySQL (mysql2 adapter), and PostgreSQL.
* Added <tt>ActiveRecord::Base.store</tt> for declaring simple single-column key/value stores.
@@ -311,7 +372,34 @@ has_many :clients, :class_name => :Client # Note that the symbol need to be capi
User.where(:first_name => "Scarlett").first_or_create!(:last_name => "Johansson")
</ruby>
-h4. Deprecations
+* Added a <tt>with_lock</tt> method to Active Record objects, which starts a transaction, locks the object (pessimistically) and yields to the block. The method takes one (optional) parameter and passes it to +lock!+.
+
+This makes it possible to write the following:
+
+<ruby>
+class Order < ActiveRecord::Base
+ def cancel!
+ transaction do
+ lock!
+ # ... cancelling logic
+ end
+ end
+end
+</ruby>
+
+as:
+
+<ruby>
+class Order < ActiveRecord::Base
+ def cancel!
+ with_lock do
+ # ... cancelling logic
+ end
+ end
+end
+</ruby>
+
+h4(#activerecord_deprecations). Deprecations
* Automatic closure of connections in threads is deprecated. For example the following code is deprecated:
@@ -359,7 +447,7 @@ h3. Active Model
* Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior.
-h4. Deprecations
+h4(#activemodel_deprecations). Deprecations
* Deprecated <tt>define_attr_method</tt> in <tt>ActiveModel::AttributeMethods</tt> because this only existed to support methods like +set_table_name+ in Active Record, which are themselves being deprecated.
@@ -419,7 +507,7 @@ Event.where(:created_at => Time.now.all_day)
* Removed <tt>ActiveSupport::SecureRandom</tt> in favor of <tt>SecureRandom</tt> from the standard library.
-h4. Deprecations
+h4(#activesupport_deprecations). Deprecations
* +ActiveSupport::Base64+ is deprecated in favor of <tt>::Base64</tt>.
@@ -429,7 +517,7 @@ h4. Deprecations
* Deprecated <tt>ActiveSupport::MessageEncryptor#encrypt</tt> and <tt>ActiveSupport::MessageEncryptor#decrypt</tt>.
-* <tt>ActiveSupport::BufferedLogger#silence</tt> is deprecated. If you want to squelch logs for a certain block, change the log level for that block.
+* <tt>ActiveSupport::BufferedLogger#silence</tt> is deprecated. If you want to squelch logs for a certain block, change the log level for that block.
* <tt>ActiveSupport::BufferedLogger#open_log</tt> is deprecated. This method should not have been public in the first place.
diff --git a/railties/guides/source/_welcome.html.erb b/railties/guides/source/_welcome.html.erb
index bcbb49a0ec..9d2e9c1d68 100644
--- a/railties/guides/source/_welcome.html.erb
+++ b/railties/guides/source/_welcome.html.erb
@@ -10,7 +10,7 @@
</p>
<% else %>
<p>
- These are the new guides for Rails 3.1 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>.
+ These are the new guides for Rails 3.2 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>.
These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together.
</p>
<% end %>
diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile
index bc85f07ecc..52d134ace5 100644
--- a/railties/guides/source/action_controller_overview.textile
+++ b/railties/guides/source/action_controller_overview.textile
@@ -563,7 +563,7 @@ The request object contains a lot of useful information about the request coming
|domain(n=2)|The hostname's first +n+ segments, starting from the right (the TLD).|
|format|The content type requested by the client.|
|method|The HTTP method used for the request.|
-|get?, post?, put?, delete?, head?|Returns true if the HTTP method is GET/POST/PUT/DELETE/HEAD.|
+|get?, post?, patch?, put?, delete?, head?|Returns true if the HTTP method is GET/POST/PATCH/PUT/DELETE/HEAD.|
|headers|Returns a hash containing the headers associated with the request.|
|port|The port number (integer) used for the request.|
|protocol|Returns a string containing the protocol used plus "://", for example "http://".|
diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile
index e2b69fa0d5..2c0b81121f 100644
--- a/railties/guides/source/action_view_overview.textile
+++ b/railties/guides/source/action_view_overview.textile
@@ -570,7 +570,7 @@ stylesheet_link_tag :monkey # =>
h5. auto_discovery_link_tag
-Returns a link tag that browsers and news readers can use to auto-detect an RSS or ATOM feed.
+Returns a link tag that browsers and news readers can use to auto-detect an RSS or Atom feed.
<ruby>
auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "RSS Feed"}) # =>
@@ -585,6 +585,14 @@ Computes the path to an image asset in the +public/images+ directory. Full paths
image_path("edit.png") # => /images/edit.png
</ruby>
+h5. image_url
+
+Computes the url to an image asset in the +public/images+ directory. This will call +image_path+ internally and merge with your current host or your asset host.
+
+<ruby>
+image_url("edit.png") # => http://www.example.com/images/edit.png
+</ruby>
+
h5. image_tag
Returns an html image tag for the source. The source can be a full path or a file that exists in your +public/images+ directory.
@@ -629,6 +637,14 @@ Computes the path to a JavaScript asset in the +public/javascripts+ directory. I
javascript_path "common" # => /javascripts/common.js
</ruby>
+h5. javascript_url
+
+Computes the url to a JavaScript asset in the +public/javascripts+ directory. This will call +javascript_path+ internally and merge with your current host or your asset host.
+
+<ruby>
+javascript_url "common" # => http://www.example.com/javascripts/common.js
+</ruby>
+
h5. stylesheet_link_tag
Returns a stylesheet link tag for the sources specified as arguments. If you don't specify an extension, +.css+ will be appended automatically.
@@ -659,11 +675,19 @@ Computes the path to a stylesheet asset in the +public/stylesheets+ directory. I
stylesheet_path "application" # => /stylesheets/application.css
</ruby>
+h5. stylesheet_url
+
+Computes the url to a stylesheet asset in the +public/stylesheets+ directory. This will call +stylesheet_path+ internally and merge with your current host or your asset host.
+
+<ruby>
+stylesheet_url "application" # => http://www.example.com/stylesheets/application.css
+</ruby>
+
h4. AtomFeedHelper
h5. atom_feed
-This helper makes building an ATOM feed easy. Here's a full usage example:
+This helper makes building an Atom feed easy. Here's a full usage example:
*config/routes.rb*
@@ -1124,6 +1148,79 @@ If <tt>@post.author_id</tt> is 1, this would return:
</select>
</html>
+h5. collection_radio_buttons
+
+Returns +radio_button+ tags for the collection of existing return values of +method+ for +object+'s class.
+
+Example object structure for use with this method:
+
+<ruby>
+class Post < ActiveRecord::Base
+ belongs_to :author
+end
+
+class Author < ActiveRecord::Base
+ has_many :posts
+ def name_with_initial
+ "#{first_name.first}. #{last_name}"
+ end
+end
+</ruby>
+
+Sample usage (selecting the associated Author for an instance of Post, +@post+):
+
+<ruby>
+collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial)
+</ruby>
+
+If <tt>@post.author_id</tt> is 1, this would return:
+
+<html>
+<input id="post_author_id_1" name="post[author_id]" type="radio" value="1" checked="checked" />
+<label for="post_author_id_1">D. Heinemeier Hansson</label>
+<input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
+<label for="post_author_id_2">D. Thomas</label>
+<input id="post_author_id_3" name="post[author_id]" type="radio" value="3" />
+<label for="post_author_id_3">M. Clark</label>
+</html>
+
+h5. collection_check_boxes
+
+Returns +check_box+ tags for the collection of existing return values of +method+ for +object+'s class.
+
+Example object structure for use with this method:
+
+<ruby>
+class Post < ActiveRecord::Base
+ has_and_belongs_to_many :author
+end
+
+class Author < ActiveRecord::Base
+ has_and_belongs_to_many :posts
+ def name_with_initial
+ "#{first_name.first}. #{last_name}"
+ end
+end
+</ruby>
+
+Sample usage (selecting the associated Authors for an instance of Post, +@post+):
+
+<ruby>
+collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial)
+</ruby>
+
+If <tt>@post.author_ids</tt> is [1], this would return:
+
+<html>
+<input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" checked="checked" />
+<label for="post_author_ids_1">D. Heinemeier Hansson</label>
+<input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
+<label for="post_author_ids_2">D. Thomas</label>
+<input id="post_author_ids_3" name="post[author_ids][]" type="checkbox" value="3" />
+<label for="post_author_ids_3">M. Clark</label>
+<input name="post[author_ids][]" type="hidden" value="" />
+</html>
+
h5. country_options_for_select
Returns a string of option tags for pretty much any country in the world.
diff --git a/railties/guides/source/active_model_basics.textile b/railties/guides/source/active_model_basics.textile
index 9c8ad24cee..98b3533000 100644
--- a/railties/guides/source/active_model_basics.textile
+++ b/railties/guides/source/active_model_basics.textile
@@ -56,7 +56,7 @@ class Person
before_update :reset_me
def update
- _run_update_callbacks do
+ run_callbacks(:update) do
# This will call when we are trying to call update on object.
end
end
diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile
index beada85ce3..21bbc64255 100644
--- a/railties/guides/source/active_record_querying.textile
+++ b/railties/guides/source/active_record_querying.textile
@@ -404,6 +404,8 @@ Or ordering by multiple fields:
<ruby>
Client.order("orders_count ASC, created_at DESC")
+# OR
+Client.order("orders_count ASC", "created_at DESC")
</ruby>
h3. Selecting Specific Fields
@@ -606,9 +608,33 @@ SELECT * FROM clients WHERE orders_count > 10 ORDER BY clients.id DESC
This method accepts *no* arguments.
+h3. Null Relation
+
+The +none+ method returns a chainable relation with no records. Any subsequent conditions chained to the returned relation will continue generating empty relations. This is useful in scenarios where you need a chainable response to a method or a scope that could return zero results.
+
+<ruby>
+Post.none # returns an empty Relation and fires no queries.
+</ruby>
+
+<ruby>
+# The visible_posts method below is expected to return a Relation.
+@posts = current_user.visible_posts.where(:name => params[:name])
+
+def visible_posts
+ case role
+ when 'Country Manager'
+ Post.where(:country => country)
+ when 'Reviewer'
+ Post.published
+ when 'Bad User'
+ Post.none # => returning [] or nil breaks the caller code in this case
+ end
+end
+</ruby>
+
h3. Readonly Objects
-Active Record provides +readonly+ method on a relation to explicitly disallow modification or deletion of any of the returned object. Any attempt to alter or destroy a readonly record will not succeed, raising an +ActiveRecord::ReadOnlyRecord+ exception.
+Active Record provides +readonly+ method on a relation to explicitly disallow modification of any of the returned objects. Any attempt to alter a readonly record will not succeed, raising an +ActiveRecord::ReadOnlyRecord+ exception.
<ruby>
client = Client.readonly.first
@@ -648,15 +674,13 @@ c2.save # Raises an ActiveRecord::StaleObjectError
You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging, or otherwise apply the business logic needed to resolve the conflict.
-NOTE: You must ensure that your database schema defaults the +lock_version+ column to +0+.
-
This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
-To override the name of the +lock_version+ column, +ActiveRecord::Base+ provides a class method called +set_locking_column+:
+To override the name of the +lock_version+ column, +ActiveRecord::Base+ provides a class attribute called +locking_column+:
<ruby>
class Client < ActiveRecord::Base
- set_locking_column :lock_client_column
+ self.locking_column = :lock_client_column
end
</ruby>
@@ -692,6 +716,17 @@ Item.transaction do
end
</ruby>
+If you already have an instance of your model, you can start a transaction and acquire the lock in one go using the following code:
+
+<ruby>
+item = Item.first
+item.with_lock do
+ # This block is called within a transaction,
+ # item is already locked.
+ item.increment!(:views)
+end
+</ruby>
+
h3. Joining Tables
Active Record provides a finder method called +joins+ for specifying +JOIN+ clauses on the resulting SQL. There are multiple ways to use the +joins+ method.
@@ -954,7 +989,7 @@ If you're working with dates or times within scopes, due to how they are evaluat
<ruby>
class Post < ActiveRecord::Base
- scope :last_week, lambda { where("created_at < ?", Time.zone.now ) }
+ scope :created_before_now, lambda { where("created_at < ?", Time.zone.now ) }
end
</ruby>
@@ -966,21 +1001,21 @@ When a +lambda+ is used for a +scope+, it can take arguments:
<ruby>
class Post < ActiveRecord::Base
- scope :1_week_before, lambda { |time| where("created_at < ?", time) }
+ scope :created_before, lambda { |time| where("created_at < ?", time) }
end
</ruby>
This may then be called using this:
<ruby>
-Post.1_week_before(Time.zone.now)
+Post.created_before(Time.zone.now)
</ruby>
However, this is just duplicating the functionality that would be provided to you by a class method.
<ruby>
class Post < ActiveRecord::Base
- def self.1_week_before(time)
+ def self.created_before(time)
where("created_at < ?", time)
end
end
@@ -989,7 +1024,7 @@ end
Using a class method is the preferred way to accept arguments for scopes. These methods will still be accessible on the association objects:
<ruby>
-category.posts.1_week_before(time)
+category.posts.created_before(time)
</ruby>
h4. Working with scopes
@@ -1389,6 +1424,9 @@ A threshold of +nil+ disables automatic EXPLAINs.
The default threshold in development mode is 0.5 seconds, and +nil+ in test and
production modes.
+INFO. Automatic EXPLAIN gets disabled if Active Record has no logger, regardless
+of the value of the threshold.
+
h5. Disabling Automatic EXPLAIN
Automatic EXPLAIN can be selectively silenced with +ActiveRecord::Base.silence_auto_explain+:
diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile
index a27c292a4c..72ac8d2db9 100644
--- a/railties/guides/source/active_record_validations_callbacks.textile
+++ b/railties/guides/source/active_record_validations_callbacks.textile
@@ -614,7 +614,7 @@ As shown in the example, you can also combine standard validations with your own
h4. Custom Methods
-You can also create methods that verify the state of your models and add messages to the +errors+ collection when they are invalid. You must then register these methods by using one or more of the +validate+, +validate_on_create+ or +validate_on_update+ class methods, passing in the symbols for the validation methods' names.
+You can also create methods that verify the state of your models and add messages to the +errors+ collection when they are invalid. You must then register these methods by using the +validate+ class method, passing in the symbols for the validation methods' names.
You can pass more than one symbol for each class method and the respective validations will be run in the same order as they were registered.
@@ -637,12 +637,24 @@ class Invoice < ActiveRecord::Base
end
</ruby>
+By default such validations will run every time you call +valid?+. It is also possible to control when to run these custom validations by giving an +:on+ option to the +validate+ method, with either: +:create+ or +:update+.
+
+<ruby>
+class Invoice < ActiveRecord::Base
+ validate :active_customer, :on => :create
+
+ def active_customer
+ errors.add(:customer_id, "is not active") unless customer.active?
+ end
+end
+</ruby>
+
You can even create your own validation helpers and reuse them in several different models. For example, an application that manages surveys may find it useful to express that a certain field corresponds to a set of choices:
<ruby>
ActiveRecord::Base.class_eval do
def self.validates_as_choice(attr_name, n, options={})
- validates attr_name, :inclusion => { {:in => 1..n}.merge(options) }
+ validates attr_name, :inclusion => { { :in => 1..n }.merge!(options) }
end
end
</ruby>
@@ -659,11 +671,11 @@ h3. Working with Validation Errors
In addition to the +valid?+ and +invalid?+ methods covered earlier, Rails provides a number of methods for working with the +errors+ collection and inquiring about the validity of objects.
-The following is a list of the most commonly used methods. Please refer to the +ActiveRecord::Errors+ documentation for a list of all the available methods.
+The following is a list of the most commonly used methods. Please refer to the +ActiveModel::Errors+ documentation for a list of all the available methods.
h4(#working_with_validation_errors-errors). +errors+
-Returns an instance of the class +ActiveModel::Errors+ (which behaves like an ordered hash) containing all errors. Each key is the attribute name and the value is an array of strings with all errors.
+Returns an instance of the class +ActiveModel::Errors+ containing all errors. Each key is the attribute name and the value is an array of strings with all errors.
<ruby>
class Person < ActiveRecord::Base
@@ -889,13 +901,8 @@ Below is a simple example where we change the Rails behavior to always display t
<ruby>
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
- if instance.error_message.kind_of?(Array)
- %(#{html_tag}<span class="validation-error">&nbsp;
- #{instance.error_message.join(',')}</span>).html_safe
- else
- %(#{html_tag}<span class="validation-error">&nbsp;
- #{instance.error_message}</span>).html_safe
- end
+ errors = Array(instance.error_message).join(',')
+ %(#{html_tag}<span class="validation-error">&nbsp;#{errors}</span>).html_safe
end
</ruby>
@@ -949,6 +956,7 @@ h4. Creating an Object
* +before_validation+
* +after_validation+
* +before_save+
+* +around_save+
* +before_create+
* +around_create+
* +after_create+
@@ -959,6 +967,7 @@ h4. Updating an Object
* +before_validation+
* +after_validation+
* +before_save+
+* +around_save+
* +before_update+
* +around_update+
* +after_update+
@@ -967,8 +976,8 @@ h4. Updating an Object
h4. Destroying an Object
* +before_destroy+
-* +after_destroy+
* +around_destroy+
+* +after_destroy+
WARNING. +after_save+ runs both on create and update, but always _after_ the more specific callbacks +after_create+ and +after_update+, no matter the order in which the macro calls were executed.
@@ -1013,7 +1022,7 @@ The following methods trigger callbacks:
* +increment!+
* +save+
* +save!+
-* +save(false)+
+* +save(:validate => false)+
* +toggle!+
* +update+
* +update_attribute+
diff --git a/railties/guides/source/active_resource_basics.textile b/railties/guides/source/active_resource_basics.textile
index 851aac1a3f..37abb8a640 100644
--- a/railties/guides/source/active_resource_basics.textile
+++ b/railties/guides/source/active_resource_basics.textile
@@ -71,7 +71,7 @@ person.destroy
h3. Validations
-Module to support validation and errors with Active Resource objects. The module overrides Base#save to rescue ActiveResource::ResourceInvalid exceptions and parse the errors returned in the web service response. The module also adds an errors collection that mimics the interface of the errors provided by ActiveRecord::Errors.
+Module to support validation and errors with Active Resource objects. The module overrides Base#save to rescue ActiveResource::ResourceInvalid exceptions and parse the errors returned in the web service response. The module also adds an errors collection that mimics the interface of the errors provided by ActiveModel::Errors.
h4. Validating client side resources by overriding validation methods in base class
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index 3983667c2b..2091ce0395 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -376,7 +376,7 @@ NOTE: Defined in +active_support/core_ext/object/instance_variables.rb+.
h5. +instance_values+
The method +instance_values+ returns a hash that maps instance variable names without "@" to their
-corresponding values. Keys are strings both in Ruby 1.8 and 1.9:
+corresponding values. Keys are strings:
<ruby>
class C
@@ -692,7 +692,8 @@ NOTE: Defined in +active_support/core_ext/module/introspection.rb+.
h4. Constants
-The method +local_constants+ returns the names of the constants that have been defined in the receiver module:
+The method +local_constants+ returns the names of the constants that have been
+defined in the receiver module:
<ruby>
module X
@@ -704,13 +705,11 @@ module X
end
end
-X.local_constants # => ["X2", "X1", "Y"], assumes Ruby 1.8
-X::Y.local_constants # => ["X1", "Y1"], assumes Ruby 1.8
+X.local_constants # => [:X1, :X2, :Y]
+X::Y.local_constants # => [:Y1, :X1]
</ruby>
-The names are returned as strings in Ruby 1.8, and as symbols in Ruby 1.9. The method +local_constant_names+ always returns strings.
-
-WARNING: This method returns precise results in Ruby 1.9. In older versions of Ruby, however, it may miss some constants in case the same constant exists in the receiver module as well as in any of its ancestors and both constants point to the same object (objects are compared using +Object#object_id+).
+The names are returned as symbols. (The deprecated method +local_constant_names+ returns strings.)
NOTE: Defined in +active_support/core_ext/module/introspection.rb+.
@@ -737,8 +736,8 @@ Math.qualified_const_get("E") # => 2.718281828459045
</ruby>
These methods are analogous to their builtin counterparts. In particular,
-+qualified_constant_defined?+ accepts an optional second argument in 1.9
-to be able to say whether you want the predicate to look in the ancestors.
++qualified_constant_defined?+ accepts an optional second argument to be
+able to say whether you want the predicate to look in the ancestors.
This flag is taken into account for each constant in the expression while
walking down the path.
@@ -759,12 +758,12 @@ end
+qualified_const_defined?+ behaves this way:
<ruby>
-N.qualified_const_defined?("C::X", false) # => false (1.9 only)
-N.qualified_const_defined?("C::X", true) # => true (1.9 only)
-N.qualified_const_defined?("C::X") # => false in 1.8, true in 1.9
+N.qualified_const_defined?("C::X", false) # => false
+N.qualified_const_defined?("C::X", true) # => true
+N.qualified_const_defined?("C::X") # => true
</ruby>
-As the last example implies, in 1.9 the second argument defaults to true,
+As the last example implies, the second argument defaults to true,
as in +const_defined?+.
For coherence with the builtin methods only relative paths are accepted.
@@ -1873,9 +1872,24 @@ The method +multiple_of?+ tests whether an integer is multiple of the argument:
NOTE: Defined in +active_support/core_ext/integer/multiple.rb+.
+h4. +ordinal+
+
+The method +ordinal+ returns the ordinal suffix string corresponding to the receiver integer:
+
+<ruby>
+1.ordinal # => "st"
+2.ordinal # => "nd"
+53.ordinal # => "rd"
+2009.ordinal # => "th"
+-21.ordinal # => "st"
+-134.ordinal # => "th"
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/integer/inflections.rb+.
+
h4. +ordinalize+
-The method +ordinalize+ returns the ordinal string corresponding to the receiver integer:
+The method +ordinalize+ returns the ordinal string corresponding to the receiver integer. In comparison, note that the +ordinal+ method returns *only* the suffix string.
<ruby>
1.ordinalize # => "1st"
@@ -2238,9 +2252,6 @@ The last point is particularly worth comparing for some enumerables:
<ruby>
Array.wrap(:foo => :bar) # => [{:foo => :bar}]
Array(:foo => :bar) # => [[:foo, :bar]]
-
-Array.wrap("foo\nbar") # => ["foo\nbar"]
-Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8
</ruby>
There's also a related idiom that uses the splat operator:
diff --git a/railties/guides/source/ajax_on_rails.textile b/railties/guides/source/ajax_on_rails.textile
index 3a0ccfe9b2..5913a472fd 100644
--- a/railties/guides/source/ajax_on_rails.textile
+++ b/railties/guides/source/ajax_on_rails.textile
@@ -146,7 +146,8 @@ link_to_remote "Add new item",
:position => :bottom
</ruby>
-** *:method* Most typically you want to use a POST request when adding a remote link to your view so this is the default behavior. However, sometimes you'll want to update (PUT) or delete/destroy (DELETE) something and you can specify this with the +:method+ option. Let's see an example for a typical AJAX link for deleting an item from a list:
+** *:method* Most typically you want to use a POST request when adding a remote
+link to your view so this is the default behavior. However, sometimes you'll want to update (PATCH/PUT) or delete/destroy (DELETE) something and you can specify this with the +:method+ option. Let's see an example for a typical AJAX link for deleting an item from a list:
<ruby>
link_to_remote "Delete the item",
@@ -167,7 +168,7 @@ Note that if we wouldn't override the default behavior (POST), the above snippet
<ruby>
link_to_remote "Update record",
:url => record_url(record),
- :method => :put,
+ :method => :patch,
:with => "'status=' <plus> 'encodeURIComponent($('status').value) <plus> '&completed=' <plus> $('completed')"
</ruby>
diff --git a/railties/guides/source/asset_pipeline.textile b/railties/guides/source/asset_pipeline.textile
index da7d46ef71..ff2bd08602 100644
--- a/railties/guides/source/asset_pipeline.textile
+++ b/railties/guides/source/asset_pipeline.textile
@@ -109,22 +109,74 @@ NOTE: You must have an "ExecJS":https://github.com/sstephenson/execjs#readme sup
h4. Asset Organization
-Assets that must be precompiled can be placed inside an application in one of three locations: +app/assets+, +lib/assets+ or +vendor/assets+.
+Pipeline assets can be placed inside an application in one of three locations: +app/assets+, +lib/assets+ or +vendor/assets+.
+app/assets+ is for assets that are owned by the application, such as custom images, JavaScript files or stylesheets.
+lib/assets+ is for your own libraries' code that doesn't really fit into the scope of the application or those libraries which are shared across applications.
-+vendor/assets+ is for assets that are owned by outside entities, such as code for JavaScript plugins.
++vendor/assets+ is for assets that are owned by outside entities, such as code for JavaScript plugins and CSS frameworks.
-All subdirectories that exist within these three locations are added to the search path for Sprockets. You can see this search path by inspecting +Rails.application.config.assets.paths+ in the Rails console. When a client requests an asset, these paths are traversed (in the order that they occur in the search path) to see if they contain an asset matching the name specified. If an asset is found, it's processed by Sprockets and served.
+h5. Search paths
-You can add additional (fully qualified) paths to the pipeline in +config/application.rb+. For example:
+When a file is referenced from a manifest or a helper, Sprockets searches the three default asset locations for it.
+
+The default locations are: +app/assets/images+ and the subdirectories +javascripts+ and +stylesheets+ in all three asset locations.
+
+For example, these files:
+
+<plain>
+app/assets/javascripts/home.js
+lib/assets/javascripts/moovinator.js
+vendor/assets/javascript/slider.js
+</plain>
+
+would be referenced in a manifest like this:
+
+<plain>
+//= require home
+//= require moovinator
+//= require slider
+</plain>
+
+Assets inside subdirectories can also be accessed.
+
+<plain>
+app/assets/javascripts/sub/something.js
+</plain>
+
+is referenced as:
+
+<plain>
+//= require sub/something
+</plain>
+
+You can view the search path by inspecting +Rails.application.config.assets.paths+ in the Rails console.
+
+Additional (fully qualified) paths can be added to the pipeline in +config/application.rb+. For example:
<ruby>
config.assets.paths << Rails.root.join("app", "assets", "flash")
</ruby>
+Paths are traversed in the order that they occur in the search path.
+
+It is important to note that files you want to reference outside a manifest must be added to the precompile array or they will not be available in the production environment.
+
+h5. Using index files
+
+Sprockets uses files named +index+ (with the relevant extensions) for a special purpose.
+
+For example, if you have a jQuery library with many modules, which is stored in +lib/assets/library_name+, the file +lib/assets/library_name/index.js+ serves as the manifest for all files in this library. This file could include a list of all the required files in order, or a simple <tt>require_tree</tt> directive.
+
+The library as a whole can be accessed in the site's application manifest like so:
+
+<plain>
+//= require library_name
+</plain>
+
+This simplifies maintenance and keeps things clean by allowing related code to be grouped before inclusion elsewhere.
+
h4. Coding Links to Assets
Sprockets does not add any new methods to access your assets - you still use the familiar +javascript_include_tag+ and +stylesheet_link_tag+.
diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile
index 451653655f..ba92aedbd0 100644
--- a/railties/guides/source/association_basics.textile
+++ b/railties/guides/source/association_basics.textile
@@ -358,6 +358,7 @@ Here are a few things you should know to make efficient use of Active Record ass
* Avoiding name collisions
* Updating the schema
* Controlling association scope
+* Bi-directional associations
h4. Controlling Caching
@@ -501,6 +502,59 @@ module MyApplication
end
</ruby>
+h4. Bi-directional Associations
+
+It's normal for associations to work in two directions, requiring declaration on two different models:
+
+<ruby>
+class Customer < ActiveRecord::Base
+ has_many :orders
+end
+
+class Order < ActiveRecord::Base
+ belongs_to :customer
+end
+</ruby>
+
+By default, Active Record doesn't know about the connection between these associations. This can lead to two copies of an object getting out of sync:
+
+<ruby>
+c = Customer.first
+o = c.orders.first
+c.first_name == o.customer.first_name # => true
+c.first_name = 'Manny'
+c.first_name == o.customer.first_name # => false
+</ruby>
+
+This happens because c and o.customer are two different in-memory representations of the same data, and neither one is automatically refreshed from changes to the other. Active Record provides the +:inverse_of+ option so that you can inform it of these relations:
+
+<ruby>
+class Customer < ActiveRecord::Base
+ has_many :orders, :inverse_of => :customer
+end
+
+class Order < ActiveRecord::Base
+ belongs_to :customer, :inverse_of => :orders
+end
+</ruby>
+
+With these changes, Active Record will only load one copy of the customer object, preventing inconsistencies and making your application more efficient:
+
+<ruby>
+c = Customer.first
+o = c.orders.first
+c.first_name == o.customer.first_name # => true
+c.first_name = 'Manny'
+c.first_name == o.customer.first_name # => true
+</ruby>
+
+There are a few limitations to +inverse_of+ support:
+
+* They do not work with <tt>:through</tt> associations.
+* They do not work with <tt>:polymorphic</tt> associations.
+* They do not work with <tt>:as</tt> associations.
+* For +belongs_to+ associations, +has_many+ inverse associations are ignored.
+
h3. Detailed Association Reference
The following sections give the details of each type of association, including the methods that they add and the options that you can use when declaring an association.
@@ -594,6 +648,7 @@ The +belongs_to+ association supports these options:
* +:dependent+
* +:foreign_key+
* +:include+
+* +:inverse_of+
* +:polymorphic+
* +:readonly+
* +:select+
@@ -720,6 +775,20 @@ end
NOTE: There's no need to use +:include+ for immediate associations - that is, if you have +Order belongs_to :customer+, then the customer is eager-loaded automatically when it's needed.
+h6(#belongs_to-inverse_of). +:inverse_of+
+
+The +:inverse_of+ option specifies the name of the +has_many+ or +has_one+ association that is the inverse of this association. Does not work in combination with the +:polymorphic+ options.
+
+<ruby>
+class Customer < ActiveRecord::Base
+ has_many :orders, :inverse_of => :customer
+end
+
+class Order < ActiveRecord::Base
+ belongs_to :customer, :inverse_of => :orders
+end
+</ruby>
+
h6(#belongs_to-polymorphic). +:polymorphic+
Passing +true+ to the +:polymorphic+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphic-associations">earlier in this guide</a>.
@@ -859,6 +928,7 @@ The +has_one+ association supports these options:
* +:dependent+
* +:foreign_key+
* +:include+
+* +:inverse_of+
* +:order+
* +:primary_key+
* +:readonly+
@@ -899,6 +969,9 @@ end
h6(#has_one-dependent). +:dependent+
If you set the +:dependent+ option to +:destroy+, then deleting this object will call the +destroy+ method on the associated object to delete that object. If you set the +:dependent+ option to +:delete+, then deleting this object will delete the associated object _without_ calling its +destroy+ method. If you set the +:dependent+ option to +:nullify+, then deleting this object will set the foreign key in the association object to +NULL+.
+If you set the +:dependent+ option to +:restrict+, then the deletion of the object is restricted if a dependent associated object exist and a +DeleteRestrictionError+ exception is raised.
+
+NOTE: The default behavior for +:dependent => :restrict+ is to raise a +DeleteRestrictionError+ when associated objects exist. Since Rails 4.0 this behavior is being deprecated in favor of adding an error to the base model. To silence the warning in Rails 4.0, you should fix your code to not expect this Exception and add +config.active_record.dependent_restrict_raises = false+ to your application config.
h6(#has_one-foreign_key). +:foreign_key+
@@ -948,6 +1021,20 @@ class Representative < ActiveRecord::Base
end
</ruby>
+h6(#has_one-inverse_of). +:inverse_of+
+
+The +:inverse_of+ option specifies the name of the +belongs_to+ association that is the inverse of this association. Does not work in combination with the +:through+ or +:as+ options.
+
+<ruby>
+class Supplier < ActiveRecord::Base
+ has_one :account, :inverse_of => :supplier
+end
+
+class Account < ActiveRecord::Base
+ belongs_to :supplier, :inverse_of => :account
+end
+</ruby>
+
h6(#has_one-order). +:order+
The +:order+ option dictates the order in which associated objects will be received (in the syntax used by an SQL +ORDER BY+ clause). Because a +has_one+ association will only retrieve a single associated object, this option should not be needed.
@@ -1120,7 +1207,7 @@ h6(#has_many-collection-find). <tt><em>collection</em>.find(...)</tt>
The <tt><em>collection</em>.find</tt> method finds objects within the collection. It uses the same syntax and options as +ActiveRecord::Base.find+.
<ruby>
-@open_orders = @customer.orders.where(:open => 1)
+@open_orders = @customer.orders.find(1)
</ruby>
h6(#has_many-collection-where). <tt><em>collection</em>.where(...)</tt>
@@ -1177,6 +1264,7 @@ The +has_many+ association supports these options:
* +:foreign_key+
* +:group+
* +:include+
+* +:inverse_of+
* +:limit+
* +:offset+
* +:order+
@@ -1242,11 +1330,14 @@ h6(#has_many-counter_sql). +:counter_sql+
Normally Rails automatically generates the proper SQL to count the association members. With the +:counter_sql+ option, you can specify a complete SQL statement to count them yourself.
-NOTE: If you specify +:finder_sql+ but not +:counter_sql+, then the counter SQL will be generated by substituting +SELECT COUNT(*) FROM+ for the +SELECT ... FROM+ clause of your +:finder_sql+ statement.
+NOTE: If you specify +:finder_sql+ but not +:counter_sql+, then the counter SQL will be generated by substituting the +SELECT ... FROM+ clause of your +:finder_sql+ statement by +SELECT COUNT(*) FROM+.
h6(#has_many-dependent). +:dependent+
If you set the +:dependent+ option to +:destroy+, then deleting this object will call the +destroy+ method on the associated objects to delete those objects. If you set the +:dependent+ option to +:delete_all+, then deleting this object will delete the associated objects _without_ calling their +destroy+ method. If you set the +:dependent+ option to +:nullify+, then deleting this object will set the foreign key in the associated objects to +NULL+.
+If you set the +:dependent+ option to +:restrict+, then the deletion of the object is restricted if a dependent associated object exist and a +DeleteRestrictionError+ exception is raised.
+
+NOTE: The default behavior for +:dependent => :restrict+ is to raise a +DeleteRestrictionError+ when associated objects exist. Since Rails 4.0 this behavior is being deprecated in favor of adding an error to the base model. To silence the warning in Rails 4.0, you should fix your code to not expect this Exception and add +config.active_record.dependent_restrict_raises = false+ to your application config.
NOTE: This option is ignored when you use the +:through+ option on the association.
@@ -1316,6 +1407,20 @@ class LineItem < ActiveRecord::Base
end
</ruby>
+h6(#has_many-inverse_of). +:inverse_of+
+
+The +:inverse_of+ option specifies the name of the +belongs_to+ association that is the inverse of this association. Does not work in combination with the +:through+ or +:as+ options.
+
+<ruby>
+class Customer < ActiveRecord::Base
+ has_many :orders, :inverse_of => :customer
+end
+
+class Order < ActiveRecord::Base
+ belongs_to :customer, :inverse_of => :orders
+end
+</ruby>
+
h6(#has_many-limit). +:limit+
The +:limit+ option lets you restrict the total number of objects that will be fetched through an association.
@@ -1545,12 +1650,9 @@ h6(#has_and_belongs_to_many-collection-find). <tt><em>collection</em>.find(...)<
The <tt><em>collection</em>.find</tt> method finds objects within the collection. It uses the same syntax and options as +ActiveRecord::Base.find+. It also adds the additional condition that the object must be in the collection.
<ruby>
-@new_assemblies = @part.assemblies.all(
- :conditions => ["created_at > ?", 2.days.ago])
+@assembly = @part.assemblies.find(1)
</ruby>
-NOTE: Beginning with Rails 3, supplying options to the +ActiveRecord::Base.find+ method is discouraged. Use <tt><em>collection</em>.where</tt> instead when you need to pass conditions.
-
h6(#has_and_belongs_to_many-collection-where). <tt><em>collection</em>.where(...)</tt>
The <tt><em>collection</em>.where</tt> method finds objects within the collection based on the conditions supplied but the objects are loaded lazily meaning that the database is queried only when the object(s) are accessed. It also adds the additional condition that the object must be in the collection.
@@ -1669,7 +1771,7 @@ h6(#has_and_belongs_to_many-counter_sql). +:counter_sql+
Normally Rails automatically generates the proper SQL to count the association members. With the +:counter_sql+ option, you can specify a complete SQL statement to count them yourself.
-NOTE: If you specify +:finder_sql+ but not +:counter_sql+, then the counter SQL will be generated by substituting +SELECT COUNT(*) FROM+ for the +SELECT ... FROM+ clause of your +:finder_sql+ statement.
+NOTE: If you specify +:finder_sql+ but not +:counter_sql+, then the counter SQL will be generated by substituting the +SELECT ... FROM+ clause of your +:finder_sql+ statement by +SELECT COUNT(*) FROM+.
h6(#has_and_belongs_to_many-delete_sql). +:delete_sql+
diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile
index fe4a84dae9..8ae8c61ae6 100644
--- a/railties/guides/source/command_line.textile
+++ b/railties/guides/source/command_line.textile
@@ -521,7 +521,9 @@ development:
adapter: postgresql
encoding: unicode
database: gitapp_development
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
username: gitapp
password:
...
diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile
index 3153cac2f9..e796f44606 100644
--- a/railties/guides/source/configuring.textile
+++ b/railties/guides/source/configuring.textile
@@ -122,6 +122,17 @@ WARNING: Threadsafe operation is incompatible with the normal workings of develo
* +config.whiny_nils+ enables or disables warnings when a certain set of methods are invoked on +nil+ and it does not respond to them. Defaults to true in development and test environments.
+* +config.console+ allows you to set class that will be used as console you run +rails console+. It's best to run it in +console+ block:
+
+<ruby>
+console do
+ # this block is called only when running console,
+ # so we can safely require pry here
+ require "pry"
+ config.console = Pry
+end
+</ruby>
+
h4. Configuring Assets
Rails 3.1, by default, is set up to use the +sprockets+ gem to manage assets within an application. This gem concatenates and compresses assets in order to make serving them much less painful.
@@ -152,6 +163,7 @@ Rails 3.1, by default, is set up to use the +sprockets+ gem to manage assets wit
* +config.assets.compile+ is a boolean that can be used to turn on live Sprockets compilation in production.
+* +config.assets.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby +Logger+ class. Defaults to the same configured at +config.logger+. Setting +config.assets.logger+ to false will turn off served assets logging.
h4. Configuring Generators
@@ -202,7 +214,7 @@ Every Rails application comes with a standard set of middleware which it uses in
* +ActionDispatch::Session::CookieStore+ is responsible for storing the session in cookies. An alternate middleware can be used for this by changing the +config.action_controller.session_store+ to an alternate value. Additionally, options passed to this can be configured by using +config.action_controller.session_options+.
* +ActionDispatch::Flash+ sets up the +flash+ keys. Only available if +config.action_controller.session_store+ is set to a value.
* +ActionDispatch::ParamsParser+ parses out parameters from the request into +params+.
-* +Rack::MethodOverride+ allows the method to be overridden if +params[:_method]+ is set. This is the middleware which supports the PUT and DELETE HTTP method types.
+* +Rack::MethodOverride+ allows the method to be overridden if +params[:_method]+ is set. This is the middleware which supports the PATCH, PUT, and DELETE HTTP method types.
* +ActionDispatch::Head+ converts HEAD requests to GET requests and serves them as so.
* +ActionDispatch::BestStandardsSupport+ enables "best standards support" so that IE8 renders some elements correctly.
@@ -272,6 +284,8 @@ h4. Configuring Active Record
* +config.active_record.auto_explain_threshold_in_seconds+ configures the threshold for automatic EXPLAINs (+nil+ disables this feature). Queries exceeding the threshold get their query plan logged. Default is 0.5 in development mode.
+* +config.active_record.dependent_restrict_raises+ will control the behavior when an object with a <tt>:dependent => :restrict</tt> association is deleted. Setting this to false will prevent +DeleteRestrictionError+ from being raised and instead will add an error on the model object. Defaults to false in the development mode.
+
The MySQL adapter adds one additional configuration option:
* +ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans+ controls whether Active Record will consider all +tinyint(1)+ columns in a MySQL database to be booleans and is true by default.
@@ -332,7 +346,8 @@ h4. Configuring Action Dispatch
h4. Configuring Action View
-There are only a few configuration options for Action View, starting with four on +ActionView::Base+:
+There are only a few configuration options for Action View, starting with six on +ActionView::Base+:
+
* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is
@@ -342,7 +357,7 @@ Proc.new { |html_tag, instance| %Q(<div class="field_with_errors">#{html_tag}</d
* +config.action_view.default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+.
-* +config.action_view.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Mailer. Set to +nil+ to disable logging.
+* +config.action_view.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action View. Set to +nil+ to disable logging.
* +config.action_view.erb_trim_mode+ gives the trim mode to be used by ERB. It defaults to +'-'+. See the "ERB documentation":http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/ for more information.
@@ -533,7 +548,7 @@ Serves as a placeholder so that +:load_environment_config+ can be defined to run
*+initialize_logger+* Initializes the logger (an +ActiveSupport::BufferedLogger+ object) for the application and makes it accessible at +Rails.logger+, provided that no initializer inserted before this point has defined +Rails.logger+.
-*+initialize_cache+* If +RAILS_CACHE+ isn't set yet, initializes the cache by referencing the value in +config.cache_store+ and stores the outcome as +RAILS_CACHE+. If this object responds to the +middleware+ method, its middleware is inserted before +Rack::Runtime+ in the middleware stack.
+*+initialize_cache+* If +Rails.cache+ isn't set yet, initializes the cache by referencing the value in +config.cache_store+ and stores the outcome as +Rails.cache+. If this object responds to the +middleware+ method, its middleware is inserted before +Rack::Runtime+ in the middleware stack.
*+set_clear_dependencies_hook+* Provides a hook for +active_record.set_dispatch_hooks+ to use, which will run before this initializer. This initializer -- which runs only if +cache_classes+ is set to +false+ -- uses +ActionDispatch::Callbacks.after+ to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request.
@@ -571,7 +586,7 @@ The error occurred while evaluating nil.each
*+action_controller.logger+* Sets +ActionController::Base.logger+ -- if it's not already set -- to +Rails.logger+.
-*+action_controller.initialize_framework_caches+* Sets +ActionController::Base.cache_store+ -- if it's not already set -- to +RAILS_CACHE+.
+*+action_controller.initialize_framework_caches+* Sets +ActionController::Base.cache_store+ -- if it's not already set -- to +Rails.cache+.
*+action_controller.set_configs+* Sets up Action Controller by using the settings in +config.action_controller+ by +send+'ing the method names as setters to +ActionController::Base+ and passing the values through.
@@ -634,3 +649,23 @@ The error occurred while evaluating nil.each
*+set_routes_reloader+* Configures Action Dispatch to reload the routes file using +ActionDispatch::Callbacks.to_prepare+.
*+disable_dependency_loading+* Disables the automatic dependency loading if the +config.cache_classes+ is set to true and +config.dependency_loading+ is set to false.
+
+h3. Database pooling
+
+Active Record database connections are managed by +ActiveRecord::ConnectionAdapters::ConnectionPool+ which ensures that a connection pool synchronizes the amount of thread access to a limited number of database connections. This limit defaults to 1 and can be configured in +database.yml+.
+
+<ruby>
+development:
+ adapter: sqlite3
+ database: db/development.sqlite3
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
+ timeout: 5000
+</ruby>
+
+Since the connection pooling is handled inside of Active Record by default, all application servers (Thin, Mongrel, Unicorn etc.) should behave the same. Initially, the database connection pool is empty and it will create additional connections as the demand for them increases, until it reaches the connection pool limit.
+
+Any one request will check out a connection the first time it requires access to the database, after which it will check the connection back in, at the end of the request, meaning that the additional connection slot will be available again for the next request in the queue.
+
+NOTE. If you have enabled +Rails.threadsafe!+ mode then there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections.
diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile
index 92cb0774de..aac5e13978 100644
--- a/railties/guides/source/contributing_to_ruby_on_rails.textile
+++ b/railties/guides/source/contributing_to_ruby_on_rails.textile
@@ -271,6 +271,8 @@ When working with documentation, please take into account the "API Documentation
NOTE: As explained earlier, ordinary code patches should have proper documentation coverage. docrails is only used for isolated documentation improvements.
+NOTE: To help our CI servers you can add [ci skip] tag to your documentation commit message to skip build on that commit. Please remember to use it for commits containing only documentation changes.
+
WARNING: docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. Also, CHANGELOGs should never be edited in docrails.
h3. Contributing to the Rails Code
@@ -307,7 +309,7 @@ Rails follows a simple set of coding style conventions.
* Two spaces, no tabs.
* No trailing whitespace. Blank lines should not have any space.
-* Indent after private/protected.
+* Outdent private/protected from method definitions. Same indentation as the class/module.
* Prefer +&amp;&amp;+/+||+ over +and+/+or+.
* Prefer class << self block over self.method for class methods.
* +MyClass.my_method(my_arg)+ not +my_method( my_arg )+ or +my_method my_arg+.
@@ -380,9 +382,9 @@ Now you need to get other people to look at your patch, just as you've looked at
h4. Iterate as Necessary
-It’s entirely possible that the feedback you get will suggest changes. Don’t get discouraged: the whole point of contributing to an active open source project is to tap into community knowledge. If people are encouraging you to tweak your code, then it’s worth making the tweaks and resubmitting. If the feedback is that your code doesn’t belong in the core, you might still think about releasing it as a plugin.
+It’s entirely possible that the feedback you get will suggest changes. Don’t get discouraged: the whole point of contributing to an active open source project is to tap into community knowledge. If people are encouraging you to tweak your code, then it’s worth making the tweaks and resubmitting. If the feedback is that your code doesn’t belong in the core, you might still think about releasing it as a gem.
-And then...think about your next contribution!
+And then ... think about your next contribution!
h3. Rails Contributors
diff --git a/railties/guides/source/documents.yaml b/railties/guides/source/documents.yaml
index 58713a08a8..08aafda288 100644
--- a/railties/guides/source/documents.yaml
+++ b/railties/guides/source/documents.yaml
@@ -136,6 +136,15 @@
name: Release Notes
documents:
-
+ name: Upgrading Ruby on Rails
+ url: upgrading_ruby_on_rails.html
+ work_in_progress: true
+ description: This guide helps in upgrading applications to latest Ruby on Rails versions.
+ -
+ name: Ruby on Rails 3.2 Release Notes
+ url: 3_2_release_notes.html
+ description: Release notes for Rails 3.2.
+ -
name: Ruby on Rails 3.1 Release Notes
url: 3_1_release_notes.html
description: Release notes for Rails 3.1.
diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile
index 694b36bea1..e6058e7581 100644
--- a/railties/guides/source/engines.textile
+++ b/railties/guides/source/engines.textile
@@ -12,33 +12,43 @@ endprologue.
h3. What are engines?
-Engines can be considered miniature applications that provide functionality to their host applications. A Rails application is actually just a "supercharged" engine, with the +Rails::Application+ class inheriting from +Rails::Engine+. Therefore, engines and applications share common functionality but are at the same time two separate beasts. Engines and applications also share a common structure, as you'll see throughout this guide.
+Engines can be considered miniature applications that provide functionality to their host applications. A Rails application is actually just a "supercharged" engine, with the +Rails::Application+ class inheriting a lot of its behaviour from +Rails::Engine+.
-Engines are also closely related to plugins where the two share a common +lib+ directory structure and are both generated using the +rails plugin new+ generator.
+Therefore, engines and applications can be thought of almost the same thing, just with very minor differences, as you'll see throughout this guide. Engines and applications also share a common structure.
-The engine that will be generated for this guide will be called "blorgh". The engine will provide blogging functionality to its host applications, allowing for new posts and comments to be created. For now, you will be working solely within the engine itself and in later sections you'll see how to hook it into an application.
+Engines are also closely related to plugins where the two share a common +lib+ directory structure and are both generated using the +rails plugin new+ generator. The difference being that an engine is considered a "full plugin" by Rails -- as indicated by the +--full+ option that's passed to the generator command -- but this guide will refer to them simply as "engines" throughout. An engine *can* be a plugin, and a plugin *can* be an engine.
+
+The engine that will be created in this guide will be called "blorgh". The engine will provide blogging functionality to its host applications, allowing for new posts and comments to be created. At the beginning of this guide, you will be working solely within the engine itself, but in later sections you'll see how to hook it into an application.
Engines can also be isolated from their host applications. This means that an application is able to have a path provided by a routing helper such as +posts_path+ and use an engine also that provides a path also called +posts_path+, and the two would not clash. Along with this, controllers, models and table names are also namespaced. You'll see how to do this later in this guide.
-To see demonstrations of other engines, check out "Devise":https://github.com/plataformatec/devise, an engine that provides authentication for its parent applications, or "Forem":https://github.com/radar/forem, an engine that provides forum functionality.
+It's important to keep in mind at all times that the application should *always* take precedence over its engines. An application is the object that has final say in what goes on in the universe (with the universe being the application's environment) where the engine should only be enhancing it, rather than changing it drastically.
+
+To see demonstrations of other engines, check out "Devise":https://github.com/plataformatec/devise, an engine that provides authentication for its parent applications, or "Forem":https://github.com/radar/forem, an engine that provides forum functionality. There's also "Spree":https://github.com/spree/spree which provides an e-commerce platform, and "RefineryCMS":https://github.com/resolve/refinerycms, a CMS engine.
Finally, engines would not have be possible without the work of James Adam, Piotr Sarnacki, the Rails Core Team, and a number of other people. If you ever meet them, don't forget to say thanks!
h3. Generating an engine
-To generate an engine with Rails 3.1, you will need to run the plugin generator and pass it the +--mountable+ option. To generate the beginnings of the "blorgh" engine you will need to run this command in a terminal:
+To generate an engine with Rails 3.1, you will need to run the plugin generator and pass it the +--full+ and +--mountable+ options. To generate the beginnings of the "blorgh" engine you will need to run this command in a terminal:
<shell>
-$ rails plugin new blorgh --mountable
+$ rails plugin new blorgh --full --mountable
</shell>
-The +--mountable+ option tells the plugin generator that you want to create an engine (which is a mountable plugin, hence the option name), creating the basic directory structure of an engine by providing things such as the foundations of an +app+ folder, as well a +config/routes.rb+ file. This generator also provides a file at +lib/blorgh/engine.rb+ which is identical in function to an application's +config/application.rb+ file.
+The +--full+ option tells the plugin generator that you want to create an engine (which is a mountable plugin, hence the option name), creating the basic directory structure of an engine by providing things such as the foundations of an +app+ folder, as well a +config/routes.rb+ file. This generator also provides a file at +lib/blorgh/engine.rb+ which is identical in function to an application's +config/application.rb+ file.
+
+The +--mountable+ option tells the generator to mount the engine inside the dummy testing application located at +test/dummy+ inside the engine. It does this by placing this line in to the dummy application's +config/routes.rb+ file, located at +test/dummy/config/routes.rb+ inside the engine:
+
+<ruby>
+mount Blorgh::Engine, :at => "blorgh"
+</ruby>
h4. Inside an engine
h5. Critical files
-At the root of the engine's directory, lives a +blorgh.gemspec+ file. When you include the engine into the application later on, you will do so with this line in a Rails application's +Gemfile+:
+At the root of this brand new engine's directory, lives a +blorgh.gemspec+ file. When you include the engine into the application later on, you will do so with this line in a Rails application's +Gemfile+:
<ruby>
gem 'blorgh', :path => "vendor/engines/blorgh"
@@ -53,6 +63,8 @@ module Blorgh
end
</ruby>
+TIP: Some engines choose to use this file to put global configuration options for their engine. It's a relatively good idea, and so if you're wanting offer configuration options, the file where your engine's +module+ is defined is perfect for that. Place the methods inside the module and you'll be good to go.
+
Within +lib/blorgh/engine.rb+ is the base class for the engine:
<ruby>
@@ -63,23 +75,39 @@ module Blorgh
end
</ruby>
-By inheriting from the +Rails::Engine+ class, this engine gains all the functionality it needs, such as being able to serve requests to its controllers.
+By inheriting from the +Rails::Engine+ class, this gem notifies Rails that there's an engine at the specified path, and will correctly mount the engine inside the application, performing tasks such as adding the +app+ directory of the engine to the load path for models, mailers, controllers and views.
+
+The +isolate_namespace+ method here deserves special notice. This call is responsible for isolating the controllers, models, routes and other things into their own namespace, away from similar components inside hte application. Without this, there is a possibility that the engine's components could "leak" into the application, causing unwanted disruption, or that important engine components could be overriden by similarly named things within the application.
+
+NOTE: It is *highly* recommended that the +isolate_namespace+ line be left within the +Engine+ class definition. Without it, classes generated in an engine *may* conflict with an application.
+
+What this isolation of the namespace means is that a model generated by a call to +rails g model+ such as +rails g model post+ wouldn't be called +Post+, but instead be namespaced and called +Blorgh::Post+. In addition to this, the table for the model is namespaced, becoming +blorgh_posts+, rather than simply +posts+. Similar to the model namespacing, a controller called +PostsController+ would be +Blorgh::Postscontroller+ and the views for that controller would not be at +app/views/posts+, but rather +app/views/blorgh/posts+. Mailers would be namespaced as well.
-The +isolate_namespace+ method here deserves special notice. This call is responsible for isolating the controllers, models, routes and other things into their own namespace. Without this, there is a possibility that the engine's components could "leak" into the application, causing unwanted disruption. It is recommended that this line be left within this file.
+Finally, routes will also be isolated within the engine. This is one of the most important parts about namespacing, and is discussed later in the "Routes":#routes section of this guide.
h5. +app+ directory
-Inside the +app+ directory there lives the standard +assets+, +controllers+, +helpers+, +mailers+, +models+ and +views+ directories that you should be familiar with from an application. The +helpers+, +mailers+ and +models+ directories are empty and so aren't described in this section. We'll look more into models in a future section.
+Inside the +app+ directory there is the standard +assets+, +controllers+, +helpers+, +mailers+, +models+ and +views+ directories that you should be familiar with from an application. The +helpers+, +mailers+ and +models+ directories are empty and so aren't described in this section. We'll look more into models in a future section, when we're writing the engine.
Within the +app/assets+ directory, there is the +images+, +javascripts+ and +stylesheets+ directories which, again, you should be familiar with due to their similarities of an application. One difference here however is that each directory contains a sub-directory with the engine name. Because this engine is going to be namespaced, its assets should be too.
Within the +app/controllers+ directory there is a +blorgh+ directory and inside that a file called +application_controller.rb+. This file will provide any common functionality for the controllers of the engine. The +blorgh+ directory is where the other controllers for the engine will go. By placing them within this namespaced directory, you prevent them from possibly clashing with identically-named controllers within other engines or even within the application.
+NOTE: The +ApplicationController+ class is called as such inside an engine -- rather than +EngineController+ -- mainly due to that if you consider that an engine is really just a mini-application, it makes sense. You should also be able to convert an application to an engine with relatively little pain, and this is just one of the ways to make that process easier, albeit however so slightly.
+
Lastly, the +app/views+ directory contains a +layouts+ folder which contains file at +blorgh/application.html.erb+ which allows you to specify a layout for the engine. If this engine is to be used as a stand-alone engine, then you would add any customization to its layout in this file, rather than the applications +app/views/layouts/application.html.erb+ file.
+If you don't want to force a layout on to users of the engine, then you can delete this file and reference a different layout in the controllers of your engine.
+
h5. +script+ directory
-This directory contains one file, +script/rails+, which allows you to use the +rails+ sub-commands and generators just like you would within an application. This means that you will very easily be able to generate new controllers and models for this engine.
+This directory contains one file, +script/rails+, which enables you to use the +rails+ sub-commands and generators just like you would within an application. This means that you will very easily be able to generate new controllers and models for this engine by running commands like this:
+
+<shell>
+rails g model
+</shell>
+
+Keeping in mind, of course, that anything generated with these commands inside an engine that has +isolate_namespace+ inside the Engine class will be namespaced.
h5. +test+ directory
@@ -92,9 +120,9 @@ Rails.application.routes.draw do
end
</ruby>
-This line mounts the engine at the path of +/blorgh+, which will make it accessible through the application only at that path. We will look more into mounting an engine after some features have been developed.
+This line mounts the engine at the path of +/blorgh+, which will make it accessible through the application only at that path.
-Also in the test directory is the +test/integration+ directory, where integration tests for the engine should be placed.
+Also in the test directory is the +test/integration+ directory, where integration tests for the engine should be placed. Other directories can be created in the +test+ directory also. For example, you may wish to create a +test/unit+ directory for your unit tests.
h3. Providing engine functionality
@@ -142,11 +170,11 @@ invoke css
create app/assets/stylesheets/scaffold.css
</shell>
-The first thing that the scaffold generator does is invoke the +active_record+ generator, which generates a migration and a model for the resource. Note here, however, that the migration is called +create_blorgh_posts+ rather than the usual +create_posts+. This is due to the +isolate_namespace+ method called in the +Blorgh::Engine+ class's definition. The model here is also namespaced, being placed at +app/models/blorgh/post.rb+ rather than +app/models/post.rb+.
+The first thing that the scaffold generator does is invoke the +active_record+ generator, which generates a migration and a model for the resource. Note here, however, that the migration is called +create_blorgh_posts+ rather than the usual +create_posts+. This is due to the +isolate_namespace+ method called in the +Blorgh::Engine+ class's definition. The model here is also namespaced, being placed at +app/models/blorgh/post.rb+ rather than +app/models/post.rb+ due to the +isolate_namespace+ call within the +Engine+ class.
Next, the +test_unit+ generator is invoked for this model, generating a unit test at +test/unit/blorgh/post_test.rb+ (rather than +test/unit/post_test.rb+) and a fixture at +test/fixtures/blorgh/posts.yml+ (rather than +test/fixtures/posts.yml+).
-After that, a line for the resource is inserted into the +config/routes.rb+ file for the engine. This line is simply +resources :posts+, turning the +config/routes.rb+ file into this:
+After that, a line for the resource is inserted into the +config/routes.rb+ file for the engine. This line is simply +resources :posts+, turning the +config/routes.rb+ file for the engine into this:
<ruby>
Blorgh::Engine.routes.draw do
@@ -155,11 +183,11 @@ Blorgh::Engine.routes.draw do
end
</ruby>
-Note here that the routes are drawn upon the +Blorgh::Engine+ object rather than the +YourApp::Application+ class. This is so that the engine routes are confined to the engine itself and can be mounted at a specific point as shown in the "test directory":#test-directory section.
+Note here that the routes are drawn upon the +Blorgh::Engine+ object rather than the +YourApp::Application+ class. This is so that the engine routes are confined to the engine itself and can be mounted at a specific point as shown in the "test directory":#test-directory section. This is also what causes the engine's routes to be isolated from those routes that are within the application. This is discussed further in the "Routes":#routes section of this guide.
Next, the +scaffold_controller+ generator is invoked, generating a controlled called +Blorgh::PostsController+ (at +app/controllers/blorgh/posts_controller.rb+) and its related views at +app/views/blorgh/posts+. This generator also generates a functional test for the controller (+test/functional/blorgh/posts_controller_test.rb+) and a helper (+app/helpers/blorgh/posts_controller.rb+).
-Everything this generator has generated is neatly namespaced. The controller's class is defined within the +Blorgh+ module:
+Everything this generator has created is neatly namespaced. The controller's class is defined within the +Blorgh+ module:
<ruby>
module Blorgh
@@ -171,7 +199,7 @@ end
NOTE: The +ApplicationController+ class being inherited from here is the +Blorgh::ApplicationController+, not an application's +ApplicationController+.
-The helper is also namespaced:
+The helper inside +app/helpers/blorgh/posts_helper.rb+ is also namespaced:
<ruby>
module Blorgh
@@ -210,7 +238,7 @@ One final thing is that the +posts+ resource for this engine should be the root
root :to => "posts#index"
</ruby>
-Now people will only need to go to the root of the engine to see all the posts, rather than visiting +/posts+.
+Now people will only need to go to the root of the engine to see all the posts, rather than visiting +/posts+. This means that instead of +http://localhost:3000/blorgh/posts+, you only need to go to +http://localhost:3000/blorgh+ now.
h4. Generating a comments resource
@@ -258,7 +286,7 @@ module Blorgh
end
</ruby>
-Because the +has_many+ is defined inside a class that is inside the +Blorgh+ module, Rails will know that you want to use the +Blorgh::Comment+ model for these objects.
+NOTE: Because the +has_many+ is defined inside a class that is inside the +Blorgh+ module, Rails will know that you want to use the +Blorgh::Comment+ model for these objects, so there's no need to specify that using the +:class_name+ option here.
Next, there needs to be a form so that comments can be created on a post. To add this, put this line underneath the call to +render @post.comments+ in +app/views/blorgh/posts/show.html.erb+:
@@ -279,7 +307,7 @@ Next, the partial that this line will render needs to exist. Create a new direct
<% end %>
</erb>
-This form, when submitted, is going to attempt to post to a route of +posts/:post_id/comments+ within the engine. This route doesn't exist at the moment, but can be created by changing the +resources :posts+ line inside +config/routes.rb+ into these lines:
+When this form is submitted, it is going to attempt to perform a +POST+ request to a route of +/posts/:post_id/comments+ within the engine. This route doesn't exist at the moment, but can be created by changing the +resources :posts+ line inside +config/routes.rb+ into these lines:
<ruby>
resources :posts do
@@ -287,7 +315,9 @@ resources :posts do
end
</ruby>
-The route now will exist, but the controller that this route goes to does not. To create it, run this command:
+This creates a nested route for the comments, which is what the form requires.
+
+The route now exists, but the controller that this route goes to does not. To create it, run this command:
<shell>
$ rails g controller comments
@@ -378,10 +408,10 @@ As described earlier, by placing the gem in the +Gemfile+ it will be loaded when
To make the engine's functionality accessible from within an application, it needs to be mounted in that application's +config/routes.rb+ file:
<ruby>
-mount Blorgh::Engine, :at => "blog"
+mount Blorgh::Engine, :at => "/blog"
</ruby>
-This line will mount the engine at +blog+ in the application. Making it accessible at +http://localhost:3000/blog+ when the application runs with +rails s+.
+This line will mount the engine at +/blog+ in the application. Making it accessible at +http://localhost:3000/blog+ when the application runs with +rails server+.
NOTE: Other engines, such as Devise, handle this a little differently by making you specify custom helpers such as +devise_for+ in the routes. These helpers do exactly the same thing, mounting pieces of the engines's functionality at a pre-defined path which may be customizable.
@@ -393,6 +423,12 @@ The engine contains migrations for the +blorgh_posts+ and +blorgh_comments+ tabl
$ rake blorgh:install:migrations
</shell>
+If you have multiple engines that need migrations copied over, use +railties:install:migrations+ instead:
+
+<shell>
+$ rake railties:install:migrations
+</shell>
+
This command, when run for the first time will copy over all the migrations from the engine. When run the next time, it will only copy over migrations that haven't been copied over already. The first run for this command will output something such as this:
<shell>
@@ -410,7 +446,7 @@ When an engine is created, it may want to use specific classes from an applicati
Usually, an application would have a +User+ class that would provide the objects that would represent the posts' and comments' authors, but there could be a case where the application calls this class something different, such as +Person+. It's because of this reason that the engine should not hardcode the associations to be exactly for a +User+ class, but should allow for some flexibility around what the class is called.
-To keep it simple in this case, the application will have a class called +User+ which will represent the users of the application. It can be generated using this command:
+To keep it simple in this case, the application will have a class called +User+ which will represent the users of the application. It can be generated using this command inside the application:
<shell>
rails g model user name:string
@@ -418,7 +454,7 @@ rails g model user name:string
The +rake db:migrate+ command needs to be run here to ensure that our application has the +users+ table for future use.
-Also to keep it simple, the posts form will have a new text field called +author_name_+ where users can elect to put their name. The engine will then take this name and create a new +User+ object from it or find one that already has that name, and then associate the post with it.
+Also, to keep it simple, the posts form will have a new text field called +author_name+ where users can elect to put their name. The engine will then take this name and create a new +User+ object from it or find one that already has that name, and then associate the post with it.
First, the +author_name+ text field needs to be added to the +app/views/blorgh/posts/_form.html.erb+ partial inside the engine. This can be added above the +title+ field with this code:
@@ -486,8 +522,6 @@ Finally, the author's name should be displayed on the post's page. Add this code
</p>
</erb>
-WARNING: For posts created previously, this will break the +show+ page for them. We recommend deleting these posts and starting again, or manually assigning an author using +rails c+.
-
By outputting +@post.author+ using the +<%=+ tag the +to_s+ method will be called on the object. By default, this will look quite ugly:
<text>
@@ -532,7 +566,23 @@ The +set_author+ method also located in this class should also use this class:
self.author = Blorgh.user_class.constantize.find_or_create_by_name(author_name)
</ruby>
-To set this configuration setting within the application, an initializer should be used. By using an initializer, the configuration will be set up before the application starts and makes references to the classes of the engine which may depend on this configuration setting existing.
+To save having to call +constantize+ on the +user_class+ result all the time, you could instead just override the +user_class+ getter method inside the +Blorgh+ module in the +lib/blorgh.rb+ file to always call +constantize+ on the saved value before returning the result:
+
+<ruby>
+ def self.user_class
+ @@user_class.constantize
+ end
+</ruby>
+
+This would then turn the above code for +self.author=+ into this:
+
+<ruby>
+self.author = Blorgh.user_class.find_or_create_by_name(author_name)
+</ruby>
+
+Resulting in something a little shorter, and more implicit in its behaviour. The +user_class+ method should always return a +Class+ object.
+
+To set this configuration setting within the application, an initializer should be used. By using an initializer, the configuration will be set up before the application starts and calls the engine's models which may depend on this configuration setting existing.
Create a new initializer at +config/initializers/blorgh.rb+ inside the application where the +blorgh+ engine is installed and put this content in it:
@@ -544,23 +594,45 @@ WARNING: It's very important here to use the +String+ version of the class, rath
Go ahead and try to create a new post. You will see that it works exactly in the same way as before, except this time the engine is using the configuration setting in +config/initializers/blorgh.rb+ to learn what the class is.
-There are now no strict dependencies on what the class is, only what the class's API must be. The engine simply requires this class to define a +find_or_create_by_name+ method which returns an object of that class to be associated with a post when it's created.
+There are now no strict dependencies on what the class is, only what the class's API must be. The engine simply requires this class to define a +find_or_create_by_name+ method which returns an object of that class to be associated with a post when it's created. This object, of course, should have some sort of identifier by which it can be referenced.
h5. General engine configuration
Within an engine, there may come a time where you wish to use things such as initializers, internationalization or other configuration options. The great news is that these things are entirely possible because a Rails engine shares much the same functionality as a Rails application. In fact, a Rails application's functionality is actually a superset of what is provided by engines!
-If you wish to use initializers (code that should run before the engine is loaded), the best place for them is the +config/initializers+ folder. This directory's functionality is explained in the "Initializers section":http://guides.rubyonrails.org/configuring.html#initializers of the Configuring guide.
+If you wish to use an initializer -- code that should run before the engine is loaded -- the place for it is the +config/initializers+ folder. This directory's functionality is explained in the "Initializers section":http://guides.rubyonrails.org/configuring.html#initializers of the Configuring guide, and works precisely the same way as the +config/initializers+ directory inside an application. Same goes for if you want to use a standard initializer.
For locales, simply place the locale files in the +config/locales+ directory, just like you would in an application.
-h3. Extending engine functionality
+h3. Testing an engine
+
+When an engine is generated there is a smaller dummy application created inside it at +test/dummy+. This application is used as a mounting point for the engine to make testing the engine extremely simple. You may extend this application by generating controllers, models or views from within the directory, and then use those to test your engine.
+
+The +test+ directory should be treated like a typical Rails testing environment, allowing for unit, functional and integration tests.
+
+h4. Functional tests
+
+A matter worth taking into consideration when writing functional tests is that the tests are going to be running on an application -- the +test/dummy+ application -- rather than your engine. This is due to the setup of the testing environment; an engine needs an application as a host for testing its main functionality, especially controllers. This means that if you were to make a typical +GET+ to a controller in a controller's functional test like this:
+
+<ruby>
+get :index
+</ruby>
+
+It may not function correctly. This is because the application doesn't know how to route these requests to the engine unless you explicitly tell it *how*. To do this, you must pass the +:use_route+ option (as a parameter) on these requests also:
+
+<ruby>
+get :index, :use_route => :blorgh
+</ruby>
+
+This tells the application that you still want to perform a +GET+ request to the +index+ action of this controller, just that you want to use the engine's route to get there, rather than the application.
+
+h3. Improving engine functionality
This section looks at overriding or adding functionality to the views, controllers and models provided by an engine.
h4. Overriding views
-When Rails looks for a view to render, it will first look in the +app/views+ directory of the application. If it cannot find the view there, then it will check in the +app/views+ directories of all engines which have this directory.
+When Rails looks for a view to render, it will first look in the +app/views+ directory of the application. If it cannot find the view there, then it will check in the +app/views+ directories of all engines which have this directory.
In the +blorgh+ engine, there is a currently a file at +app/views/blorgh/posts/index.html.erb+. When the engine is asked to render the view for +Blorgh::PostsController+'s +index+ action, it will first see if it can find it at +app/views/blorgh/posts/index.html.erb+ within the application and then if it cannot it will look inside the engine.
@@ -583,36 +655,78 @@ Rather than looking like the default scaffold, the page will now look like this:
!images/engines_post_override.png(Engine scaffold overriden)!
-h4. Controllers
+h4. Routes
-TODO: Explain how to extend a controller.
-IDEA: I like Devise's +devise :controllers => { "sessions" => "sessions" }+ idea. Perhaps we could incorporate that into the guide?
+Routes inside an engine are, by default, isolated from the application. This is done by the +isolate_namespace+ call inside the +Engine+ class. This essentially means that the application and its engines can have identically named routes, and that they will not clash.
-h4. Models
+Routes inside an engine are drawn on the +Engine+ class within +config/routes.rb+, like this:
-TODO: Explain how to extend models provided by an engine.
+<ruby>
+ Blorgh::Engine.routes.draw do
+ resources :posts
+ end
+</ruby>
-h4. Routes
+By having isolated routes such as this, if you wish to link to an area of an engine from within an application, you will need to use the engine's routing proxy method. Calls to normal routing methods such as +posts_path+ may end up going to undesired locations if both the application and the engine both have such a helper defined.
-Within the application, you may wish to link to some area within the engine. Due to the fact that the engine's routes are isolated (by the +isolate_namespace+ call within the +lib/blorgh/engine.rb+ file), you will need to prefix these routes with the engine name. This means rather than having something such as:
+For instance, the following example would go to the application's +posts_path+ if that template was rendered from the application, or the engine's +posts_path+ if it was rendered from the engine:
<erb>
<%= link_to "Blog posts", posts_path %>
</erb>
-It needs to be written as:
+To make this route always use the engine's +posts_path+ routing helper method, we must call the method on the routing proxy method that shares the same name as the engine.
<erb>
<%= link_to "Blog posts", blorgh.posts_path %>
</erb>
-This allows for the engine _and_ the application to both have a +posts_path+ routing helper and to not interfere with each other. You may also reference another engine's routes from inside an engine using this same syntax.
-
If you wish to reference the application inside the engine in a similar way, use the +main_app+ helper:
<erb>
<%= link_to "Home", main_app.root_path %>
</erb>
-TODO: Mention how to use assets within an engine?
-TODO: Mention how to depend on external gems, like RedCarpet.
+If you were to use this inside an engine, it would *always* go to the application's root. If you were to leave off the +main_app+ "routing proxy" method call, it could potentially go to the engine's or application's root, depending on where it was called from.
+
+If a template is rendered from within an engine and it's attempting to use one of the application's routing helper methods, it may result in an undefined method call. If you encounter such an issue, ensure that you're not attempting to call the application's routing methods without the +main_app+ prefix from within the engine.
+
+h4. Assets
+
+Assets within an engine work in an identical way to a full application. Because the engine class inherits from +Rails::Engine+, the application will know to look up in the engine's +app/assets+ directory for potential assets.
+
+Much like all the other components of an engine, the assets should also be namespaced. This means if you have an asset called +style.css+, it should be placed at +app/assets/stylesheets/[engine name]/style.css+, rather than +app/assets/stylesheets/style.css+. If this asset wasn't namespaced, then there is a possibility that the host application could have an asset named identically, in which case the application's asset would take precedence and the engine's one would be all but ignored.
+
+Imagine that you did have an asset located at +app/assets/stylesheets/blorgh/style.css+ To include this asset inside an application, just use +stylesheet_link_tag+ and reference the asset as if it were inside the engine:
+
+<erb>
+<%= stylesheet_link_tag "blorgh/style.css" %>
+</erb>
+
+You can also specify these assets as dependencies of other assets using the Asset Pipeline require statements in processed files:
+
+<css>
+/*
+ *= require blorgh/style
+*/
+</css>
+
+For more information, read the "Asset Pipeline guide":http://guides.rubyonrails.org/asset_pipeline.html
+
+h4. Other gem dependencies
+
+Gem dependencies inside an engine should be specified inside the +.gemspec+ file that's at the root of the engine. The reason for this is because the engine may be installed as a gem. If dependencies were to be specified inside the +Gemfile+, these would not be recognised by a traditional gem install and so they would not be installed, causing the engine to malfunction.
+
+To specify a dependency that should be installed with the engine during a traditional +gem install+, specify it inside the +Gem::Specification+ block inside the +.gemspec+ file in the engine:
+
+<ruby>
+s.add_dependency "moo"
+</ruby>
+
+To specify a dependency that should only be installed as a development dependency of the application, specify it like this:
+
+<ruby>
+s.add_development_dependency "moo"
+</ruby>
+
+Both kinds of dependencies will be installed when +bundle install+ is run inside the application. The development dependencies for the gem will only be used when the tests for the engine are running.
diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile
index 64eb2d8f36..a696e4f8ae 100644
--- a/railties/guides/source/form_helpers.textile
+++ b/railties/guides/source/form_helpers.textile
@@ -150,7 +150,7 @@ NOTE: Always use labels for checkbox and radio buttons. They associate text with
h4. Other Helpers of Interest
-Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, URL fields and email fields:
+Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, URL fields and email fields:
<erb>
<%= text_area_tag(:message, "Hi, nice site", :size => "24x6") %>
@@ -158,6 +158,7 @@ Other form controls worth mentioning are textareas, password fields, hidden fiel
<%= hidden_field_tag(:parent_id, "5") %>
<%= search_field(:user, :name) %>
<%= telephone_field(:user, :phone) %>
+<%= date_field(:user, :born_on) %>
<%= url_field(:user, :homepage) %>
<%= email_field(:user, :address) %>
</erb>
@@ -170,13 +171,14 @@ Output:
<input id="parent_id" name="parent_id" type="hidden" value="5" />
<input id="user_name" name="user[name]" size="30" type="search" />
<input id="user_phone" name="user[phone]" size="30" type="tel" />
+<input id="user_born_on" name="user[born_on]" type="date" />
<input id="user_homepage" size="30" name="user[homepage]" type="url" />
<input id="user_address" size="30" name="user[address]" type="email" />
</html>
Hidden inputs are not shown to the user but instead hold data like any textual input. Values inside them can be changed with JavaScript.
-IMPORTANT: The search, telephone, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features.
+IMPORTANT: The search, telephone, date, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features.
TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the "Security Guide":security.html#logging.
@@ -290,7 +292,7 @@ form_for(@article)
## Editing an existing article
# long-style:
-form_for(@article, :url => article_path(@article), :html => { :method => "put" })
+form_for(@article, :url => article_path(@article), :html => { :method => "patch" })
# short-style:
form_for(@article)
</ruby>
@@ -318,14 +320,14 @@ form_for [:admin, :management, @article]
For more information on Rails' routing system and the associated conventions, please see the "routing guide":routing.html.
-h4. How do forms with PUT or DELETE methods work?
+h4. How do forms with PATCH, PUT, or DELETE methods work?
-The Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PUT" and "DELETE" requests (besides "GET" and "POST"). However, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms.
+The Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PATCH" and "DELETE" requests (besides "GET" and "POST"). However, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms.
Rails works around this issue by emulating other methods over POST with a hidden input named +"_method"+, which is set to reflect the desired method:
<ruby>
-form_tag(search_path, :method => "put")
+form_tag(search_path, :method => "patch")
</ruby>
output:
@@ -333,14 +335,14 @@ output:
<html>
<form accept-charset="UTF-8" action="/search" method="post">
<div style="margin:0;padding:0">
- <input name="_method" type="hidden" value="put" />
+ <input name="_method" type="hidden" value="patch" />
<input name="utf8" type="hidden" value="&#x2713;" />
<input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
</div>
...
</html>
-When parsing POSTed data, Rails will take into account the special +_method+ parameter and acts as if the HTTP method was the one specified inside it ("PUT" in this example).
+When parsing POSTed data, Rails will take into account the special +_method+ parameter and acts as if the HTTP method was the one specified inside it ("PATCH" in this example).
h3. Making Select Boxes with Ease
@@ -467,7 +469,7 @@ Rails _used_ to have a +country_select+ helper for choosing countries, but this
h3. Using Date and Time Form Helpers
-The date and time helpers differ from all the other form helpers in two important respects:
+You can choose not to use the form helpers generating HTML5 date input fields and use the alternative date and time helpers. These date and time helpers differ from all the other form helpers in two important respects:
# Dates and times are not representable by a single input element. Instead you have several, one for each component (year, month, day etc.) and so there is no single value in your +params+ hash with your date or time.
# Other helpers use the +_tag+ suffix to indicate whether a helper is a barebones helper or one that operates on model objects. With dates and times, +select_date+, +select_time+ and +select_datetime+ are the barebones helpers, +date_select+, +time_select+ and +datetime_select+ are the equivalent model object helpers.
@@ -630,10 +632,10 @@ action for a Person model, +params[:model]+ would usually be a hash of all the a
Fundamentally HTML forms don't know about any sort of structured data, all they generate is name–value pairs, where pairs are just plain strings. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses.
-TIP: You may find you can try out examples in this section faster by using the console to directly invoke Rails' parameter parser. For example,
+TIP: You may find you can try out examples in this section faster by using the console to directly invoke Racks' parameter parser. For example,
<ruby>
-ActionController::UrlEncodedPairParser.parse_query_parameters "name=fred&phone=0123456789"
+Rack::Utils.parse_query "name=fred&phone=0123456789"
# => {"name"=>"fred", "phone"=>"0123456789"}
</ruby>
@@ -754,7 +756,7 @@ produces exactly the same output as the previous example.
h3. Forms to external resources
-If you need to post some data to an external resource it is still great to build your from using rails form helpers. But sometimes you need to set an +authenticity_token+ for this resource. You can do it by passing an +:authenticity_token => 'your_external_token'+ parameter to the +form_tag+ options:
+If you need to post some data to an external resource it is still great to build your form using rails form helpers. But sometimes you need to set an +authenticity_token+ for this resource. You can do it by passing an +:authenticity_token => 'your_external_token'+ parameter to the +form_tag+ options:
<erb>
<%= form_tag 'http://farfar.away/form', :authenticity_token => 'external_token') do %>
diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile
index a36f84e9fd..d6f3c3e217 100644
--- a/railties/guides/source/getting_started.textile
+++ b/railties/guides/source/getting_started.textile
@@ -329,7 +329,9 @@ environment:
development:
adapter: sqlite3
database: db/development.sqlite3
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
timeout: 5000
</yaml>
@@ -350,7 +352,9 @@ development:
adapter: mysql2
encoding: utf8
database: blog_development
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
username: root
password:
socket: /tmp/mysql.sock
@@ -370,7 +374,9 @@ development:
adapter: postgresql
encoding: unicode
database: blog_development
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
username: blog
password:
</yaml>
@@ -447,7 +453,13 @@ start a web server on your development machine. You can do this by running:
$ rails server
</shell>
-TIP: Compiling CoffeeScript to JavaScript requires a JavaScript runtime and the absence of a runtime will give you an +execjs+ error. Usually Mac OS X and Windows come with a JavaScript runtime installed. +therubyracer+ and +therubyrhino+ are the commonly used runtimes for Ruby and JRuby respectively. You can also investigate a list of runtimes at "ExecJS":https://github.com/sstephenson/execjs.
+TIP: Compiling CoffeeScript to JavaScript requires a JavaScript runtime and
+the absence of a runtime will give you an +execjs+ error. Usually Mac OS X
+and Windows come with a JavaScript runtime installed. Rails adds the +therubyracer+ gem
+to Gemfile in a commented line for new apps and you can uncomment if you need it.
++therubyrhino+ is the recommended runtime for JRuby users and is added by default
+to Gemfile in apps generated under JRuby. You can investigate about all the
+supported runtimes at "ExecJS":https://github.com/sstephenson/execjs#readme.
This will fire up an instance of the WEBrick web server by default (Rails can
also use several other web servers). To see your application in action, open a
@@ -838,7 +850,7 @@ below:
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
</head>
-<body style="background: #EEEEEE;">
+<body style="background-color: #EEEEEE;">
<%= yield %>
@@ -847,7 +859,7 @@ below:
</erb>
Now when you refresh the +/posts+ page, you'll see a gray background to the
-page. This same gray background will be used throughout all the views for posts.
+page. This same gray background will be used throughout all the views.
h4. Creating New Posts
@@ -1008,7 +1020,7 @@ by its id value. After finding the record, Rails displays it by using
+app/views/posts/show.html.erb+:
<erb>
-<p class="notice"><%= notice %></p>
+<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
@@ -1275,7 +1287,7 @@ So first, we'll wire up the Post show template
(+/app/views/posts/show.html.erb+) to let us make a new comment:
<erb>
-<p class="notice"><%= notice %></p>
+<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
@@ -1341,7 +1353,7 @@ template. This is where we want the comment to show, so let's add that to the
+app/views/posts/show.html.erb+.
<erb>
-<p class="notice"><%= notice %></p>
+<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
@@ -1423,7 +1435,7 @@ Then you can change +app/views/posts/show.html.erb+ to look like the
following:
<erb>
-<p class="notice"><%= notice %></p>
+<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
@@ -1494,7 +1506,7 @@ create a file +app/views/comments/_form.html.erb+ containing:
Then you make the +app/views/posts/show.html.erb+ look like the following:
<erb>
-<p class="notice"><%= notice %></p>
+<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
@@ -1662,7 +1674,7 @@ right in the form where you create the post. First, create a new model to hold
the tags:
<shell>
-$ rails generate model tag name:string post:references
+$ rails generate model Tag name:string post:references
</shell>
Again, run the migration to create the database table:
@@ -1689,10 +1701,10 @@ class Post < ActiveRecord::Base
end
</ruby>
-The +:allow_destroy+ option on the nested attribute declaration tells Rails to
-display a "remove" checkbox on the view that you'll build shortly. The
-+:reject_if+ option prevents saving new tags that do not have any attributes
-filled in.
+The +:allow_destroy+ option tells Rails to enable destroying tags through the
+nested attributes (you'll handle that by displaying a "remove" checkbox on the
+view that you'll build shortly). The +:reject_if+ option prevents saving new
+tags that do not have any attributes filled in.
We will modify +views/posts/_form.html.erb+ to render a partial to make a tag:
@@ -1765,7 +1777,7 @@ Finally, we will edit the <tt>app/views/posts/show.html.erb</tt> template to
show our tags.
<erb>
-<p class="notice"><%= notice %></p>
+<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
@@ -1825,7 +1837,7 @@ Now you can edit the view in <tt>app/views/posts/show.html.erb</tt> to look like
this:
<erb>
-<p class="notice"><%= notice %></p>
+<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile
index 16ad35f345..25201888e7 100644
--- a/railties/guides/source/i18n.textile
+++ b/railties/guides/source/i18n.textile
@@ -819,13 +819,13 @@ h5. Action View Helper Methods
* The +number_to_currency+, +number_with_precision+, +number_to_percentage+, +number_with_delimiter+, and +number_to_human_size+ helpers use the number format settings located in the "number":https://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L2 scope.
-h5. Active Record Methods
+h5. Active Model Methods
* +model_name.human+ and +human_attribute_name+ use translations for model names and attribute names if available in the "activerecord.models":https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L29 scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes".
-* +ActiveRecord::Errors#generate_message+ (which is used by Active Record validations but may also be used manually) uses +model_name.human+ and +human_attribute_name+ (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes".
+* +ActiveModel::Errors#generate_message+ (which is used by Active Model validations but may also be used manually) uses +model_name.human+ and +human_attribute_name+ (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes".
-* +ActiveRecord::Errors#full_messages+ prepends the attribute name to the error message using a separator that will be looked up from "activerecord.errors.format.separator":https://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L91 (and which defaults to +'&nbsp;'+).
+* +ActiveModel::Errors#full_messages+ prepends the attribute name to the error message using a separator that will be looked up from "errors.format":https://github.com/rails/rails/blob/master/activemodel/lib/active_model/locale/en.yml#L4 (and which defaults to +"%{attribute} %{message}"+).
h5. Active Support Methods
diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb
index 84427b380c..35b6fc7014 100644
--- a/railties/guides/source/layout.html.erb
+++ b/railties/guides/source/layout.html.erb
@@ -38,7 +38,6 @@
<div id="header">
<div class="wrapper clearfix">
<h1><a href="index.html" title="Return to home page">Guides.rubyonrails.org</a></h1>
- <p class="hide"><a href="#mainCol">Skip navigation</a>.</p>
<ul class="nav">
<li><a href="index.html">Home</a></li>
<li class="index"><a href="index.html" onclick="guideMenu(); return false;" id="guidesMenu">Guides Index</a>
diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile
index 5cff2d0893..be352cfe3d 100644
--- a/railties/guides/source/layouts_and_rendering.textile
+++ b/railties/guides/source/layouts_and_rendering.textile
@@ -656,7 +656,7 @@ WARNING: The asset tag helpers do _not_ verify the existence of the assets at th
h5. Linking to Feeds with the +auto_discovery_link_tag+
-The +auto_discovery_link_tag+ helper builds HTML that most browsers and newsreaders can use to detect the presences of RSS or ATOM feeds. It takes the type of the link (+:rss+ or +:atom+), a hash of options that are passed through to url_for, and a hash of options for the tag:
+The +auto_discovery_link_tag+ helper builds HTML that most browsers and newsreaders can use to detect the presence of RSS or Atom feeds. It takes the type of the link (+:rss+ or +:atom+), a hash of options that are passed through to url_for, and a hash of options for the tag:
<erb>
<%= auto_discovery_link_tag(:rss, {:action => "feed"},
@@ -667,7 +667,7 @@ There are three tag options available for the +auto_discovery_link_tag+:
* +:rel+ specifies the +rel+ value in the link. The default value is "alternate".
* +:type+ specifies an explicit MIME type. Rails will generate an appropriate MIME type automatically.
-* +:title+ specifies the title of the link. The default value is the upshifted +:type+ value, for example, "ATOM" or "RSS".
+* +:title+ specifies the title of the link. The default value is the uppercased +:type+ value, for example, "ATOM" or "RSS".
h5. Linking to JavaScript Files with the +javascript_include_tag+
@@ -724,7 +724,7 @@ Outputting +script+ tags such as this:
These two files for jQuery, +jquery.js+ and +jquery_ujs.js+ must be placed inside +public/javascripts+ if the application doesn't use the asset pipeline. These files can be downloaded from the "jquery-rails repository on GitHub":https://github.com/indirect/jquery-rails/tree/master/vendor/assets/javascripts
-WARNING: If you are using the asset pipeline, this tag will render a +script+ tag for an asset called +defaults.js+, which would not exist in your application unless you've explicitly defined it to be.
+WARNING: If you are using the asset pipeline, this tag will render a +script+ tag for an asset called +defaults.js+, which would not exist in your application unless you've explicitly created it.
And you can in any case override the +:defaults+ expansion in <tt>config/application.rb</tt>:
@@ -1188,7 +1188,7 @@ You can also specify a second partial to be rendered between instances of the ma
h5. Spacer Templates
<erb>
-<%= render @products, :spacer_template => "product_ruler" %>
+<%= render :partial => @products, :spacer_template => "product_ruler" %>
</erb>
Rails will render the +_product_ruler+ partial (with no data passed in to it) between each pair of +_product+ partials.
diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile
index 66160f8b26..c11f8e221b 100644
--- a/railties/guides/source/migrations.textile
+++ b/railties/guides/source/migrations.textile
@@ -114,6 +114,7 @@ database independent way (you'll read about them in detail later):
* +change_column+
* +change_table+
* +create_table+
+* +create_join_table+
* +drop_table+
* +remove_column+
* +remove_index+
@@ -384,6 +385,35 @@ end
will append +ENGINE=BLACKHOLE+ to the SQL statement used to create the table
(when using MySQL, the default is +ENGINE=InnoDB+).
+h4. Creating a Join Table
+
+Migration method +create_join_table+ creates a HABTM join table. A typical use
+would be
+
+<ruby>
+create_join_table :products, :categories
+</ruby>
+
+which creates a +categories_products+ table with two columns called +category_id+ and +product_id+.
+These columns have the option +:null+ set to +false+ by default.
+
+You can pass the option +:table_name+ with you want to customize the table name. For example,
+
+<ruby>
+create_join_table :products, :categories, :table_name => :categorization
+</ruby>
+
+will create a +categorization+ table.
+
+By default, +create_join_table+ will create two columns with no options, but you can specify these
+options using the +:column_options+ option. For example,
+
+<ruby>
+create_join_table :products, :categories, :column_options => {:null => true}
+</ruby>
+
+will create the +product_id+ and +category_id+ with the +:null+ option as +true+.
+
h4. Changing Tables
A close cousin of +create_table+ is +change_table+, used for changing existing
diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile
index ccff2a1eed..07fd95c825 100644
--- a/railties/guides/source/plugins.textile
+++ b/railties/guides/source/plugins.textile
@@ -30,7 +30,7 @@ Before you continue, take a moment to decide if your new plugin will be potentia
* If your plugin is specific to your application, your new plugin will be a _vendored plugin_.
* If you think your plugin may be used across applications, build it as a _gemified plugin_.
-h4. Or generate a gemified plugin.
+h4. Generate a gemified plugin.
Writing your Rails plugin as a gem, rather than as a vendored plugin,
lets you share your plugin across different rails applications using
diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile
index 0823fb14e3..df2bd9d0c9 100644
--- a/railties/guides/source/routing.textile
+++ b/railties/guides/source/routing.textile
@@ -50,7 +50,7 @@ Resource routing allows you to quickly declare all of the common routes for a gi
h4. Resources on the Web
-Browsers request pages from Rails by making a request for a URL using a specific HTTP method, such as +GET+, +POST+, +PUT+ and +DELETE+. Each method is a request to perform an operation on the resource. A resource route maps a number of related requests to actions in a single controller.
+Browsers request pages from Rails by making a request for a URL using a specific HTTP method, such as +GET+, +POST+, +PATCH+, +PUT+ and +DELETE+. Each method is a request to perform an operation on the resource. A resource route maps a number of related requests to actions in a single controller.
When your Rails application receives an incoming request for
@@ -82,10 +82,9 @@ creates seven different routes in your application, all mapping to the +Photos+
|POST |/photos |create |create a new photo |
|GET |/photos/:id |show |display a specific photo |
|GET |/photos/:id/edit |edit |return an HTML form for editing a photo |
-|PUT |/photos/:id |update |update a specific photo |
+|PATCH/PUT |/photos/:id |update |update a specific photo |
|DELETE |/photos/:id |destroy |delete a specific photo |
-
NOTE: Rails routes are matched in the order they are specified, so if you have a +resources :photos+ above a +get 'photos/poll'+ the +show+ action's route for the +resources+ line will be matched before the +get+ line. To fix this, move the +get+ line *above* the +resources+ line so that it is matched first.
h4. Paths and URLs
@@ -138,7 +137,7 @@ creates six different routes in your application, all mapping to the +Geocoders+
|POST |/geocoder |create |create the new geocoder |
|GET |/geocoder |show |display the one and only geocoder resource |
|GET |/geocoder/edit |edit |return an HTML form for editing the geocoder |
-|PUT |/geocoder |update |update the one and only geocoder resource |
+|PATCH/PUT |/geocoder |update |update the one and only geocoder resource |
|DELETE |/geocoder |destroy |delete the geocoder resource |
NOTE: Because you might want to use the same controller for a singular route (+/account+) and a plural route (+/accounts/45+), singular resources map to plural controllers.
@@ -169,7 +168,7 @@ This will create a number of routes for each of the +posts+ and +comments+ contr
|POST |/admin/posts |create | admin_posts_path |
|GET |/admin/posts/:id |show | admin_post_path(:id) |
|GET |/admin/posts/:id/edit |edit | edit_admin_post_path(:id) |
-|PUT |/admin/posts/:id |update | admin_post_path(:id) |
+|PATCH/PUT |/admin/posts/:id |update | admin_post_path(:id) |
|DELETE |/admin/posts/:id |destroy | admin_post_path(:id) |
If you want to route +/posts+ (without the prefix +/admin+) to +Admin::PostsController+, you could use
@@ -208,7 +207,7 @@ In each of these cases, the named routes remain the same as if you did not use +
|POST |/admin/posts |create | posts_path |
|GET |/admin/posts/:id |show | post_path(:id) |
|GET |/admin/posts/:id/edit|edit | edit_post_path(:id)|
-|PUT |/admin/posts/:id |update | post_path(:id) |
+|PATCH/PUT |/admin/posts/:id |update | post_path(:id) |
|DELETE |/admin/posts/:id |destroy | post_path(:id) |
h4. Nested Resources
@@ -235,16 +234,15 @@ end
In addition to the routes for magazines, this declaration will also route ads to an +AdsController+. The ad URLs require a magazine:
-|_.HTTP Verb |_.Path |_.action |_.used for |
-|GET |/magazines/:id/ads |index |display a list of all ads for a specific magazine |
-|GET |/magazines/:id/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine |
-|POST |/magazines/:id/ads |create |create a new ad belonging to a specific magazine |
+|_.HTTP Verb |_.Path |_.action |_.used for |
+|GET |/magazines/:id/ads |index |display a list of all ads for a specific magazine |
+|GET |/magazines/:id/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine |
+|POST |/magazines/:id/ads |create |create a new ad belonging to a specific magazine |
|GET |/magazines/:id/ads/:id |show |display a specific ad belonging to a specific magazine |
|GET |/magazines/:id/ads/:id/edit |edit |return an HTML form for editing an ad belonging to a specific magazine |
-|PUT |/magazines/:id/ads/:id |update |update a specific ad belonging to a specific magazine |
+|PATCH/PUT |/magazines/:id/ads/:id |update |update a specific ad belonging to a specific magazine |
|DELETE |/magazines/:id/ads/:id |destroy |delete a specific ad belonging to a specific magazine |
-
This will also create routing helpers such as +magazine_ads_url+ and +edit_magazine_ad_path+. These helpers take an instance of Magazine as the first parameter (+magazine_ads_url(@magazine)+).
h5. Limits to Nesting
@@ -323,7 +321,7 @@ end
This will recognize +/photos/1/preview+ with GET, and route to the +preview+ action of +PhotosController+. It will also create the +preview_photo_url+ and +preview_photo_path+ helpers.
-Within the block of member routes, each route name specifies the HTTP verb that it will recognize. You can use +get+, +put+, +post+, or +delete+ here. If you don't have multiple +member+ routes, you can also pass +:on+ to a route, eliminating the block:
+Within the block of member routes, each route name specifies the HTTP verb that it will recognize. You can use +get+, +patch+, +put+, +post+, or +delete+ here. If you don't have multiple +member+ routes, you can also pass +:on+ to a route, eliminating the block:
<ruby>
resources :photos do
@@ -642,7 +640,7 @@ will recognize incoming paths beginning with +/photos+ but route to the +Images+
|POST |/photos |create | photos_path |
|GET |/photos/:id |show | photo_path(:id) |
|GET |/photos/:id/edit |edit | edit_photo_path(:id) |
-|PUT |/photos/:id |update | photo_path(:id) |
+|PATCH/PUT |/photos/:id |update | photo_path(:id) |
|DELETE |/photos/:id |destroy | photo_path(:id) |
NOTE: Use +photos_path+, +new_photo_path+, etc. to generate paths for this resource.
@@ -686,7 +684,7 @@ will recognize incoming paths beginning with +/photos+ and route the requests to
|POST |/photos |create | images_path |
|GET |/photos/:id |show | image_path(:id) |
|GET |/photos/:id/edit |edit | edit_image_path(:id) |
-|PUT |/photos/:id |update | image_path(:id) |
+|PATCH/PUT |/photos/:id |update | image_path(:id) |
|DELETE |/photos/:id |destroy | image_path(:id) |
h4. Overriding the +new+ and +edit+ Segments
@@ -790,7 +788,7 @@ Rails now creates routes to the +CategoriesController+.
|POST |/kategorien |create | categories_path |
|GET |/kategorien/:id |show | category_path(:id) |
|GET |/kategorien/:id/bearbeiten |edit | edit_category_path(:id) |
-|PUT |/kategorien/:id |update | category_path(:id) |
+|PATCH/PUT |/kategorien/:id |update | category_path(:id) |
|DELETE |/kategorien/:id |destroy | category_path(:id) |
h4. Overriding the Singular Form
diff --git a/railties/guides/source/ruby_on_rails_guides_guidelines.textile b/railties/guides/source/ruby_on_rails_guides_guidelines.textile
index 29aefd25f8..f3c8fa654d 100644
--- a/railties/guides/source/ruby_on_rails_guides_guidelines.textile
+++ b/railties/guides/source/ruby_on_rails_guides_guidelines.textile
@@ -40,7 +40,9 @@ The guides and the API should be coherent where appropriate. Please have a look
Those guidelines apply also to guides.
-h3. HTML Generation
+h3. HTML Guides
+
+h4. Generation
To generate all the guides, just +cd+ into the +railties+ directory and execute:
@@ -68,7 +70,7 @@ If you want to generate guides in languages other than English, you can keep the
bundle exec rake generate_guides GUIDES_LANGUAGE=es
</plain>
-h3. HTML Validation
+h4. Validation
Please validate the generated HTML with:
@@ -77,3 +79,13 @@ bundle exec rake validate_guides
</plain>
Particularly, titles get an ID generated from their content and this often leads to duplicates. Please set +WARNINGS=1+ when generating guides to detect them. The warning messages suggest a way to fix them.
+
+h3. Kindle Guides
+
+h4. Generation
+
+To generate guides for the Kindle, you need to provide +KINDLE=1+ as an environment variable:
+
+<plain>
+KINDLE=1 bundle exec rake generate_guides
+</plain>
diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile
index c2ef7bf9b5..b1a09c0c05 100644
--- a/railties/guides/source/security.textile
+++ b/railties/guides/source/security.textile
@@ -385,7 +385,7 @@ params[:user] # => {:name => “ow3ned”, :admin => true}
So if you create a new user using mass-assignment, it may be too easy to become an administrator.
-Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the <tt>attributes=</tt> method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3+. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example:
+Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the <tt>attributes=</tt> method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3<plus>. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example:
<ruby>
class Person < ActiveRecord::Base
diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile
index e0386b95b4..c367f532ae 100644
--- a/railties/guides/source/testing.textile
+++ b/railties/guides/source/testing.textile
@@ -114,25 +114,18 @@ Rails by default automatically loads all fixtures from the +test/fixtures+ folde
* Load the fixture data into the table
* Dump the fixture data into a variable in case you want to access it directly
-h5. Hashes with Special Powers
+h5. Fixtures are ActiveRecord objects
-Fixtures are basically Hash objects. As mentioned in point #3 above, you can access the hash object directly because it is automatically setup as a local variable of the test case. For example:
+Fixtures are instances of ActiveRecord. As mentioned in point #3 above, you can access the object directly because it is automatically setup as a local variable of the test case. For example:
<ruby>
-# this will return the Hash for the fixture named david
+# this will return the User object for the fixture named david
users(:david)
# this will return the property for david called id
users(:david).id
-</ruby>
-
-Fixtures can also transform themselves into the form of the original class. Thus, you can get at the methods only available to that class.
-
-<ruby>
-# using the find method, we grab the "real" david as a User
-david = users(:david).find
-# and now we have access to methods only available to a User class
+# one can also access methods available on the User class
email(david.girlfriend.email, david.location_tonight)
</ruby>
@@ -490,10 +483,11 @@ Now you can try running all the tests and they should pass.
h4. Available Request Types for Functional Tests
-If you're familiar with the HTTP protocol, you'll know that +get+ is a type of request. There are 5 request types supported in Rails functional tests:
+If you're familiar with the HTTP protocol, you'll know that +get+ is a type of request. There are 6 request types supported in Rails functional tests:
* +get+
* +post+
+* +patch+
* +put+
* +head+
* +delete+
@@ -645,6 +639,7 @@ In addition to the standard testing helpers, there are some additional helpers a
|+request_via_redirect(http_method, path, [parameters], [headers])+ |Allows you to make an HTTP request and follow any subsequent redirects.|
|+post_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP POST request and follow any subsequent redirects.|
|+get_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP GET request and follow any subsequent redirects.|
+|+patch_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP PATCH request and follow any subsequent redirects.|
|+put_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP PUT request and follow any subsequent redirects.|
|+delete_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP DELETE request and follow any subsequent redirects.|
|+open_session+ |Opens a new session instance.|
@@ -817,7 +812,7 @@ class PostsControllerTest < ActionController::TestCase
end
test "should update post" do
- put :update, :id => @post.id, :post => { }
+ patch :update, :id => @post.id, :post => { }
assert_redirected_to post_path(assigns(:post))
end
diff --git a/railties/guides/source/upgrading_ruby_on_rails.textile b/railties/guides/source/upgrading_ruby_on_rails.textile
new file mode 100644
index 0000000000..3588a67196
--- /dev/null
+++ b/railties/guides/source/upgrading_ruby_on_rails.textile
@@ -0,0 +1,164 @@
+h2. A Guide for Upgrading Ruby on Rails
+
+This guide provides steps to be followed when you upgrade your applications to a newer version of Ruby on Rails. These steps are also available in individual release guides.
+
+endprologue.
+
+h3. Rails Upgrades
+
+When you're upgrading an existing application, it's always a great idea to have good test coverage before going in. Rails 3 and above requires Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially and you should upgrade as early as possible. Rails 3.2.x will be the last branch to support 1.8.7 and Rails 4 (current edge) will support only Ruby 1.9.3.
+
+TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump on to 1.9.2 or 1.9.3 for smooth sailing.
+
+h3. Upgrading from Rails 3.1 to Rails 3.2
+
+We recommend that you first upgrade to Rails 3.1 in case you haven't and make sure your application still runs as expected before attempting an update to Rails 3.2.
+
+The following changes are meant for upgrading your application to Rails 3.2.1, the latest 3.2.x version of Rails.
+
+h4(#gemfile3_2). Gemfile
+
+Make the following changes to your +Gemfile+.
+
+<ruby>
+gem 'rails', '= 3.2.1'
+
+group :assets do
+ gem 'sass-rails', '~> 3.2.3'
+ gem 'coffee-rails', '~> 3.2.1'
+ gem 'uglifier', '>= 1.0.3'
+end
+</ruby>
+
+h4(#config_dev3_2). config/environments/development.rb
+
+* There are a couple of new configuration changes you'd want to add:
+
+<ruby>
+# Raise exception on mass assignment protection for Active Record models
+config.active_record.mass_assignment_sanitizer = :strict
+
+# Log the query plan for queries taking more than this (works
+# with SQLite, MySQL, and PostgreSQL)
+config.active_record.auto_explain_threshold_in_seconds = 0.5
+</ruby>
+
+h4(#config_test3_2). config/environments/test.rb
+
+The <tt>mass_assignment_sanitizer</tt> config also needs to be added in <tt>config/environments/test.rb</tt>:
+
+<ruby>
+# Raise exception on mass assignment protection for Active Record models
+config.active_record.mass_assignment_sanitizer = :strict
+</ruby>
+
+h4(#plugins3_2). vendor/plugins
+
+* Rails 3.2 deprecates <tt>vendor/plugins</tt> and Rails 4.0 will remove them completely. You can start replacing these plugins by extracting them as gems and adding them in your Gemfile. If you choose not to make them gems, you can move them into, say, <tt>lib/my_plugin/*</tt> and add an appropriate initializer in <tt>config/initializers/my_plugin.rb</tt>.
+
+h3. Upgrading from Rails 3.0 to Rails 3.1
+
+We recommend that you first upgrade to Rails 3.0 in case you haven't and make sure your application still runs as expected before attempting an update to Rails 3.1.
+
+The following changes are meant for upgrading your application to Rails 3.1.3, the latest 3.1.x version of Rails.
+
+h4(#gemfile3_1). Gemfile
+
+Make the following changes to your +Gemfile+.
+
+<ruby>
+gem 'rails', '= 3.1.3'
+gem 'mysql2'
+
+# Needed for the new asset pipeline
+group :assets do
+ gem 'sass-rails', "~> 3.1.5"
+ gem 'coffee-rails', "~> 3.1.1"
+ gem 'uglifier', ">= 1.0.3"
+end
+
+# jQuery is the default JavaScript library in Rails 3.1
+gem 'jquery-rails'
+</ruby>
+
+h4(#config_app3_1). config/application.rb
+
+* The asset pipeline requires the following additions:
+
+<ruby>
+config.assets.enabled = true
+config.assets.version = '1.0'
+</ruby>
+
+* If your application is using the "/assets" route for a resource you may want change the prefix used for assets to avoid conflicts:
+
+<ruby>
+# Defaults to '/assets'
+config.assets.prefix = '/asset-files'
+</ruby>
+
+h4(#config_dev3_1). config/environments/development.rb
+
+* Remove the RJS setting <tt>config.action_view.debug_rjs = true</tt>.
+
+* Add the following, if you enable the asset pipeline.
+
+<ruby>
+# Do not compress assets
+config.assets.compress = false
+
+# Expands the lines which load the assets
+config.assets.debug = true
+</ruby>
+
+h4(#config_prod3_1). config/environments/production.rb
+
+* Again, most of the changes below are for the asset pipeline. You can read more about these in the "Asset Pipeline":asset_pipeline.html guide.
+
+<ruby>
+# Compress JavaScripts and CSS
+config.assets.compress = true
+
+# Don't fallback to assets pipeline if a precompiled asset is missed
+config.assets.compile = false
+
+# Generate digests for assets URLs
+config.assets.digest = true
+
+# Defaults to Rails.root.join("public/assets")
+# config.assets.manifest = YOUR_PATH
+
+# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
+# config.assets.precompile += %w( search.js )
+
+# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
+# config.force_ssl = true
+</ruby>
+
+h4(#config_test3_1). config/environments/test.rb
+
+<ruby>
+# Configure static asset server for tests with Cache-Control for performance
+config.serve_static_assets = true
+config.static_cache_control = "public, max-age=3600"
+</ruby>
+
+h4(#config_wp3_1). config/initializers/wrap_parameters.rb
+
+* Add this file with the following contents, if you wish to wrap parameters into a nested hash. This is on by default in new applications.
+
+<ruby>
+# Be sure to restart your server when you modify this file.
+# This file contains settings for ActionController::ParamsWrapper which
+# is enabled by default.
+
+# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
+ActiveSupport.on_load(:action_controller) do
+ wrap_parameters :format => [:json]
+end
+
+# Disable root element in JSON by default.
+ActiveSupport.on_load(:active_record) do
+ self.include_root_in_json = false
+end
+</ruby>
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index 658756ad51..77a09507a8 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -8,6 +8,7 @@ require 'active_support/core_ext/array/extract_options'
require 'rails/application'
require 'rails/version'
+require 'rails/deprecation'
require 'active_support/railtie'
require 'action_dispatch/railtie'
@@ -77,7 +78,11 @@ module Rails
end
def cache
- RAILS_CACHE
+ @@cache ||= nil
+ end
+
+ def cache=(cache)
+ @@cache = cache
end
# Returns all rails groups for loading based on:
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 7103dad1f3..c163081bfc 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -114,11 +114,8 @@ module Rails
# Returns an array of file paths appended with a hash of directories-extensions
# suitable for ActiveSupport::FileUpdateChecker API.
def watchable_args
- files = []
- files.concat config.watchable_files
+ files, dirs = config.watchable_files.dup, config.watchable_dirs.dup
- dirs = {}
- dirs.merge! config.watchable_dirs
ActiveSupport::Dependencies.autoload_paths.each do |path|
dirs[path.to_s] = [:rb]
end
@@ -258,6 +255,9 @@ module Rails
middleware.use ::ActionDispatch::Cookies
if config.session_store
+ if config.force_ssl && !config.session_options.key?(:secure)
+ config.session_options[:secure] = true
+ end
middleware.use config.session_store, config.session_options
middleware.use ::ActionDispatch::Flash
end
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index f96a7d1772..93a0fba10b 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -30,7 +30,7 @@ module Rails
f = File.open path, 'a'
f.binmode
- f.sync = !Rails.env.production? # make sure every write flushes
+ f.sync = config.autoflush_log # if true make sure every write flushes
logger = ActiveSupport::TaggedLogging.new(
ActiveSupport::Logger.new(f)
@@ -50,11 +50,11 @@ module Rails
# Initialize cache early in the stack so railties can make use of it.
initializer :initialize_cache, :group => :all do
- unless defined?(RAILS_CACHE)
- silence_warnings { Object.const_set "RAILS_CACHE", ActiveSupport::Cache.lookup_store(config.cache_store) }
+ unless Rails.cache
+ Rails.cache = ActiveSupport::Cache.lookup_store(config.cache_store)
- if RAILS_CACHE.respond_to?(:middleware)
- config.middleware.insert_before("Rack::Runtime", RAILS_CACHE.middleware)
+ if Rails.cache.respond_to?(:middleware)
+ config.middleware.insert_before("Rack::Runtime", Rails.cache.middleware)
end
end
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 7b6cb9d8d7..1e424d9b4a 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/string/encoding'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/file_update_checker'
require 'rails/engine/configuration'
@@ -6,8 +5,8 @@ require 'rails/engine/configuration'
module Rails
class Application
class Configuration < ::Rails::Engine::Configuration
- attr_accessor :allow_concurrency, :asset_host, :asset_path, :assets,
- :cache_classes, :cache_store, :consider_all_requests_local,
+ attr_accessor :allow_concurrency, :asset_host, :asset_path, :assets, :autoflush_log,
+ :cache_classes, :cache_store, :consider_all_requests_local, :console,
:dependency_loading, :exceptions_app, :file_watcher, :filter_parameters,
:force_ssl, :helpers_paths, :logger, :log_tags, :preload_frameworks,
:railties_order, :relative_url_root, :secret_token,
@@ -41,6 +40,7 @@ module Rails
@reload_classes_only_on_change = true
@file_watcher = ActiveSupport::FileUpdateChecker
@exceptions_app = nil
+ @autoflush_log = true
@assets = ActiveSupport::OrderedOptions.new
@assets.enabled = false
@@ -57,6 +57,7 @@ module Rails
@assets.js_compressor = nil
@assets.css_compressor = nil
@assets.initialize_on_precompile = true
+ @assets.logger = nil
end
def compiled_asset_path
@@ -91,10 +92,10 @@ module Rails
# after boot, and disables reloading code on every request, as these are
# fundamentally incompatible with thread safety.
def threadsafe!
- self.preload_frameworks = true
- self.cache_classes = true
- self.dependency_loading = false
- self.allow_concurrency = true
+ @preload_frameworks = true
+ @cache_classes = true
+ @dependency_loading = false
+ @allow_concurrency = true
self
end
@@ -111,11 +112,10 @@ module Rails
end
def colorize_logging
- @colorize_logging
+ ActiveSupport::LogSubscriber.colorize_logging
end
def colorize_logging=(val)
- @colorize_logging = val
ActiveSupport::LogSubscriber.colorize_logging = val
self.generators.colorize_logging = val
end
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index b9944bed26..7da495211d 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -2,7 +2,6 @@ module Rails
class Application
module Finisher
include Initializable
- $rails_rake_task = nil
initializer :add_generator_templates do
config.generators.templates.unshift(*paths["lib/templates"].existent)
@@ -49,7 +48,7 @@ module Rails
end
initializer :eager_load! do
- if config.cache_classes && !$rails_rake_task
+ if config.cache_classes && !(defined?($rails_rake_task) && $rails_rake_task)
ActiveSupport.run_load_hooks(:before_eager_load, self)
eager_load!
end
diff --git a/railties/lib/rails/application/route_inspector.rb b/railties/lib/rails/application/route_inspector.rb
index 5ca366c5f2..2ca0c68243 100644
--- a/railties/lib/rails/application/route_inspector.rb
+++ b/railties/lib/rails/application/route_inspector.rb
@@ -51,7 +51,7 @@ module Rails
end
def internal?
- path =~ %r{/rails/info/properties|^/assets}
+ path =~ %r{/rails/info/properties|^#{Rails.application.config.assets.prefix}}
end
def engine?
diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb
index 36fd9aea19..cc26db849d 100644
--- a/railties/lib/rails/backtrace_cleaner.rb
+++ b/railties/lib/rails/backtrace_cleaner.rb
@@ -26,17 +26,4 @@ module Rails
add_filter { |line| line.sub(gems_regexp, '\2 (\3) \4') }
end
end
-
- # For installing the BacktraceCleaner in the test/unit
- module BacktraceFilterForTestUnit #:nodoc:
- def self.included(klass)
- klass.send :alias_method_chain, :filter_backtrace, :cleaning
- end
-
- def filter_backtrace_with_cleaning(backtrace, prefix=nil)
- backtrace = filter_backtrace_without_cleaning(backtrace, prefix)
- backtrace = backtrace.first.split("\n") if backtrace.size == 1
- Rails.backtrace_cleaner.clean(backtrace)
- end
- end
end
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index 435ea83ad8..03538b2422 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -37,24 +37,26 @@ class CodeStatistics #:nodoc:
next unless file_name =~ pattern
- f = File.open(directory + "/" + file_name)
comment_started = false
- while line = f.gets
- stats["lines"] += 1
- if(comment_started)
- if line =~ /^=end/
- comment_started = false
- end
- next
- else
- if line =~ /^=begin/
- comment_started = true
+
+ File.open(directory + "/" + file_name) do |f|
+ while line = f.gets
+ stats["lines"] += 1
+ if(comment_started)
+ if line =~ /^=end/
+ comment_started = false
+ end
next
+ else
+ if line =~ /^=begin/
+ comment_started = true
+ next
+ end
end
+ stats["classes"] += 1 if line =~ /^\s*class\s+[_A-Z]/
+ stats["methods"] += 1 if line =~ /^\s*def\s+[_a-z]/
+ stats["codelines"] += 1 unless line =~ /^\s*$/ || line =~ /^\s*#/
end
- stats["classes"] += 1 if line =~ /^\s*class\s+[_A-Z]/
- stats["methods"] += 1 if line =~ /^\s*def\s+[_a-z]/
- stats["codelines"] += 1 unless line =~ /^\s*$/ || line =~ /^\s*#/
end
end
diff --git a/railties/lib/rails/commands/benchmarker.rb b/railties/lib/rails/commands/benchmarker.rb
index 6c52d0f70f..b745b45e17 100644
--- a/railties/lib/rails/commands/benchmarker.rb
+++ b/railties/lib/rails/commands/benchmarker.rb
@@ -21,7 +21,7 @@ def options
options
end
-class BenchmarkerTest < ActionDispatch::PerformanceTest
+class BenchmarkerTest < ActionDispatch::PerformanceTest #:nodoc:
self.profile_options = options
ARGV.each do |expression|
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index 3acac2a6f0..86376ac7e6 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -4,47 +4,68 @@ require 'irb/completion'
module Rails
class Console
- def self.start(app)
- new(app).start
- end
+ attr_reader :options, :app, :console, :arguments
- def initialize(app)
- @app = app
+ def self.start(*args)
+ new(*args).start
end
- def start
- options = {}
-
- OptionParser.new do |opt|
- opt.banner = "Usage: console [environment] [options]"
- opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |v| options[:sandbox] = v }
- opt.on("--debugger", 'Enable ruby-debugging for the console.') { |v| options[:debugger] = v }
- opt.on('--irb', "DEPRECATED: Invoke `/your/choice/of/ruby script/rails console` instead") { |v| abort '--irb option is no longer supported. Invoke `/your/choice/of/ruby script/rails console` instead' }
- opt.parse!(ARGV)
- end
+ def initialize(app, arguments = ARGV)
+ @app = app
+ @arguments = arguments
+ app.load_console
+ @console = app.config.console || IRB
+ end
- @app.sandbox = options[:sandbox]
- @app.load_console
+ def options
+ @options ||= begin
+ options = {}
- if options[:debugger]
- begin
- require 'ruby-debug'
- puts "=> Debugger enabled"
- rescue Exception
- puts "You need to install ruby-debug19 to run the console in debugging mode. With gems, use 'gem install ruby-debug19'"
- exit
+ OptionParser.new do |opt|
+ opt.banner = "Usage: console [environment] [options]"
+ opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |v| options[:sandbox] = v }
+ opt.on("--debugger", 'Enable ruby-debugging for the console.') { |v| options[:debugger] = v }
+ opt.parse!(arguments)
end
+
+ options
end
+ end
+
+ def sandbox?
+ options[:sandbox]
+ end
- if options[:sandbox]
+ def debugger?
+ options[:debugger]
+ end
+
+ def start
+ app.sandbox = sandbox?
+
+ require_debugger if debugger?
+
+ if sandbox?
puts "Loading #{Rails.env} environment in sandbox (Rails #{Rails.version})"
puts "Any modifications you make will be rolled back on exit"
else
puts "Loading #{Rails.env} environment (Rails #{Rails.version})"
end
- IRB::ExtendCommandBundle.send :include, Rails::ConsoleMethods
- IRB.start
+ if defined?(console::ExtendCommandBundle)
+ console::ExtendCommandBundle.send :include, Rails::ConsoleMethods
+ end
+ console.start
+ end
+
+ def require_debugger
+ begin
+ require 'ruby-debug'
+ puts "=> Debugger enabled"
+ rescue Exception
+ puts "You need to install ruby-debug19 to run the console in debugging mode. With gems, use 'gem install ruby-debug19'"
+ exit
+ end
end
end
end
diff --git a/railties/lib/rails/commands/profiler.rb b/railties/lib/rails/commands/profiler.rb
index ea6347c918..3f6966b4f0 100644
--- a/railties/lib/rails/commands/profiler.rb
+++ b/railties/lib/rails/commands/profiler.rb
@@ -19,7 +19,7 @@ def options
options
end
-class ProfilerTest < ActionDispatch::PerformanceTest
+class ProfilerTest < ActionDispatch::PerformanceTest #:nodoc:
self.profile_options = options
ARGV.each do |expression|
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 20484a10c8..0b757cbe28 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -67,6 +67,13 @@ module Rails
FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make))
end
+ unless options[:daemonize]
+ wrapped_app # touch the app so the logger is set up
+
+ console = ActiveSupport::Logger.new($stdout)
+ Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
+ end
+
super
ensure
# The '-h' option calls exit before @options is set.
@@ -76,7 +83,6 @@ module Rails
def middleware
middlewares = []
- middlewares << [Rails::Rack::LogTailer, log_path] unless options[:daemonize]
middlewares << [Rails::Rack::Debugger] if options[:debugger]
middlewares << [::Rack::ContentLength]
Hash.new(middlewares)
@@ -89,6 +95,7 @@ module Rails
def default_options
super.merge({
:Port => 3000,
+ :DoNotReverseLookup => true,
:environment => (ENV['RAILS_ENV'] || "development").dup,
:daemonize => false,
:debugger => false,
diff --git a/railties/lib/rails/deprecation.rb b/railties/lib/rails/deprecation.rb
new file mode 100644
index 0000000000..c5811b2629
--- /dev/null
+++ b/railties/lib/rails/deprecation.rb
@@ -0,0 +1,18 @@
+require 'active_support/deprecation/proxy_wrappers'
+
+module Rails
+ class DeprecatedConstant < ActiveSupport::Deprecation::DeprecatedConstantProxy
+ def self.deprecate(old, current)
+ constant = new(old, current)
+ eval "::#{old} = constant"
+ end
+
+ private
+
+ def target
+ ::Kernel.eval @new_const.to_s
+ end
+ end
+
+ DeprecatedConstant.deprecate('RAILS_CACHE', '::Rails.cache')
+end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 20321a502d..af2bde5a6e 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -228,7 +228,7 @@ module Rails
# resources :articles
# end
#
- # The routes above will automatically point to <tt>MyEngine::ApplicationController</tt>. Furthermore, you don't
+ # The routes above will automatically point to <tt>MyEngine::ArticlesController</tt>. Furthermore, you don't
# need to use longer url helpers like <tt>my_engine_articles_path</tt>. Instead, you should simply use
# <tt>articles_path</tt> as you would do with your application.
#
@@ -300,7 +300,7 @@ module Rails
# helper MyEngine::SharedEngineHelper
# end
#
- # If you want to include all of the engine's helpers, you can use #helpers method on an engine's
+ # If you want to include all of the engine's helpers, you can use #helper method on an engine's
# instance:
#
# class ApplicationController < ActionController::Base
@@ -326,7 +326,7 @@ module Rails
# migration in the application and rerun copying migrations.
#
# If your engine has migrations, you may also want to prepare data for the database in
- # the <tt>seeds.rb</tt> file. You can load that data using the <tt>load_seed</tt> method, e.g.
+ # the <tt>db/seeds.rb</tt> file. You can load that data using the <tt>load_seed</tt> method, e.g.
#
# MyEngine::Engine.load_seed
#
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 45f55a2a0a..32793b1a70 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -246,7 +246,7 @@ module Rails
sentinel = /\.routes\.draw do\s*$/
in_root do
- inject_into_file 'config/routes.rb', "\n #{routing_code}\n", { :after => sentinel, :verbose => false }
+ inject_into_file 'config/routes.rb', "\n #{routing_code}", { :after => sentinel, :verbose => false }
end
end
diff --git a/railties/lib/rails/generators/active_model.rb b/railties/lib/rails/generators/active_model.rb
index 4b828340d2..454327f765 100644
--- a/railties/lib/rails/generators/active_model.rb
+++ b/railties/lib/rails/generators/active_model.rb
@@ -37,7 +37,7 @@ module Rails
# GET show
# GET edit
- # PUT update
+ # PATCH/PUT update
# DELETE destroy
def self.find(klass, params=nil)
"#{klass}.find(#{params})"
@@ -58,13 +58,13 @@ module Rails
"#{name}.save"
end
- # PUT update
+ # PATCH/PUT update
def update_attributes(params=nil)
"#{name}.update_attributes(#{params})"
end
# POST create
- # PUT update
+ # PATCH/PUT update
def errors
"#{name}.errors"
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 046b8f3925..7c449657b5 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -181,10 +181,6 @@ module Rails
end
end
- def ruby_debugger_gemfile_entry
- "gem 'ruby-debug19', :require => 'ruby-debug'"
- end
-
def assets_gemfile_entry
return if options[:skip_sprockets]
@@ -195,7 +191,9 @@ module Rails
group :assets do
gem 'sass-rails', :git => 'https://github.com/rails/sass-rails.git'
gem 'coffee-rails', :git => 'https://github.com/rails/coffee-rails.git'
- #{"gem 'therubyrhino'\n" if defined?(JRUBY_VERSION)}
+
+ # See https://github.com/sstephenson/execjs#readme for more supported runtimes
+ #{javascript_runtime_gemfile_entry}
gem 'uglifier', '>= 1.0.3'
end
GEMFILE
@@ -206,7 +204,9 @@ module Rails
group :assets do
gem 'sass-rails', '~> 4.0.0.beta'
gem 'coffee-rails', '~> 4.0.0.beta'
- #{"gem 'therubyrhino'\n" if defined?(JRUBY_VERSION)}
+
+ # See https://github.com/sstephenson/execjs#readme for more supported runtimes
+ #{javascript_runtime_gemfile_entry}
gem 'uglifier', '>= 1.0.3'
end
GEMFILE
@@ -219,6 +219,14 @@ module Rails
"gem '#{options[:javascript]}-rails'" unless options[:skip_javascript]
end
+ def javascript_runtime_gemfile_entry
+ if defined?(JRUBY_VERSION)
+ "gem 'therubyrhino'\n"
+ else
+ "# gem 'therubyracer'\n"
+ end
+ end
+
def bundle_command(command)
say_status :run, "bundle #{command}"
@@ -236,7 +244,7 @@ module Rails
end
def run_bundle
- bundle_command('install') unless options[:skip_gemfile] || options[:skip_bundle]
+ bundle_command('install') unless options[:skip_gemfile] || options[:skip_bundle] || options[:pretend]
end
def empty_directory_with_gitkeep(destination, config = {})
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index a98244c525..60e94486bb 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -48,6 +48,12 @@ module Rails
@namespace ||= super.sub(/_generator$/, '').sub(/:generators:/, ':')
end
+ # Convenience method to hide this generator from the available ones when
+ # running rails generator command.
+ def self.hide!
+ Rails::Generators.hide_namespace self.namespace
+ end
+
# Invoke a generator based on the value supplied by the user to the
# given option named "name". A class option is created when this method
# is invoked and you can set a hash to customize it.
@@ -254,17 +260,13 @@ module Rails
nesting = class_name.split('::')
last_name = nesting.pop
- # Hack to limit const_defined? to non-inherited on 1.9
- extra = []
- extra << false unless Object.method(:const_defined?).arity == 1
-
# Extract the last Module in the nesting
last = nesting.inject(Object) do |last_module, nest|
- break unless last_module.const_defined?(nest, *extra)
+ break unless last_module.const_defined?(nest, false)
last_module.const_get(nest)
end
- if last && last.const_defined?(last_name.camelize, *extra)
+ if last && last.const_defined?(last_name.camelize, false)
raise Error, "The name '#{class_name}' is either already used in your application " <<
"or reserved by Ruby on Rails. Please choose an alternative and run " <<
"this generator again."
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
index 7b1a2a1841..85296ca37b 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -10,16 +10,14 @@
<th></th>
</tr>
-<%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
- <tr>
+ <%%= content_tag_for(:tr, @<%= plural_table_name %>) do |<%= singular_table_name %>| %>
<% attributes.each do |attribute| -%>
<td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
<% end -%>
<td><%%= link_to 'Show', <%= singular_table_name %> %></td>
<td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td>
<td><%%= link_to 'Destroy', <%= singular_table_name %>, <%= key_value :confirm, "'Are you sure?'" %>, <%= key_value :method, ":delete" %> %></td>
- </tr>
-<%% end %>
+ <%% end %>
</table>
<br />
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index 61479b9068..7dfc1aa599 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -5,6 +5,9 @@ require 'active_support/core_ext/object/blank'
module Rails
module Generators
class GeneratedAttribute
+ INDEX_OPTIONS = %w(index uniq)
+ UNIQ_INDEX_OPTIONS = %w(uniq)
+
attr_accessor :name, :type
attr_reader :attr_options
@@ -15,7 +18,7 @@ module Rails
# if user provided "name:index" instead of "name:string:index"
# type should be set blank so GeneratedAttribute's constructor
# could set it to :string
- has_index, type = type, nil if %w(index uniq).include?(type)
+ has_index, type = type, nil if INDEX_OPTIONS.include?(type)
type, attr_options = *parse_type_and_options(type)
new(name, type, has_index, attr_options)
@@ -29,7 +32,7 @@ module Rails
case type
when /(string|text|binary|integer)\{(\d+)\}/
return $1, :limit => $2.to_i
- when /decimal\{(\d+),(\d+)\}/
+ when /decimal\{(\d+)[,.-](\d+)\}/
return :decimal, :precision => $1.to_i, :scale => $2.to_i
else
return type, {}
@@ -40,8 +43,8 @@ module Rails
def initialize(name, type=nil, index_type=false, attr_options={})
@name = name
@type = (type.presence || :string).to_sym
- @has_index = %w(index uniq).include?(index_type)
- @has_uniq_index = %w(uniq).include?(index_type)
+ @has_index = INDEX_OPTIONS.include?(index_type)
+ @has_uniq_index = UNIQ_INDEX_OPTIONS.include?(index_type)
@attr_options = attr_options
end
@@ -84,7 +87,7 @@ module Rails
end
def reference?
- self.type.in?([:references, :belongs_to])
+ self.type.in?(:references, :belongs_to)
end
def has_index?
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 5e9c385ab8..5fdfe8f04e 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -22,4 +22,4 @@ source 'https://rubygems.org'
# gem 'capistrano', :group => :development
# To use debugger
-# <%= ruby_debugger_gemfile_entry %>
+# gem 'ruby-debug19', :require => 'ruby-debug'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index bcd3a2ad24..acf47a03e5 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -13,9 +13,9 @@ require "active_resource/railtie"
<% end -%>
if defined?(Bundler)
- # If you precompile assets before deploying to production, use this line
+ # If you precompile assets before deploying to production, use this line.
Bundler.require(*Rails.groups(:assets => %w(development test)))
- # If you want your assets lazily compiled in production, use this line
+ # If you want your assets lazily compiled in production, use this line.
# Bundler.require(:default, :assets, Rails.env)
end
@@ -47,7 +47,7 @@ module <%= app_const_base %>
# Use SQL instead of Active Record's schema dumper when creating the database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
- # like if you have constraints or database-specific column types
+ # like if you have constraints or database-specific column types.
# config.active_record.schema_format = :sql
# Enforce whitelist mode for mass assignment.
@@ -56,11 +56,16 @@ module <%= app_const_base %>
# parameters by using an attr_accessible or attr_protected declaration.
# config.active_record.whitelist_attributes = true
+ # Specifies wether or not has_many or has_one association option :dependent => :restrict raises
+ # an exception. If set to true, then an ActiveRecord::DeleteRestrictionError exception would be
+ # raised. If set to false, then an error will be added on the model instead.
+ <%= comment_if :skip_active_record %>config.active_record.dependent_restrict_raises = false
+
<% unless options.skip_sprockets? -%>
- # Enable the asset pipeline
+ # Enable the asset pipeline.
config.assets.enabled = true
- # Version of your assets, change this if you want to expire all your assets
+ # Version of your assets, change this if you want to expire all your assets.
config.assets.version = '1.0'
<% end -%>
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
index cce166c7c3..950016ad92 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
@@ -1,5 +1,5 @@
# MySQL. Versions 4.1 and 5.0 are recommended.
-#
+#
# Install the MYSQL driver
# gem install mysql2
#
@@ -11,9 +11,10 @@
development:
adapter: mysql2
encoding: utf8
- reconnect: false
database: <%= app_name %>_development
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
username: root
password:
<% if mysql_socket -%>
@@ -28,9 +29,10 @@ development:
test:
adapter: mysql2
encoding: utf8
- reconnect: false
database: <%= app_name %>_test
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
username: root
password:
<% if mysql_socket -%>
@@ -42,9 +44,10 @@ test:
production:
adapter: mysql2
encoding: utf8
- reconnect: false
database: <%= app_name %>_production
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
username: root
password:
<% if mysql_socket -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
index f08f86aac3..a8ed15c2dc 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
@@ -16,7 +16,9 @@ development:
adapter: postgresql
encoding: unicode
database: <%= app_name %>_development
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
username: <%= app_name %>
password:
@@ -42,7 +44,9 @@ test:
adapter: postgresql
encoding: unicode
database: <%= app_name %>_test
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
username: <%= app_name %>
password:
@@ -50,6 +54,8 @@ production:
adapter: postgresql
encoding: unicode
database: <%= app_name %>_production
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
username: <%= app_name %>
password:
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
index 51a4dd459d..32a998ad72 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
@@ -6,7 +6,9 @@
development:
adapter: sqlite3
database: db/development.sqlite3
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
timeout: 5000
# Warning: The database defined as "test" will be erased and
@@ -15,11 +17,15 @@ development:
test:
adapter: sqlite3
database: db/test.sqlite3
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
timeout: 5000
production:
adapter: sqlite3
database: db/production.sqlite3
- pool: 5
+ # Maximum number of database connections available per process. Please
+ # increase this number in multithreaded applications.
+ pool: 1
timeout: 5000
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index a1d41fd7dd..eb4dfa7c89 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -1,38 +1,38 @@
<%= app_const %>.configure do
- # Settings specified here will take precedence over those in config/application.rb
+ # Settings specified here will take precedence over those in config/application.rb.
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
- # Show full error reports and disable caching
+ # Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
- # Don't care if the mailer can't send
+ # Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
- # Print deprecation notices to the Rails logger
+ # Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
- # Only use best-standards-support built into browsers
+ # Only use best-standards-support built into browsers.
config.action_dispatch.best_standards_support = :builtin
<%- unless options.skip_active_record? -%>
- # Raise exception on mass assignment protection for ActiveRecord models
+ # Raise exception on mass assignment protection for Active Record models.
config.active_record.mass_assignment_sanitizer = :strict
# Log the query plan for queries taking more than this (works
- # with SQLite, MySQL, and PostgreSQL)
+ # with SQLite, MySQL, and PostgreSQL).
config.active_record.auto_explain_threshold_in_seconds = 0.5
<%- end -%>
<%- unless options.skip_sprockets? -%>
- # Do not compress assets
+ # Do not compress assets.
config.assets.compress = false
- # Expands the lines which load the assets
+ # Expands the lines which load the assets.
config.assets.debug = true
<%- end -%>
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 0f571f7c1a..7041550fd0 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -1,73 +1,76 @@
<%= app_const %>.configure do
- # Settings specified here will take precedence over those in config/application.rb
+ # Settings specified here will take precedence over those in config/application.rb.
- # Code is not reloaded between requests
+ # Code is not reloaded between requests.
config.cache_classes = true
- # Full error reports are disabled and caching is turned on
+ # Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
- # Disable Rails's static asset server (Apache or nginx will already do this)
+ # Disable Rails's static asset server (Apache or nginx will already do this).
config.serve_static_assets = false
<%- unless options.skip_sprockets? -%>
- # Compress JavaScripts and CSS
+ # Compress JavaScripts and CSS.
config.assets.compress = true
- # Don't fallback to assets pipeline if a precompiled asset is missed
+ # Don't fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
- # Generate digests for assets URLs
+ # Generate digests for assets URLs.
config.assets.digest = true
- # Defaults to Rails.root.join("public/assets")
+ # Defaults to Rails.root.join("public/assets").
# config.assets.manifest = YOUR_PATH
<%- end -%>
- # Specifies the header that your server uses for sending files
+ # Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
- # See everything in the log (default is :info)
+ # See everything in the log (default is :info).
# config.log_level = :debug
- # Prepend all log lines with the following tags
+ # Prepend all log lines with the following tags.
# config.log_tags = [ :subdomain, :uuid ]
- # Use a different logger for distributed setups
+ # Use a different logger for distributed setups.
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
- # Use a different cache store in production
+ # Use a different cache store in production.
# config.cache_store = :mem_cache_store
- # Enable serving of images, stylesheets, and JavaScripts from an asset server
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = "http://assets.example.com"
<%- unless options.skip_sprockets? -%>
- # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
+ # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added).
# config.assets.precompile += %w( search.js )
<%- end -%>
- # Disable delivery errors, bad email addresses will be ignored
+ # Disable delivery errors, bad email addresses will be ignored.
# config.action_mailer.raise_delivery_errors = false
- # Enable threaded mode
+ # Enable threaded mode.
# config.threadsafe!
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
- # the I18n.default_locale when a translation can not be found)
+ # the I18n.default_locale when a translation can not be found).
config.i18n.fallbacks = true
- # Send deprecation notices to registered listeners
+ # Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
<%- unless options.skip_active_record? -%>
# Log the query plan for queries taking more than this (works
- # with SQLite, MySQL, and PostgreSQL)
+ # with SQLite, MySQL, and PostgreSQL).
# config.active_record.auto_explain_threshold_in_seconds = 0.5
<%- end -%>
+
+ # Disable automatic flushing of the log to improve performance.
+ # config.autoflush_log = false
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
index 86016da189..b725dd19f6 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
@@ -1,5 +1,5 @@
<%= app_const %>.configure do
- # Settings specified here will take precedence over those in config/application.rb
+ # Settings specified here will take precedence over those in config/application.rb.
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
@@ -7,19 +7,19 @@
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
- # Configure static asset server for tests with Cache-Control for performance
+ # Configure static asset server for tests with Cache-Control for performance.
config.serve_static_assets = true
config.static_cache_control = "public, max-age=3600"
- # Show full error reports and disable caching
+ # Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
- # Raise exceptions instead of rendering exception templates
+ # Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false
- # Disable request forgery protection in test environment
- config.action_controller.allow_forgery_protection = false
+ # Disable request forgery protection in test environment.
+ config.action_controller.allow_forgery_protection = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
@@ -27,10 +27,10 @@
config.action_mailer.delivery_method = :test
<%- unless options.skip_active_record? -%>
- # Raise exception on mass assignment protection for ActiveRecord models
+ # Raise exception on mass assignment protection for Active Record models.
config.active_record.mass_assignment_sanitizer = :strict
<%- end -%>
- # Print deprecation notices to the stderr
+ # Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index 0e900a34bb..f4263d1b98 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -285,7 +285,9 @@ task :default => :test
end
def valid_const?
- if camelized =~ /^\d/
+ if original_name =~ /[^0-9a-zA-Z_]+/
+ raise Error, "Invalid plugin name #{original_name}. Please give a name which use only alphabetic or numeric or \"_\" characters."
+ elsif camelized =~ /^\d/
raise Error, "Invalid plugin name #{original_name}. Please give a name which does not start with numbers."
elsif RESERVED_NAMES.include?(name)
raise Error, "Invalid plugin name #{original_name}. Please give a name which does not match one of the reserved rails words."
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
index f4efd3af74..d316b00c43 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
@@ -20,4 +20,4 @@ gem "jquery-rails"
<% end -%>
# To use debugger
-# <%= ruby_debugger_gemfile_entry %>
+# gem 'ruby-debug19', :require => 'ruby-debug'
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
index 6ed6adcf1b..b7bc69d2e5 100755
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
@@ -4,13 +4,8 @@ begin
rescue LoadError
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
end
-begin
- require 'rdoc/task'
-rescue LoadError
- require 'rdoc/rdoc'
- require 'rake/rdoctask'
- RDoc::Task = Rake::RDocTask
-end
+
+require 'rdoc/task'
RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
index 4ff15fd288..ee49534a04 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -54,8 +54,8 @@ class <%= controller_class_name %>Controller < ApplicationController
end
end
- # PUT <%= route_url %>/1
- # PUT <%= route_url %>/1.json
+ # PATCH/PUT <%= route_url %>/1
+ # PATCH/PUT <%= route_url %>/1.json
def update
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
diff --git a/railties/lib/rails/rack/log_tailer.rb b/railties/lib/rails/rack/log_tailer.rb
index 830d840894..18f22e8089 100644
--- a/railties/lib/rails/rack/log_tailer.rb
+++ b/railties/lib/rails/rack/log_tailer.rb
@@ -4,10 +4,13 @@ module Rails
def initialize(app, log = nil)
@app = app
- path = Pathname.new(log || "#{File.expand_path(Rails.root)}/log/#{Rails.env}.log").cleanpath
- @cursor = ::File.size(path)
+ path = Pathname.new(log || "#{::File.expand_path(Rails.root)}/log/#{Rails.env}.log").cleanpath
- @file = ::File.open(path, 'r')
+ @cursor = @file = nil
+ if ::File.exists?(path)
+ @cursor = ::File.size(path)
+ @file = ::File.open(path, 'r')
+ end
end
def call(env)
@@ -17,6 +20,7 @@ module Rails
end
def tail!
+ return unless @cursor
@file.seek @cursor
unless @file.eof?
diff --git a/railties/lib/rails/rubyprof_ext.rb b/railties/lib/rails/rubyprof_ext.rb
index f6e90357ce..017eba3a76 100644
--- a/railties/lib/rails/rubyprof_ext.rb
+++ b/railties/lib/rails/rubyprof_ext.rb
@@ -12,7 +12,7 @@ module Prof #:nodoc:
io.puts " time seconds seconds calls ms/call ms/call name"
sum = 0.0
- for r in results
+ results.each do |r|
sum += r.self_time
name = if r.method_class.nil?
diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb
index 684beb32a3..9bfc2b16ab 100644
--- a/railties/lib/rails/source_annotation_extractor.rb
+++ b/railties/lib/rails/source_annotation_extractor.rb
@@ -53,7 +53,7 @@ class SourceAnnotationExtractor
# Returns a hash that maps filenames under +dir+ (recursively) to arrays
# with their annotations. Only files with annotations are included, and only
- # those with extension +.builder+, +.rb+, +.erb+, +.haml+ and +.slim+
+ # those with extension +.builder+, +.rb+, +.erb+, +.haml+, +.slim+ and +.coffee+
# are taken into account.
def find_in(dir)
results = {}
@@ -63,7 +63,7 @@ class SourceAnnotationExtractor
if File.directory?(item)
results.update(find_in(item))
- elsif item =~ /\.(builder|rb)$/
+ elsif item =~ /\.(builder|rb|coffee)$/
results.update(extract_annotations_from(item, /#\s*(#{tag}):?\s*(.*)$/))
elsif item =~ /\.erb$/
results.update(extract_annotations_from(item, /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/))
diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake
index e09379c8c2..cec346d86b 100644
--- a/railties/lib/rails/tasks/documentation.rake
+++ b/railties/lib/rails/tasks/documentation.rake
@@ -1,10 +1,4 @@
-begin
- require 'rdoc/task'
-rescue LoadError
- require 'rdoc/rdoc'
- require 'rake/rdoctask'
- RDoc::Task = Rake::RDocTask
-end
+require 'rdoc/task'
# Monkey-patch to remove redoc'ing and clobber descriptions to cut down on rake -T noise
class RDocTaskWithoutDescriptions < RDoc::Task
diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake
index 40f8c1034a..f684e71267 100644
--- a/railties/lib/rails/tasks/statistics.rake
+++ b/railties/lib/rails/tasks/statistics.rake
@@ -2,6 +2,7 @@ STATS_DIRECTORIES = [
%w(Controllers app/controllers),
%w(Helpers app/helpers),
%w(Models app/models),
+ %w(Mailers app/mailers),
%w(Libraries lib/),
%w(APIs app/apis),
%w(Integration\ tests test/integration),
diff --git a/railties/lib/rails/test_unit/sub_test_task.rb b/railties/lib/rails/test_unit/sub_test_task.rb
index 284c70050f..87b6f9b5a4 100644
--- a/railties/lib/rails/test_unit/sub_test_task.rb
+++ b/railties/lib/rails/test_unit/sub_test_task.rb
@@ -1,36 +1,8 @@
module Rails
- # Don't abort when tests fail; move on the next test task.
# Silence the default description to cut down on `rake -T` noise.
class SubTestTask < Rake::TestTask
- # Create the tasks defined by this task lib.
- def define
- lib_path = @libs.join(File::PATH_SEPARATOR)
- task @name do
- run_code = ''
- RakeFileUtils.verbose(@verbose) do
- run_code =
- case @loader
- when :direct
- "-e 'ARGV.each{|f| load f}'"
- when :testrb
- "-S testrb #{fix}"
- when :rake
- rake_loader
- end
- @ruby_opts.unshift( "-I\"#{lib_path}\"" )
- @ruby_opts.unshift( "-w" ) if @warning
-
- begin
- ruby @ruby_opts.join(" ") +
- " \"#{run_code}\" " +
- file_list.collect { |fn| "\"#{fn}\"" }.join(' ') +
- " #{option_list}"
- rescue => error
- warn "Error running #{name}: #{error.inspect}"
- end
- end
- end
- self
+ def desc(string)
+ # Ignore the description.
end
end
end
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 2c0b167a99..55bebb549b 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -55,7 +55,21 @@ namespace :test do
# Placeholder task for other Railtie and plugins to enhance. See Active Record for an example.
end
- task :run => %w(test:units test:functionals test:integration)
+ task :run do
+ errors = %w(test:units test:functionals test:integration).collect do |task|
+ begin
+ Rake::Task[task].invoke
+ nil
+ rescue => e
+ { :task => task, :exception => e }
+ end
+ end.compact
+
+ if errors.any?
+ puts errors.map { |e| "Errors running #{e[:task]}! #{e[:exception].inspect}" }.join("\n")
+ abort
+ end
+ end
Rake::TestTask.new(:recent => "test:prepare") do |t|
since = TEST_CHANGES_SINCE
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index c18e56a606..1469c9af4d 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -17,10 +17,6 @@ module ApplicationTests
teardown_app
end
- def app
- @app ||= Rails.application
- end
-
def precompile!
quietly do
Dir.chdir(app_path){ `bundle exec rake assets:precompile` }
@@ -68,7 +64,7 @@ module ApplicationTests
files << Dir["#{app_path}/public/assets/foo/application.js"].first
files.each do |file|
assert_not_nil file, "Expected application.js asset to be generated, but none found"
- assert_equal "alert()", File.read(file)
+ assert_equal "alert();", File.read(file)
end
end
@@ -318,7 +314,7 @@ module ApplicationTests
Dir.chdir(app_path){ `bundle exec rake assets:clean` }
end
- files = Dir["#{app_path}/public/assets/**/*", "#{app_path}/tmp/cache/*"]
+ files = Dir["#{app_path}/public/assets/**/*", "#{app_path}/tmp/cache/assets/*"]
assert_equal 0, files.length, "Expected no assets, but found #{files.join(', ')}"
end
@@ -427,6 +423,12 @@ module ApplicationTests
assert_equal "NoPost;\n", File.read("#{app_path}/public/assets/application.js")
end
+ test "initialization on the assets group should set assets_dir" do
+ require "#{app_path}/config/application"
+ Rails.application.initialize!(:assets)
+ assert_not_nil Rails.application.config.action_controller.assets_dir
+ end
+
test "enhancements to assets:precompile should only run once" do
app_file "lib/tasks/enhance.rake", "Rake::Task['assets:precompile'].enhance { puts 'enhancement' }"
output = precompile!
@@ -490,6 +492,18 @@ module ApplicationTests
assert_match 'src="/sub/uri/assets/rails.png"', File.read("#{app_path}/public/assets/app.js")
end
+ test "assets:cache:clean should clean cache" do
+ ENV["RAILS_ENV"] = "production"
+ precompile!
+
+ quietly do
+ Dir.chdir(app_path){ `bundle exec rake assets:cache:clean` }
+ end
+
+ require "#{app_path}/config/environment"
+ assert_equal 0, Dir.entries(Rails.application.assets.cache.cache_path).size - 2 # reject [".", ".."]
+ end
+
private
def app_with_assets_in_view
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 397f4da8fc..c9310aff87 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -146,7 +146,7 @@ module ApplicationTests
test "frameworks are not preloaded by default" do
require "#{app_path}/config/environment"
- assert ActionController.autoload?(:RecordIdentifier)
+ assert ActionController.autoload?(:Caching)
end
test "frameworks are preloaded with config.preload_frameworks is set" do
@@ -156,7 +156,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
- assert !ActionController.autoload?(:RecordIdentifier)
+ assert !ActionController.autoload?(:Caching)
end
test "filter_parameters should be able to set via config.filter_parameters" do
@@ -246,6 +246,51 @@ module ApplicationTests
assert last_response.body =~ /csrf\-param/
end
+ test "default method for update can be changed" do
+ app_file 'app/models/post.rb', <<-RUBY
+ class Post
+ extend ActiveModel::Naming
+ def to_key; [1]; end
+ def persisted?; true; end
+ end
+ RUBY
+
+ app_file 'app/controllers/posts_controller.rb', <<-RUBY
+ class PostsController < ApplicationController
+ def show
+ render :inline => "<%= begin; form_for(Post.new) {}; rescue => e; e.to_s; end %>"
+ end
+
+ def update
+ render :text => "update"
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ routes.prepend do
+ resources :posts
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ get "/posts/1"
+ assert_match /patch/, last_response.body
+
+ patch "/posts/1"
+ assert_match /update/, last_response.body
+
+ patch "/posts/1"
+ assert_equal 200, last_response.status
+
+ put "/posts/1"
+ assert_match /update/, last_response.body
+
+ put "/posts/1"
+ assert_equal 200, last_response.status
+ end
+
test "request forgery token param can be changed" do
make_basic_app do
app.config.action_controller.request_forgery_protection_token = '_xsrf_token_here'
@@ -287,6 +332,14 @@ module ApplicationTests
assert_equal Rails.application, ActionDispatch.test_app
end
+ test "sets ActionDispatch::Response.default_charset" do
+ make_basic_app do |app|
+ app.config.action_dispatch.default_charset = "utf-16"
+ end
+
+ assert_equal "utf-16", ActionDispatch::Response.default_charset
+ end
+
test "sets all Active Record models to whitelist all attributes by default" do
add_to_config <<-RUBY
config.active_record.whitelist_attributes = true
@@ -526,5 +579,10 @@ module ApplicationTests
assert_equal app.env_config['action_dispatch.logger'], Rails.logger
assert_equal app.env_config['action_dispatch.backtrace_cleaner'], Rails.backtrace_cleaner
end
+
+ test "config.colorize_logging default is true" do
+ make_basic_app
+ assert app.config.colorize_logging
+ end
end
end
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index 457e81e174..5ad51f8476 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -63,7 +63,7 @@ class LoadingTest < ActiveSupport::TestCase
assert ::AppTemplate::Application.config.loaded
end
- test "descendants are cleaned on each request without cache classes" do
+ test "descendants loaded after framework initialization are cleaned on each request without cache classes" do
add_to_config <<-RUBY
config.cache_classes = false
config.reload_classes_only_on_change = false
@@ -87,11 +87,11 @@ class LoadingTest < ActiveSupport::TestCase
require "#{rails_root}/config/environment"
setup_ar!
- assert_equal [], ActiveRecord::Base.descendants
+ assert_equal [ActiveRecord::SchemaMigration], ActiveRecord::Base.descendants
get "/load"
- assert_equal [Post], ActiveRecord::Base.descendants
+ assert_equal [ActiveRecord::SchemaMigration, Post], ActiveRecord::Base.descendants
get "/unload"
- assert_equal [], ActiveRecord::Base.descendants
+ assert_equal [ActiveRecord::SchemaMigration], ActiveRecord::Base.descendants
end
test "initialize_cant_be_called_twice" do
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
new file mode 100644
index 0000000000..f4e77ee244
--- /dev/null
+++ b/railties/test/application/middleware/session_test.rb
@@ -0,0 +1,30 @@
+# encoding: utf-8
+require 'isolation/abstract_unit'
+require 'rack/test'
+
+module ApplicationTests
+ class MiddlewareSessionTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
+
+ def setup
+ build_app
+ boot_rails
+ FileUtils.rm_rf "#{app_path}/config/environments"
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ def app
+ @app ||= Rails.application
+ end
+
+ test "config.force_ssl sets cookie to secure only" do
+ add_to_config "config.force_ssl = true"
+ require "#{app_path}/config/environment"
+ assert app.config.session_options[:secure], "Expected session to be marked as secure"
+ end
+ end
+end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 2f1df1fa59..d0a550d2f0 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -132,13 +132,13 @@ module ApplicationTests
assert_equal "Rack::Config", middleware.second
end
- test "RAILS_CACHE does not respond to middleware" do
+ test "Rails.cache does not respond to middleware" do
add_to_config "config.cache_store = :memory_store"
boot!
assert_equal "Rack::Runtime", middleware.third
end
- test "RAILS_CACHE does respond to middleware" do
+ test "Rails.cache does respond to middleware" do
boot!
assert_equal "Rack::Runtime", middleware.fourth
end
diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb
index 55f5a349de..4029984ce9 100644
--- a/railties/test/application/paths_test.rb
+++ b/railties/test/application/paths_test.rb
@@ -15,7 +15,6 @@ module ApplicationTests
app.config.session_store nil
end
RUBY
- use_frameworks [:action_controller, :action_view, :action_mailer, :active_record]
require "#{app_path}/config/environment"
@paths = Rails.application.config.paths
end
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
index 301e516192..c94334f189 100644
--- a/railties/test/application/rake/migrations_test.rb
+++ b/railties/test/application/rake/migrations_test.rb
@@ -67,7 +67,7 @@ module ApplicationTests
`rails generate migration add_email_to_users email:string`
end
- Dir.chdir(app_path) { `rake db:migrate`}
+ Dir.chdir(app_path) { `rake db:migrate` }
output = Dir.chdir(app_path) { `rake db:migrate:status` }
assert_match(/up\s+\d{14}\s+Create users/, output)
@@ -80,6 +80,27 @@ module ApplicationTests
assert_match(/down\s+\d{14}\s+Add email to users/, output)
end
+ test 'migration status without timestamps' do
+ add_to_config('config.active_record.timestamped_migrations = false')
+
+ Dir.chdir(app_path) do
+ `rails generate model user username:string password:string`
+ `rails generate migration add_email_to_users email:string`
+ end
+
+ Dir.chdir(app_path) { `rake db:migrate` }
+ output = Dir.chdir(app_path) { `rake db:migrate:status` }
+
+ assert_match(/up\s+\d{3,}\s+Create users/, output)
+ assert_match(/up\s+\d{3,}\s+Add email to users/, output)
+
+ Dir.chdir(app_path) { `rake db:rollback STEP=1` }
+ output = Dir.chdir(app_path) { `rake db:migrate:status` }
+
+ assert_match(/up\s+\d{3,}\s+Create users/, output)
+ assert_match(/down\s+\d{3,}\s+Add email to users/, output)
+ end
+
test 'test migration status after rollback and redo' do
Dir.chdir(app_path) do
`rails generate model user username:string password:string`
@@ -104,6 +125,33 @@ module ApplicationTests
assert_match(/up\s+\d{14}\s+Create users/, output)
assert_match(/up\s+\d{14}\s+Add email to users/, output)
end
+
+ test 'migration status after rollback and redo without timestamps' do
+ add_to_config('config.active_record.timestamped_migrations = false')
+
+ Dir.chdir(app_path) do
+ `rails generate model user username:string password:string`
+ `rails generate migration add_email_to_users email:string`
+ end
+
+ Dir.chdir(app_path) { `rake db:migrate` }
+ output = Dir.chdir(app_path) { `rake db:migrate:status` }
+
+ assert_match(/up\s+\d{3,}\s+Create users/, output)
+ assert_match(/up\s+\d{3,}\s+Add email to users/, output)
+
+ Dir.chdir(app_path) { `rake db:rollback STEP=2` }
+ output = Dir.chdir(app_path) { `rake db:migrate:status` }
+
+ assert_match(/down\s+\d{3,}\s+Create users/, output)
+ assert_match(/down\s+\d{3,}\s+Add email to users/, output)
+
+ Dir.chdir(app_path) { `rake db:migrate:redo` }
+ output = Dir.chdir(app_path) { `rake db:migrate:status` }
+
+ assert_match(/up\s+\d{3,}\s+Create users/, output)
+ assert_match(/up\s+\d{3,}\s+Add email to users/, output)
+ end
end
end
end
diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb
index e121d6f1ab..4ab20afc47 100644
--- a/railties/test/application/rake/notes_test.rb
+++ b/railties/test/application/rake/notes_test.rb
@@ -7,7 +7,7 @@ module ApplicationTests
build_app
require "rails/all"
end
-
+
def teardown
teardown_app
end
@@ -17,6 +17,7 @@ module ApplicationTests
app_file "app/views/home/index.html.erb", "<% # TODO: note in erb %>"
app_file "app/views/home/index.html.haml", "-# TODO: note in haml"
app_file "app/views/home/index.html.slim", "/ TODO: note in slim"
+ app_file "app/assets/javascripts/application.js.coffee", "# TODO: note in coffee"
app_file "app/controllers/application_controller.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in ruby"
boot_rails
@@ -25,25 +26,26 @@ module ApplicationTests
require 'rake/testtask'
Rails.application.load_tasks
-
+
Dir.chdir(app_path) do
output = `bundle exec rake notes`
lines = output.scan(/\[([0-9\s]+)\]/).flatten
-
+
assert_match /note in erb/, output
assert_match /note in haml/, output
assert_match /note in slim/, output
assert_match /note in ruby/, output
+ assert_match /note in coffee/, output
- assert_equal 4, lines.size
- assert_equal 4, lines[0].size
- assert_equal 4, lines[1].size
- assert_equal 4, lines[2].size
- assert_equal 4, lines[3].size
+ assert_equal 5, lines.size
+
+ lines.each do |line_number|
+ assert_equal 4, line_number.size
+ end
end
-
+
end
-
+
private
def boot_rails
super
diff --git a/railties/test/application/route_inspect_test.rb b/railties/test/application/route_inspect_test.rb
index fcfa87e395..6393cfff4b 100644
--- a/railties/test/application/route_inspect_test.rb
+++ b/railties/test/application/route_inspect_test.rb
@@ -8,6 +8,11 @@ module ApplicationTests
def setup
@set = ActionDispatch::Routing::RouteSet.new
@inspector = Rails::Application::RouteInspector.new
+ app = ActiveSupport::OrderedOptions.new
+ app.config = ActiveSupport::OrderedOptions.new
+ app.config.assets = ActiveSupport::OrderedOptions.new
+ app.config.assets.prefix = '/sprockets'
+ Rails.stubs(:application).returns(app)
end
def test_displaying_routes_for_engines
@@ -62,6 +67,7 @@ module ApplicationTests
" new_article GET /articles/new(.:format) articles#new",
"edit_article GET /articles/:id/edit(.:format) articles#edit",
" article GET /articles/:id(.:format) articles#show",
+ " PATCH /articles/:id(.:format) articles#update",
" PUT /articles/:id(.:format) articles#update",
" DELETE /articles/:id(.:format) articles#destroy" ]
assert_equal expected, output
@@ -144,5 +150,14 @@ module ApplicationTests
output = @inspector.format @set.routes
assert_equal [" /foo #{RackApp.name} {:constraint=>( my custom constraint )}"], output
end
+
+ def test_rake_routes_dont_show_app_mounted_in_assets_prefix
+ @set.draw do
+ match '/sprockets' => RackApp
+ end
+ output = @inspector.format @set.routes
+ assert_no_match(/RackApp/, output.first)
+ assert_no_match(/\/sprockets/, output.first)
+ end
end
end
diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb
new file mode 100644
index 0000000000..01847ae58c
--- /dev/null
+++ b/railties/test/commands/console_test.rb
@@ -0,0 +1,87 @@
+require 'abstract_unit'
+require 'rails/commands/console'
+
+class Rails::ConsoleTest < ActiveSupport::TestCase
+ class FakeConsole
+ end
+
+ def setup
+ end
+
+ def test_sandbox_option
+ console = Rails::Console.new(app, ["--sandbox"])
+ assert console.sandbox?
+ end
+
+ def test_short_version_of_sandbox_option
+ console = Rails::Console.new(app, ["-s"])
+ assert console.sandbox?
+ end
+
+ def test_debugger_option
+ console = Rails::Console.new(app, ["--debugger"])
+ assert console.debugger?
+ end
+
+ def test_no_options
+ console = Rails::Console.new(app, [])
+ assert !console.debugger?
+ assert !console.sandbox?
+ end
+
+ def test_start
+ app.expects(:sandbox=).with(nil)
+ FakeConsole.expects(:start)
+
+ start
+
+ assert_match /Loading \w+ environment \(Rails/, output
+ end
+
+ def test_start_with_debugger
+ app.expects(:sandbox=).with(nil)
+ rails_console.expects(:require_debugger).returns(nil)
+ FakeConsole.expects(:start)
+
+ start ["--debugger"]
+ end
+
+ def test_start_with_sandbox
+ app.expects(:sandbox=).with(true)
+ FakeConsole.expects(:start)
+
+ start ["--sandbox"]
+
+ assert_match /Loading \w+ environment in sandbox \(Rails/, output
+ end
+
+ def test_console_defaults_to_IRB
+ config = mock("config", :console => nil)
+ app = mock("app", :config => config)
+ app.expects(:load_console).returns(nil)
+
+ assert_equal IRB, Rails::Console.new(app).console
+ end
+
+ private
+
+ attr_reader :output
+
+ def rails_console
+ @rails_console ||= Rails::Console.new(app)
+ end
+
+ def start(argv = [])
+ rails_console.stubs(:arguments => argv)
+ @output = output = capture(:stdout) { rails_console.start }
+ end
+
+ def app
+ @app ||= begin
+ config = mock("config", :console => FakeConsole)
+ app = mock("app", :config => config)
+ app.expects(:load_console)
+ app
+ end
+ end
+end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 70ef37ee0f..a3c24c392b 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -202,6 +202,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--skip-active-record"]
assert_no_file "config/database.yml"
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
+ assert_file "config/application.rb", /#\s+config\.active_record\.dependent_restrict_raises = false/
assert_file "test/test_helper.rb" do |helper_content|
assert_no_match(/fixtures :all/, helper_content)
end
@@ -229,14 +230,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "test/performance/browsing_test.rb"
end
- def test_inclusion_of_therubyrhino_under_jruby
+ def test_inclusion_of_javascript_runtime
run_generator([destination_root])
if defined?(JRUBY_VERSION)
assert_file "Gemfile", /gem\s+["']therubyrhino["']$/
else
- assert_file "Gemfile" do |content|
- assert_no_match(/gem\s+["']therubyrhino["']$/, content)
- end
+ assert_file "Gemfile", /# gem\s+["']therubyracer["']$/
end
end
@@ -351,6 +350,16 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_active_record_dependent_restrict_raises_is_present_application_config
+ run_generator
+ assert_file "config/application.rb", /config\.active_record\.dependent_restrict_raises = false/
+ end
+
+ def test_pretend_option
+ output = run_generator [File.join(destination_root, "myapp"), "--pretend"]
+ assert_no_match(/run bundle install/, output)
+ end
+
protected
def action(*args, &block)
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index 68fbd58061..4e08e5dae1 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -105,14 +105,14 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
def test_add_migration_with_attributes_index_declaration_and_attribute_options
migration = "add_title_and_content_to_books"
- run_generator [migration, "title:string{40}:index", "content:string{255}", "price:decimal{5,2}:index", "discount:decimal{3,2}:uniq"]
+ run_generator [migration, "title:string{40}:index", "content:string{255}", "price:decimal{1,2}:index", "discount:decimal{3.4}:uniq"]
assert_migration "db/migrate/#{migration}.rb" do |content|
assert_method :change, content do |up|
assert_match(/add_column :books, :title, :string, limit: 40/, up)
assert_match(/add_column :books, :content, :string, limit: 255/, up)
- assert_match(/add_column :books, :price, :decimal, precision: 5, scale: 2/, up)
- assert_match(/add_column :books, :discount, :decimal, precision: 3, scale: 2/, up)
+ assert_match(/add_column :books, :price, :decimal, precision: 1, scale: 2/, up)
+ assert_match(/add_column :books, :discount, :decimal, precision: 3, scale: 4/, up)
end
assert_match(/add_index :books, :title/, content)
assert_match(/add_index :books, :price/, content)
diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb
index f23701e99e..2bc2c33a72 100644
--- a/railties/test/generators/named_base_test.rb
+++ b/railties/test/generators/named_base_test.rb
@@ -108,6 +108,15 @@ class NamedBaseTest < Rails::Generators::TestCase
assert_name g, 'sheep_index', :index_helper
end
+ def test_hide_namespace
+ g = generator ['Hidden']
+ g.class.stubs(:namespace).returns('hidden')
+
+ assert !Rails::Generators.hidden_namespaces.include?('hidden')
+ g.class.hide!
+ assert Rails::Generators.hidden_namespaces.include?('hidden')
+ end
+
protected
def assert_name(generator, value, method)
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index f0164ed667..4bb5f04a79 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -27,13 +27,14 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
include SharedGeneratorTests
def test_invalid_plugin_name_raises_an_error
- content = capture(:stderr){ run_generator [File.join(destination_root, "43-things")] }
- assert_equal "Invalid plugin name 43-things. Please give a name which does not start with numbers.\n", content
- end
-
- def test_invalid_plugin_name_is_fixed
- run_generator [File.join(destination_root, "things-43")]
- assert_file "things-43/lib/things-43.rb", /module Things43/
+ content = capture(:stderr){ run_generator [File.join(destination_root, "things-43")] }
+ assert_equal "Invalid plugin name things-43. Please give a name which use only alphabetic or numeric or \"_\" characters.\n", content
+
+ content = capture(:stderr){ run_generator [File.join(destination_root, "things4.3")] }
+ assert_equal "Invalid plugin name things4.3. Please give a name which use only alphabetic or numeric or \"_\" characters.\n", content
+
+ content = capture(:stderr){ run_generator [File.join(destination_root, "43things")] }
+ assert_equal "Invalid plugin name 43things. Please give a name which does not start with numbers.\n", content
end
def test_camelcase_plugin_name_underscores_filenames
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 5f9ee220dc..60e7e57a91 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -201,10 +201,16 @@ class GeneratorsTest < Rails::Generators::TestCase
mspec = Rails::Generators.find_by_namespace :fixjour
assert mspec.source_paths.include?(File.join(Rails.root, "lib", "templates", "fixjour"))
end
-
+
def test_usage_with_embedded_ruby
require File.expand_path("fixtures/lib/generators/usage_template/usage_template_generator", File.dirname(__FILE__))
output = capture(:stdout) { Rails::Generators.invoke :usage_template, ['--help'] }
assert_match /:: 2 ::/, output
end
+
+ def test_hide_namespace
+ assert !Rails::Generators.hidden_namespaces.include?("special:namespace")
+ Rails::Generators.hide_namespace("special:namespace")
+ assert Rails::Generators.hidden_namespaces.include?("special:namespace")
+ end
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 72588af631..4fb5e6a4eb 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -247,7 +247,7 @@ module TestHelpers
:activemodel,
:activerecord,
:activeresource] - arr
- remove_from_config "config.active_record.identity_map = true" if to_remove.include? :activerecord
+ remove_from_config "config.active_record.dependent_restrict_raises = false" if to_remove.include? :activerecord
$:.reject! {|path| path =~ %r'/(#{to_remove.join('|')})/' }
end