aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile16
-rw-r--r--README.rdoc2
-rwxr-xr-xRakefile1
-rw-r--r--actionmailer/README.rdoc15
-rw-r--r--actionmailer/actionmailer.gemspec4
-rw-r--r--actionmailer/lib/action_mailer/base.rb38
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb8
-rw-r--r--actionmailer/test/abstract_unit.rb10
-rw-r--r--actionmailer/test/base_test.rb40
-rw-r--r--actionpack/CHANGELOG57
-rw-r--r--actionpack/README.rdoc7
-rw-r--r--actionpack/actionpack.gemspec8
-rw-r--r--actionpack/lib/abstract_controller/asset_paths.rb4
-rw-r--r--actionpack/lib/abstract_controller/base.rb35
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb2
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb22
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb2
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb28
-rw-r--r--actionpack/lib/abstract_controller/view_paths.rb8
-rw-r--r--actionpack/lib/action_controller.rb2
-rw-r--r--actionpack/lib/action_controller/base.rb6
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb13
-rw-r--r--actionpack/lib/action_controller/metal.rb8
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb145
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb35
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb52
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb30
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb24
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb26
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb15
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb11
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb45
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb176
-rw-r--r--actionpack/lib/action_controller/railtie.rb1
-rw-r--r--actionpack/lib/action_controller/railties/paths.rb8
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb14
-rw-r--r--actionpack/lib/action_controller/test_case.rb4
-rw-r--r--actionpack/lib/action_dispatch.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb109
-rw-r--r--actionpack/lib/action_dispatch/middleware/closed_error.rb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb28
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb91
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb43
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb48
-rw-r--r--actionpack/lib/action_dispatch/routing/route.rb23
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb6
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb6
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb204
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb28
-rw-r--r--actionpack/lib/action_view.rb11
-rw-r--r--actionpack/lib/action_view/base.rb53
-rw-r--r--actionpack/lib/action_view/buffers.rb43
-rw-r--r--actionpack/lib/action_view/context.rb1
-rw-r--r--actionpack/lib/action_view/flows.rb79
-rw-r--r--actionpack/lib/action_view/helpers.rb6
-rw-r--r--actionpack/lib/action_view/helpers/asset_paths.rb80
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb28
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb5
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb159
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb155
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb31
-rw-r--r--actionpack/lib/action_view/helpers/atom_feed_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb23
-rw-r--r--actionpack/lib/action_view/helpers/csrf_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb20
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb25
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb124
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb852
-rw-r--r--actionpack/lib/action_view/helpers/record_tag_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/sanitize_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/scriptaculous_helper.rb263
-rw-r--r--actionpack/lib/action_view/helpers/sprockets_helper.rb69
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb13
-rw-r--r--actionpack/lib/action_view/path_set.rb1
-rw-r--r--actionpack/lib/action_view/railtie.rb2
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb4
-rw-r--r--actionpack/lib/action_view/renderer/streaming_template_renderer.rb130
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb30
-rw-r--r--actionpack/lib/action_view/rendering.rb38
-rw-r--r--actionpack/lib/action_view/template.rb37
-rw-r--r--actionpack/lib/action_view/template/handlers.rb2
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb22
-rw-r--r--actionpack/lib/action_view/template/handlers/rjs.rb13
-rw-r--r--actionpack/lib/action_view/template/resolver.rb107
-rw-r--r--actionpack/lib/action_view/test_case.rb2
-rw-r--r--actionpack/lib/action_view/testing/resolvers.rb12
-rw-r--r--actionpack/lib/sprockets/railtie.rb100
-rw-r--r--actionpack/test/controller/assert_select_test.rb414
-rw-r--r--actionpack/test/controller/caching_test.rb14
-rw-r--r--actionpack/test/controller/content_type_test.rb9
-rw-r--r--actionpack/test/controller/filters_test.rb27
-rw-r--r--actionpack/test/controller/flash_hash_test.rb90
-rw-r--r--actionpack/test/controller/flash_test.rb73
-rw-r--r--actionpack/test/controller/force_ssl_test.rb83
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb16
-rw-r--r--actionpack/test/controller/integration_test.rb8
-rw-r--r--actionpack/test/controller/mime_responds_test.rb89
-rw-r--r--actionpack/test/controller/new_base/content_type_test.rb9
-rw-r--r--actionpack/test/controller/new_base/render_implicit_action_test.rb18
-rw-r--r--actionpack/test/controller/new_base/render_layout_test.rb16
-rw-r--r--actionpack/test/controller/new_base/render_once_test.rb86
-rw-r--r--actionpack/test/controller/new_base/render_rjs_test.rb71
-rw-r--r--actionpack/test/controller/new_base/render_streaming_test.rb101
-rw-r--r--actionpack/test/controller/new_base/render_test.rb3
-rw-r--r--actionpack/test/controller/render_js_test.rb9
-rw-r--r--actionpack/test/controller/render_other_test.rb237
-rw-r--r--actionpack/test/controller/render_test.rb9
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb8
-rw-r--r--actionpack/test/controller/resources_test.rb7
-rw-r--r--actionpack/test/controller/test_test.rb12
-rw-r--r--actionpack/test/controller/view_paths_test.rb29
-rw-r--r--actionpack/test/dispatch/cookies_test.rb96
-rw-r--r--actionpack/test/dispatch/mapper_test.rb88
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb1
-rw-r--r--actionpack/test/dispatch/response_test.rb16
-rw-r--r--actionpack/test/dispatch/routing_test.rb3
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb16
-rw-r--r--actionpack/test/dispatch/static_test.rb33
-rw-r--r--actionpack/test/fixtures/custom_pattern/another.html.erb1
-rw-r--r--actionpack/test/fixtures/custom_pattern/html/another.erb1
-rw-r--r--actionpack/test/fixtures/custom_pattern/html/path.erb1
-rw-r--r--actionpack/test/fixtures/filter_test/implicit_actions/edit.html.erb1
-rw-r--r--actionpack/test/fixtures/filter_test/implicit_actions/show.html.erb1
-rw-r--r--actionpack/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs6
-rw-r--r--actionpack/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs1
-rw-r--r--actionpack/test/fixtures/layouts/streaming.erb4
-rw-r--r--actionpack/test/fixtures/old_content_type/render_default_for_rjs.rjs1
-rw-r--r--actionpack/test/fixtures/respond_to/all_types_with_layout.js.rjs1
-rw-r--r--actionpack/test/fixtures/respond_to/using_defaults.js.rjs1
-rw-r--r--actionpack/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs1
-rw-r--r--actionpack/test/fixtures/respond_with/using_invalid_resource_with_template.xml.erb1
-rw-r--r--actionpack/test/fixtures/respond_with/using_options_with_template.xml.erb1
-rw-r--r--actionpack/test/fixtures/respond_with/using_resource.js.rjs1
-rw-r--r--actionpack/test/fixtures/sprockets/app/images/logo.png (renamed from railties/lib/rails/generators/rails/app/templates/public/images/rails.png)bin6646 -> 6646 bytes
-rw-r--r--actionpack/test/fixtures/sprockets/app/javascripts/application.js0
-rw-r--r--actionpack/test/fixtures/sprockets/app/javascripts/dir/xmlhr.js0
-rw-r--r--actionpack/test/fixtures/sprockets/app/javascripts/xmlhr.js0
-rw-r--r--actionpack/test/fixtures/sprockets/app/stylesheets/application.css0
-rw-r--r--actionpack/test/fixtures/sprockets/app/stylesheets/dir/style.css0
-rw-r--r--actionpack/test/fixtures/sprockets/app/stylesheets/style.css0
-rw-r--r--actionpack/test/fixtures/test/delete_with_js.rjs2
-rw-r--r--actionpack/test/fixtures/test/enum_rjs_test.rjs6
-rw-r--r--actionpack/test/fixtures/test/greeting.js.rjs1
-rw-r--r--actionpack/test/fixtures/test/nested_streaming.erb3
-rw-r--r--actionpack/test/fixtures/test/render_explicit_html_template.js.rjs1
-rw-r--r--actionpack/test/fixtures/test/render_implicit_html_template.js.rjs1
-rw-r--r--actionpack/test/fixtures/test/streaming.erb3
-rw-r--r--actionpack/test/fixtures/test/streaming_buster.erb3
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb38
-rw-r--r--actionpack/test/template/capture_helper_test.rb16
-rw-r--r--actionpack/test/template/date_helper_test.rb10
-rw-r--r--actionpack/test/template/erb_util_test.rb3
-rw-r--r--actionpack/test/template/form_helper_test.rb36
-rw-r--r--actionpack/test/template/form_options_helper_test.rb3
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb3
-rw-r--r--actionpack/test/template/javascript_helper_test.rb40
-rw-r--r--actionpack/test/template/lookup_context_test.rb25
-rw-r--r--actionpack/test/template/number_helper_test.rb5
-rw-r--r--actionpack/test/template/prototype_helper_test.rb476
-rw-r--r--actionpack/test/template/render_test.rb17
-rw-r--r--actionpack/test/template/resolver_patterns_test.rb31
-rw-r--r--actionpack/test/template/scriptaculous_helper_test.rb86
-rw-r--r--actionpack/test/template/sprockets_helper_test.rb119
-rw-r--r--actionpack/test/template/streaming_render_test.rb105
-rw-r--r--actionpack/test/template/template_test.rb38
-rw-r--r--actionpack/test/template/text_helper_test.rb16
-rw-r--r--activemodel/CHANGELOG9
-rw-r--r--activemodel/activemodel.gemspec4
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb54
-rw-r--r--activemodel/lib/active_model/dirty.rb2
-rw-r--r--activemodel/lib/active_model/errors.rb25
-rw-r--r--activemodel/lib/active_model/lint.rb4
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb117
-rw-r--r--activemodel/lib/active_model/naming.rb15
-rw-r--r--activemodel/lib/active_model/observer_array.rb98
-rw-r--r--activemodel/lib/active_model/observing.rb35
-rw-r--r--activemodel/lib/active_model/secure_password.rb7
-rw-r--r--activemodel/lib/active_model/translation.rb2
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb27
-rw-r--r--activemodel/lib/active_model/validations/format.rb46
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb26
-rw-r--r--activemodel/lib/active_model/validations/length.rb3
-rw-r--r--activemodel/lib/active_model/validator.rb7
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb59
-rw-r--r--activemodel/test/cases/mass_assignment_security/sanitizer_test.rb3
-rw-r--r--activemodel/test/cases/mass_assignment_security_test.rb39
-rw-r--r--activemodel/test/cases/observer_array_test.rb122
-rw-r--r--activemodel/test/cases/observing_test.rb5
-rw-r--r--activemodel/test/cases/secure_password_test.rb23
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb (renamed from activemodel/test/cases/serializeration/json_serialization_test.rb)0
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb (renamed from activemodel/test/cases/serializeration/xml_serialization_test.rb)0
-rw-r--r--activemodel/test/cases/validations/exclusion_validation_test.rb12
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb24
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb12
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb11
-rw-r--r--activemodel/test/models/mass_assignment_specific.rb11
-rw-r--r--activemodel/test/models/observers.rb27
-rw-r--r--activerecord/CHANGELOG72
-rw-r--r--activerecord/activerecord.gemspec1
-rw-r--r--activerecord/lib/active_record/associations.rb78
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb85
-rw-r--r--activerecord/lib/active_record/associations/association.rb45
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb120
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb24
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb4
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb25
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb22
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb32
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb265
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_part.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_helper.rb56
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb110
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb13
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb36
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb12
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb9
-rw-r--r--activerecord/lib/active_record/base.rb227
-rw-r--r--activerecord/lib/active_record/callbacks.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb48
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb30
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb38
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb39
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb110
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb325
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb22
-rw-r--r--activerecord/lib/active_record/fixtures.rb6
-rw-r--r--activerecord/lib/active_record/identity_map.rb9
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/locking/pessimistic.rb6
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb3
-rw-r--r--activerecord/lib/active_record/named_scope.rb81
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb7
-rw-r--r--activerecord/lib/active_record/persistence.rb37
-rw-r--r--activerecord/lib/active_record/railtie.rb3
-rw-r--r--activerecord/lib/active_record/railties/databases.rake14
-rw-r--r--activerecord/lib/active_record/railties/jdbcmysql_error.rb16
-rw-r--r--activerecord/lib/active_record/reflection.rb121
-rw-r--r--activerecord/lib/active_record/relation.rb70
-rw-r--r--activerecord/lib/active_record/relation/batches.rb2
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb35
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb47
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb18
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb10
-rw-r--r--activerecord/lib/active_record/result.rb4
-rw-r--r--activerecord/lib/active_record/transactions.rb1
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb54
-rw-r--r--activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb3
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb69
-rw-r--r--activerecord/test/cases/adapters/mysql/quoting_test.rb26
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb36
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb25
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb69
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb25
-rw-r--r--activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb210
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb93
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb231
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb30
-rw-r--r--activerecord/test/cases/associations/eager_test.rb22
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb20
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb36
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb14
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb28
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb38
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb546
-rw-r--r--activerecord/test/cases/associations_test.rb4
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb5
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb56
-rw-r--r--activerecord/test/cases/base_test.rb46
-rw-r--r--activerecord/test/cases/batches_test.rb12
-rw-r--r--activerecord/test/cases/binary_test.rb19
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb2
-rw-r--r--activerecord/test/cases/calculations_test.rb41
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb33
-rw-r--r--activerecord/test/cases/connection_management_test.rb95
-rw-r--r--activerecord/test/cases/connection_pool_test.rb8
-rw-r--r--activerecord/test/cases/defaults_test.rb3
-rw-r--r--activerecord/test/cases/dirty_test.rb2
-rw-r--r--activerecord/test/cases/finder_test.rb54
-rw-r--r--activerecord/test/cases/habtm_destroy_order_test.rb34
-rw-r--r--activerecord/test/cases/identity_map_test.rb40
-rw-r--r--activerecord/test/cases/json_serialization_test.rb8
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb30
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb71
-rw-r--r--activerecord/test/cases/method_scoping_test.rb12
-rw-r--r--activerecord/test/cases/named_scope_test.rb51
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb8
-rw-r--r--activerecord/test/cases/persistence_test.rb128
-rw-r--r--activerecord/test/cases/primary_keys_test.rb9
-rw-r--r--activerecord/test/cases/query_cache_test.rb2
-rw-r--r--activerecord/test/cases/quoting_test.rb13
-rw-r--r--activerecord/test/cases/reflection_test.rb57
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb119
-rw-r--r--activerecord/test/cases/relation_test.rb2
-rw-r--r--activerecord/test/cases/relations_test.rb108
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb7
-rw-r--r--activerecord/test/cases/timestamp_test.rb3
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb26
-rw-r--r--activerecord/test/fixtures/authors.yml6
-rw-r--r--activerecord/test/fixtures/books.yml2
-rw-r--r--activerecord/test/fixtures/categories.yml5
-rw-r--r--activerecord/test/fixtures/categories_posts.yml8
-rw-r--r--activerecord/test/fixtures/categorizations.yml6
-rw-r--r--activerecord/test/fixtures/clubs.yml4
-rw-r--r--activerecord/test/fixtures/essays.yml6
-rw-r--r--activerecord/test/fixtures/member_details.yml8
-rw-r--r--activerecord/test/fixtures/members.yml3
-rw-r--r--activerecord/test/fixtures/memberships.yml7
-rw-r--r--activerecord/test/fixtures/owners.yml1
-rw-r--r--activerecord/test/fixtures/posts.yml28
-rw-r--r--activerecord/test/fixtures/ratings.yml14
-rw-r--r--activerecord/test/fixtures/taggings.yml50
-rw-r--r--activerecord/test/fixtures/tags.yml6
-rw-r--r--activerecord/test/models/author.rb42
-rw-r--r--activerecord/test/models/book.rb2
-rw-r--r--activerecord/test/models/bulb.rb12
-rw-r--r--activerecord/test/models/car.rb3
-rw-r--r--activerecord/test/models/categorization.rb3
-rw-r--r--activerecord/test/models/category.rb2
-rw-r--r--activerecord/test/models/club.rb1
-rw-r--r--activerecord/test/models/comment.rb1
-rw-r--r--activerecord/test/models/developer.rb50
-rw-r--r--activerecord/test/models/essay.rb2
-rw-r--r--activerecord/test/models/job.rb2
-rw-r--r--activerecord/test/models/loose_person.rb24
-rw-r--r--activerecord/test/models/member.rb11
-rw-r--r--activerecord/test/models/member_detail.rb2
-rw-r--r--activerecord/test/models/organization.rb8
-rw-r--r--activerecord/test/models/person.rb22
-rw-r--r--activerecord/test/models/pirate.rb2
-rw-r--r--activerecord/test/models/post.rb22
-rw-r--r--activerecord/test/models/rating.rb4
-rw-r--r--activerecord/test/models/reference.rb6
-rw-r--r--activerecord/test/models/tagging.rb2
-rw-r--r--activerecord/test/models/without_table.rb2
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb10
-rw-r--r--activerecord/test/schema/schema.rb17
-rw-r--r--activeresource/activeresource.gemspec1
-rw-r--r--activeresource/lib/active_resource/connection.rb3
-rw-r--r--activeresource/lib/active_resource/http_mock.rb3
-rw-r--r--activeresource/test/cases/http_mock_test.rb3
-rw-r--r--activeresource/test/connection_test.rb2
-rw-r--r--activesupport/CHANGELOG4
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support.rb1
-rw-r--r--activesupport/lib/active_support/buffered_logger.rb13
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb7
-rw-r--r--activesupport/lib/active_support/callbacks.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/array/random_access.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/zones.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb49
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb57
-rw-r--r--activesupport/lib/active_support/core_ext/object/inclusion.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/string.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/string/inquiry.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb41
-rw-r--r--activesupport/lib/active_support/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb2
-rw-r--r--activesupport/lib/active_support/json/backends/jsongem.rb47
-rw-r--r--activesupport/lib/active_support/json/backends/yajl.rb44
-rw-r--r--activesupport/lib/active_support/json/backends/yaml.rb111
-rw-r--r--activesupport/lib/active_support/json/decoding.rb60
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb2
-rw-r--r--activesupport/lib/active_support/railtie.rb2
-rw-r--r--activesupport/lib/active_support/test_case.rb9
-rw-r--r--activesupport/lib/active_support/testing/mochaing.rb7
-rw-r--r--activesupport/lib/active_support/testing/pending.rb46
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb5
-rw-r--r--activesupport/test/callback_inheritance_test.rb30
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb2
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb9
-rw-r--r--activesupport/test/core_ext/object/inclusion_test.rb50
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb5
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb47
-rw-r--r--activesupport/test/file_watcher_test.rb233
-rw-r--r--activesupport/test/inflector_test.rb42
-rw-r--r--activesupport/test/json/decoding_test.rb17
-rw-r--r--activesupport/test/ordered_options_test.rb26
-rw-r--r--activesupport/test/transliterate_test.rb3
-rwxr-xr-xbin/rails6
-rw-r--r--load_paths.rb39
-rw-r--r--rails.gemspec1
-rw-r--r--railties/CHANGELOG26
-rw-r--r--railties/guides/rails_guides/textile_extensions.rb6
-rw-r--r--railties/guides/source/2_3_release_notes.textile2
-rw-r--r--railties/guides/source/3_0_release_notes.textile4
-rw-r--r--railties/guides/source/action_controller_overview.textile41
-rw-r--r--railties/guides/source/action_view_overview.textile240
-rw-r--r--railties/guides/source/active_record_querying.textile80
-rw-r--r--railties/guides/source/active_record_validations_callbacks.textile34
-rw-r--r--railties/guides/source/active_support_core_extensions.textile58
-rw-r--r--railties/guides/source/ajax_on_rails.textile147
-rw-r--r--railties/guides/source/api_documentation_guidelines.textile2
-rw-r--r--railties/guides/source/caching_with_rails.textile14
-rw-r--r--railties/guides/source/command_line.textile15
-rw-r--r--railties/guides/source/configuring.textile7
-rw-r--r--railties/guides/source/contributing_to_ruby_on_rails.textile31
-rw-r--r--railties/guides/source/debugging_rails_applications.textile57
-rw-r--r--railties/guides/source/form_helpers.textile8
-rw-r--r--railties/guides/source/generators.textile4
-rw-r--r--railties/guides/source/getting_started.textile100
-rw-r--r--railties/guides/source/i18n.textile16
-rw-r--r--railties/guides/source/index.html.erb2
-rw-r--r--railties/guides/source/initialization.textile25
-rw-r--r--railties/guides/source/layout.html.erb2
-rw-r--r--railties/guides/source/layouts_and_rendering.textile38
-rw-r--r--railties/guides/source/migrations.textile4
-rw-r--r--railties/guides/source/plugins.textile59
-rw-r--r--railties/guides/source/rails_application_templates.textile44
-rw-r--r--railties/guides/source/routing.textile16
-rw-r--r--railties/guides/source/ruby_on_rails_guides_guidelines.textile17
-rw-r--r--railties/guides/source/security.textile49
-rw-r--r--railties/guides/source/testing.textile18
-rw-r--r--railties/lib/rails/application.rb30
-rw-r--r--railties/lib/rails/application/configuration.rb25
-rw-r--r--railties/lib/rails/application/finisher.rb6
-rw-r--r--railties/lib/rails/commands.rb4
-rw-r--r--railties/lib/rails/commands/application.rb3
-rw-r--r--railties/lib/rails/commands/benchmarker.rb4
-rw-r--r--railties/lib/rails/commands/console.rb6
-rw-r--r--railties/lib/rails/commands/destroy.rb4
-rw-r--r--railties/lib/rails/commands/generate.rb4
-rw-r--r--railties/lib/rails/commands/profiler.rb4
-rw-r--r--railties/lib/rails/engine.rb105
-rw-r--r--railties/lib/rails/engine/configuration.rb11
-rw-r--r--railties/lib/rails/generators.rb9
-rw-r--r--railties/lib/rails/generators/app_base.rb96
-rw-r--r--railties/lib/rails/generators/base.rb3
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb2
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb3
-rw-r--r--railties/lib/rails/generators/named_base.rb13
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb94
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile27
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.pngbin0 -> 6646 bytes
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt11
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb9
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml30
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml48
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml17
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt25
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/db/seeds.rb)4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/index.html2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/javascripts/application.js2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb (renamed from railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/controls.js (renamed from railties/lib/rails/generators/rails/app/templates/public/javascripts/controls.js)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/dragdrop.js (renamed from railties/lib/rails/generators/rails/app/templates/public/javascripts/dragdrop.js)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/effects.js (renamed from railties/lib/rails/generators/rails/app/templates/public/javascripts/effects.js)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery.js (renamed from railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery.js)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js (renamed from railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery_ujs.js)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype.js (renamed from railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js (renamed from railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype_ujs.js)0
-rw-r--r--railties/lib/rails/generators/rails/assets/USAGE20
-rw-r--r--railties/lib/rails/generators/rails/assets/assets_generator.rb39
-rw-r--r--railties/lib/rails/generators/rails/assets/templates/javascript.js2
-rw-r--r--railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee3
-rw-r--r--railties/lib/rails/generators/rails/assets/templates/stylesheet.css4
-rw-r--r--railties/lib/rails/generators/rails/assets/templates/stylesheet.css.scss5
-rw-r--r--railties/lib/rails/generators/rails/controller/controller_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb73
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/app/models/.empty_directory0
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb7
-rw-r--r--railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb21
-rw-r--r--railties/lib/rails/generators/rails/scaffold/templates/scaffold.css (renamed from railties/lib/rails/generators/rails/stylesheets/templates/scaffold.css)0
-rw-r--r--railties/lib/rails/generators/rails/scaffold/templates/scaffold.css.scss58
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb38
-rw-r--r--railties/lib/rails/generators/rails/stylesheets/USAGE5
-rw-r--r--railties/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb9
-rw-r--r--railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb7
-rw-r--r--railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb7
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb7
-rw-r--r--railties/lib/rails/generators/test_unit/model/templates/unit_test.rb7
-rw-r--r--railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb7
-rw-r--r--railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt7
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb10
-rw-r--r--railties/lib/rails/railtie/configuration.rb15
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb10
-rw-r--r--railties/lib/rails/tasks.rb2
-rw-r--r--railties/lib/rails/tasks/assets.rake10
-rw-r--r--railties/lib/rails/tasks/railties.rake29
-rw-r--r--railties/lib/rails/test_help.rb12
-rw-r--r--railties/railties.gemspec2
-rw-r--r--railties/test/application/configuration_test.rb99
-rw-r--r--railties/test/application/middleware_test.rb6
-rw-r--r--railties/test/generators/app_generator_test.rb92
-rw-r--r--railties/test/generators/assets_generator_test.rb26
-rw-r--r--railties/test/generators/controller_generator_test.rb6
-rw-r--r--railties/test/generators/namespaced_generators_test.rb8
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb65
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb18
-rw-r--r--railties/test/generators/scaffold_generator_test.rb52
-rw-r--r--railties/test/generators/shared_generator_tests.rb27
-rw-r--r--railties/test/generators/stylesheets_generator_test.rb17
-rw-r--r--railties/test/isolation/abstract_unit.rb3
-rw-r--r--railties/test/railties/engine_test.rb292
-rw-r--r--railties/test/railties/shared_tests.rb42
-rw-r--r--tasks/release.rb26
527 files changed, 10035 insertions, 7322 deletions
diff --git a/Gemfile b/Gemfile
index 7d8949409c..8b6ddbd7c9 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,6 +11,11 @@ end
gem "rack", :git => "git://github.com/rack/rack.git"
gem "rack-test", :git => "git://github.com/brynary/rack-test.git"
+gem "sprockets", :git => "git://github.com/sstephenson/sprockets.git"
+gem "coffee-script"
+gem "sass"
+gem "uglifier"
+
gem "rake", ">= 0.8.7"
gem "mocha", ">= 0.9.8"
@@ -22,12 +27,12 @@ end
# AS
gem "memcache-client", ">= 1.8.5"
-gem "fssm", "~> 0.2.5"
platforms :mri_18 do
gem "system_timer"
gem "ruby-debug", ">= 0.10.3"
gem 'ruby-prof'
+ gem "json"
end
platforms :mri_19 do
@@ -36,6 +41,9 @@ platforms :mri_19 do
end
platforms :ruby do
+ if ENV["RB_FSEVENT"]
+ gem 'rb-fsevent'
+ end
gem 'json'
gem 'yajl-ruby'
gem "nokogiri", ">= 1.4.4"
@@ -44,15 +52,15 @@ platforms :ruby do
gem "sqlite3", "~> 1.3.3"
group :db do
- gem "pg", ">= 0.9.0"
+ gem "pg", ">= 0.11.0"
gem "mysql", ">= 2.8.1"
- gem "mysql2", :git => "git://github.com/brianmario/mysql2.git"
+ gem "mysql2", ">= 0.3.0"
end
end
platforms :jruby do
gem "ruby-debug", ">= 0.10.3"
-
+ gem "json"
gem "activerecord-jdbcsqlite3-adapter"
# This is needed by now to let tests work on JRuby
diff --git a/README.rdoc b/README.rdoc
index 0b209cf56f..216a122c66 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -48,7 +48,7 @@ more separate. Each of these packages can be used independently outside of
"Welcome aboard: You're riding Ruby on Rails!"
-5. Follow the guidelines to start developing your application. You can find the following resources handy:
+5. Follow the guidelines to start developing your application. You may find the following resources handy:
* The README file created within your application.
* The {Getting Started with Rails}[http://guides.rubyonrails.org/getting_started.html].
diff --git a/Rakefile b/Rakefile
index 53acd077d7..607ce01fd0 100755
--- a/Rakefile
+++ b/Rakefile
@@ -85,6 +85,7 @@ RDoc::Task.new do |rdoc|
rdoc.rdoc_files.include('actionmailer/README.rdoc')
rdoc.rdoc_files.include('actionmailer/CHANGELOG')
rdoc.rdoc_files.include('actionmailer/lib/action_mailer/base.rb')
+ rdoc.rdoc_files.include('actionmailer/lib/action_mailer/mail_helper.rb')
rdoc.rdoc_files.exclude('actionmailer/lib/action_mailer/vendor/*')
rdoc.rdoc_files.include('activesupport/README.rdoc')
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index 0fa18d751b..9b206fbcc7 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -32,7 +32,7 @@ This can be as simple as:
end
The body of the email is created by using an Action View template (regular
-ERb) that has the instance variables that are declared in the mailer action.
+ERB) that has the instance variables that are declared in the mailer action.
So the corresponding body template for the method above could look like this:
@@ -72,6 +72,19 @@ Or you can just chain the methods together like:
Notifier.welcome.deliver # Creates the email and sends it immediately
+== Setting defaults
+
+It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from ActionMailer::Base. This method accepts a Hash as the parameter. You can use any of the headers e-mail messages has, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you wont need to worry about that. Finally it is also possible to pass in a Proc that will get evaluated when it is needed.
+
+Note that every value you set with this method will get over written if you use the same key in your mailer method.
+
+Example:
+
+ class Authenticationmailer < ActionMailer::Base
+ default :from => "awesome@application.com", :subject => Proc.new { "E-mail was generated at #{Time.now}" }
+ .....
+ end
+
== Receiving emails
To receive emails, you need to implement a public instance method called <tt>receive</tt> that takes an
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index 2ae85f8b57..447e25ca8a 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -17,8 +17,6 @@ Gem::Specification.new do |s|
s.require_path = 'lib'
s.requirements << 'none'
- s.has_rdoc = true
-
s.add_dependency('actionpack', version)
- s.add_dependency('mail', '~> 2.2.15')
+ s.add_dependency('mail', '~> 2.3.0')
end
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 16fcf112b7..1e91d62e24 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -4,6 +4,8 @@ require 'action_mailer/collector'
require 'active_support/core_ext/array/wrap'
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'
module ActionMailer #:nodoc:
@@ -295,6 +297,10 @@ module ActionMailer #:nodoc:
# information and a cryptographic Message Digest 5 algorithm to hash important information)
# * <tt>:enable_starttls_auto</tt> - When set to true, detects if STARTTLS is enabled in your SMTP server
# and starts to use it.
+ # * <tt>:openssl_verify_mode</tt> - When using TLS, you can set how OpenSSL checks the certificate. This is
+ # really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name
+ # of an OpenSSL verify constant ('none', 'peer', 'client_once','fail_if_no_peer_cert') or directly the
+ # constant (OpenSSL::SSL::VERIFY_NONE, OpenSSL::SSL::VERIFY_PEER,...).
#
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
# * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
@@ -349,9 +355,6 @@ module ActionMailer #:nodoc:
helper ActionMailer::MailHelper
include ActionMailer::OldApi
- delegate :register_observer, :to => Mail
- delegate :register_interceptor, :to => Mail
-
private_class_method :new #:nodoc:
class_attribute :default_params
@@ -363,6 +366,32 @@ module ActionMailer #:nodoc:
}.freeze
class << self
+ # Register one or more Observers which will be notified when mail is delivered.
+ def register_observers(*observers)
+ observers.flatten.compact.each { |observer| register_observer(observer) }
+ end
+
+ # Register one or more Interceptors which will be called before mail is sent.
+ def register_interceptors(*interceptors)
+ interceptors.flatten.compact.each { |interceptor| register_interceptor(interceptor) }
+ end
+
+ # Register an Observer which will be notified when mail is delivered.
+ # Either a class or a string can be passed in as the Observer. If a string is passed in
+ # it will be <tt>constantize</tt>d.
+ def register_observer(observer)
+ delivery_observer = (observer.is_a?(String) ? observer.constantize : observer)
+ Mail.register_observer(delivery_observer)
+ end
+
+ # Register an Inteceptor which will be called before mail is sent.
+ # Either a class or a string can be passed in as the Observer. If a string is passed in
+ # it will be <tt>constantize</tt>d.
+ def register_interceptor(interceptor)
+ delivery_interceptor = (interceptor.is_a?(String) ? interceptor.constantize : interceptor)
+ Mail.register_interceptor(delivery_interceptor)
+ end
+
def mailer_name
@mailer_name ||= name.underscore
end
@@ -657,6 +686,9 @@ module ActionMailer #:nodoc:
end
end
+ # Translates the +subject+ using Rails I18n class under <tt>[:actionmailer, mailer_scope, action_name]</tt> scope.
+ # If it does not find a translation for the +subject+ under the specified scope it will default to a
+ # humanized version of the <tt>action_name</tt>.
def default_i18n_subject #:nodoc:
mailer_scope = self.class.mailer_name.gsub('/', '.')
I18n.t(:subject, :scope => [mailer_scope, action_name], :default => action_name.humanize)
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index 4ec478067f..444754d0e9 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -19,13 +19,17 @@ module ActionMailer
options.stylesheets_dir ||= paths["public/stylesheets"].first
# make sure readers methods get compiled
- options.asset_path ||= app.config.asset_path
- options.asset_host ||= app.config.asset_host
+ options.asset_path ||= app.config.asset_path
+ options.asset_host ||= app.config.asset_host
ActiveSupport.on_load(:action_mailer) do
include AbstractController::UrlFor
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
include app.routes.mounted_helpers
+
+ register_interceptors(options.delete(:interceptors))
+ register_observers(options.delete(:observers))
+
options.each { |k,v| send("#{k}=", v) }
end
end
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index ce664bf301..0b076e1ff9 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -23,11 +23,6 @@ if "ruby".encoding_aware?
end
end
-silence_warnings do
- # These external dependencies have warnings :/
- require 'mail'
-end
-
lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
@@ -35,6 +30,11 @@ require 'test/unit'
require 'action_mailer'
require 'action_mailer/test_case'
+silence_warnings do
+ # These external dependencies have warnings :/
+ require 'mail'
+end
+
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 6a7931da8c..9fdd0e1ced 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -478,6 +478,11 @@ class BaseTest < ActiveSupport::TestCase
end
end
+ class MySecondObserver
+ def self.delivered_email(mail)
+ end
+ end
+
test "you can register an observer to the mail object that gets informed on email delivery" do
ActionMailer::Base.register_observer(MyObserver)
mail = BaseMailer.welcome
@@ -485,11 +490,31 @@ class BaseTest < ActiveSupport::TestCase
mail.deliver
end
+ test "you can register an observer using its stringified name to the mail object that gets informed on email delivery" do
+ ActionMailer::Base.register_observer("BaseTest::MyObserver")
+ mail = BaseMailer.welcome
+ MyObserver.expects(:delivered_email).with(mail)
+ mail.deliver
+ end
+
+ test "you can register multiple observers to the mail object that both get informed on email delivery" do
+ ActionMailer::Base.register_observers("BaseTest::MyObserver", MySecondObserver)
+ mail = BaseMailer.welcome
+ MyObserver.expects(:delivered_email).with(mail)
+ MySecondObserver.expects(:delivered_email).with(mail)
+ mail.deliver
+ end
+
class MyInterceptor
def self.delivering_email(mail)
end
end
+ class MySecondInterceptor
+ def self.delivering_email(mail)
+ end
+ end
+
test "you can register an interceptor to the mail object that gets passed the mail object before delivery" do
ActionMailer::Base.register_interceptor(MyInterceptor)
mail = BaseMailer.welcome
@@ -497,6 +522,21 @@ class BaseTest < ActiveSupport::TestCase
mail.deliver
end
+ test "you can register an interceptor using its stringified name to the mail object that gets passed the mail object before delivery" do
+ ActionMailer::Base.register_interceptor("BaseTest::MyInterceptor")
+ mail = BaseMailer.welcome
+ MyInterceptor.expects(:delivering_email).with(mail)
+ mail.deliver
+ end
+
+ test "you can register multiple interceptors to the mail object that both get passed the mail object before delivery" do
+ ActionMailer::Base.register_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
+ mail = BaseMailer.welcome
+ MyInterceptor.expects(:delivering_email).with(mail)
+ MySecondInterceptor.expects(:delivering_email).with(mail)
+ mail.deliver
+ end
+
test "being able to put proc's into the defaults hash and they get evaluated on mail sending" do
mail1 = ProcMailer.welcome
yesterday = 1.day.ago
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 5ab92c8cfc..76dbfe7895 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,62 @@
*Rails 3.1.0 (unreleased)*
+* RJS has been extracted out to a gem. [fxn]
+
+* Implicit actions named not_implemented can be rendered [Santiago Pastorino]
+
+* Wildcard route will always matching the optional format segment by default. For example if you have this route:
+
+ map '*pages' => 'pages#show'
+
+ by requesting '/foo/bar.json', your `params[:pages]` will be equals to "foo/bar" with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `:format => false` like this:
+
+ map '*pages' => 'pages#show', :format => false
+
+* Added Base.http_basic_authenticate_with to do simple http basic authentication with a single class method call [DHH]
+
+ class PostsController < ApplicationController
+ USER_NAME, PASSWORD = "dhh", "secret"
+
+ before_filter :authenticate, :except => [ :index ]
+
+ def index
+ render :text => "Everyone can see me!"
+ end
+
+ def edit
+ render :text => "I'm only accessible if you know the password"
+ end
+
+ private
+ def authenticate
+ authenticate_or_request_with_http_basic do |user_name, password|
+ user_name == USER_NAME && password == PASSWORD
+ end
+ end
+ end
+
+ ..can now be written as
+
+ class PostsController < ApplicationController
+ http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index
+
+ def index
+ render :text => "Everyone can see me!"
+ end
+
+ def edit
+ render :text => "I'm only accessible if you know the password"
+ end
+ end
+
+* Allow you to add `force_ssl` into controller to force browser to transfer data via HTTPS protocol on that particular controller. You can also specify `:only` or `:except` to specific it to particular action. [DHH and Prem Sichanugrist]
+
+* Allow FormHelper#form_for to specify the :method as a direct option instead of through the :html hash [DHH]
+
+ form_for(@post, remote: true, method: :delete) instead of form_for(@post, remote: true, html: { method: :delete })
+
+* Make JavaScriptHelper#j() an alias for JavaScriptHelper#escape_javascript() -- note this then supersedes the Object#j() method that the JSON gem adds within templates using the JavaScriptHelper [DHH]
+
* Sensitive query string parameters (specified in config.filter_parameters) will now be filtered out from the request paths in the log file. [Prem Sichanugrist, fxn]
* URL parameters which return false for to_param now appear in the query string (previously they were removed) [Andrew White]
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 3661d27d51..1a56c44db5 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -19,9 +19,8 @@ It consists of several modules:
* Action View, which handles view template lookup and rendering, and provides
view helpers that assist when building HTML forms, Atom feeds and more.
- Template formats that Action View handles are ERb (embedded Ruby, typically
- used to inline short Ruby snippets inside HTML), XML Builder and RJS
- (dynamically generated JavaScript from Ruby code).
+ Template formats that Action View handles are ERB (embedded Ruby, typically
+ used to inline short Ruby snippets inside HTML), and XML Builder.
With the Ruby on Rails framework, users only directly interface with the
Action Controller module. Necessary Action Dispatch functionality is activated
@@ -57,7 +56,7 @@ A short rundown of some of the major features:
{Learn more}[link:classes/ActionController/Base.html]
-* ERb templates (static content mixed with dynamic output from ruby)
+* ERB templates (static content mixed with dynamic output from ruby)
<% for post in @posts %>
Title: <%= post.title %>
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index f6bc5e0d37..f771737779 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -17,16 +17,14 @@ Gem::Specification.new do |s|
s.require_path = 'lib'
s.requirements << 'none'
- s.has_rdoc = true
-
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
s.add_dependency('rack-cache', '~> 1.0.0')
s.add_dependency('builder', '~> 3.0.0')
- s.add_dependency('i18n', '~> 0.5.0')
+ s.add_dependency('i18n', '~> 0.6.0beta1')
s.add_dependency('rack', '~> 1.2.1')
s.add_dependency('rack-test', '~> 0.5.7')
- s.add_dependency('rack-mount', '~> 0.6.13')
+ s.add_dependency('rack-mount', '~> 0.7.1')
s.add_dependency('tzinfo', '~> 0.3.23')
- s.add_dependency('erubis', '~> 2.6.6')
+ s.add_dependency('erubis', '~> 2.7.0')
end
diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb
index 9ca2fb742f..ad14cd6d87 100644
--- a/actionpack/lib/abstract_controller/asset_paths.rb
+++ b/actionpack/lib/abstract_controller/asset_paths.rb
@@ -3,7 +3,7 @@ module AbstractController
extend ActiveSupport::Concern
included do
- config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir, :stylesheets_dir
+ config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir, :stylesheets_dir, :use_sprockets
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index c384fd0978..0951267fea 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -1,3 +1,4 @@
+require 'erubis'
require 'active_support/configurable'
require 'active_support/descendants_tracker'
require 'active_support/core_ext/module/anonymous'
@@ -18,6 +19,7 @@ module AbstractController
include ActiveSupport::Configurable
extend ActiveSupport::DescendantsTracker
+ undef_method :not_implemented
class << self
attr_reader :abstract
alias_method :abstract?, :abstract
@@ -128,20 +130,23 @@ module AbstractController
self.class.action_methods
end
- private
+ # Returns true if the name can be considered an action. This can
+ # be overridden in subclasses to modify the semantics of what
+ # can be considered an action.
+ #
+ # For instance, this is overriden by ActionController to add
+ # the implicit rendering feature.
+ #
+ # ==== Parameters
+ # * <tt>name</tt> - The name of an action to be tested
+ #
+ # ==== Returns
+ # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
+ def action_method?(name)
+ self.class.action_methods.include?(name)
+ end
- # Returns true if the name can be considered an action. This can
- # be overridden in subclasses to modify the semantics of what
- # can be considered an action.
- #
- # ==== Parameters
- # * <tt>name</tt> - The name of an action to be tested
- #
- # ==== Returns
- # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
- def action_method?(name)
- self.class.action_methods.include?(name)
- end
+ private
# Call the action. Override this in a subclass to modify the
# behavior around processing an action. This, and not #process,
@@ -160,8 +165,8 @@ module AbstractController
# If the action name was not found, but a method called "action_missing"
# was found, #method_for_action will return "_handle_action_missing".
# This method calls #action_missing with the current action name.
- def _handle_action_missing
- action_missing(@_action_name)
+ def _handle_action_missing(*args)
+ action_missing(@_action_name, *args)
end
# Takes an action name and returns the name of the method that will
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 95992c2698..f7b2b7ff53 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -29,7 +29,7 @@ module AbstractController
#
# ==== Options
# * <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>except</tt> - The callback should be run for all actions except this action
def _normalize_callback_options(options)
if only = options[:only]
only = Array(only).map {|o| "action_name == '#{o}'"}.join(" || ")
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 20f8601a8e..0ff1c0491a 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -112,17 +112,6 @@ module AbstractController
default_helper_module! unless anonymous?
end
- private
- # Makes all the (instance) methods in the helper module available to templates
- # rendered through this controller.
- #
- # ==== Parameters
- # * <tt>module</tt> - The module to include into the current helper module
- # for the class
- def add_template_helper(mod)
- _helpers.module_eval { include mod }
- end
-
# Returns a list of modules, normalized from the acceptable kinds of
# helpers with the following behavior:
#
@@ -155,6 +144,17 @@ module AbstractController
end
end
+ private
+ # Makes all the (instance) methods in the helper module available to templates
+ # rendered through this controller.
+ #
+ # ==== Parameters
+ # * <tt>module</tt> - The module to include into the current helper module
+ # for the class
+ def add_template_helper(mod)
+ _helpers.module_eval { include mod }
+ end
+
def default_helper_module!
module_name = name.sub(/Controller$/, '')
module_path = module_name.underscore
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 4ee54474cc..d1b87b67ee 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -334,7 +334,7 @@ module AbstractController
# ==== Parameters
# * <tt>details</tt> - A list of details to restrict the search by. This
# might include details like the format or locale of the template.
- # * <tt>require_logout</tt> - If this is true, raise an ArgumentError
+ # * <tt>require_layout</tt> - If this is true, raise an ArgumentError
# with details about the fact that the exception could not be
# found (defaults to false)
#
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 691310d5d2..306bd41e2d 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -1,5 +1,6 @@
require "abstract_controller/base"
require "action_view"
+require "active_support/core_ext/object/instance_variables"
module AbstractController
class DoubleRenderError < Error
@@ -104,16 +105,16 @@ module AbstractController
# Normalize arguments, options and then delegates render_to_body and
# sticks the result in self.response_body.
def render(*args, &block)
- self.response_body = render_to_string(*args, &block)
+ options = _normalize_render(*args, &block)
+ self.response_body = render_to_body(options)
end
# Raw rendering of a template to a string. Just convert the results of
- # render_to_body into a String.
+ # render_response into a String.
# :api: plugin
def render_to_string(*args, &block)
- options = _normalize_args(*args, &block)
- _normalize_options(options)
- render_to_body(options).tap { self.response_body = nil }
+ options = _normalize_render(*args, &block)
+ render_to_body(options)
end
# Raw rendering of a template to a Rack-compatible body.
@@ -150,8 +151,17 @@ module AbstractController
hash
end
- # Normalize options by converting render "foo" to render :action => "foo" and
+ # Normalize args and options.
+ # :api: private
+ def _normalize_render(*args, &block)
+ options = _normalize_args(*args, &block)
+ _normalize_options(options)
+ options
+ end
+
+ # Normalize args by converting render "foo" to render :action => "foo" and
# render "foo/bar" to render :file => "foo/bar".
+ # :api: plugin
def _normalize_args(action=nil, options={})
case action
when NilClass
@@ -168,12 +178,14 @@ module AbstractController
options
end
+ # Normalize options.
+ # :api: plugin
def _normalize_options(options)
if options[:partial] == true
options[:partial] = action_name
end
- if (options.keys & [:partial, :file, :template, :once]).empty?
+ if (options.keys & [:partial, :file, :template]).empty?
options[:prefixes] ||= _prefixes
end
@@ -181,6 +193,8 @@ module AbstractController
options
end
+ # Process extra options.
+ # :api: plugin
def _process_options(options)
end
end
diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb
index 6544c8949a..cea0f5ad1e 100644
--- a/actionpack/lib/abstract_controller/view_paths.rb
+++ b/actionpack/lib/abstract_controller/view_paths.rb
@@ -36,7 +36,7 @@ module AbstractController
# ==== Parameters
# * <tt>path</tt> - If a String is provided, it gets converted into
# the default view path. You may also provide a custom view path
- # (see ActionView::ViewPathSet for more information)
+ # (see ActionView::PathSet for more information)
def append_view_path(path)
self.view_paths = view_paths.dup + Array(path)
end
@@ -46,7 +46,7 @@ module AbstractController
# ==== Parameters
# * <tt>path</tt> - If a String is provided, it gets converted into
# the default view path. You may also provide a custom view path
- # (see ActionView::ViewPathSet for more information)
+ # (see ActionView::PathSet for more information)
def prepend_view_path(path)
self.view_paths = Array(path) + view_paths.dup
end
@@ -59,8 +59,8 @@ module AbstractController
# Set the view paths.
#
# ==== Parameters
- # * <tt>paths</tt> - If a ViewPathSet is provided, use that;
- # otherwise, process the parameter into a ViewPathSet.
+ # * <tt>paths</tt> - If a PathSet is provided, use that;
+ # otherwise, process the parameter into a PathSet.
def view_paths=(paths)
self._view_paths = ActionView::Base.process_view_paths(paths)
self._view_paths.freeze
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 5b81cd39f4..aab2b9dc25 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -13,7 +13,9 @@ module ActionController
autoload :Compatibility
autoload :ConditionalGet
autoload :Cookies
+ autoload :DataStreaming
autoload :Flash
+ autoload :ForceSSL
autoload :Head
autoload :Helpers
autoload :HideActions
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 81c0698fb8..ca0dccf575 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -105,7 +105,7 @@ module ActionController
# == Renders
#
# Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering
- # of a template. Included in the Action Pack is the Action View, which enables rendering of ERb templates. It's automatically configured.
+ # of a template. Included in the Action Pack is the Action View, which enables rendering of ERB templates. It's automatically configured.
# The controller passes objects to the view by assigning instance variables:
#
# def show
@@ -128,7 +128,7 @@ module ActionController
# end
# end
#
- # Read more about writing ERb and Builder templates in ActionView::Base.
+ # Read more about writing ERB and Builder templates in ActionView::Base.
#
# == Redirects
#
@@ -198,7 +198,9 @@ module ActionController
Cookies,
Flash,
RequestForgeryProtection,
+ ForceSSL,
Streaming,
+ DataStreaming,
RecordIdentifier,
HttpAuthentication::Basic::ControllerMethods,
HttpAuthentication::Digest::ControllerMethods,
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index 2c8a6e4d4d..5fc6956266 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -56,19 +56,18 @@ module ActionController #:nodoc:
#
# caches_page :public
#
- # caches_action :index, :if => proc do |c|
- # !c.request.format.json? # cache if is not a JSON request
+ # caches_action :index, :if => proc 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 |c|
- # if c.params[:user_id]
- # c.send(:user_list_url,
- # c.params[:user_id], c.params[:id])
+ # caches_action :feed, :cache_path => proc do
+ # if params[:user_id]
+ # user_list_url(params[:user_id, params[:id])
# else
- # c.send(:list_url, c.params[:id])
+ # list_url(params[:id])
# end
# end
# end
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index e5db31061b..0133b2ecbc 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -131,7 +131,7 @@ module ActionController
attr_internal :headers, :response, :request
delegate :session, :to => "@_request"
- def initialize(*)
+ def initialize
@_headers = {"Content-Type" => "text/html"}
@_status = 200
@_request = nil
@@ -201,19 +201,23 @@ module ActionController
class_attribute :middleware_stack
self.middleware_stack = ActionController::MiddlewareStack.new
- def self.inherited(base)
+ def self.inherited(base) #nodoc:
base.middleware_stack = self.middleware_stack.dup
super
end
+ # Adds given middleware class and its args to bottom of middleware_stack
def self.use(*args, &block)
middleware_stack.use(*args, &block)
end
+ # Alias for middleware_stack
def self.middleware
middleware_stack
end
+ # Makes the controller a rack endpoint that points to the action in
+ # the given env's action_dispatch.request.path_parameters key.
def self.call(env)
action(env['action_dispatch.request.path_parameters'][:action]).call(env)
end
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
new file mode 100644
index 0000000000..997bc6e958
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -0,0 +1,145 @@
+require 'active_support/core_ext/file/path'
+
+module ActionController #:nodoc:
+ # Methods for sending arbitrary data and for streaming files to the browser,
+ # instead of rendering.
+ module DataStreaming
+ extend ActiveSupport::Concern
+
+ include ActionController::Rendering
+
+ DEFAULT_SEND_FILE_OPTIONS = {
+ :type => 'application/octet-stream'.freeze,
+ :disposition => 'attachment'.freeze,
+ }.freeze
+
+ protected
+ # Sends the file. This uses a server-appropriate method (such as X-Sendfile)
+ # via the Rack::Sendfile middleware. The header to use is set via
+ # config.action_dispatch.x_sendfile_header, and defaults to "X-Sendfile".
+ # Your server can also configure this for you by setting the X-Sendfile-Type header.
+ #
+ # Be careful to sanitize the path parameter if it is coming from a web
+ # page. <tt>send_file(params[:path])</tt> allows a malicious user to
+ # download any file on your server.
+ #
+ # Options:
+ # * <tt>:filename</tt> - suggests a filename for the browser to use.
+ # Defaults to <tt>File.basename(path)</tt>.
+ # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
+ # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
+ # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
+ # Valid values are 'inline' and 'attachment' (default).
+ # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
+ # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
+ # the URL, which is necessary for i18n filenames on certain browsers
+ # (setting <tt>:filename</tt> overrides this option).
+ #
+ # The default Content-Type and Content-Disposition headers are
+ # set to download arbitrary binary files in as many browsers as
+ # possible. IE versions 4, 5, 5.5, and 6 are all known to have
+ # a variety of quirks (especially when downloading over SSL).
+ #
+ # Simple download:
+ #
+ # send_file '/path/to.zip'
+ #
+ # Show a JPEG in the browser:
+ #
+ # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
+ #
+ # Show a 404 page in the browser:
+ #
+ # send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404
+ #
+ # Read about the other Content-* HTTP headers if you'd like to
+ # provide the user with more information (such as Content-Description) in
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11.
+ #
+ # Also be aware that the document may be cached by proxies and browsers.
+ # The Pragma and Cache-Control headers declare how the file may be cached
+ # by intermediaries. They default to require clients to validate with
+ # the server before releasing cached responses. See
+ # http://www.mnot.net/cache_docs/ for an overview of web caching and
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+ # for the Cache-Control header spec.
+ def send_file(path, options = {}) #:doc:
+ raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
+
+ options[:filename] ||= File.basename(path) unless options[:url_based_filename]
+ send_file_headers! options
+
+ self.status = options[:status] || 200
+ self.content_type = options[:content_type] if options.key?(:content_type)
+ self.response_body = File.open(path, "rb")
+ end
+
+ # Sends the given binary data to the browser. This method is similar to
+ # <tt>render :text => data</tt>, but also allows you to specify whether
+ # the browser should display the response as a file attachment (i.e. in a
+ # download dialog) or as inline data. You may also set the content type,
+ # the apparent file name, and other things.
+ #
+ # Options:
+ # * <tt>:filename</tt> - suggests a filename for the browser to use.
+ # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
+ # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
+ # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
+ # Valid values are 'inline' and 'attachment' (default).
+ # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
+ #
+ # Generic data download:
+ #
+ # send_data buffer
+ #
+ # Download a dynamically-generated tarball:
+ #
+ # send_data generate_tgz('dir'), :filename => 'dir.tgz'
+ #
+ # Display an image Active Record in the browser:
+ #
+ # send_data image.data, :type => image.content_type, :disposition => 'inline'
+ #
+ # See +send_file+ for more information on HTTP Content-* headers and caching.
+ def send_data(data, options = {}) #:doc:
+ send_file_headers! options.dup
+ render options.slice(:status, :content_type).merge(:text => data)
+ end
+
+ private
+ def send_file_headers!(options)
+ options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
+ [:type, :disposition].each do |arg|
+ raise ArgumentError, ":#{arg} option required" if options[arg].nil?
+ end
+
+ disposition = options[:disposition]
+ disposition += %(; filename="#{options[:filename]}") if options[:filename]
+
+ content_type = options[:type]
+
+ if content_type.is_a?(Symbol)
+ extension = Mime[content_type]
+ raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension
+ self.content_type = extension
+ else
+ self.content_type = content_type
+ end
+
+ headers.merge!(
+ 'Content-Disposition' => disposition,
+ 'Content-Transfer-Encoding' => 'binary'
+ )
+
+ response.sending_file = true
+
+ # Fix a problem with IE 6.0 on opening downloaded files:
+ # If Cache-Control: no-cache is set (which Rails does by default),
+ # IE removes the file it just downloaded from its cache immediately
+ # after it displays the "open/save" dialog, which means that if you
+ # hit "open" the file isn't there anymore when the application that
+ # is called for handling the download is run, so let's workaround that
+ response.cache_control[:public] ||= false
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
new file mode 100644
index 0000000000..eb8ed7dfbd
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -0,0 +1,35 @@
+module ActionController
+ # This module provides a method which will redirects browser to use HTTPS
+ # protocol. This will ensure that user's sensitive information will be
+ # transferred safely over the internet. You _should_ always force browser
+ # to use HTTPS when you're transferring sensitive information such as
+ # user authentication, account information, or credit card information.
+ #
+ # Note that if you really concern about your application safety, you might
+ # consider using +config.force_ssl+ in your configuration config file instead.
+ # That will ensure all the data transferred via HTTPS protocol and prevent
+ # user from getting session hijacked when accessing the site under unsecured
+ # HTTP protocol.
+ module ForceSSL
+ extend ActiveSupport::Concern
+ include AbstractController::Callbacks
+
+ module ClassMethods
+ # 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.
+ #
+ # ==== Options
+ # * <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
+ def force_ssl(options = {})
+ before_filter(options) do
+ if !request.ssl? && !Rails.env.development?
+ redirect_to :protocol => 'https://', :status => :moved_permanently
+ end
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 91a88ab68a..75757db564 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -76,35 +76,35 @@ module ActionController
@helper_proxy ||= ActionView::Base.new.extend(_helpers)
end
- private
- # Overwrite modules_for_helpers to accept :all as argument, which loads
- # all helpers in helpers_path.
- #
- # ==== Parameters
- # * <tt>args</tt> - A list of helpers
- #
- # ==== Returns
- # * <tt>array</tt> - A normalized list of modules for the list of helpers provided.
- def modules_for_helpers(args)
- args += all_application_helpers if args.delete(:all)
- super(args)
- end
+ # Overwrite modules_for_helpers to accept :all as argument, which loads
+ # all helpers in helpers_path.
+ #
+ # ==== Parameters
+ # * <tt>args</tt> - A list of helpers
+ #
+ # ==== Returns
+ # * <tt>array</tt> - A normalized list of modules for the list of helpers provided.
+ def modules_for_helpers(args)
+ args += all_application_helpers if args.delete(:all)
+ super(args)
+ end
- # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt>
- def all_application_helpers
- all_helpers_from_path(helpers_path)
+ def all_helpers_from_path(path)
+ helpers = []
+ Array.wrap(path).each do |_path|
+ extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
+ helpers += Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
end
+ helpers.sort!
+ helpers.uniq!
+ helpers
+ end
- def all_helpers_from_path(path)
- helpers = []
- Array.wrap(path).each do |_path|
- extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
- helpers += Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
- end
- helpers.sort!
- helpers.uniq!
- helpers
- end
+ private
+ # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt>
+ def all_application_helpers
+ all_helpers_from_path(helpers_path)
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 39c804d707..1d6df89007 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -8,9 +8,7 @@ module ActionController
# === Simple \Basic example
#
# class PostsController < ApplicationController
- # USER_NAME, PASSWORD = "dhh", "secret"
- #
- # before_filter :authenticate, :except => [ :index ]
+ # http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index
#
# def index
# render :text => "Everyone can see me!"
@@ -19,15 +17,7 @@ module ActionController
# def edit
# render :text => "I'm only accessible if you know the password"
# end
- #
- # private
- # def authenticate
- # authenticate_or_request_with_http_basic do |user_name, password|
- # user_name == USER_NAME && password == PASSWORD
- # end
- # end
- # end
- #
+ # end
#
# === Advanced \Basic example
#
@@ -77,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]
#
@@ -115,6 +105,18 @@ module ActionController
extend self
module ControllerMethods
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def http_basic_authenticate_with(options = {})
+ before_filter(options.except(:name, :password, :realm)) do
+ authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
+ name == options[:name] && password == options[:password]
+ end
+ end
+ end
+ end
+
def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
end
@@ -378,7 +380,6 @@ module ActionController
#
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Token
-
extend self
module ControllerMethods
@@ -458,6 +459,5 @@ module ActionController
controller.__send__ :render, :text => "HTTP Token: Access denied.\n", :status => :unauthorized
end
end
-
end
end
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index cfa7004048..3ec0c4c6a4 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -1,21 +1,21 @@
module ActionController
module ImplicitRender
- def send_action(*)
- ret = super
- default_render unless response_body
- ret
+ def send_action(method, *args)
+ if respond_to?(method, true)
+ ret = super
+ default_render unless response_body
+ ret
+ else
+ default_render
+ end
end
- def default_render
- render
+ def default_render(*args)
+ render(*args)
end
- def method_for_action(action_name)
- super || begin
- if template_exists?(action_name.to_s, _prefixes)
- "default_render"
- end
- end
+ def action_method?(action_name)
+ super || template_exists?(action_name.to_s, _prefixes)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index a2e06fe0a6..f10287afb4 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -1,10 +1,13 @@
require 'abstract_controller/collector'
require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/object/inclusion'
module ActionController #:nodoc:
- module MimeResponds #:nodoc:
+ module MimeResponds
extend ActiveSupport::Concern
+ include ActionController::ImplicitRender
+
included do
class_attribute :responder, :mimes_for_respond_to
self.responder = ActionController::Responder
@@ -32,10 +35,10 @@ module ActionController #:nodoc:
# and all actions except <tt>:edit</tt> respond to <tt>:xml</tt> and
# <tt>:json</tt>.
#
- # respond_to :rjs, :only => :create
+ # respond_to :json, :only => :create
#
# This specifies that the <tt>:create</tt> action and no other responds
- # to <tt>:rjs</tt>.
+ # to <tt>:json</tt>.
def respond_to(*mimes)
options = mimes.extract_options!
@@ -105,8 +108,8 @@ module ActionController #:nodoc:
# end
# end
#
- # If the client wants HTML, we just redirect them back to the person list. If they want Javascript
- # (format.js), then it is an RJS request and we render the RJS template associated with this action.
+ # If the client wants HTML, we just redirect them back to the person list. If they want JavaScript,
+ # then it is an Ajax request and we render the JavaScript template associated with this action.
# Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
# include the person's company in the rendered XML, so you get something like this:
#
@@ -189,7 +192,7 @@ module ActionController #:nodoc:
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
+ response.call(nil)
end
end
@@ -222,6 +225,9 @@ module ActionController #:nodoc:
# is quite simple (it just needs to respond to call), you can even give
# a proc to it.
#
+ # In order to use respond_with, first you need to declare the formats your
+ # controller responds to in the class level with a call to <tt>respond_to</tt>.
+ #
def respond_with(*resources, &block)
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?
@@ -245,9 +251,9 @@ module ActionController #:nodoc:
config = self.class.mimes_for_respond_to[mime]
if config[:except]
- !config[:except].include?(action)
+ !action.in?(config[:except])
elsif config[:only]
- config[:only].include?(action)
+ action.in?(config[:only])
else
true
end
@@ -257,9 +263,9 @@ 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)
+ def retrieve_response_from_mimes(mimes=nil, &block) #:nodoc:
mimes ||= collect_mimes_from_class_level
- collector = Collector.new(mimes) { default_render }
+ collector = Collector.new(mimes) { |options| default_render(options || {}) }
block.call(collector) if block_given?
if format = request.negotiate_mime(collector.order)
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 38711c8462..0ad9dbeda9 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -41,7 +41,7 @@ module ActionController
end
# Hash of available renderers, mapping a renderer name to its proc.
- # Default keys are :json, :js, :xml and :update.
+ # Default keys are :json, :js, :xml.
RENDERERS = {}
# Adds a new renderer to call within controller actions.
@@ -95,24 +95,17 @@ module ActionController
json = json.to_json(options) unless json.kind_of?(String)
json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
self.content_type ||= Mime::JSON
- self.response_body = json
+ json
end
add :js do |js, options|
self.content_type ||= Mime::JS
- self.response_body = js.respond_to?(:to_js) ? js.to_js(options) : js
+ js.respond_to?(:to_js) ? js.to_js(options) : js
end
add :xml do |xml, options|
self.content_type ||= Mime::XML
- self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
- end
-
- add :update do |proc, options|
- view_context = self.view_context
- generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc)
- self.content_type = Mime::JS
- self.response_body = generator.to_s
+ xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
end
end
end
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 32d52c84c4..70fd79bb8b 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -18,6 +18,17 @@ module ActionController
response_body
end
+ # Overwrite render_to_string because body can now be set to a rack body.
+ def render_to_string(*)
+ if self.response_body = super
+ string = ""
+ response_body.each { |r| string << r }
+ string
+ end
+ ensure
+ self.response_body = nil
+ end
+
private
# Normalize arguments by catching blocks and setting them on :update.
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index 4b45413cf8..59a3621f72 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -77,6 +77,37 @@ module ActionController #:nodoc:
#
# respond_with(@project, :manager, @task)
#
+ # === Custom options
+ #
+ # <code>respond_with</code> also allow you to pass options that are forwarded
+ # to the underlying render call. Those options are only applied success
+ # scenarios. For instance, you can do the following in the create method above:
+ #
+ # def create
+ # @project = Project.find(params[:project_id])
+ # @task = @project.comments.build(params[:task])
+ # flash[:notice] = 'Task was successfully created.' if @task.save
+ # respond_with(@project, @task, :status => 201)
+ # end
+ #
+ # This will return status 201 if the task was saved with success. If not,
+ # it will simply ignore the given options and return status 422 and the
+ # resource errors. To customize the failure scenario, you can pass a
+ # a block to <code>respond_with</code>:
+ #
+ # def create
+ # @project = Project.find(params[:project_id])
+ # @task = @project.comments.build(params[:task])
+ # respond_with(@project, @task, :status => 201) do |format|
+ # if @task.save
+ # flash[:notice] = 'Task was successfully created.'
+ # else
+ # format.html { render "some_special_template" }
+ # end
+ # end
+ # end
+ #
+ # Using <code>respond_with</code> with a block follows the same syntax as <code>respond_to</code>.
class Responder
attr_reader :controller, :request, :format, :resource, :resources, :options
@@ -131,7 +162,11 @@ module ActionController #:nodoc:
# responds to :to_format and display it.
#
def to_format
- default_render
+ if get? || !has_errors?
+ default_render
+ else
+ display_errors
+ end
rescue ActionView::MissingTemplate => e
api_behavior(e)
end
@@ -155,8 +190,6 @@ module ActionController #:nodoc:
if get?
display resource
- elsif has_errors?
- display resource.errors, :status => :unprocessable_entity
elsif post?
display resource, :status => :created, :location => api_location
elsif has_empty_resource_definition?
@@ -185,7 +218,7 @@ module ActionController #:nodoc:
# controller.
#
def default_render
- @default_response.call
+ @default_response.call(options)
end
# Display is just a shortcut to render a resource with the current format.
@@ -209,6 +242,10 @@ module ActionController #:nodoc:
controller.render given_options.merge!(options).merge!(format => resource)
end
+ def display_errors
+ controller.render format => resource.errors, :status => :unprocessable_entity
+ end
+
# Check whether the resource has errors.
#
def has_errors?
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 312dc8eb3e..b9bd49f670 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -1,145 +1,61 @@
require 'active_support/core_ext/file/path'
+require 'rack/chunked'
module ActionController #:nodoc:
- # Methods for sending arbitrary data and for streaming files to the browser,
- # instead of rendering.
+ # Methods for sending streaming templates back to the client.
module Streaming
extend ActiveSupport::Concern
- include ActionController::Rendering
+ include AbstractController::Rendering
+ attr_internal :stream
- DEFAULT_SEND_FILE_OPTIONS = {
- :type => 'application/octet-stream'.freeze,
- :disposition => 'attachment'.freeze,
- }.freeze
-
- protected
- # Sends the file. This uses a server-appropriate method (such as X-Sendfile)
- # via the Rack::Sendfile middleware. The header to use is set via
- # config.action_dispatch.x_sendfile_header, and defaults to "X-Sendfile".
- # Your server can also configure this for you by setting the X-Sendfile-Type header.
- #
- # Be careful to sanitize the path parameter if it is coming from a web
- # page. <tt>send_file(params[:path])</tt> allows a malicious user to
- # download any file on your server.
- #
- # Options:
- # * <tt>:filename</tt> - suggests a filename for the browser to use.
- # Defaults to <tt>File.basename(path)</tt>.
- # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
- # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
- # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
- # Valid values are 'inline' and 'attachment' (default).
- # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
- # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
- # the URL, which is necessary for i18n filenames on certain browsers
- # (setting <tt>:filename</tt> overrides this option).
- #
- # The default Content-Type and Content-Disposition headers are
- # set to download arbitrary binary files in as many browsers as
- # possible. IE versions 4, 5, 5.5, and 6 are all known to have
- # a variety of quirks (especially when downloading over SSL).
- #
- # Simple download:
- #
- # send_file '/path/to.zip'
- #
- # Show a JPEG in the browser:
- #
- # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
- #
- # Show a 404 page in the browser:
- #
- # send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404
- #
- # Read about the other Content-* HTTP headers if you'd like to
- # provide the user with more information (such as Content-Description) in
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11.
- #
- # Also be aware that the document may be cached by proxies and browsers.
- # The Pragma and Cache-Control headers declare how the file may be cached
- # by intermediaries. They default to require clients to validate with
- # the server before releasing cached responses. See
- # http://www.mnot.net/cache_docs/ for an overview of web caching and
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
- # for the Cache-Control header spec.
- def send_file(path, options = {}) #:doc:
- raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
-
- options[:filename] ||= File.basename(path) unless options[:url_based_filename]
- send_file_headers! options
-
- self.status = options[:status] || 200
- self.content_type = options[:content_type] if options.key?(:content_type)
- self.response_body = File.open(path, "rb")
- end
-
- # Sends the given binary data to the browser. This method is similar to
- # <tt>render :text => data</tt>, but also allows you to specify whether
- # the browser should display the response as a file attachment (i.e. in a
- # download dialog) or as inline data. You may also set the content type,
- # the apparent file name, and other things.
- #
- # Options:
- # * <tt>:filename</tt> - suggests a filename for the browser to use.
- # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
- # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
- # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
- # Valid values are 'inline' and 'attachment' (default).
- # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
- #
- # Generic data download:
- #
- # send_data buffer
- #
- # Download a dynamically-generated tarball:
- #
- # send_data generate_tgz('dir'), :filename => 'dir.tgz'
- #
- # Display an image Active Record in the browser:
- #
- # send_data image.data, :type => image.content_type, :disposition => 'inline'
- #
- # See +send_file+ for more information on HTTP Content-* headers and caching.
- def send_data(data, options = {}) #:doc:
- send_file_headers! options.dup
- render options.slice(:status, :content_type).merge(:text => data)
- end
-
- private
- def send_file_headers!(options)
- options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
- [:type, :disposition].each do |arg|
- raise ArgumentError, ":#{arg} option required" if options[arg].nil?
+ module ClassMethods
+ # Render streaming templates. It accepts :only, :except, :if and :unless as options
+ # to specify when to stream, as in ActionController filters.
+ def stream(options={})
+ if defined?(Fiber)
+ before_filter :_stream_filter, options
+ else
+ raise "You cannot use streaming if Fiber is not available."
end
+ end
+ end
- disposition = options[:disposition]
- disposition += %(; filename="#{options[:filename]}") if options[:filename]
-
- content_type = options[:type]
+ protected
- if content_type.is_a?(Symbol)
- extension = Mime[content_type]
- raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension
- self.content_type = extension
+ # Mark following render calls as streaming.
+ def _stream_filter #:nodoc:
+ self.stream = true
+ end
+
+ # Consider the stream option when normalazing options.
+ def _normalize_options(options) #:nodoc:
+ super
+ options[:stream] = self.stream unless options.key?(:stream)
+ end
+
+ # Set proper cache control and transfer encoding when streaming
+ def _process_options(options) #:nodoc:
+ super
+ if options[:stream]
+ if env["HTTP_VERSION"] == "HTTP/1.0"
+ options.delete(:stream)
else
- self.content_type = content_type
+ headers["Cache-Control"] ||= "no-cache"
+ headers["Transfer-Encoding"] = "chunked"
+ headers.delete("Content-Length")
end
-
- headers.merge!(
- 'Content-Disposition' => disposition,
- 'Content-Transfer-Encoding' => 'binary'
- )
-
- response.sending_file = true
-
- # Fix a problem with IE 6.0 on opening downloaded files:
- # If Cache-Control: no-cache is set (which Rails does by default),
- # IE removes the file it just downloaded from its cache immediately
- # after it displays the "open/save" dialog, which means that if you
- # hit "open" the file isn't there anymore when the application that
- # is called for handling the download is run, so let's workaround that
- response.cache_control[:public] ||= false
end
+ end
+
+ # Call render_to_body if we are streaming instead of usual +render+.
+ def _render_template(options) #:nodoc:
+ if options.delete(:stream)
+ Rack::Chunked::Body.new view_context.render_body(options)
+ else
+ super
+ end
+ end
end
end
+ \ No newline at end of file
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index f0c29825ba..d2ba052c8d 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -4,6 +4,7 @@ require "action_dispatch/railtie"
require "action_view/railtie"
require "abstract_controller/railties/routes_helpers"
require "action_controller/railties/paths"
+require "sprockets/railtie"
module ActionController
class Railtie < Rails::Railtie
diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb
index dce3c2fe88..699c44c62c 100644
--- a/actionpack/lib/action_controller/railties/paths.rb
+++ b/actionpack/lib/action_controller/railties/paths.rb
@@ -16,14 +16,6 @@ module ActionController
if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
klass.helper :all
end
-
- if app.config.serve_static_assets && namespace
- paths = namespace._railtie.config.paths
-
- klass.config.assets_dir = paths["public"].first
- klass.config.javascripts_dir = paths["public/javascripts"].first
- klass.config.stylesheets_dir = paths["public/stylesheets"].first
- end
end
end
end
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index 3de40b0de3..2def78b51a 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -18,18 +18,12 @@ module ActionController
# post = Post.find(params[:id])
# post.destroy
#
- # respond_to do |format|
- # format.html { redirect_to(post) } # Calls polymorphic_url(post) which in turn calls post_url(post)
- # format.js do
- # # Calls: new Effect.fade('post_45');
- # render(:update) { |page| page[post].visual_effect(:fade) }
- # end
- # end
+ # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
# end
#
- # As the example above shows, you can stop caring to a large extent what the actual id of the post is. You just know
- # that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
- # convention and allows you to write less code if you follow it.
+ # As the example above shows, you can stop caring to a large extent what the actual id of the post is.
+ # You just know that one is being assigned and that the subsequent calls in redirect_to expect that
+ # same naming convention and allows you to write less code if you follow it.
module RecordIdentifier
extend self
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index bc4f8bb9ce..0085f542aa 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -147,7 +147,9 @@ module ActionController
if value.is_a? Fixnum
value = value.to_s
elsif value.is_a? Array
- value = Result.new(value)
+ value = Result.new(value.map { |v| v.is_a?(String) ? v.dup : v })
+ elsif value.is_a? String
+ value = value.dup
end
if extra_keys.include?(key.to_sym)
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 49971fc9f8..7f972fc281 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -60,6 +60,7 @@ module ActionDispatch
autoload :Static
end
+ autoload :ClosedError, 'action_dispatch/middleware/closed_error'
autoload :MiddlewareStack, 'action_dispatch/middleware/stack'
autoload :Routing
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 8e03a7879f..1f4f3ac0da 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -32,24 +32,35 @@ module ActionDispatch # :nodoc:
# puts @response.body
# end
# end
- class Response < Rack::Response
- attr_accessor :request, :blank
+ class Response
+ attr_accessor :request, :header, :status
+ attr_writer :sending_file
- attr_writer :header, :sending_file
alias_method :headers=, :header=
+ alias_method :headers, :header
+
+ delegate :[], :[]=, :to => :@header
+ delegate :each, :to => :@body
+
+ # Sets the HTTP response's content MIME type. For example, in the controller
+ # you could write this:
+ #
+ # response.content_type = "text/plain"
+ #
+ # If a character set has been defined for this response (see charset=) then
+ # the character set information will also be included in the content type
+ # information.
+ attr_accessor :charset, :content_type
+
+ CONTENT_TYPE = "Content-Type"
+
+ cattr_accessor(:default_charset) { "utf-8" }
module Setup
def initialize(status = 200, header = {}, body = [])
- @writer = lambda { |x| @body << x }
- @block = nil
- @length = 0
+ self.body, self.header, self.status = body, header, status
- @header = header
- self.body, self.status = body, status
-
- @cookie = []
@sending_file = false
-
@blank = false
if content_type = self["Content-Type"]
@@ -62,6 +73,7 @@ module ActionDispatch # :nodoc:
end
end
+ include Rack::Response::Helpers
include Setup
include ActionDispatch::Http::Cache::Response
@@ -106,13 +118,29 @@ module ActionDispatch # :nodoc:
def body=(body)
@blank = true if body == EMPTY
- @body = body.respond_to?(:to_str) ? [body] : body
+
+ # Explicitly check for strings. This is *wrong* theoretically
+ # but if we don't check this, the performance on string bodies
+ # is bad on Ruby 1.8 (because strings responds to each then).
+ @body = if body.respond_to?(:to_str) || !body.respond_to?(:each)
+ [body]
+ else
+ body
+ end
end
def body_parts
@body
end
+ def set_cookie(key, value)
+ ::Rack::Utils.set_cookie_header!(header, key, value)
+ end
+
+ def delete_cookie(key, value={})
+ ::Rack::Utils.delete_cookie_header!(header, key, value)
+ end
+
def location
headers['Location']
end
@@ -122,46 +150,21 @@ module ActionDispatch # :nodoc:
headers['Location'] = url
end
- # Sets the HTTP response's content MIME type. For example, in the controller
- # you could write this:
- #
- # response.content_type = "text/plain"
- #
- # If a character set has been defined for this response (see charset=) then
- # the character set information will also be included in the content type
- # information.
- attr_accessor :charset, :content_type
-
- CONTENT_TYPE = "Content-Type"
-
- cattr_accessor(:default_charset) { "utf-8" }
-
def to_a
assign_default_content_type_and_charset!
handle_conditional_get!
- self["Set-Cookie"] = self["Set-Cookie"].join("\n") if self["Set-Cookie"].respond_to?(:join)
- super
- end
- alias prepare! to_a
+ @header["Set-Cookie"] = @header["Set-Cookie"].join("\n") if @header["Set-Cookie"].respond_to?(:join)
- def each(&callback)
- if @body.respond_to?(:call)
- @writer = lambda { |x| callback.call(x) }
- @body.call(self, self)
+ if [204, 304].include?(@status)
+ @header.delete "Content-Type"
+ [@status, @header, []]
else
- @body.each { |part| callback.call(part.to_s) }
+ [@status, @header, self]
end
-
- @writer = callback
- @block.call(self) if @block
- end
-
- def write(str)
- str = str.to_s
- @writer.call str
- str
end
+ alias prepare! to_a
+ alias to_ary to_a # For implicit splat on 1.9.2
# Returns the response cookies, converted to a Hash of (name => value) pairs
#
@@ -180,18 +183,18 @@ module ActionDispatch # :nodoc:
cookies
end
- private
- def assign_default_content_type_and_charset!
- return if headers[CONTENT_TYPE].present?
+ private
- @content_type ||= Mime::HTML
- @charset ||= self.class.default_charset
+ def assign_default_content_type_and_charset!
+ return if headers[CONTENT_TYPE].present?
- type = @content_type.to_s.dup
- type << "; charset=#{@charset}" unless @sending_file
+ @content_type ||= Mime::HTML
+ @charset ||= self.class.default_charset
- headers[CONTENT_TYPE] = type
- end
+ type = @content_type.to_s.dup
+ type << "; charset=#{@charset}" unless @sending_file
+ headers[CONTENT_TYPE] = type
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/closed_error.rb b/actionpack/lib/action_dispatch/middleware/closed_error.rb
new file mode 100644
index 0000000000..0a4db47f4b
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/closed_error.rb
@@ -0,0 +1,7 @@
+module ActionDispatch
+ class ClosedError < StandardError #:nodoc:
+ def initialize(kind)
+ super "Cannot modify #{kind} because it was closed. This means it was already streamed back to the client or converted to HTTP headers."
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 7ac608f0a8..24ebb8fed7 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -83,7 +83,7 @@ module ActionDispatch
# Raised when storing more than 4K of session data.
class CookieOverflow < StandardError; end
- class CookieJar < Hash #:nodoc:
+ class CookieJar #:nodoc:
# This regular expression is used to split the levels of a domain.
# The top level domain can be any string without a period or
@@ -115,13 +115,22 @@ module ActionDispatch
@delete_cookies = {}
@host = host
@secure = secure
-
- super()
+ @closed = false
+ @cookies = {}
end
+ attr_reader :closed
+ alias :closed? :closed
+ def close!; @closed = true end
+
# Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
def [](name)
- super(name.to_s)
+ @cookies[name.to_s]
+ end
+
+ def update(other_hash)
+ @cookies.update other_hash
+ self
end
def handle_options(options) #:nodoc:
@@ -145,6 +154,7 @@ module ActionDispatch
# Sets the cookie named +name+. The second argument may be the very cookie
# value, or a hash of options as documented above.
def []=(key, options)
+ raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
value = options[:value]
@@ -153,7 +163,7 @@ module ActionDispatch
options = { :value => value }
end
- value = super(key.to_s, value)
+ value = @cookies[key.to_s] = value
handle_options(options)
@@ -170,7 +180,7 @@ module ActionDispatch
handle_options(options)
- value = super(key.to_s)
+ value = @cookies.delete(key.to_s)
@delete_cookies[key] = options
value
end
@@ -225,6 +235,7 @@ module ActionDispatch
end
def []=(key, options)
+ raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
else
@@ -263,6 +274,7 @@ module ActionDispatch
end
def []=(key, options)
+ raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
options[:value] = @verifier.generate(options[:value])
@@ -305,6 +317,7 @@ module ActionDispatch
end
def call(env)
+ cookie_jar = nil
status, headers, body = @app.call(env)
if cookie_jar = env['action_dispatch.cookies']
@@ -315,6 +328,9 @@ module ActionDispatch
end
[status, headers, body]
+ ensure
+ cookie_jar = ActionDispatch::Request.new(env).cookie_jar unless cookie_jar
+ cookie_jar.close!
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 21aeeb217a..2adbce031b 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -4,7 +4,7 @@ module ActionDispatch
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
# to put a new one.
def flash
- @env['action_dispatch.request.flash_hash'] ||= (session["flash"] || Flash::FlashHash.new)
+ @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new)
end
end
@@ -40,7 +40,11 @@ module ActionDispatch
#
# See docs on the FlashHash class for more details about the flash.
class Flash
+ KEY = 'action_dispatch.request.flash_hash'.freeze
+
class FlashNow #:nodoc:
+ attr_accessor :flash
+
def initialize(flash)
@flash = flash
end
@@ -66,27 +70,75 @@ module ActionDispatch
end
end
- class FlashHash < Hash
+ class FlashHash
+ include Enumerable
+
def initialize #:nodoc:
+ @used = Set.new
+ @closed = false
+ @flashes = {}
+ @now = nil
+ end
+
+ def initialize_copy(other)
+ if other.now_is_loaded?
+ @now = other.now.dup
+ @now.flash = self
+ end
super
- @used = Set.new
end
def []=(k, v) #:nodoc:
+ raise ClosedError, :flash if closed?
keep(k)
- super
+ @flashes[k] = v
+ end
+
+ def [](k)
+ @flashes[k]
end
def update(h) #:nodoc:
h.keys.each { |k| keep(k) }
- super
+ @flashes.update h
+ self
+ end
+
+ def keys
+ @flashes.keys
+ end
+
+ def key?(name)
+ @flashes.key? name
+ end
+
+ def delete(key)
+ @flashes.delete key
+ self
+ end
+
+ def to_hash
+ @flashes.dup
+ end
+
+ def empty?
+ @flashes.empty?
+ end
+
+ def clear
+ @flashes.clear
+ end
+
+ def each(&block)
+ @flashes.each(&block)
end
alias :merge! :update
def replace(h) #:nodoc:
@used = Set.new
- super
+ @flashes.replace h
+ self
end
# Sets a flash that will not be available to the next action, only to the current.
@@ -100,9 +152,13 @@ module ActionDispatch
#
# Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
def now
- FlashNow.new(self)
+ @now ||= FlashNow.new(self)
end
+ attr_reader :closed
+ alias :closed? :closed
+ def close!; @closed = true; end
+
# Keeps either the entire current flash or a specific flash entry available for the next action:
#
# flash.keep # keeps the entire flash
@@ -156,7 +212,12 @@ module ActionDispatch
self[:notice] = message
end
- private
+ protected
+
+ def now_is_loaded?
+ !!@now
+ end
+
# Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
# use() # marks the entire flash as used
# use('msg') # marks the "msg" entry as used
@@ -182,10 +243,18 @@ module ActionDispatch
@app.call(env)
ensure
session = env['rack.session'] || {}
- flash_hash = env['action_dispatch.request.flash_hash']
+ flash_hash = env[KEY]
+
+ if flash_hash
+ if !flash_hash.empty? || session.key?('flash')
+ session["flash"] = flash_hash
+ new_hash = flash_hash.dup
+ else
+ new_hash = flash_hash
+ end
- if flash_hash && (!flash_hash.empty? || session.key?('flash'))
- session["flash"] = flash_hash
+ env[KEY] = new_hash
+ new_hash.close!
end
if session.key?('flash') && session['flash'].empty?
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 64d3a87fd0..1a811ce1b1 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -29,7 +29,9 @@ module ActionDispatch
end
def generate_sid
- ActiveSupport::SecureRandom.hex(16)
+ sid = ActiveSupport::SecureRandom.hex(16)
+ sid.encode!('UTF-8') if sid.respond_to?(:encode!)
+ sid
end
protected
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index dbe3206808..b1adf3d2d1 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -50,7 +50,7 @@ module ActionDispatch
# Only this middleware cares about RoutingError. So, let's just raise
# it here.
if headers['X-Cascade'] == 'pass'
- raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}"
+ raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
end
rescue Exception => exception
raise exception if env['action_dispatch.show_exceptions'] == false
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index c57f694c4d..348f7b86b8 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -2,25 +2,23 @@ require 'rack/utils'
module ActionDispatch
class FileHandler
- def initialize(at, root)
- @at, @root = at.chomp('/'), root.chomp('/')
- @compiled_at = @at.blank? ? nil : /^#{Regexp.escape(at)}/
+ def initialize(root)
+ @root = root.chomp('/')
@compiled_root = /^#{Regexp.escape(root)}/
@file_server = ::Rack::File.new(@root)
end
def match?(path)
path = path.dup
- if !@compiled_at || path.sub!(@compiled_at, '')
- full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path))
- paths = "#{full_path}#{ext}"
- matches = Dir[paths]
- match = matches.detect { |m| File.file?(m) }
- if match
- match.sub!(@compiled_root, '')
- match
- end
+ full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path))
+ paths = "#{full_path}#{ext}"
+
+ matches = Dir[paths]
+ match = matches.detect { |m| File.file?(m) }
+ if match
+ match.sub!(@compiled_root, '')
+ match
end
end
@@ -39,9 +37,9 @@ module ActionDispatch
class Static
FILE_METHODS = %w(GET HEAD).freeze
- def initialize(app, roots)
+ def initialize(app, path)
@app = app
- @file_handlers = create_file_handlers(roots)
+ @file_handler = FileHandler.new(path)
end
def call(env)
@@ -49,24 +47,13 @@ module ActionDispatch
method = env['REQUEST_METHOD']
if FILE_METHODS.include?(method)
- @file_handlers.each do |file_handler|
- if match = file_handler.match?(path)
- env["PATH_INFO"] = match
- return file_handler.call(env)
- end
+ if match = @file_handler.match?(path)
+ env["PATH_INFO"] = match
+ return @file_handler.call(env)
end
end
@app.call(env)
end
-
- private
- def create_file_handlers(roots)
- roots = { '' => roots } unless roots.is_a?(Hash)
-
- roots.map do |at, root|
- FileHandler.new(at, root) if File.exist?(root)
- end.compact
- end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
index 50d8ca9484..2099fd069a 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
@@ -1,7 +1,7 @@
<h1>
<%=h @exception.class.to_s %>
<% if @request.parameters['controller'] %>
- in <%=h @request.parameters['controller'].humanize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
+ in <%=h @request.parameters['controller'].classify.pluralize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
<% end %>
</h1>
<pre><%=h @exception.message %></pre>
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 1733c8032a..a65f6e1fce 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,6 +1,7 @@
require 'erb'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/object/inclusion'
require 'active_support/inflector'
require 'action_dispatch/routing/redirection'
@@ -104,10 +105,16 @@ module ActionDispatch
@options.reverse_merge!(:controller => /.+?/)
end
+ # Add a constraint for wildcard route to make it non-greedy and match the
+ # optional format part of the route by default
+ if path.match(/\*([^\/]+)$/) && @options[:format] != false
+ @options.reverse_merge!(:"#{$1}" => /.+?/)
+ end
+
if @options[:format] == false
@options.delete(:format)
path
- elsif path.include?(":format")
+ elsif path.include?(":format") || path.end_with?('/')
path
else
"#{path}(.:format)"
@@ -195,8 +202,8 @@ module ActionDispatch
def request_method_condition
if via = @options[:via]
- via = Array(via).map { |m| m.to_s.dasherize.upcase }
- { :request_method => %r[^#{via.join('|')}$] }
+ list = Array(via).map { |m| m.to_s.dasherize.upcase }
+ { :request_method => list }
else
{ }
end
@@ -243,10 +250,6 @@ module ActionDispatch
end
module Base
- def initialize(set) #:nodoc:
- @set = set
- end
-
# You can specify what Rails should route "/" to with the root method:
#
# root :to => 'pages#main'
@@ -368,9 +371,17 @@ module ActionDispatch
# match 'path' => 'c#a', :defaults => { :format => 'jpg' }
#
# See <tt>Scoping#defaults</tt> for its scope equivalent.
+ #
+ # [:anchor]
+ # Boolean to anchor a #match pattern. Default is true. When set to
+ # false, the pattern matches any request prefixed with the given path.
+ #
+ # # Matches any request starting with 'path'
+ # match 'path' => 'c#a', :anchor => false
def match(path, options=nil)
- mapping = Mapping.new(@set, @scope, path, options || {}).to_route
- @set.add_route(*mapping)
+ mapping = Mapping.new(@set, @scope, path, options || {})
+ app, conditions, requirements, defaults, as, anchor = mapping.to_route
+ @set.add_route(app, conditions, requirements, defaults, as, anchor)
self
end
@@ -558,11 +569,6 @@ module ActionDispatch
# PUT /admin/posts/1
# DELETE /admin/posts/1
module Scoping
- def initialize(*args) #:nodoc:
- @scope = {}
- super
- end
-
# Scopes a set of routes to the given default options.
#
# Take the following route definition as an example:
@@ -956,11 +962,6 @@ module ActionDispatch
alias :nested_scope :path
end
- def initialize(*args) #:nodoc:
- super
- @scope[:path_names] = @set.resources_path_names
- end
-
def resources_path_names(options)
@scope[:path_names].merge!(options)
end
@@ -1345,11 +1346,11 @@ module ActionDispatch
end
def resource_scope? #:nodoc:
- [:resource, :resources].include?(@scope[:scope_level])
+ @scope[:scope_level].in?([:resource, :resources])
end
def resource_method_scope? #:nodoc:
- [:collection, :member, :new].include?(@scope[:scope_level])
+ @scope[:scope_level].in?([:collection, :member, :new])
end
def with_exclusive_scope
@@ -1473,6 +1474,11 @@ module ActionDispatch
end
end
+ def initialize(set) #:nodoc:
+ @set = set
+ @scope = { :path_names => @set.resources_path_names }
+ end
+
include Base
include HttpHelpers
include Redirection
diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb
index eae9d4ea6d..a049510182 100644
--- a/actionpack/lib/action_dispatch/routing/route.rb
+++ b/actionpack/lib/action_dispatch/routing/route.rb
@@ -12,6 +12,8 @@ module ActionDispatch
@defaults = defaults
@name = name
+ # FIXME: we should not be doing this much work in a constructor.
+
@requirements = requirements.merge(defaults)
@requirements.delete(:controller) if @requirements[:controller].is_a?(Regexp)
@requirements.delete_if { |k, v|
@@ -23,21 +25,22 @@ module ActionDispatch
conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS, anchor)
end
- @conditions = Hash[conditions.map { |k,v| [k, Rack::Mount::RegexpWithNamedGroups.new(v)] }]
+ @verbs = conditions[:request_method] || []
+
+ @conditions = conditions.dup
+
+ # Rack-Mount requires that :request_method be a regular expression.
+ # :request_method represents the HTTP verb that matches this route.
+ #
+ # Here we munge values before they get sent on to rack-mount.
+ @conditions[:request_method] = %r[^#{verb}$] unless @verbs.empty?
+ @conditions[:path_info] = Rack::Mount::RegexpWithNamedGroups.new(@conditions[:path_info]) if @conditions[:path_info]
@conditions.delete_if{ |k,v| k != :path_info && !valid_condition?(k) }
@requirements.delete_if{ |k,v| !valid_condition?(k) }
end
def verb
- if method = conditions[:request_method]
- case method
- when Regexp
- source = method.source.upcase
- source =~ /\A\^[-A-Z|]+\$\Z/ ? source[1..-2] : source
- else
- method.to_s.upcase
- end
- end
+ @verbs.join '|'
end
def segment_keys
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index b28f6c2297..1d09091dc7 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -275,8 +275,7 @@ module ActionDispatch
module MountedHelpers
end
- def mounted_helpers(name = :main_app)
- define_mounted_helper(name) if name
+ def mounted_helpers
MountedHelpers
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 77a15f3e97..e209978fb7 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/inclusion'
+
module ActionDispatch
module Assertions
# A small suite of assertions that test responses from \Rails applications.
@@ -33,14 +35,14 @@ module ActionDispatch
def assert_response(type, message = nil)
validate_request!
- if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?")
+ if type.in?([:success, :missing, :redirect, :error]) && @response.send("#{type}?")
assert_block("") { true } # to count the assertion
elsif type.is_a?(Fixnum) && @response.response_code == type
assert_block("") { true } # to count the assertion
elsif type.is_a?(Symbol) && @response.response_code == Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
assert_block("") { true } # to count the assertion
else
- assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
+ assert(false, build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code))
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 11e8c63fa0..b760db42e2 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -46,7 +46,7 @@ module ActionDispatch
expected_options.stringify_keys!
msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>",
request.path_parameters, expected_options, expected_options.diff(request.path_parameters))
- assert_block(msg) { request.path_parameters == expected_options }
+ assert_equal(expected_options, request.path_parameters, msg)
end
# Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
@@ -84,11 +84,11 @@ module ActionDispatch
found_extras = options.reject {|k, v| ! extra_keys.include? k}
msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
- assert_block(msg) { found_extras == extras }
+ assert_equal(extras, found_extras, msg)
msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
expected_path)
- assert_block(msg) { expected_path == generated_path }
+ assert_equal(expected_path, generated_path, msg)
end
# Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index 2b862fb7d6..c67a0664dc 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -1,4 +1,5 @@
require 'action_controller/vendor/html-scanner'
+require 'active_support/core_ext/object/inclusion'
#--
# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
@@ -18,7 +19,7 @@ module ActionDispatch
# from the response HTML or elements selected by the enclosing assertion.
#
# In addition to HTML responses, you can make the following assertions:
- # * +assert_select_rjs+ - Assertions on HTML content of RJS update and insertion operations.
+ #
# * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions.
# * +assert_select_email+ - Assertions on the HTML body of an e-mail.
#
@@ -79,7 +80,7 @@ module ActionDispatch
return matches
else
- root = response_from_page_or_rjs
+ root = response_from_page
end
case arg
@@ -203,7 +204,7 @@ module ActionDispatch
root.children.concat @selected
else
# Otherwise just operate on the response document.
- root = response_from_page_or_rjs
+ root = response_from_page
end
# First or second argument is the selector: string and we pass
@@ -325,144 +326,6 @@ module ActionDispatch
end
end
- # Selects content from the RJS response.
- #
- # === Narrowing down
- #
- # With no arguments, asserts that one or more elements are updated or
- # inserted by RJS statements.
- #
- # Use the +id+ argument to narrow down the assertion to only statements
- # that update or insert an element with that identifier.
- #
- # Use the first argument to narrow down assertions to only statements
- # of that type. Possible values are <tt>:replace</tt>, <tt>:replace_html</tt>,
- # <tt>:show</tt>, <tt>:hide</tt>, <tt>:toggle</tt>, <tt>:remove</tta>,
- # <tt>:insert_html</tt> and <tt>:redirect</tt>.
- #
- # Use the argument <tt>:insert</tt> followed by an insertion position to narrow
- # down the assertion to only statements that insert elements in that
- # position. Possible values are <tt>:top</tt>, <tt>:bottom</tt>, <tt>:before</tt>
- # and <tt>:after</tt>.
- #
- # Use the argument <tt>:redirect</tt> followed by a path to check that an statement
- # which redirects to the specified path is generated.
- #
- # Using the <tt>:remove</tt> statement, you will be able to pass a block, but it will
- # be ignored as there is no HTML passed for this statement.
- #
- # === Using blocks
- #
- # Without a block, +assert_select_rjs+ merely asserts that the response
- # contains one or more RJS statements that replace or update content.
- #
- # With a block, +assert_select_rjs+ also selects all elements used in
- # these statements and passes them to the block. Nested assertions are
- # supported.
- #
- # Calling +assert_select_rjs+ with no arguments and using nested asserts
- # asserts that the HTML content is returned by one or more RJS statements.
- # Using +assert_select+ directly makes the same assertion on the content,
- # but without distinguishing whether the content is returned in an HTML
- # or JavaScript.
- #
- # ==== Examples
- #
- # # Replacing the element foo.
- # # page.replace 'foo', ...
- # assert_select_rjs :replace, "foo"
- #
- # # Replacing with the chained RJS proxy.
- # # page[:foo].replace ...
- # assert_select_rjs :chained_replace, 'foo'
- #
- # # Inserting into the element bar, top position.
- # assert_select_rjs :insert, :top, "bar"
- #
- # # Remove the element bar
- # assert_select_rjs :remove, "bar"
- #
- # # Changing the element foo, with an image.
- # assert_select_rjs "foo" do
- # assert_select "img[src=/images/logo.gif""
- # end
- #
- # # RJS inserts or updates a list with four items.
- # assert_select_rjs do
- # assert_select "ol>li", 4
- # end
- #
- # # The same, but shorter.
- # assert_select "ol>li", 4
- #
- # # Checking for a redirect.
- # assert_select_rjs :redirect, root_path
- def assert_select_rjs(*args, &block)
- rjs_type = args.first.is_a?(Symbol) ? args.shift : nil
- id = args.first.is_a?(String) ? args.shift : nil
-
- # If the first argument is a symbol, it's the type of RJS statement we're looking
- # for (update, replace, insertion, etc). Otherwise, we're looking for just about
- # any RJS statement.
- if rjs_type
- if rjs_type == :insert
- position = args.shift
- id = args.shift
- insertion = "insert_#{position}".to_sym
- raise ArgumentError, "Unknown RJS insertion type #{position}" unless RJS_STATEMENTS[insertion]
- statement = "(#{RJS_STATEMENTS[insertion]})"
- else
- raise ArgumentError, "Unknown RJS statement type #{rjs_type}" unless RJS_STATEMENTS[rjs_type]
- statement = "(#{RJS_STATEMENTS[rjs_type]})"
- end
- else
- statement = "#{RJS_STATEMENTS[:any]}"
- end
-
- # Next argument we're looking for is the element identifier. If missing, we pick
- # any element, otherwise we replace it in the statement.
- pattern = Regexp.new(
- id ? statement.gsub(RJS_ANY_ID, "\"#{id}\"") : statement
- )
-
- # Duplicate the body since the next step involves destroying it.
- matches = nil
- case rjs_type
- when :remove, :show, :hide, :toggle
- matches = @response.body.match(pattern)
- else
- @response.body.gsub(pattern) do |match|
- html = unescape_rjs(match)
- matches ||= []
- matches.concat HTML::Document.new(html).root.children.select { |n| n.tag? }
- ""
- end
- end
-
- if matches
- assert_block("") { true } # to count the assertion
- if block_given? && !([:remove, :show, :hide, :toggle].include? rjs_type)
- begin
- @selected ||= nil
- in_scope, @selected = @selected, matches
- yield matches
- ensure
- @selected = in_scope
- end
- end
- matches
- else
- # RJS statement not found.
- case rjs_type
- when :remove, :show, :hide, :toggle
- flunk_message = "No RJS statement that #{rjs_type.to_s}s '#{id}' was rendered."
- else
- flunk_message = "No RJS statement that replaces or inserts HTML content."
- end
- flunk args.shift || flunk_message
- end
- end
-
# Extracts the content of an element, treats it as encoded HTML and runs
# nested assertion on it.
#
@@ -562,62 +425,9 @@ module ActionDispatch
end
protected
- RJS_PATTERN_HTML = "\"((\\\\\"|[^\"])*)\""
- RJS_ANY_ID = "\"([^\"])*\""
- RJS_STATEMENTS = {
- :chained_replace => "\\$\\(#{RJS_ANY_ID}\\)\\.replace\\(#{RJS_PATTERN_HTML}\\)",
- :chained_replace_html => "\\$\\(#{RJS_ANY_ID}\\)\\.update\\(#{RJS_PATTERN_HTML}\\)",
- :replace_html => "Element\\.update\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)",
- :replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)",
- :redirect => "window.location.href = #{RJS_ANY_ID}"
- }
- [:remove, :show, :hide, :toggle].each do |action|
- RJS_STATEMENTS[action] = "Element\\.#{action}\\(#{RJS_ANY_ID}\\)"
- end
- RJS_INSERTIONS = ["top", "bottom", "before", "after"]
- RJS_INSERTIONS.each do |insertion|
- RJS_STATEMENTS["insert_#{insertion}".to_sym] = "Element.insert\\(#{RJS_ANY_ID}, \\{ #{insertion}: #{RJS_PATTERN_HTML} \\}\\)"
- end
- RJS_STATEMENTS[:insert_html] = "Element.insert\\(#{RJS_ANY_ID}, \\{ (#{RJS_INSERTIONS.join('|')}): #{RJS_PATTERN_HTML} \\}\\)"
- RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})")
- RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/
-
- # +assert_select+ and +css_select+ call this to obtain the content in the HTML
- # page, or from all the RJS statements, depending on the type of response.
- def response_from_page_or_rjs()
- content_type = @response.content_type
-
- if content_type && Mime::JS =~ content_type
- body = @response.body.dup
- root = HTML::Node.new(nil)
-
- while true
- next if body.sub!(RJS_STATEMENTS[:any]) do |match|
- html = unescape_rjs(match)
- matches = HTML::Document.new(html).root.children.select { |n| n.tag? }
- root.children.concat matches
- ""
- end
- break
- end
-
- root
- else
- html_document.root
- end
- end
-
- # Unescapes a RJS string.
- def unescape_rjs(rjs_string)
- # RJS encodes double quotes and line breaks.
- unescaped= rjs_string.gsub('\"', '"')
- unescaped.gsub!(/\\\//, '/')
- unescaped.gsub!('\n', "\n")
- unescaped.gsub!('\076', '>')
- unescaped.gsub!('\074', '<')
- # RJS encodes non-ascii characters.
- unescaped.gsub!(RJS_PATTERN_UNICODE_ESCAPED_CHAR) {|u| [$1.hex].pack('U*')}
- unescaped
+ # +assert_select+ and +css_select+ call this to obtain the content in the HTML page.
+ def response_from_page
+ html_document.root
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 5c6416a19e..7d707d03a9 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -1,6 +1,7 @@
require 'stringio'
require 'uri'
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'
@@ -26,31 +27,31 @@ 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, PUT, DELETE, and HEAD requests with +#post+,
+ # +#put+, +#delete+, and +#head+.
def get(path, parameters = nil, headers = nil)
process :get, path, parameters, headers
end
- # Performs a POST request with the given parameters. See get() for more
+ # Performs a POST request with the given parameters. See +#get+ for more
# details.
def post(path, parameters = nil, headers = nil)
process :post, path, parameters, headers
end
- # Performs a PUT request with the given parameters. See get() for more
+ # Performs a PUT request with the given parameters. See +#get+ for more
# details.
def put(path, parameters = nil, headers = nil)
process :put, path, parameters, headers
end
- # Performs a DELETE request with the given parameters. See get() for
+ # Performs a DELETE request with the given parameters. See +#get+ for
# more details.
def delete(path, parameters = nil, headers = nil)
process :delete, path, parameters, headers
end
- # Performs a HEAD request with the given parameters. See get() for more
+ # Performs a HEAD request with the given parameters. See +#get+ for more
# details.
def head(path, parameters = nil, headers = nil)
process :head, path, parameters, headers
@@ -59,7 +60,7 @@ module ActionDispatch
# 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
+ # 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.
@@ -243,7 +244,8 @@ module ActionDispatch
end
# Performs the actual request.
- def process(method, path, parameters = nil, rack_environment = nil)
+ def process(method, path, parameters = nil, env = nil)
+ env ||= {}
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
@@ -259,7 +261,7 @@ module ActionDispatch
hostname, port = host.split(':')
- env = {
+ default_env = {
:method => method,
:params => parameters,
@@ -277,9 +279,7 @@ module ActionDispatch
session = Rack::Test::Session.new(_mock_session)
- (rack_environment || {}).each do |key, value|
- env[key] = value
- end
+ env.reverse_merge!(default_env)
# NOTE: rack-test v0.5 doesn't build a default uri correctly
# Make sure requested path is always a full uri
@@ -321,7 +321,7 @@ module ActionDispatch
define_method(method) do |*args|
reset! unless integration_session
# reset the html_document variable, but only for new get/post calls
- @html_document = nil unless %w(cookies assigns).include?(method)
+ @html_document = nil unless method.in?(["cookies", "assigns"])
integration_session.__send__(method, *args).tap do
copy_session_variables!
end
@@ -384,7 +384,7 @@ module ActionDispatch
end
end
- # An test that spans multiple controllers and actions,
+ # An integration test spans multiple controllers and actions,
# tying them all together to ensure they work together as expected. It tests
# more completely than either unit or functional tests do, exercising the
# entire stack, from the dispatcher to the database.
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 60665387b6..4547aceb28 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -44,6 +44,7 @@ module ActionView
autoload :AbstractRenderer
autoload :PartialRenderer
autoload :TemplateRenderer
+ autoload :StreamingTemplateRenderer
end
autoload_at "action_view/template/resolver" do
@@ -53,6 +54,16 @@ module ActionView
autoload :FallbackFileSystemResolver
end
+ autoload_at "action_view/buffers" do
+ autoload :OutputBuffer
+ autoload :StreamingBuffer
+ end
+
+ autoload_at "action_view/flows" do
+ autoload :OutputFlow
+ autoload :StreamingFlow
+ end
+
autoload_at "action_view/template/error" do
autoload :MissingTemplate
autoload :ActionViewError
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index ab8c6259c5..87501d5b88 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -8,13 +8,12 @@ require 'action_view/log_subscriber'
module ActionView #:nodoc:
# = Action View Base
#
- # Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
+ # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
# (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> (or <tt>.rxml</tt>) extension then Jim Weirich's Builder::XmlMarkup library is used.
- # If the template file has a <tt>.rjs</tt> extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.
#
- # == ERb
+ # == ERB
#
- # You trigger ERb by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
+ # You trigger ERB by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
# following loop for names:
#
# <b>Names of all the people</b>
@@ -23,7 +22,7 @@ module ActionView #:nodoc:
# <% end %>
#
# The loop is setup in regular embedding tags <% %> and the name is written using the output embedding tag <%= %>. Note that this
- # is not just a usage suggestion. Regular output functions like print or puts won't work with ERb templates. So this would be wrong:
+ # is not just a usage suggestion. Regular output functions like print or puts won't work with ERB templates. So this would be wrong:
#
# <%# WRONG %>
# Hi, Mr. <% puts "Frodo" %>
@@ -81,7 +80,7 @@ module ActionView #:nodoc:
#
# == Builder
#
- # Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An XmlMarkup object
+ # Builder templates are a more programmatic alternative to ERB. They are especially useful for generating XML content. An XmlMarkup object
# named +xml+ is automatically made available to templates with a <tt>.builder</tt> extension.
#
# Here are some basic examples:
@@ -131,41 +130,19 @@ module ActionView #:nodoc:
# end
#
# More builder documentation can be found at http://builder.rubyforge.org.
- #
- # == JavaScriptGenerator
- #
- # JavaScriptGenerator templates end in <tt>.rjs</tt>. Unlike conventional templates which are used to
- # render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to
- # modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax
- # and make updates to the page where the request originated from.
- #
- # An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block.
- #
- # When an <tt>.rjs</tt> action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example:
- #
- # link_to_remote :url => {:action => 'delete'}
- #
- # The subsequently rendered <tt>delete.rjs</tt> might look like:
- #
- # page.replace_html 'sidebar', :partial => 'sidebar'
- # page.remove "person-#{@person.id}"
- # page.visual_effect :highlight, 'user-list'
- #
- # This refreshes the sidebar, removes a person element and highlights the user list.
- #
- # See the ActionView::Helpers::PrototypeHelper::JavaScriptGenerator::GeneratorMethods documentation for more details.
class Base
include Helpers, Rendering, Partials, ::ERB::Util, Context
- # Specify whether RJS responses should be wrapped in a try/catch block
- # that alert()s the caught exception (and then re-raises it).
- cattr_accessor :debug_rjs
- @@debug_rjs = false
-
# Specify the proc used to decorate input tags that refer to attributes with errors.
cattr_accessor :field_error_proc
@@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
+ # How to complete the streaming when an exception occurs.
+ # This is our best guess: first try to close the attribute, then the tag.
+ # Currently this is private API and may be changed at *any* time.
+ cattr_accessor :streaming_completion_on_exception
+ @@streaming_completion_on_exception = %("><script type="text/javascript">window.location = "/500.html"</script></html>)
+
class_attribute :helpers
class_attribute :_routes
@@ -182,7 +159,7 @@ module ActionView #:nodoc:
end
end
- attr_accessor :_template
+ attr_accessor :_template, :_view_flow
attr_internal :request, :controller, :config, :assigns, :lookup_context
delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context
@@ -210,8 +187,8 @@ module ActionView #:nodoc:
self.helpers = Module.new unless self.class.helpers
@_config = {}
- @_content_for = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
@_virtual_path = nil
+ @_view_flow = OutputFlow.new
@output_buffer = nil
if @_controller = controller
@@ -224,10 +201,6 @@ module ActionView #:nodoc:
@_lookup_context.formats = formats if formats
end
- def store_content_for(key, value)
- @_content_for[key] = value
- end
-
def controller_path
@controller_path ||= controller && controller.controller_path
end
diff --git a/actionpack/lib/action_view/buffers.rb b/actionpack/lib/action_view/buffers.rb
new file mode 100644
index 0000000000..089fc68706
--- /dev/null
+++ b/actionpack/lib/action_view/buffers.rb
@@ -0,0 +1,43 @@
+require 'active_support/core_ext/string/output_safety'
+
+module ActionView
+ class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
+ def initialize(*)
+ super
+ encode! if encoding_aware?
+ end
+
+ def <<(value)
+ super(value.to_s)
+ end
+ alias :append= :<<
+ alias :safe_append= :safe_concat
+ end
+
+ class StreamingBuffer #:nodoc:
+ def initialize(block)
+ @block = block
+ end
+
+ def <<(value)
+ value = value.to_s
+ value = ERB::Util.h(value) unless value.html_safe?
+ @block.call(value)
+ end
+ alias :concat :<<
+ alias :append= :<<
+
+ def safe_concat(value)
+ @block.call(value.to_s)
+ end
+ alias :safe_append= :safe_concat
+
+ def html_safe?
+ true
+ end
+
+ def html_safe
+ self
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb
index 39d88333e8..a2a64de206 100644
--- a/actionpack/lib/action_view/context.rb
+++ b/actionpack/lib/action_view/context.rb
@@ -2,6 +2,7 @@ module ActionView
module CompiledTemplates #:nodoc:
# holds compiled template code
end
+
# = Action View Context
#
# Action View contexts are supplied to Action Controller to render template.
diff --git a/actionpack/lib/action_view/flows.rb b/actionpack/lib/action_view/flows.rb
new file mode 100644
index 0000000000..386a06511f
--- /dev/null
+++ b/actionpack/lib/action_view/flows.rb
@@ -0,0 +1,79 @@
+require 'active_support/core_ext/string/output_safety'
+
+module ActionView
+ class OutputFlow #:nodoc:
+ attr_reader :content
+
+ def initialize
+ @content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
+ end
+
+ # Called by _layout_for to read stored values.
+ def get(key)
+ @content[key]
+ end
+
+ # Called by each renderer object to set the layout contents.
+ def set(key, value)
+ @content[key] = value
+ end
+
+ # Called by content_for
+ def append(key, value)
+ @content[key] << value
+ end
+
+ # Called by provide
+ def append!(key, value)
+ @content[key] << value
+ end
+ end
+
+ class StreamingFlow < OutputFlow #:nodoc:
+ def initialize(view, fiber)
+ @view = view
+ @parent = nil
+ @child = view.output_buffer
+ @content = view._view_flow.content
+ @fiber = fiber
+ @root = Fiber.current.object_id
+ end
+
+ # Try to get an stored content. If the content
+ # is not available and we are inside the layout
+ # fiber, we set that we are waiting for the given
+ # key and yield.
+ def get(key)
+ return super if @content.key?(key)
+
+ if inside_fiber?
+ view = @view
+
+ begin
+ @waiting_for = key
+ view.output_buffer, @parent = @child, view.output_buffer
+ Fiber.yield
+ ensure
+ @waiting_for = nil
+ view.output_buffer, @child = @parent, view.output_buffer
+ end
+ end
+
+ super
+ end
+
+ # Appends the contents for the given key. This is called
+ # by provides and resumes back to the fiber if it is
+ # the key it is waiting for.
+ def append!(key, value)
+ super
+ @fiber.resume if @waiting_for == key
+ end
+
+ private
+
+ def inside_fiber?
+ Fiber.current.object_id != @root
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index d338ce616a..205116f610 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -17,11 +17,10 @@ module ActionView #:nodoc:
autoload :FormTagHelper
autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
autoload :NumberHelper
- autoload :PrototypeHelper
autoload :OutputSafetyHelper
autoload :RecordTagHelper
autoload :SanitizeHelper
- autoload :ScriptaculousHelper
+ autoload :SprocketsHelper
autoload :TagHelper
autoload :TextHelper
autoload :TranslationHelper
@@ -47,11 +46,10 @@ module ActionView #:nodoc:
include FormTagHelper
include JavaScriptHelper
include NumberHelper
- include PrototypeHelper
include OutputSafetyHelper
include RecordTagHelper
include SanitizeHelper
- include ScriptaculousHelper
+ include SprocketsHelper
include TagHelper
include TextHelper
include TranslationHelper
diff --git a/actionpack/lib/action_view/helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_paths.rb
new file mode 100644
index 0000000000..cb6737b94e
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/asset_paths.rb
@@ -0,0 +1,80 @@
+require 'active_support/core_ext/file'
+require 'action_view/helpers/asset_paths'
+
+module ActionView
+ module Helpers
+
+ class AssetPaths #:nodoc:
+ attr_reader :config, :controller
+
+ def initialize(config, controller)
+ @config = config
+ @controller = controller
+ end
+
+ # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched.
+ # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
+ # roots. Rewrite the asset path for cache-busting asset ids. Include
+ # asset host, if configured, with the correct request protocol.
+ def compute_public_path(source, dir, ext = nil, include_host = true)
+ source = source.to_s
+ return source if is_uri?(source)
+
+ source = rewrite_extension(source, dir, ext) if ext
+ source = "/#{dir}/#{source}" unless source[0] == ?/
+ source = rewrite_asset_path(source, dir)
+
+ if controller && include_host
+ has_request = controller.respond_to?(:request)
+ source = rewrite_host_and_protocol(source, has_request)
+ end
+
+ source
+ end
+
+ def is_uri?(path)
+ path =~ %r{^[-a-z]+://|^cid:|^//}
+ end
+
+ private
+
+ def rewrite_extension(source, dir, ext)
+ raise NotImplementedError
+ end
+
+ def rewrite_asset_path(source, path = nil)
+ raise NotImplementedError
+ end
+
+ def rewrite_host_and_protocol(source, has_request)
+ host = compute_asset_host(source)
+ if has_request && host && !is_uri?(host)
+ host = "#{controller.request.protocol}#{host}"
+ end
+ "#{host}#{source}"
+ end
+
+ # Pick an asset host for this source. Returns +nil+ if no host is set,
+ # the host if no wildcard is set, the host interpolated with the
+ # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
+ # or the value returned from invoking the proc if it's a proc or the value from
+ # invoking call if it's an object responding to call.
+ def compute_asset_host(source)
+ if host = config.asset_host
+ if host.is_a?(Proc) || host.respond_to?(:call)
+ case host.is_a?(Proc) ? host.arity : host.method(:call).arity
+ when 2
+ request = controller.respond_to?(:request) && controller.request
+ host.call(source, request)
+ else
+ host.call(source)
+ end
+ else
+ (host =~ /%d/) ? host % (source.hash % 4) : host
+ end
+ end
+ end
+ end
+
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index f6b2d4f3f4..9bc847a1ab 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -57,7 +57,7 @@ module ActionView
# +asset_host+ to a proc like this:
#
# ActionController::Base.asset_host = Proc.new { |source|
- # "http://assets#{source.hash % 2 + 1}.example.com"
+ # "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com"
# }
# image_tag("rails.png")
# # => <img alt="Rails" src="http://assets1.example.com/images/rails.png?1230601161" />
@@ -268,13 +268,17 @@ module ActionView
# image_path("edit.png") # => "/images/edit.png"
# image_path("icons/edit.png") # => "/images/icons/edit.png"
# image_path("/icons/edit.png") # => "/icons/edit.png"
- # image_path("http://www.railsapplication.com/img/edit.png") # => "http://www.railsapplication.com/img/edit.png"
+ # image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
#
# If you have images as application resources this method may conflict with their named routes.
# The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and
# plugin authors are encouraged to do so.
def image_path(source)
- asset_paths.compute_public_path(source, 'images')
+ if config.use_sprockets
+ asset_path(source)
+ else
+ asset_paths.compute_public_path(source, 'images')
+ end
end
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
@@ -287,9 +291,13 @@ module ActionView
# video_path("hd.avi") # => /videos/hd.avi
# video_path("trailers/hd.avi") # => /videos/trailers/hd.avi
# video_path("/trailers/hd.avi") # => /trailers/hd.avi
- # video_path("http://www.railsapplication.com/vid/hd.avi") # => http://www.railsapplication.com/vid/hd.avi
+ # video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi
def video_path(source)
- asset_paths.compute_public_path(source, 'videos')
+ if config.use_sprockets
+ asset_path(source)
+ else
+ asset_paths.compute_public_path(source, 'videos')
+ end
end
alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
@@ -302,9 +310,13 @@ module ActionView
# audio_path("horse.wav") # => /audios/horse.wav
# audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav
# audio_path("/sounds/horse.wav") # => /sounds/horse.wav
- # audio_path("http://www.railsapplication.com/sounds/horse.wav") # => http://www.railsapplication.com/sounds/horse.wav
+ # audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav
def audio_path(source)
- asset_paths.compute_public_path(source, 'audios')
+ if config.use_sprockets
+ asset_path(source)
+ else
+ asset_paths.compute_public_path(source, 'audios')
+ end
end
alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
@@ -434,7 +446,7 @@ module ActionView
private
def asset_paths
- @asset_paths ||= AssetPaths.new(config, controller)
+ @asset_paths ||= AssetTagHelper::AssetPaths.new(config, controller)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
index 52eb43a1cd..e4662a2919 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
@@ -8,9 +8,11 @@ module ActionView
module AssetTagHelper
class AssetIncludeTag
- attr_reader :config, :asset_paths
+ include TagHelper
+ attr_reader :config, :asset_paths
class_attribute :expansions
+
def self.inherited(base)
base.expansions = { }
end
@@ -56,7 +58,6 @@ module ActionView
end
end
-
private
def path_to_asset(source, include_host = true)
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
index 014a03c54d..38860431b4 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
@@ -1,10 +1,11 @@
require 'active_support/core_ext/file'
+require 'action_view/helpers/asset_paths'
module ActionView
module Helpers
module AssetTagHelper
- class AssetPaths
+ class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc:
# You can enable or disable the asset tag ids cache.
# With the cache enabled, the asset tag helper methods will make fewer
# expensive file system calls (the default implementation checks the file
@@ -14,34 +15,6 @@ module ActionView
# ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids = false
mattr_accessor :cache_asset_ids
- attr_reader :config, :controller
-
- def initialize(config, controller)
- @config = config
- @controller = controller
- end
-
- # Add the extension +ext+ if not present. Return full URLs otherwise untouched.
- # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
- # roots. Rewrite the asset path for cache-busting asset ids. Include
- # asset host, if configured, with the correct request protocol.
- def compute_public_path(source, dir, ext = nil, include_host = true)
- return source if is_uri?(source)
-
- source = rewrite_extension(source, dir, ext) if ext
- source = "/#{dir}/#{source}" unless source[0] == ?/
- if controller.respond_to?(:env) && controller.env["action_dispatch.asset_path"]
- source = rewrite_asset_path(source, controller.env["action_dispatch.asset_path"])
- end
- source = rewrite_asset_path(source, config.asset_path)
-
- has_request = controller.respond_to?(:request)
- source = rewrite_relative_url_root(source, controller.config.relative_url_root) if has_request && include_host
- source = rewrite_host_and_protocol(source, has_request) if include_host
-
- source
- end
-
# Add or change an asset id in the asset id cache. This can be used
# for SASS on Heroku.
# :api: public
@@ -51,101 +24,75 @@ module ActionView
end
end
- def is_uri?(path)
- path =~ %r{^[-a-z]+://|^cid:}
- end
+ private
- private
+ def rewrite_extension(source, dir, ext)
+ source_ext = File.extname(source)
- def rewrite_extension(source, dir, ext)
- source_ext = File.extname(source)
+ source_with_ext = if source_ext.empty?
+ "#{source}.#{ext}"
+ elsif ext != source_ext[1..-1]
+ with_ext = "#{source}.#{ext}"
+ with_ext if File.exist?(File.join(config.assets_dir, dir, with_ext))
+ end
- source_with_ext = if source_ext.empty?
- "#{source}.#{ext}"
- elsif ext != source_ext[1..-1]
- with_ext = "#{source}.#{ext}"
- with_ext if File.exist?(File.join(config.assets_dir, dir, with_ext))
- end
+ source_with_ext || source
+ end
- source_with_ext || source
- end
+ # Break out the asset path rewrite in case plugins wish to put the asset id
+ # someplace other than the query string.
+ def rewrite_asset_path(source, path = nil)
+ path = config.asset_path
- # Break out the asset path rewrite in case plugins wish to put the asset id
- # someplace other than the query string.
- def rewrite_asset_path(source, path = nil)
- if path && path.respond_to?(:call)
- return path.call(source)
- elsif path && path.is_a?(String)
- return path % [source]
- end
+ if path && path.respond_to?(:call)
+ return path.call(source)
+ elsif path && path.is_a?(String)
+ return path % [source]
+ end
- asset_id = rails_asset_id(source)
- if asset_id.empty?
- source
- else
- "#{source}?#{asset_id}"
- end
+ asset_id = rails_asset_id(source)
+ if asset_id.empty?
+ source
+ else
+ "#{source}?#{asset_id}"
end
+ end
- mattr_accessor :asset_ids_cache
- self.asset_ids_cache = {}
+ mattr_accessor :asset_ids_cache
+ self.asset_ids_cache = {}
- mattr_accessor :asset_ids_cache_guard
- self.asset_ids_cache_guard = Mutex.new
+ mattr_accessor :asset_ids_cache_guard
+ self.asset_ids_cache_guard = Mutex.new
- # Use the RAILS_ASSET_ID environment variable or the source's
- # modification time as its cache-busting asset id.
- def rails_asset_id(source)
- if asset_id = ENV["RAILS_ASSET_ID"]
+ # Use the RAILS_ASSET_ID environment variable or the source's
+ # modification time as its cache-busting asset id.
+ def rails_asset_id(source)
+ if asset_id = ENV["RAILS_ASSET_ID"]
+ asset_id
+ else
+ if self.cache_asset_ids && (asset_id = self.asset_ids_cache[source])
asset_id
else
- if self.cache_asset_ids && (asset_id = self.asset_ids_cache[source])
- asset_id
- else
- path = File.join(config.assets_dir, source)
- asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : ''
+ path = File.join(config.assets_dir, source)
+ asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : ''
- if self.cache_asset_ids
- add_to_asset_ids_cache(source, asset_id)
- end
-
- asset_id
+ if self.cache_asset_ids
+ add_to_asset_ids_cache(source, asset_id)
end
- end
- end
-
- def rewrite_relative_url_root(source, relative_url_root)
- relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source
- end
- def rewrite_host_and_protocol(source, has_request)
- host = compute_asset_host(source)
- if has_request && host && !is_uri?(host)
- host = "#{controller.request.protocol}#{host}"
+ asset_id
end
- "#{host}#{source}"
end
+ end
- # Pick an asset host for this source. Returns +nil+ if no host is set,
- # the host if no wildcard is set, the host interpolated with the
- # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
- # or the value returned from invoking the proc if it's a proc or the value from
- # invoking call if it's an object responding to call.
- def compute_asset_host(source)
- if host = config.asset_host
- if host.is_a?(Proc) || host.respond_to?(:call)
- case host.is_a?(Proc) ? host.arity : host.method(:call).arity
- when 2
- request = controller.respond_to?(:request) && controller.request
- host.call(source, request)
- else
- host.call(source)
- end
- else
- (host =~ /%d/) ? host % (source.hash % 4) : host
- end
- end
- end
+ def rewrite_relative_url_root(source, relative_url_root)
+ relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source
+ end
+
+ def rewrite_host_and_protocol(source, has_request)
+ source = rewrite_relative_url_root(source, controller.config.relative_url_root) if has_request
+ super(source, has_request)
+ 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 82bbfcc7d2..3d815b5e1f 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
@@ -1,6 +1,5 @@
require 'active_support/concern'
require 'active_support/core_ext/file'
-require 'action_view/helpers/tag_helper'
require 'action_view/helpers/asset_tag_helpers/asset_include_tag'
module ActionView
@@ -8,8 +7,6 @@ module ActionView
module AssetTagHelper
class JavascriptIncludeTag < AssetIncludeTag
- include TagHelper
-
def asset_name
'javascript'
end
@@ -80,105 +77,125 @@ module ActionView
# Used internally by javascript_include_tag to build the script path.
#
# ==== Examples
- # javascript_path "xmlhr" # => /javascripts/xmlhr.js
- # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
- # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
- # javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr
- # javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js
+ # javascript_path "xmlhr" # => /javascripts/xmlhr.js
+ # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
+ # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
+ # javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
+ # javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
def javascript_path(source)
- asset_paths.compute_public_path(source, 'javascripts', 'js')
+ if config.use_sprockets
+ asset_path(source, 'js')
+ else
+ asset_paths.compute_public_path(source, 'javascripts', 'js')
+ end
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
- # Returns an HTML script tag for each of the +sources+ provided. You
- # can pass in the filename (.js extension is optional) of JavaScript files
- # that exist in your <tt>public/javascripts</tt> directory for inclusion into the
- # current page or you can pass the full path relative to your document
- # root. To include the Prototype and Scriptaculous JavaScript libraries in
- # your application, pass <tt>:defaults</tt> as the source. When using
- # <tt>:defaults</tt>, if an <tt>application.js</tt> file exists in
- # <tt>public/javascripts</tt> it will be included as well. You can modify the
- # HTML attributes of the script tag by passing a hash as the last argument.
+ # 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
+ # to <tt>public/javascripts</tt>, full paths are assumed to be relative to the document
+ # root. Relative paths are idiomatic, use absolute paths only when needed.
+ #
+ # When passing paths, the ".js" extension is optional.
+ #
+ # To include the default JavaScript expansion pass <tt>:defaults</tt> as source.
+ # By default, <tt>:defaults</tt> loads jQuery. If the application was generated
+ # with "-j prototype" the libraries Prototype and Scriptaculous are loaded instead.
+ # In any case, the defaults can be overridden in <tt>config/application.rb</tt>:
+ #
+ # config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js)
+ #
+ # When using <tt>:defaults</tt>, if an <tt>application.js</tt> file exists in
+ # <tt>public/javascripts</tt> it will be included as well at the end.
+ #
+ # You can modify the HTML attributes of the script tag by passing a hash as the
+ # last argument.
#
# ==== Examples
- # javascript_include_tag "xmlhr" # =>
- # <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
+ # javascript_include_tag "xmlhr"
+ # # => <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
#
- # javascript_include_tag "xmlhr.js" # =>
- # <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
+ # javascript_include_tag "xmlhr.js"
+ # # => <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
#
- # javascript_include_tag "common.javascript", "/elsewhere/cools" # =>
- # <script type="text/javascript" src="/javascripts/common.javascript?1284139606"></script>
- # <script type="text/javascript" src="/elsewhere/cools.js?1423139606"></script>
+ # javascript_include_tag "common.javascript", "/elsewhere/cools"
+ # # => <script type="text/javascript" src="/javascripts/common.javascript?1284139606"></script>
+ # # <script type="text/javascript" src="/elsewhere/cools.js?1423139606"></script>
#
- # javascript_include_tag "http://www.railsapplication.com/xmlhr" # =>
- # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script>
+ # javascript_include_tag "http://www.example.com/xmlhr"
+ # # => <script type="text/javascript" src="http://www.example.com/xmlhr.js?1284139606"></script>
#
- # javascript_include_tag "http://www.railsapplication.com/xmlhr.js" # =>
- # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script>
+ # javascript_include_tag "http://www.example.com/xmlhr.js"
+ # # => <script type="text/javascript" src="http://www.example.com/xmlhr.js?1284139606"></script>
#
- # javascript_include_tag :defaults # =>
- # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/effects.js?1284139606"></script>
- # ...
- # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
+ # javascript_include_tag :defaults
+ # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/rails.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
#
# * = The application.js file is only referenced if it exists
#
- # You can also include all javascripts in the +javascripts+ directory using <tt>:all</tt> as the source:
+ # You can also include all JavaScripts in the +javascripts+ directory using <tt>:all</tt> as the source:
#
- # javascript_include_tag :all # =>
- # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/effects.js?1284139606"></script>
- # ...
- # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script>
+ # javascript_include_tag :all
+ # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/rails.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script>
#
- # Note that the default javascript files will be included first. So Prototype and Scriptaculous are available to
- # all subsequently included files.
+ # Note that your defaults of choice will be included first, so they will be available to all subsequently
+ # included files.
#
- # If you want Rails to search in all the subdirectories under javascripts, you should explicitly set <tt>:recursive</tt>:
+ # If you want Rails to search in all the subdirectories under <tt>public/javascripts</tt>, you should
+ # explicitly set <tt>:recursive</tt>:
#
# javascript_include_tag :all, :recursive => true
#
- # == Caching multiple javascripts into one
+ # == Caching multiple JavaScripts into one
#
- # You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
- # compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching
- # is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development
- # environment).
+ # You can also cache multiple JavaScripts into one file, which requires less HTTP connections to download
+ # and can better be compressed by gzip (leading to faster transfers). Caching will only happen if
+ # <tt>config.perform_caching</tt> is set to true (which is the case by default for the Rails
+ # production environment, but not for the development environment).
#
# ==== Examples
- # javascript_include_tag :all, :cache => true # when config.perform_caching is false =>
- # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/effects.js?1284139606"></script>
- # ...
- # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script>
#
- # javascript_include_tag :all, :cache => true # when config.perform_caching is true =>
- # <script type="text/javascript" src="/javascripts/all.js?1344139789"></script>
+ # # assuming config.perform_caching is false
+ # javascript_include_tag :all, :cache => true
+ # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/rails.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script>
+ #
+ # # assuming config.perform_caching is true
+ # javascript_include_tag :all, :cache => true
+ # # => <script type="text/javascript" src="/javascripts/all.js?1344139789"></script>
#
- # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is false =>
- # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/cart.js?1289139157"></script>
- # <script type="text/javascript" src="/javascripts/checkout.js?1299139816"></script>
+ # # assuming config.perform_caching is false
+ # javascript_include_tag "jquery", "cart", "checkout", :cache => "shop"
+ # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/cart.js?1289139157"></script>
+ # # <script type="text/javascript" src="/javascripts/checkout.js?1299139816"></script>
#
- # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is true =>
- # <script type="text/javascript" src="/javascripts/shop.js?1299139816"></script>
+ # # assuming config.perform_caching is true
+ # javascript_include_tag "jquery", "cart", "checkout", :cache => "shop"
+ # # => <script type="text/javascript" src="/javascripts/shop.js?1299139816"></script>
#
# The <tt>:recursive</tt> option is also available for caching:
#
# javascript_include_tag :all, :cache => true, :recursive => true
def javascript_include_tag(*sources)
- @javascript_include ||= JavascriptIncludeTag.new(config, asset_paths)
- @javascript_include.include_tag(*sources)
+ if config.use_sprockets
+ sprockets_javascript_include_tag(*sources)
+ else
+ @javascript_include ||= JavascriptIncludeTag.new(config, asset_paths)
+ @javascript_include.include_tag(*sources)
+ end
end
-
end
-
end
end
end
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 a48c87b49a..a95eb221be 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
@@ -1,6 +1,5 @@
require 'active_support/concern'
require 'active_support/core_ext/file'
-require 'action_view/helpers/tag_helper'
require 'action_view/helpers/asset_tag_helpers/asset_include_tag'
module ActionView
@@ -8,8 +7,6 @@ module ActionView
module AssetTagHelper
class StylesheetIncludeTag < AssetIncludeTag
- include TagHelper
-
def asset_name
'stylesheet'
end
@@ -57,13 +54,17 @@ module ActionView
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
#
# ==== Examples
- # stylesheet_path "style" # => /stylesheets/style.css
- # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
- # stylesheet_path "/dir/style.css" # => /dir/style.css
- # stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style
- # stylesheet_path "http://www.railsapplication.com/css/style.css" # => http://www.railsapplication.com/css/style.css
+ # stylesheet_path "style" # => /stylesheets/style.css
+ # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
+ # stylesheet_path "/dir/style.css" # => /dir/style.css
+ # stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
+ # stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
def stylesheet_path(source)
- asset_paths.compute_public_path(source, 'stylesheets', 'css')
+ if config.use_sprockets
+ asset_path(source, 'css')
+ else
+ asset_paths.compute_public_path(source, 'stylesheets', 'css')
+ end
end
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
@@ -78,8 +79,8 @@ module ActionView
# stylesheet_link_tag "style.css" # =>
# <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />
#
- # stylesheet_link_tag "http://www.railsapplication.com/style.css" # =>
- # <link href="http://www.railsapplication.com/style.css" media="screen" rel="stylesheet" type="text/css" />
+ # stylesheet_link_tag "http://www.example.com/style.css" # =>
+ # <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" type="text/css" />
#
# stylesheet_link_tag "style", :media => "all" # =>
# <link href="/stylesheets/style.css" media="all" rel="stylesheet" type="text/css" />
@@ -136,8 +137,12 @@ module ActionView
# stylesheet_link_tag :all, :concat => true
#
def stylesheet_link_tag(*sources)
- @stylesheet_include ||= StylesheetIncludeTag.new(config, asset_paths)
- @stylesheet_include.include_tag(*sources)
+ if config.use_sprockets
+ sprockets_stylesheet_link_tag(*sources)
+ else
+ @stylesheet_include ||= StylesheetIncludeTag.new(config, asset_paths)
+ @stylesheet_include.include_tag(*sources)
+ end
end
end
diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
index db9d7a08ff..96e5722252 100644
--- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
@@ -4,7 +4,7 @@ module ActionView
# = Action View Atom Feed Helpers
module Helpers #:nodoc:
module AtomFeedHelper
- # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERb or any other
+ # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
# template languages).
#
# Full usage example:
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index c88bd1efd5..0139714240 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -14,7 +14,7 @@ module ActionView
# variable. You can then use this variable anywhere in your templates or layout.
#
# ==== Examples
- # The capture method can be used in ERb templates...
+ # The capture method can be used in ERB templates...
#
# <% @greeting = capture do %>
# Welcome to my shiny new web page! The date and time is
@@ -107,8 +107,8 @@ module ActionView
# <%= javascript_include_tag :defaults %>
# <% end %>
#
- # That will place <tt>script</tt> tags for Prototype, Scriptaculous, and application.js (if it exists)
- # on the page; this technique is useful if you'll only be using these scripts in a few views.
+ # That will place +script+ tags for your default set of JavaScript files on the page;
+ # this technique is useful if you'll only be using these scripts in a few views.
#
# Note that content_for concatenates the blocks it is given for a particular
# identifier in order. For example:
@@ -135,8 +135,19 @@ module ActionView
# for elements that will be fragment cached.
def content_for(name, content = nil, &block)
content = capture(&block) if block_given?
- @_content_for[name] << content if content
- @_content_for[name] unless content
+ result = @_view_flow.append(name, content) if content
+ result unless content
+ end
+
+ # The same as +content_for+ but when used with streaming flushes
+ # straight back to the layout. In other words, if you want to
+ # concatenate several times to the same buffer when rendering a given
+ # template, you should use +content_for+, if not, use +provide+ to tell
+ # the layout to stop looking for more contents.
+ def provide(name, content = nil, &block)
+ content = capture(&block) if block_given?
+ result = @_view_flow.append!(name, content) if content
+ result unless content
end
# content_for? simply checks whether any content has been captured yet using content_for
@@ -158,7 +169,7 @@ module ActionView
# </body>
# </html>
def content_for?(name)
- @_content_for[name].present?
+ @_view_flow.get(name).present?
end
# Use an alternate output buffer for the duration of the block.
diff --git a/actionpack/lib/action_view/helpers/csrf_helper.rb b/actionpack/lib/action_view/helpers/csrf_helper.rb
index 65c8debc76..1f2bc28cac 100644
--- a/actionpack/lib/action_view/helpers/csrf_helper.rb
+++ b/actionpack/lib/action_view/helpers/csrf_helper.rb
@@ -17,10 +17,12 @@ module ActionView
# Note that regular forms generate hidden fields, and that Ajax calls are whitelisted,
# so they do not use these tags.
def csrf_meta_tags
- <<-METAS.strip_heredoc.chomp.html_safe if protect_against_forgery?
- <meta name="csrf-param" content="#{Rack::Utils.escape_html(request_forgery_protection_token)}"/>
- <meta name="csrf-token" content="#{Rack::Utils.escape_html(form_authenticity_token)}"/>
- METAS
+ if protect_against_forgery?
+ [
+ tag('meta', :name => 'csrf-param', :content => request_forgery_protection_token),
+ tag('meta', :name => 'csrf-token', :content => form_authenticity_token)
+ ].join("\n").html_safe
+ end
end
# For backwards compatibility.
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 6cd1565031..9277359d5c 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -26,9 +26,9 @@ module ActionView
# 30 secs <-> 1 min, 29 secs # => 1 minute
# 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
# 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
- # 89 mins, 29 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
- # 23 hrs, 59 mins, 29 secs <-> 47 hrs, 59 mins, 29 secs # => 1 day
- # 47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
+ # 89 mins, 30 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
+ # 23 hrs, 59 mins, 30 secs <-> 41 hrs, 59 mins, 29 secs # => 1 day
+ # 41 hrs, 59 mins, 30 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
# 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month
# 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months
# 1 yr <-> 1 yr, 3 months # => about 1 year
@@ -51,7 +51,7 @@ module ActionView
# distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
# distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds
# distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years
- # distance_of_time_in_words(from_time, from_time + 60.hours) # => about 3 days
+ # distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days
# distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute
# distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute
# distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
@@ -89,8 +89,8 @@ module ActionView
when 2..44 then locale.t :x_minutes, :count => distance_in_minutes
when 45..89 then locale.t :about_x_hours, :count => 1
when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
- when 1440..2529 then locale.t :x_days, :count => 1
- when 2530..43199 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
+ when 1440..2519 then locale.t :x_days, :count => 1
+ when 2520..43199 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
when 43200..86399 then locale.t :about_x_months, :count => 1
when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
else
@@ -112,10 +112,12 @@ module ActionView
#
# ==== Examples
# time_ago_in_words(3.minutes.from_now) # => 3 minutes
- # time_ago_in_words(Time.now - 15.hours) # => 15 hours
+ # time_ago_in_words(Time.now - 15.hours) # => about 15 hours
# time_ago_in_words(Time.now) # => less than a minute
#
- # from_time = Time.now - 3.days - 14.minutes - 25.seconds # => 3 days
+ # from_time = Time.now - 3.days - 14.minutes - 25.seconds
+ # time_ago_in_words(from_time) # => 3 days
+ #
def time_ago_in_words(from_time, include_seconds = false)
distance_of_time_in_words(from_time, Time.now, include_seconds)
end
@@ -464,7 +466,7 @@ module ActionView
#
# # Generates a select field for hours with a custom prompt. Use :prompt => true for a
# # generic prompt.
- # select_hour(13, :prompt =>'Choose hour')
+ # select_hour(13, :prompt => 'Choose hour')
#
def select_hour(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_hour
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 48abf119f1..efe30441b1 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -185,7 +185,7 @@ module ActionView
#
# is equivalent to something like:
#
- # <%= form_for @post, :as => :post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
+ # <%= form_for @post, :as => :post, :url => post_path(@post), :method => :put, :html => { :class => "edit_post", :id => "edit_post_45" } do |f| %>
# ...
# <% end %>
#
@@ -236,6 +236,16 @@ module ActionView
# Where <tt>@document = Document.find(params[:id])</tt> and
# <tt>@comment = Comment.new</tt>.
#
+ # === Setting the method
+ #
+ # You can force the form to use the full array of HTTP verbs by setting
+ #
+ # :method => (:get|:post|: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
+ # to interpret.
+ #
# === Unobtrusive JavaScript
#
# Specifying:
@@ -298,7 +308,7 @@ module ActionView
#
# In this case, if you use this:
#
- # <%= render :partial => f %>
+ # <%= render f %>
#
# The rendered template is <tt>people/_labelling_form</tt> and the local
# variable referencing the form builder is called
@@ -350,6 +360,7 @@ module ActionView
end
options[:html][:remote] = options.delete(: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)
@@ -599,10 +610,11 @@ module ActionView
# label(:post, :body)
# # => <label for="post_body">Write your entire text here</label>
#
- # Localization can also be based purely on the translation of the attribute-name like this:
+ # Localization can also be based purely on the translation of the attribute-name
+ # (if you are using ActiveRecord):
#
- # activemodel:
- # attribute:
+ # activerecord:
+ # attributes:
# post:
# cost: "Total cost"
#
@@ -935,7 +947,8 @@ module ActionView
label_tag(name_and_id["id"], options, &block)
else
content = if text.blank?
- I18n.t("helpers.label.#{object_name}.#{method_name}", :default => "").presence
+ method_and_value = tag_value.present? ? "#{method_name}.#{tag_value}" : method_name
+ I18n.t("helpers.label.#{object_name}.#{method_and_value}", :default => "").presence
else
text.to_s
end
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index cd3a3eac80..d7228bab67 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -1,42 +1,8 @@
require 'action_view/helpers/tag_helper'
module ActionView
- # = Action View JavaScript Helpers
module Helpers
- # Provides functionality for working with JavaScript in your views.
- #
- # == Ajax, controls and visual effects
- #
- # * For information on using Ajax, see
- # ActionView::Helpers::PrototypeHelper.
- # * For information on using controls and visual effects, see
- # ActionView::Helpers::ScriptaculousHelper.
- #
- # == Including the JavaScript libraries into your pages
- #
- # Rails includes the Prototype JavaScript framework and the Scriptaculous
- # JavaScript controls and visual effects library. If you wish to use
- # these libraries and their helpers (ActionView::Helpers::PrototypeHelper
- # and ActionView::Helpers::ScriptaculousHelper), you must do one of the
- # following:
- #
- # * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD
- # section of your page (recommended): This function will return
- # references to the JavaScript files created by the +rails+ command in
- # your <tt>public/javascripts</tt> directory. Using it is recommended as
- # the browser can then cache the libraries instead of fetching all the
- # functions anew on every request.
- # * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but
- # will only include the Prototype core library, which means you are able
- # to use all basic AJAX functionality. For the Scriptaculous-based
- # JavaScript helpers, like visual effects, autocompletion, drag and drop
- # and so on, you should use the method described above.
- #
- # For documentation on +javascript_include_tag+ see
- # ActionView::Helpers::AssetTagHelper.
module JavaScriptHelper
- include PrototypeHelper
-
JS_ESCAPE_MAP = {
'\\' => '\\\\',
'</' => '<\/',
@@ -47,6 +13,9 @@ module ActionView
"'" => "\\'" }
# Escape carrier returns and single and double quotes for JavaScript segments.
+ # Also available through the alias j(). This is particularly helpful in JavaScript responses, like:
+ #
+ # $('some_element').replaceWith('<%=j render 'some/element_template' %>');
def escape_javascript(javascript)
if javascript
javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] }
@@ -55,6 +24,8 @@ module ActionView
end
end
+ alias_method :j, :escape_javascript
+
# Returns a JavaScript tag with the +content+ inside. Example:
# javascript_tag "alert('All is good')"
#
@@ -91,87 +62,34 @@ module ActionView
"\n//#{cdata_section("\n#{content}\n//")}\n".html_safe
end
- # Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
- # onclick handler.
- #
- # The first argument +name+ is used as the button's value or display text.
+ # Returns a button whose +onclick+ handler triggers the passed JavaScript.
#
- # The next arguments are optional and may include the javascript function definition and a hash of html_options.
+ # The helper receives a name, JavaScript code, and an optional hash of HTML options. The
+ # name is used as button label and the JavaScript code goes into its +onclick+ attribute.
+ # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+.
#
- # The +function+ argument can be omitted in favor of an +update_page+
- # block, which evaluates to a string when the template is rendered
- # (instead of making an Ajax request first).
+ # button_to_function "Greeting", "alert('Hello world!')", :class => "ok"
+ # # => <input class="ok" onclick="alert('Hello world!');" type="button" value="Greeting" />
#
- # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
- #
- # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
- #
- # Examples:
- # button_to_function "Greeting", "alert('Hello world!')"
- # button_to_function "Delete", "if (confirm('Really?')) do_delete()"
- # button_to_function "Details" do |page|
- # page[:details].visual_effect :toggle_slide
- # end
- # button_to_function "Details", :class => "details_button" do |page|
- # page[:details].visual_effect :toggle_slide
- # end
- def button_to_function(name, *args, &block)
- html_options = args.extract_options!.symbolize_keys
-
- function = block_given? ? update_page(&block) : args[0] || ''
+ def button_to_function(name, function=nil, html_options={})
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
end
- # Returns a link of the given +name+ that will trigger a JavaScript +function+ using the
- # onclick handler and return false after the fact.
+ # Returns a link whose +onclick+ handler triggers the passed JavaScript.
#
- # The first argument +name+ is used as the link text.
+ # The helper receives a name, JavaScript code, and an optional hash of HTML options. The
+ # name is used as the link text and the JavaScript code goes into the +onclick+ attribute.
+ # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+. Once all
+ # the JavaScript is set, the helper appends "; return false;".
#
- # The next arguments are optional and may include the javascript function definition and a hash of html_options.
+ # The +href+ attribute of the tag is set to "#" unles +html_options+ has one.
#
- # The +function+ argument can be omitted in favor of an +update_page+
- # block, which evaluates to a string when the template is rendered
- # (instead of making an Ajax request first).
+ # link_to_function "Greeting", "alert('Hello world!')", :class => "nav_link"
+ # # => <a class="nav_link" href="#" onclick="alert('Hello world!'); return false;">Greeting</a>
#
- # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
- #
- # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
- #
- #
- # Examples:
- # link_to_function "Greeting", "alert('Hello world!')"
- # Produces:
- # <a onclick="alert('Hello world!'); return false;" href="#">Greeting</a>
- #
- # link_to_function(image_tag("delete"), "if (confirm('Really?')) do_delete()")
- # Produces:
- # <a onclick="if (confirm('Really?')) do_delete(); return false;" href="#">
- # <img src="/images/delete.png?" alt="Delete"/>
- # </a>
- #
- # link_to_function("Show me more", nil, :id => "more_link") do |page|
- # page[:details].visual_effect :toggle_blind
- # page[:more_link].replace_html "Show me less"
- # end
- # Produces:
- # <a href="#" id="more_link" onclick="try {
- # $(&quot;details&quot;).visualEffect(&quot;toggle_blind&quot;);
- # $(&quot;more_link&quot;).update(&quot;Show me less&quot;);
- # }
- # catch (e) {
- # alert('RJS error:\n\n' + e.toString());
- # alert('$(\&quot;details\&quot;).visualEffect(\&quot;toggle_blind\&quot;);
- # \n$(\&quot;more_link\&quot;).update(\&quot;Show me less\&quot;);');
- # throw e
- # };
- # return false;">Show me more</a>
- #
- def link_to_function(name, *args, &block)
- html_options = args.extract_options!.symbolize_keys
-
- function = block_given? ? update_page(&block) : args[0] || ''
+ def link_to_function(name, function, html_options={})
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
href = html_options[:href] || '#'
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 4e44843c4b..b545031fcc 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -100,7 +100,7 @@ module ActionView
# number_to_currency(1234567890.506, :precision => 3) # => $1,234,567,890.506
# number_to_currency(1234567890.506, :locale => :fr) # => 1 234 567 890,506 €
#
- # number_to_currency(1234567890.50, :negative_format => "(%u%n)")
+ # number_to_currency(-1234567890.50, :negative_format => "(%u%n)")
# # => ($1,234,567,890.51)
# number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "")
# # => &pound;1234567890,50
@@ -238,7 +238,7 @@ module ActionView
# number_with_precision(111.2345, :precision => 1, :significant => true) # => 100
# number_with_precision(13, :precision => 5, :significant => true) # => 13.000
# number_with_precision(111.234, :locale => :fr) # => 111,234
- # number_with_precision(13, :precision => 5, :significant => true, strip_insignificant_zeros => true)
+ # number_with_precision(13, :precision => 5, :significant => true, :strip_insignificant_zeros => true)
# # => 13
# number_with_precision(389.32314, :precision => 4, :significant => true) # => 389.3
# number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
@@ -318,7 +318,7 @@ module ActionView
# Non-significant zeros after the fractional separator are stripped out by default (set
# <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
# number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
- # number_to_human_size(524288000, :precision=>5) # => "500 MB"
+ # number_to_human_size(524288000, :precision => 5) # => "500 MB"
def number_to_human_size(number, options = {})
options.symbolize_keys!
@@ -407,7 +407,7 @@ module ActionView
# Unsignificant zeros after the decimal separator are stripped out by default (set
# <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
# number_to_human(12345012345, :significant_digits => 6) # => "12.345 Billion"
- # number_to_human(500000000, :precision=>5) # => "500 Million"
+ # number_to_human(500000000, :precision => 5) # => "500 Million"
#
# ==== Custom Unit Quantifiers
#
@@ -472,7 +472,7 @@ module ActionView
end.keys.map{|e_name| inverted_du[e_name] }.sort_by{|e| -e}
number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
- display_exponent = unit_exponents.find{|e| number_exponent >= e }
+ display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
number /= 10 ** display_exponent
unit = case units
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
deleted file mode 100644
index 18e303778c..0000000000
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ /dev/null
@@ -1,852 +0,0 @@
-require 'set'
-require 'active_support/json'
-require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/string/output_safety'
-
-module ActionView
- # = Action View Prototype Helpers
- module Helpers
- # Prototype[http://www.prototypejs.org/] is a JavaScript library that provides
- # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
- # Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php]
- # functionality, and more traditional object-oriented facilities for JavaScript.
- # This module provides a set of helpers to make it more convenient to call
- # functions from Prototype using Rails, including functionality to call remote
- # Rails methods (that is, making a background request to a Rails action) using Ajax.
- # This means that you can call actions in your controllers without
- # reloading the page, but still update certain parts of it using
- # injections into the DOM. A common use case is having a form that adds
- # a new element to a list without reloading the page or updating a shopping
- # cart total when a new item is added.
- #
- # == Usage
- # To be able to use these helpers, you must first include the Prototype
- # JavaScript framework in your pages.
- #
- # javascript_include_tag 'prototype'
- #
- # (See the documentation for
- # ActionView::Helpers::JavaScriptHelper for more information on including
- # this and other JavaScript files in your Rails templates.)
- #
- # Now you're ready to call a remote action either through a link...
- #
- # link_to_remote "Add to cart",
- # :url => { :action => "add", :id => product.id },
- # :update => { :success => "cart", :failure => "error" }
- #
- # ...through a form...
- #
- # <%= form_remote_tag :url => '/shipping' do -%>
- # <div><%= submit_tag 'Recalculate Shipping' %></div>
- # <% end -%>
- #
- # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
- # are listed here); check out the documentation for each method to find out more about its usage and options.
- #
- # === Common Options
- # See link_to_remote for documentation of options common to all Ajax
- # helpers; any of the options specified by link_to_remote can be used
- # by the other helpers.
- #
- # == Designing your Rails actions for Ajax
- # When building your action handlers (that is, the Rails actions that receive your background requests), it's
- # important to remember a few things. First, whatever your action would normally return to the browser, it will
- # return to the Ajax call. As such, you typically don't want to render with a layout. This call will cause
- # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up.
- # You can turn the layout off on particular actions by doing the following:
- #
- # class SiteController < ActionController::Base
- # layout "standard", :except => [:ajax_method, :more_ajax, :another_ajax]
- # end
- #
- # Optionally, you could do this in the method you wish to lack a layout:
- #
- # render :layout => false
- #
- # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the
- # method that Ajax uses to make background requests) method.
- # def name
- # # Is this an XmlHttpRequest request?
- # if (request.xhr?)
- # render :text => @name.to_s
- # else
- # # No? Then render an action.
- # render :action => 'view_attribute', :attr => @name
- # end
- # end
- #
- # The else clause can be left off and the current action will render with full layout and template. An extension
- # to this solution was posted to Ryan Heneise's blog at ArtOfMission["http://www.artofmission.com/"].
- #
- # layout proc{ |c| c.request.xhr? ? false : "application" }
- #
- # Dropping this in your ApplicationController turns the layout off for every request that is an "xhr" request.
- #
- # If you are just returning a little data or don't want to build a template for your output, you may opt to simply
- # render text output, like this:
- #
- # render :text => 'Return this from my method!'
- #
- # Since whatever the method returns is injected into the DOM, this will simply inject some text (or HTML, if you
- # tell it to). This is usually how small updates, such updating a cart total or a file count, are handled.
- #
- # == Updating multiple elements
- # See JavaScriptGenerator for information on updating multiple elements
- # on the page in an Ajax response.
- module PrototypeHelper
- CALLBACKS = Set.new([ :create, :uninitialized, :loading, :loaded,
- :interactive, :complete, :failure, :success ] +
- (100..599).to_a)
- AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url,
- :asynchronous, :method, :insertion, :position,
- :form, :with, :update, :script, :type ]).merge(CALLBACKS)
-
- # Returns the JavaScript needed for a remote function.
- # See the link_to_remote documentation at https://github.com/rails/prototype_legacy_helper as it takes the same arguments.
- #
- # Example:
- # # Generates: <select id="options" onchange="new Ajax.Updater('options',
- # # '/testing/update_options', {asynchronous:true, evalScripts:true})">
- # <select id="options" onchange="<%= remote_function(:update => "options",
- # :url => { :action => :update_options }) %>">
- # <option value="0">Hello</option>
- # <option value="1">World</option>
- # </select>
- def remote_function(options)
- javascript_options = options_for_ajax(options)
-
- update = ''
- if options[:update] && options[:update].is_a?(Hash)
- update = []
- update << "success:'#{options[:update][:success]}'" if options[:update][:success]
- update << "failure:'#{options[:update][:failure]}'" if options[:update][:failure]
- update = '{' + update.join(',') + '}'
- elsif options[:update]
- update << "'#{options[:update]}'"
- end
-
- function = update.empty? ?
- "new Ajax.Request(" :
- "new Ajax.Updater(#{update}, "
-
- url_options = options[:url]
- function << "'#{ERB::Util.html_escape(escape_javascript(url_for(url_options)))}'"
- function << ", #{javascript_options})"
-
- function = "#{options[:before]}; #{function}" if options[:before]
- function = "#{function}; #{options[:after]}" if options[:after]
- function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]
- function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm]
-
- return function.html_safe
- end
-
- # All the methods were moved to GeneratorMethods so that
- # #include_helpers_from_context has nothing to overwrite.
- class JavaScriptGenerator #:nodoc:
- def initialize(context, &block) #:nodoc:
- @context, @lines = context, []
- include_helpers_from_context
- @context.with_output_buffer(@lines) do
- @context.instance_exec(self, &block)
- end
- end
-
- private
- def include_helpers_from_context
- extend @context.helpers if @context.respond_to?(:helpers)
- extend GeneratorMethods
- end
-
- # JavaScriptGenerator generates blocks of JavaScript code that allow you
- # to change the content and presentation of multiple DOM elements. Use
- # this in your Ajax response bodies, either in a <tt>\<script></tt> tag
- # or as plain JavaScript sent with a Content-type of "text/javascript".
- #
- # Create new instances with PrototypeHelper#update_page or with
- # ActionController::Base#render, then call +insert_html+, +replace_html+,
- # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in
- # methods on the yielded generator in any order you like to modify the
- # content and appearance of the current page.
- #
- # Example:
- #
- # # Generates:
- # # new Element.insert("list", { bottom: "<li>Some item</li>" });
- # # new Effect.Highlight("list");
- # # ["status-indicator", "cancel-link"].each(Element.hide);
- # update_page do |page|
- # page.insert_html :bottom, 'list', "<li>#{@item.name}</li>"
- # page.visual_effect :highlight, 'list'
- # page.hide 'status-indicator', 'cancel-link'
- # end
- #
- #
- # Helper methods can be used in conjunction with JavaScriptGenerator.
- # When a helper method is called inside an update block on the +page+
- # object, that method will also have access to a +page+ object.
- #
- # Example:
- #
- # module ApplicationHelper
- # def update_time
- # page.replace_html 'time', Time.now.to_s(:db)
- # page.visual_effect :highlight, 'time'
- # end
- # end
- #
- # # Controller action
- # def poll
- # render(:update) { |page| page.update_time }
- # end
- #
- # Calls to JavaScriptGenerator not matching a helper method below
- # generate a proxy to the JavaScript Class named by the method called.
- #
- # Examples:
- #
- # # Generates:
- # # Foo.init();
- # update_page do |page|
- # page.foo.init
- # end
- #
- # # Generates:
- # # Event.observe('one', 'click', function () {
- # # $('two').show();
- # # });
- # update_page do |page|
- # page.event.observe('one', 'click') do |p|
- # p[:two].show
- # end
- # end
- #
- # You can also use PrototypeHelper#update_page_tag instead of
- # PrototypeHelper#update_page to wrap the generated JavaScript in a
- # <tt>\<script></tt> tag.
- module GeneratorMethods
- def to_s #:nodoc:
- (@lines * $/).tap do |javascript|
- if ActionView::Base.debug_rjs
- source = javascript.dup
- javascript.replace "try {\n#{source}\n} catch (e) "
- javascript << "{ alert('RJS error:\\n\\n' + e.toString()); alert('#{source.gsub('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}'); throw e }"
- end
- end
- end
-
- # Returns a element reference by finding it through +id+ in the DOM. This element can then be
- # used for further method calls. Examples:
- #
- # page['blank_slate'] # => $('blank_slate');
- # page['blank_slate'].show # => $('blank_slate').show();
- # page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
- #
- # You can also pass in a record, which will use ActionController::RecordIdentifier.dom_id to lookup
- # the correct id:
- #
- # page[@post] # => $('post_45')
- # page[Post.new] # => $('new_post')
- def [](id)
- case id
- when String, Symbol, NilClass
- JavaScriptElementProxy.new(self, id)
- else
- JavaScriptElementProxy.new(self, ActionController::RecordIdentifier.dom_id(id))
- end
- end
-
- # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
- # expression as an argument to another JavaScriptGenerator method.
- def literal(code)
- ::ActiveSupport::JSON::Variable.new(code.to_s)
- end
-
- # Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
- # used for further method calls. Examples:
- #
- # page.select('p') # => $$('p');
- # page.select('p.welcome b').first # => $$('p.welcome b').first();
- # page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
- #
- # You can also use prototype enumerations with the collection. Observe:
- #
- # # Generates: $$('#items li').each(function(value) { value.hide(); });
- # page.select('#items li').each do |value|
- # value.hide
- # end
- #
- # Though you can call the block param anything you want, they are always rendered in the
- # javascript as 'value, index.' Other enumerations, like collect() return the last statement:
- #
- # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
- # page.select('#items li').collect('hidden') do |item|
- # item.hide
- # end
- #
- def select(pattern)
- JavaScriptElementCollectionProxy.new(self, pattern)
- end
-
- # Inserts HTML at the specified +position+ relative to the DOM element
- # identified by the given +id+.
- #
- # +position+ may be one of:
- #
- # <tt>:top</tt>:: HTML is inserted inside the element, before the
- # element's existing content.
- # <tt>:bottom</tt>:: HTML is inserted inside the element, after the
- # element's existing content.
- # <tt>:before</tt>:: HTML is inserted immediately preceding the element.
- # <tt>:after</tt>:: HTML is inserted immediately following the element.
- #
- # +options_for_render+ may be either a string of HTML to insert, or a hash
- # of options to be passed to ActionView::Base#render. For example:
- #
- # # Insert the rendered 'navigation' partial just before the DOM
- # # element with ID 'content'.
- # # Generates: Element.insert("content", { before: "-- Contents of 'navigation' partial --" });
- # page.insert_html :before, 'content', :partial => 'navigation'
- #
- # # Add a list item to the bottom of the <ul> with ID 'list'.
- # # Generates: Element.insert("list", { bottom: "<li>Last item</li>" });
- # page.insert_html :bottom, 'list', '<li>Last item</li>'
- #
- def insert_html(position, id, *options_for_render)
- content = javascript_object_for(render(*options_for_render))
- record "Element.insert(\"#{id}\", { #{position.to_s.downcase}: #{content} });"
- end
-
- # Replaces the inner HTML of the DOM element with the given +id+.
- #
- # +options_for_render+ may be either a string of HTML to insert, or a hash
- # of options to be passed to ActionView::Base#render. For example:
- #
- # # Replace the HTML of the DOM element having ID 'person-45' with the
- # # 'person' partial for the appropriate object.
- # # Generates: Element.update("person-45", "-- Contents of 'person' partial --");
- # page.replace_html 'person-45', :partial => 'person', :object => @person
- #
- def replace_html(id, *options_for_render)
- call 'Element.update', id, render(*options_for_render)
- end
-
- # Replaces the "outer HTML" (i.e., the entire element, not just its
- # contents) of the DOM element with the given +id+.
- #
- # +options_for_render+ may be either a string of HTML to insert, or a hash
- # of options to be passed to ActionView::Base#render. For example:
- #
- # # Replace the DOM element having ID 'person-45' with the
- # # 'person' partial for the appropriate object.
- # page.replace 'person-45', :partial => 'person', :object => @person
- #
- # This allows the same partial that is used for the +insert_html+ to
- # be also used for the input to +replace+ without resorting to
- # the use of wrapper elements.
- #
- # Examples:
- #
- # <div id="people">
- # <%= render :partial => 'person', :collection => @people %>
- # </div>
- #
- # # Insert a new person
- # #
- # # Generates: new Insertion.Bottom({object: "Matz", partial: "person"}, "");
- # page.insert_html :bottom, :partial => 'person', :object => @person
- #
- # # Replace an existing person
- #
- # # Generates: Element.replace("person_45", "-- Contents of partial --");
- # page.replace 'person_45', :partial => 'person', :object => @person
- #
- def replace(id, *options_for_render)
- call 'Element.replace', id, render(*options_for_render)
- end
-
- # Removes the DOM elements with the given +ids+ from the page.
- #
- # Example:
- #
- # # Remove a few people
- # # Generates: ["person_23", "person_9", "person_2"].each(Element.remove);
- # page.remove 'person_23', 'person_9', 'person_2'
- #
- def remove(*ids)
- loop_on_multiple_args 'Element.remove', ids
- end
-
- # Shows hidden DOM elements with the given +ids+.
- #
- # Example:
- #
- # # Show a few people
- # # Generates: ["person_6", "person_13", "person_223"].each(Element.show);
- # page.show 'person_6', 'person_13', 'person_223'
- #
- def show(*ids)
- loop_on_multiple_args 'Element.show', ids
- end
-
- # Hides the visible DOM elements with the given +ids+.
- #
- # Example:
- #
- # # Hide a few people
- # # Generates: ["person_29", "person_9", "person_0"].each(Element.hide);
- # page.hide 'person_29', 'person_9', 'person_0'
- #
- def hide(*ids)
- loop_on_multiple_args 'Element.hide', ids
- end
-
- # Toggles the visibility of the DOM elements with the given +ids+.
- # Example:
- #
- # # Show a few people
- # # Generates: ["person_14", "person_12", "person_23"].each(Element.toggle);
- # page.toggle 'person_14', 'person_12', 'person_23' # Hides the elements
- # page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements
- #
- def toggle(*ids)
- loop_on_multiple_args 'Element.toggle', ids
- end
-
- # Displays an alert dialog with the given +message+.
- #
- # Example:
- #
- # # Generates: alert('This message is from Rails!')
- # page.alert('This message is from Rails!')
- def alert(message)
- call 'alert', message
- end
-
- # Redirects the browser to the given +location+ using JavaScript, in the same form as +url_for+.
- #
- # Examples:
- #
- # # Generates: window.location.href = "/mycontroller";
- # page.redirect_to(:action => 'index')
- #
- # # Generates: window.location.href = "/account/signup";
- # page.redirect_to(:controller => 'account', :action => 'signup')
- def redirect_to(location)
- url = location.is_a?(String) ? location : @context.url_for(location)
- record "window.location.href = #{url.inspect}"
- end
-
- # Reloads the browser's current +location+ using JavaScript
- #
- # Examples:
- #
- # # Generates: window.location.reload();
- # page.reload
- def reload
- record 'window.location.reload()'
- end
-
- # Calls the JavaScript +function+, optionally with the given +arguments+.
- #
- # If a block is given, the block will be passed to a new JavaScriptGenerator;
- # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt>
- # and passed as the called function's final argument.
- #
- # Examples:
- #
- # # Generates: Element.replace(my_element, "My content to replace with.")
- # page.call 'Element.replace', 'my_element', "My content to replace with."
- #
- # # Generates: alert('My message!')
- # page.call 'alert', 'My message!'
- #
- # # Generates:
- # # my_method(function() {
- # # $("one").show();
- # # $("two").hide();
- # # });
- # page.call(:my_method) do |p|
- # p[:one].show
- # p[:two].hide
- # end
- def call(function, *arguments, &block)
- record "#{function}(#{arguments_for_call(arguments, block)})"
- end
-
- # Assigns the JavaScript +variable+ the given +value+.
- #
- # Examples:
- #
- # # Generates: my_string = "This is mine!";
- # page.assign 'my_string', 'This is mine!'
- #
- # # Generates: record_count = 33;
- # page.assign 'record_count', 33
- #
- # # Generates: tabulated_total = 47
- # page.assign 'tabulated_total', @total_from_cart
- #
- def assign(variable, value)
- record "#{variable} = #{javascript_object_for(value)}"
- end
-
- # Writes raw JavaScript to the page.
- #
- # Example:
- #
- # page << "alert('JavaScript with Prototype.');"
- def <<(javascript)
- @lines << javascript
- end
-
- # Executes the content of the block after a delay of +seconds+. Example:
- #
- # # Generates:
- # # setTimeout(function() {
- # # ;
- # # new Effect.Fade("notice",{});
- # # }, 20000);
- # page.delay(20) do
- # page.visual_effect :fade, 'notice'
- # end
- def delay(seconds = 1)
- record "setTimeout(function() {\n\n"
- yield
- record "}, #{(seconds * 1000).to_i})"
- end
-
- private
- def loop_on_multiple_args(method, ids)
- record(ids.size>1 ?
- "#{javascript_object_for(ids)}.each(#{method})" :
- "#{method}(#{javascript_object_for(ids.first)})")
- end
-
- def page
- self
- end
-
- def record(line)
- line = "#{line.to_s.chomp.gsub(/\;\z/, '')};"
- self << line
- line
- end
-
- def render(*options)
- with_formats(:html) do
- case option = options.first
- when Hash
- @context.render(*options)
- else
- option.to_s
- end
- end
- end
-
- def with_formats(*args)
- @context ? @context.lookup_context.update_details(:formats => args) { yield } : yield
- end
-
- def javascript_object_for(object)
- ::ActiveSupport::JSON.encode(object)
- end
-
- def arguments_for_call(arguments, block = nil)
- arguments << block_to_function(block) if block
- arguments.map { |argument| javascript_object_for(argument) }.join ', '
- end
-
- def block_to_function(block)
- generator = self.class.new(@context, &block)
- literal("function() { #{generator.to_s} }")
- end
-
- def method_missing(method, *arguments)
- JavaScriptProxy.new(self, method.to_s.camelize)
- end
- end
- end
-
- # Yields a JavaScriptGenerator and returns the generated JavaScript code.
- # Use this to update multiple elements on a page in an Ajax response.
- # See JavaScriptGenerator for more information.
- #
- # Example:
- #
- # update_page do |page|
- # page.hide 'spinner'
- # end
- def update_page(&block)
- JavaScriptGenerator.new(self, &block).to_s.html_safe
- end
-
- # Works like update_page but wraps the generated JavaScript in a
- # <tt>\<script></tt> tag. Use this to include generated JavaScript in an
- # ERb template. See JavaScriptGenerator for more information.
- #
- # +html_options+ may be a hash of <tt>\<script></tt> attributes to be
- # passed to ActionView::Helpers::JavaScriptHelper#javascript_tag.
- def update_page_tag(html_options = {}, &block)
- javascript_tag update_page(&block), html_options
- end
-
- protected
- def options_for_javascript(options)
- if options.empty?
- '{}'
- else
- "{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}"
- end
- end
-
- def options_for_ajax(options)
- js_options = build_callbacks(options)
-
- js_options['asynchronous'] = options[:type] != :synchronous
- js_options['method'] = method_option_to_s(options[:method]) if options[:method]
- js_options['insertion'] = "'#{options[:position].to_s.downcase}'" if options[:position]
- js_options['evalScripts'] = options[:script].nil? || options[:script]
-
- if options[:form]
- js_options['parameters'] = 'Form.serialize(this)'
- elsif options[:submit]
- js_options['parameters'] = "Form.serialize('#{options[:submit]}')"
- elsif options[:with]
- js_options['parameters'] = options[:with]
- end
-
- if protect_against_forgery? && !options[:form]
- if js_options['parameters']
- js_options['parameters'] << " + '&"
- else
- js_options['parameters'] = "'"
- end
- js_options['parameters'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')"
- end
-
- options_for_javascript(js_options)
- end
-
- def method_option_to_s(method)
- (method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'"
- end
-
- def build_callbacks(options)
- callbacks = {}
- options.each do |callback, code|
- if CALLBACKS.include?(callback)
- name = 'on' + callback.to_s.capitalize
- callbacks[name] = "function(request){#{code}}"
- end
- end
- callbacks
- end
- end
-
- # Converts chained method calls on DOM proxy elements into JavaScript chains
- class JavaScriptProxy < ActiveSupport::BasicObject #:nodoc:
-
- def initialize(generator, root = nil)
- @generator = generator
- @generator << root if root
- end
-
- def is_a?(klass)
- klass == JavaScriptProxy
- end
-
- private
- def method_missing(method, *arguments, &block)
- if method.to_s =~ /(.*)=$/
- assign($1, arguments.first)
- else
- call("#{method.to_s.camelize(:lower)}", *arguments, &block)
- end
- end
-
- def call(function, *arguments, &block)
- append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})")
- self
- end
-
- def assign(variable, value)
- append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}")
- end
-
- def function_chain
- @function_chain ||= @generator.instance_variable_get(:@lines)
- end
-
- def append_to_function_chain!(call)
- function_chain[-1].chomp!(';')
- function_chain[-1] += ".#{call};"
- end
- end
-
- class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
- def initialize(generator, id)
- @id = id
- super(generator, "$(#{::ActiveSupport::JSON.encode(id)})")
- end
-
- # Allows access of element attributes through +attribute+. Examples:
- #
- # page['foo']['style'] # => $('foo').style;
- # page['foo']['style']['color'] # => $('blank_slate').style.color;
- # page['foo']['style']['color'] = 'red' # => $('blank_slate').style.color = 'red';
- # page['foo']['style'].color = 'red' # => $('blank_slate').style.color = 'red';
- def [](attribute)
- append_to_function_chain!(attribute)
- self
- end
-
- def []=(variable, value)
- assign(variable, value)
- end
-
- def replace_html(*options_for_render)
- call 'update', @generator.send(:render, *options_for_render)
- end
-
- def replace(*options_for_render)
- call 'replace', @generator.send(:render, *options_for_render)
- end
-
- def reload(options_for_replace = {})
- replace(options_for_replace.merge({ :partial => @id.to_s }))
- end
-
- end
-
- class JavaScriptVariableProxy < JavaScriptProxy #:nodoc:
- def initialize(generator, variable)
- @variable = ::ActiveSupport::JSON::Variable.new(variable)
- @empty = true # only record lines if we have to. gets rid of unnecessary linebreaks
- super(generator)
- end
-
- # The JSON Encoder calls this to check for the +to_json+ method
- # Since it's a blank slate object, I suppose it responds to anything.
- def respond_to?(*)
- true
- end
-
- def as_json(options = nil)
- @variable
- end
-
- private
- def append_to_function_chain!(call)
- @generator << @variable if @empty
- @empty = false
- super
- end
- end
-
- class JavaScriptCollectionProxy < JavaScriptProxy #:nodoc:
- ENUMERABLE_METHODS_WITH_RETURN = [:all, :any, :collect, :map, :detect, :find, :find_all, :select, :max, :min, :partition, :reject, :sort_by, :in_groups_of, :each_slice] unless defined? ENUMERABLE_METHODS_WITH_RETURN
- ENUMERABLE_METHODS = ENUMERABLE_METHODS_WITH_RETURN + [:each] unless defined? ENUMERABLE_METHODS
- attr_reader :generator
- delegate :arguments_for_call, :to => :generator
-
- def initialize(generator, pattern)
- super(generator, @pattern = pattern)
- end
-
- def each_slice(variable, number, &block)
- if block
- enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block
- else
- add_variable_assignment!(variable)
- append_enumerable_function!("eachSlice(#{::ActiveSupport::JSON.encode(number)});")
- end
- end
-
- def grep(variable, pattern, &block)
- enumerate :grep, :variable => variable, :return => true, :method_args => [::ActiveSupport::JSON::Variable.new(pattern.inspect)], :yield_args => %w(value index), &block
- end
-
- def in_groups_of(variable, number, fill_with = nil)
- arguments = [number]
- arguments << fill_with unless fill_with.nil?
- add_variable_assignment!(variable)
- append_enumerable_function!("inGroupsOf(#{arguments_for_call arguments});")
- end
-
- def inject(variable, memo, &block)
- enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block
- end
-
- def pluck(variable, property)
- add_variable_assignment!(variable)
- append_enumerable_function!("pluck(#{::ActiveSupport::JSON.encode(property)});")
- end
-
- def zip(variable, *arguments, &block)
- add_variable_assignment!(variable)
- append_enumerable_function!("zip(#{arguments_for_call arguments}")
- if block
- function_chain[-1] += ", function(array) {"
- yield ::ActiveSupport::JSON::Variable.new('array')
- add_return_statement!
- @generator << '});'
- else
- function_chain[-1] += ');'
- end
- end
-
- private
- def method_missing(method, *arguments, &block)
- if ENUMERABLE_METHODS.include?(method)
- returnable = ENUMERABLE_METHODS_WITH_RETURN.include?(method)
- variable = arguments.first if returnable
- enumerate(method, {:variable => (arguments.first if returnable), :return => returnable, :yield_args => %w(value index)}, &block)
- else
- super
- end
- end
-
- # Options
- # * variable - name of the variable to set the result of the enumeration to
- # * method_args - array of the javascript enumeration method args that occur before the function
- # * yield_args - array of the javascript yield args
- # * return - true if the enumeration should return the last statement
- def enumerate(enumerable, options = {}, &block)
- options[:method_args] ||= []
- options[:yield_args] ||= []
- yield_args = options[:yield_args] * ', '
- method_args = arguments_for_call options[:method_args] # foo, bar, function
- method_args << ', ' unless method_args.blank?
- add_variable_assignment!(options[:variable]) if options[:variable]
- append_enumerable_function!("#{enumerable.to_s.camelize(:lower)}(#{method_args}function(#{yield_args}) {")
- # only yield as many params as were passed in the block
- yield(*options[:yield_args].collect { |p| JavaScriptVariableProxy.new(@generator, p) }[0..block.arity-1])
- add_return_statement! if options[:return]
- @generator << '});'
- end
-
- def add_variable_assignment!(variable)
- function_chain.push("var #{variable} = #{function_chain.pop}")
- end
-
- def add_return_statement!
- unless function_chain.last =~ /return/
- function_chain.push("return #{function_chain.pop.chomp(';')};")
- end
- end
-
- def append_enumerable_function!(call)
- function_chain[-1].chomp!(';')
- function_chain[-1] += ".#{call}"
- end
- end
-
- class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
- def initialize(generator, pattern)
- super(generator, "$$(#{::ActiveSupport::JSON.encode(pattern)})")
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb
index 4d300a1469..142a25f118 100644
--- a/actionpack/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb
@@ -10,7 +10,7 @@ module ActionView
# relate to the specified Active Record object. Usage example:
#
# <%= div_for(@person, :class => "foo") do %>
- # <%=h @person.name %>
+ # <%= @person.name %>
# <% end %>
#
# produces:
@@ -25,8 +25,8 @@ module ActionView
# that relate to the specified Active Record object. For example:
#
# <%= content_tag_for(:tr, @person) do %>
- # <td><%=h @person.first_name %></td>
- # <td><%=h @person.last_name %></td>
+ # <td><%= @person.first_name %></td>
+ # <td><%= @person.last_name %></td>
# <% end %>
#
# would produce the following HTML (assuming @person is an instance of
diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb
index 0fee34f8a4..841be0a567 100644
--- a/actionpack/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb
@@ -94,7 +94,7 @@ module ActionView
# # => Please e-mail me at me@email.com.
#
# strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.')
- # # => Blog: Visit
+ # # => Blog: Visit.
def strip_links(html)
self.class.link_sanitizer.sanitize(html)
end
diff --git a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb b/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
deleted file mode 100644
index 8610c2469e..0000000000
--- a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
+++ /dev/null
@@ -1,263 +0,0 @@
-require 'action_view/helpers/javascript_helper'
-require 'active_support/json'
-
-module ActionView
- # = Action View Scriptaculous Helpers
- module Helpers
- # Provides a set of helpers for calling Scriptaculous[http://script.aculo.us/]
- # JavaScript functions, including those which create Ajax controls and visual
- # effects.
- #
- # To be able to use these helpers, you must include the Prototype
- # JavaScript framework and the Scriptaculous JavaScript library in your
- # pages. See the documentation for ActionView::Helpers::JavaScriptHelper
- # for more information on including the necessary JavaScript.
- #
- # The Scriptaculous helpers' behavior can be tweaked with various options.
- #
- # See the documentation at http://script.aculo.us for more information on
- # using these helpers in your application.
- module ScriptaculousHelper
- TOGGLE_EFFECTS = [:toggle_appear, :toggle_slide, :toggle_blind]
-
- # Returns a JavaScript snippet to be used on the Ajax callbacks for
- # starting visual effects.
- #
- # If no +element_id+ is given, it assumes "element" which should be a local
- # variable in the generated JavaScript execution context. This can be
- # used for example with +drop_receiving_element+:
- #
- # <%= drop_receiving_element (...), :loading => visual_effect(:fade) %>
- #
- # This would fade the element that was dropped on the drop receiving
- # element.
- #
- # For toggling visual effects, you can use <tt>:toggle_appear</tt>, <tt>:toggle_slide</tt>, and
- # <tt>:toggle_blind</tt> which will alternate between appear/fade, slidedown/slideup, and
- # blinddown/blindup respectively.
- #
- # You can change the behaviour with various options, see
- # http://script.aculo.us for more documentation.
- def visual_effect(name, element_id = false, js_options = {})
- element = element_id ? ActiveSupport::JSON.encode(element_id) : "element"
-
- js_options[:queue] = if js_options[:queue].is_a?(Hash)
- '{' + js_options[:queue].map {|k, v| k == :limit ? "#{k}:#{v}" : "#{k}:'#{v}'" }.join(',') + '}'
- elsif js_options[:queue]
- "'#{js_options[:queue]}'"
- end if js_options[:queue]
-
- [:endcolor, :direction, :startcolor, :scaleMode, :restorecolor].each do |option|
- js_options[option] = "'#{js_options[option]}'" if js_options[option]
- end
-
- if TOGGLE_EFFECTS.include? name.to_sym
- "Effect.toggle(#{element},'#{name.to_s.gsub(/^toggle_/,'')}',#{options_for_javascript(js_options)});"
- else
- "new Effect.#{name.to_s.camelize}(#{element},#{options_for_javascript(js_options)});"
- end
- end
-
- # Makes the element with the DOM ID specified by +element_id+ sortable
- # by drag-and-drop and make an Ajax call whenever the sort order has
- # changed. By default, the action called gets the serialized sortable
- # element as parameters.
- #
- # Example:
- #
- # <%= sortable_element("my_list", :url => { :action => "order" }) %>
- #
- # In the example, the action gets a "my_list" array parameter
- # containing the values of the ids of elements the sortable consists
- # of, in the current order.
- #
- # Important: For this to work, the sortable elements must have id
- # attributes in the form "string_identifier". For example, "item_1". Only
- # the identifier part of the id attribute will be serialized.
- #
- # Additional +options+ are:
- #
- # * <tt>:format</tt> - A regular expression to determine what to send as the
- # serialized id to the server (the default is <tt>/^[^_]*_(.*)$/</tt>).
- #
- # * <tt>:constraint</tt> - Whether to constrain the dragging to either
- # <tt>:horizontal</tt> or <tt>:vertical</tt> (or false to make it unconstrained).
- #
- # * <tt>:overlap</tt> - Calculate the item overlap in the <tt>:horizontal</tt>
- # or <tt>:vertical</tt> direction.
- #
- # * <tt>:tag</tt> - Which children of the container element to treat as
- # sortable (default is <tt>li</tt>).
- #
- # * <tt>:containment</tt> - Takes an element or array of elements to treat as
- # potential drop targets (defaults to the original target element).
- #
- # * <tt>:only</tt> - A CSS class name or array of class names used to filter
- # out child elements as candidates.
- #
- # * <tt>:scroll</tt> - Determines whether to scroll the list during drag
- # operations if the list runs past the visual border.
- #
- # * <tt>:tree</tt> - Determines whether to treat nested lists as part of the
- # main sortable list. This means that you can create multi-layer lists,
- # and not only sort items at the same level, but drag and sort items
- # between levels.
- #
- # * <tt>:hoverclass</tt> - If set, the Droppable will have this additional CSS class
- # when an accepted Draggable is hovered over it.
- #
- # * <tt>:handle</tt> - Sets whether the element should only be draggable by an
- # embedded handle. The value may be a string referencing a CSS class value
- # (as of script.aculo.us V1.5). The first child/grandchild/etc. element
- # found within the element that has this CSS class value will be used as
- # the handle.
- #
- # * <tt>:ghosting</tt> - Clones the element and drags the clone, leaving
- # the original in place until the clone is dropped (default is <tt>false</tt>).
- #
- # * <tt>:dropOnEmpty</tt> - If true the Sortable container will be made into
- # a Droppable, that can receive a Draggable (as according to the containment
- # rules) as a child element when there are no more elements inside (default
- # is <tt>false</tt>).
- #
- # * <tt>:onChange</tt> - Called whenever the sort order changes while dragging. When
- # dragging from one Sortable to another, the callback is called once on each
- # Sortable. Gets the affected element as its parameter.
- #
- # * <tt>:onUpdate</tt> - Called when the drag ends and the Sortable's order is
- # changed in any way. When dragging from one Sortable to another, the callback
- # is called once on each Sortable. Gets the container as its parameter.
- #
- # See http://script.aculo.us for more documentation.
- def sortable_element(element_id, options = {})
- javascript_tag(sortable_element_js(element_id, options).chop!)
- end
-
- def sortable_element_js(element_id, options = {}) #:nodoc:
- options[:with] ||= "Sortable.serialize(#{ActiveSupport::JSON.encode(element_id)})"
- options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
- options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
-
- [:tag, :overlap, :constraint, :handle].each do |option|
- options[option] = "'#{options[option]}'" if options[option]
- end
-
- options[:containment] = array_or_string_for_javascript(options[:containment]) if options[:containment]
- options[:only] = array_or_string_for_javascript(options[:only]) if options[:only]
-
- %(Sortable.create(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
- end
-
- # Makes the element with the DOM ID specified by +element_id+ draggable.
- #
- # Example:
- # <%= draggable_element("my_image", :revert => true)
- #
- # You can change the behaviour with various options, see
- # http://script.aculo.us for more documentation.
- def draggable_element(element_id, options = {})
- javascript_tag(draggable_element_js(element_id, options).chop!)
- end
-
- def draggable_element_js(element_id, options = {}) #:nodoc:
- %(new Draggable(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
- end
-
- # Makes the element with the DOM ID specified by +element_id+ receive
- # dropped draggable elements (created by +draggable_element+).
- # and make an AJAX call. By default, the action called gets the DOM ID
- # of the element as parameter.
- #
- # Example:
- # <%= drop_receiving_element("my_cart", :url =>
- # { :controller => "cart", :action => "add" }) %>
- #
- # You can change the behaviour with various options, see
- # http://script.aculo.us for more documentation.
- #
- # Some of these +options+ include:
- # * <tt>:accept</tt> - Set this to a string or an array of strings describing the
- # allowable CSS classes that the +draggable_element+ must have in order
- # to be accepted by this +drop_receiving_element+.
- #
- # * <tt>:confirm</tt> - Adds a confirmation dialog. Example:
- #
- # :confirm => "Are you sure you want to do this?"
- #
- # * <tt>:hoverclass</tt> - If set, the +drop_receiving_element+ will have
- # this additional CSS class when an accepted +draggable_element+ is
- # hovered over it.
- #
- # * <tt>:onDrop</tt> - Called when a +draggable_element+ is dropped onto
- # this element. Override this callback with a JavaScript expression to
- # change the default drop behaviour. Example:
- #
- # :onDrop => "function(draggable_element, droppable_element, event) { alert('I like bananas') }"
- #
- # This callback gets three parameters: The Draggable element, the Droppable
- # element and the Event object. You can extract additional information about
- # the drop - like if the Ctrl or Shift keys were pressed - from the Event object.
- #
- # * <tt>:with</tt> - A JavaScript expression specifying the parameters for
- # the XMLHttpRequest. Any expressions should return a valid URL query string.
- def drop_receiving_element(element_id, options = {})
- javascript_tag(drop_receiving_element_js(element_id, options).chop!)
- end
-
- def drop_receiving_element_js(element_id, options = {}) #:nodoc:
- options[:with] ||= "'id=' + encodeURIComponent(element.id)"
- options[:onDrop] ||= "function(element){" + remote_function(options) + "}"
- options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
-
- options[:accept] = array_or_string_for_javascript(options[:accept]) if options[:accept]
- options[:hoverclass] = "'#{options[:hoverclass]}'" if options[:hoverclass]
-
- # Confirmation happens during the onDrop callback, so it can be removed from the options
- options.delete(:confirm) if options[:confirm]
-
- %(Droppables.add(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
- end
-
- protected
- def array_or_string_for_javascript(option)
- if option.kind_of?(Array)
- "['#{option.join('\',\'')}']"
- elsif !option.nil?
- "'#{option}'"
- end
- end
- end
-
- module PrototypeHelper
- class JavaScriptGenerator
- module GeneratorMethods
- # Starts a script.aculo.us visual effect. See
- # ActionView::Helpers::ScriptaculousHelper for more information.
- def visual_effect(name, id = nil, options = {})
- record @context.send(:visual_effect, name, id, options)
- end
-
- # Creates a script.aculo.us sortable element. Useful
- # to recreate sortable elements after items get added
- # or deleted.
- # See ActionView::Helpers::ScriptaculousHelper for more information.
- def sortable(id, options = {})
- record @context.send(:sortable_element_js, id, options)
- end
-
- # Creates a script.aculo.us draggable element.
- # See ActionView::Helpers::ScriptaculousHelper for more information.
- def draggable(id, options = {})
- record @context.send(:draggable_element_js, id, options)
- end
-
- # Creates a script.aculo.us drop receiving element.
- # See ActionView::Helpers::ScriptaculousHelper for more information.
- def drop_receiving(id, options = {})
- record @context.send(:drop_receiving_element_js, id, options)
- end
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/sprockets_helper.rb b/actionpack/lib/action_view/helpers/sprockets_helper.rb
new file mode 100644
index 0000000000..b43b91178c
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/sprockets_helper.rb
@@ -0,0 +1,69 @@
+require 'uri'
+require 'action_view/helpers/asset_paths'
+
+module ActionView
+ module Helpers
+ module SprocketsHelper
+ def asset_path(source, default_ext = nil)
+ sprockets_asset_paths.compute_public_path(source, 'assets', default_ext, true)
+ end
+
+ def sprockets_javascript_include_tag(source, options = {})
+ options = {
+ 'type' => "text/javascript",
+ 'src' => asset_path(source, 'js')
+ }.merge(options.stringify_keys)
+
+ content_tag 'script', "", options
+ end
+
+ def sprockets_stylesheet_link_tag(source, options = {})
+ options = {
+ 'rel' => "stylesheet",
+ 'type' => "text/css",
+ 'media' => "screen",
+ 'href' => asset_path(source, 'css')
+ }.merge(options.stringify_keys)
+
+ tag 'link', options
+ end
+
+ private
+
+ def sprockets_asset_paths
+ @sprockets_asset_paths ||= begin
+ config = self.config if respond_to?(:config)
+ controller = self.controller if respond_to?(:controller)
+ SprocketsHelper::AssetPaths.new(config, controller)
+ end
+ end
+
+ class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc:
+ def rewrite_asset_path(source, dir)
+ if source =~ /^\/#{dir}\/(.+)/
+ assets.path($1, performing_caching?, dir)
+ else
+ source
+ end
+ end
+
+ def rewrite_extension(source, dir, ext)
+ if ext && File.extname(source).empty?
+ "#{source}.#{ext}"
+ else
+ source
+ end
+ end
+
+ def assets
+ Rails.application.assets
+ end
+
+ # When included in Sprockets::Context, we need to ask the top-level config as the controller is not available
+ def performing_caching?
+ @config ? @config.perform_caching : Rails.application.config.action_controller.perform_caching
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 2d3c5fe7e7..06e2b027da 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -19,7 +19,7 @@ module ActionView
# simple_format('<a href="http://example.com/">Example</a>')
# # => "<p><a href=\"http://example.com/\">Example</a></p>"
#
- # simple_format('<a href="javascript:alert('no!')">Example</a>')
+ # simple_format('<a href="javascript:alert(\'no!\')">Example</a>')
# # => "<p><a>Example</a></p>"
#
# If you want to escape all content, you should invoke the +h+ method before
@@ -295,15 +295,15 @@ module ActionView
# +link+ as its optional second parameter and the +html_options+ hash
# as its optional third parameter:
# post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com."
- # auto_link(post_body, :urls) # => Once upon\na time
+ # auto_link(post_body, :urls)
# # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\">http://www.myblog.com</a>.
# Please e-mail me at me@email.com."
#
- # auto_link(post_body, :all, :target => "_blank") # => Once upon\na time
+ # auto_link(post_body, :all, :target => "_blank")
# # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.myblog.com</a>.
# Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>."
def auto_link(text, *args, &block)#link = :all, html = {}, &block)
- return ''.html_safe if text.blank?
+ return '' if text.blank?
options = args.size == 2 ? {} : args.extract_options! # this is necessary because the old auto_link API has a Hash as its last parameter
unless args.empty?
@@ -507,7 +507,7 @@ module ActionView
end
content_tag(:a, link_text, link_attributes.merge('href' => href), !!options[:sanitize]) + punctuation.reverse.join('')
end
- end.html_safe
+ end
end
# Turns all email addresses into clickable links. If a block is given,
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index 59e6ce878f..26ebae6546 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -5,7 +5,7 @@ module I18n
class ExceptionHandler
include Module.new {
def call(exception, locale, key, options)
- exception.is_a?(MissingTranslationData) ? super.html_safe : super
+ exception.is_a?(MissingTranslation) ? super.html_safe : super
end
}
end
@@ -17,8 +17,8 @@ module ActionView
module TranslationHelper
# Delegates to I18n#translate but also performs three additional functions.
#
- # First, it'll pass the :rescue_format => :html option to I18n so that any caught
- # MissingTranslationData exceptions will be turned into inline spans that
+ # First, it'll pass the :rescue_format => :html option to I18n so that any
+ # thrown MissingTranslation messages will be turned into inline spans that
#
# * have a "translation-missing" class set,
# * contain the missing key as a title attribute and
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 2cd2dca711..ffa9a5bb0b 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -68,7 +68,7 @@ module ActionView
# # => /books/find
#
# <%= url_for(:action => 'login', :controller => 'members', :only_path => false, :protocol => 'https') %>
- # # => https://www.railsapplication.com/members/login/
+ # # => https://www.example.com/members/login/
#
# <%= url_for(:action => 'play', :anchor => 'player') %>
# # => /messages/play/#player
@@ -81,9 +81,12 @@ module ActionView
# # => /workshops
#
# <%= url_for(@workshop) %>
- # # calls @workshop.to_s
+ # # calls @workshop.to_param which by default returns the id
# # => /workshops/5
#
+ # # to_param can be re-defined in a model to provide different URL names:
+ # # => /workshops/1-workshop-name
+ #
# <%= url_for("http://www.example.com") %>
# # => http://www.example.com
#
@@ -183,7 +186,7 @@ module ActionView
# link_to "Profiles", :controller => "profiles"
# # => <a href="/profiles">Profiles</a>
#
- # You can use a block as well if your link target is hard to fit into the name parameter. ERb example:
+ # You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
#
# <%= link_to(@profile) do %>
# <strong><%= @profile.name %></strong> -- <span>Check it out!</span>
@@ -552,10 +555,10 @@ module ActionView
# current_page?(:controller => 'shop', :action => 'checkout')
# # => true
#
- # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc', :page=>'1')
+ # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc', :page => '1')
# # => true
#
- # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc', :page=>'2')
+ # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc', :page => '2')
# # => false
#
# current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc')
diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb
index e3de3e1eac..8b840a6463 100644
--- a/actionpack/lib/action_view/path_set.rb
+++ b/actionpack/lib/action_view/path_set.rb
@@ -15,6 +15,7 @@ module ActionView #:nodoc:
end
def find_all(path, prefixes = [], *args)
+ prefixes = [prefixes] if String === prefixes
prefixes.each do |prefix|
each do |resolver|
templates = resolver.find_all(path, prefix, *args)
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index 501ec07b09..f20ba7e6d3 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -6,7 +6,7 @@ module ActionView
class Railtie < Rails::Railtie
config.action_view = ActiveSupport::OrderedOptions.new
config.action_view.stylesheet_expansions = {}
- config.action_view.javascript_expansions = { :defaults => ['prototype', 'effects', 'dragdrop', 'controls', 'rails'] }
+ config.action_view.javascript_expansions = { :defaults => %w(jquery rails) }
initializer "action_view.cache_asset_ids" do |app|
unless app.config.cache_classes
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index 94c0a8a8fb..10cd37d56f 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -1,5 +1,3 @@
-require 'action_view/renderer/abstract_renderer'
-
module ActionView
class PartialRenderer < AbstractRenderer #:nodoc:
PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} }
@@ -79,7 +77,7 @@ module ActionView
locals[as] = object
content = @template.render(view, locals) do |*name|
- view._layout_for(*name, &block)
+ view._block_layout_for(*name, &block)
end
content = layout.render(view, locals){ content } if layout
diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
new file mode 100644
index 0000000000..03aab444f8
--- /dev/null
+++ b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
@@ -0,0 +1,130 @@
+# 1.9 ships with Fibers but we need to require the extra
+# methods explicitly. We only load those extra methods if
+# Fiber is available in the first place.
+require 'fiber' if defined?(Fiber)
+
+module ActionView
+ # Consider the following layout:
+ #
+ # <%= yield :header %>
+ # 2
+ # <%= yield %>
+ # 5
+ # <%= yield :footer %>
+ #
+ # And template:
+ #
+ # <%= provide :header, "1" %>
+ # 3
+ # 4
+ # <%= provide :footer, "6" %>
+ #
+ # It will stream:
+ #
+ # "1\n", "2\n", "3\n4\n", "5\n", "6\n"
+ #
+ # Notice that once you <%= yield %>, it will render the whole template
+ # before streaming again. In the future, we can also support streaming
+ # from the template and not only the layout.
+ #
+ # Also, notice we use +provide+ instead of +content_for+, as +provide+
+ # gives the control back to the layout as soon as it is called.
+ # With +content_for+, it would render all the template to find all
+ # +content_for+ calls. For instance, consider this layout:
+ #
+ # <%= yield :header %>
+ #
+ # With this template:
+ #
+ # <%= content_for :header, "1" %>
+ # <%= provide :header, "2" %>
+ # <%= provide :header, "3" %>
+ #
+ # It will return "12\n" because +content_for+ continues rendering the
+ # template but it is returns back to the layout as soon as it sees the
+ # first +provide+.
+ #
+ # == TODO
+ #
+ # * Support streaming from child templates, partials and so on.
+ # * Integrate exceptions with exceptron
+ class StreamingTemplateRenderer < TemplateRenderer #:nodoc:
+ # A valid Rack::Body (i.e. it responds to each).
+ # It is initialized with a block that, when called, starts
+ # rendering the template.
+ class Body #:nodoc:
+ def initialize(&start)
+ @start = start
+ end
+
+ def each(&block)
+ begin
+ @start.call(block)
+ rescue
+ block.call ActionView::Base.streaming_completion_on_exception
+ end
+ self
+ end
+ end
+
+ # For streaming, instead of rendering a given a template, we return a Body
+ # object that responds to each. This object is initialized with a block
+ # that knows how to render the template.
+ def render_template(template, layout_name = nil, locals = {}) #:nodoc:
+ return [super] unless layout_name && template.supports_streaming?
+
+ locals ||= {}
+ layout = layout_name && find_layout(layout_name, locals.keys)
+
+ Body.new do |buffer|
+ delayed_render(buffer, template, layout, @view, locals)
+ end
+ end
+
+ private
+
+ def delayed_render(buffer, template, layout, view, locals)
+ # Wrap the given buffer in the StreamingBuffer and pass it to the
+ # underlying template handler. Now, everytime something is concatenated
+ # to the buffer, it is not appended to an array, but streamed straight
+ # to the client.
+ output = ActionView::StreamingBuffer.new(buffer)
+ yielder = lambda { |*name| view._layout_for(*name) }
+
+ instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
+ fiber = Fiber.new do
+ if layout
+ layout.render(view, locals, output, &yielder)
+ else
+ # If you don't have a layout, just render the thing
+ # and concatenate the final result. This is the same
+ # as a layout with just <%= yield %>
+ output.safe_concat view._layout_for
+ end
+ end
+
+ # Set the view flow to support streaming. It will be aware
+ # when to stop rendering the layout because it needs to search
+ # something in the template and vice-versa.
+ view._view_flow = StreamingFlow.new(view, fiber)
+
+ # Yo! Start the fiber!
+ fiber.resume
+
+ # If the fiber is still alive, it means we need something
+ # from the template, so start rendering it. If not, it means
+ # the layout exited without requiring anything from the template.
+ if fiber.alive?
+ content = template.render(view, locals, &yielder)
+
+ # Once rendering the template is done, sets its content in the :layout key.
+ view._view_flow.set(:layout, content)
+
+ # In case the layout continues yielding, we need to resume
+ # the fiber until all yields are handled.
+ fiber.resume while fiber.alive?
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb
index 9ae1636131..6b5ead463f 100644
--- a/actionpack/lib/action_view/renderer/template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/template_renderer.rb
@@ -1,41 +1,16 @@
-require 'set'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/array/wrap'
-require 'action_view/renderer/abstract_renderer'
module ActionView
class TemplateRenderer < AbstractRenderer #:nodoc:
- attr_reader :rendered
-
- def initialize(view)
- super
- @rendered = Set.new
- end
-
def render(options)
wrap_formats(options[:template] || options[:file]) do
template = determine_template(options)
+ freeze_formats(template.formats, true)
render_template(template, options[:layout], options[:locals])
end
end
- def render_once(options)
- paths, locals = options[:once], options[:locals] || {}
- layout, keys = options[:layout], locals.keys
- prefixes = options.fetch(:prefixes, @view.controller_prefixes)
-
- raise "render :once expects a String or an Array to be given" unless paths
-
- render_with_layout(layout, locals) do
- contents = []
- Array.wrap(paths).each do |path|
- template = find_template(path, prefixes, false, keys)
- contents << render_template(template, nil, locals) if @rendered.add?(template)
- end
- contents.join("\n")
- end
- end
-
# Determine the template to be rendered using the given options.
def determine_template(options) #:nodoc:
keys = options[:locals].try(:keys) || []
@@ -56,7 +31,6 @@ module ActionView
# Renders the given template. An string representing the layout can be
# supplied as well.
def render_template(template, layout_name = nil, locals = {}) #:nodoc:
- freeze_formats(template.formats, true)
view, locals = @view, locals || {}
render_with_layout(layout_name, locals) do |layout|
@@ -72,7 +46,7 @@ module ActionView
if layout
view = @view
- view.store_content_for(:layout, content)
+ view._view_flow.set(:layout, content)
layout.render(view, locals){ |*name| view._layout_for(*name) }
else
content
diff --git a/actionpack/lib/action_view/rendering.rb b/actionpack/lib/action_view/rendering.rb
index baa5d2c3fd..2bce2fb045 100644
--- a/actionpack/lib/action_view/rendering.rb
+++ b/actionpack/lib/action_view/rendering.rb
@@ -6,11 +6,9 @@ module ActionView
# Returns the result of a render that's dictated by the options hash. The primary options are:
#
# * <tt>:partial</tt> - See ActionView::Partials.
- # * <tt>:update</tt> - Calls update_page with the block given.
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
# * <tt>:text</tt> - Renders the text passed in out.
- # * <tt>:once</tt> - Accepts a string or an array of strings and Rails will ensure they each of them are rendered just once.
#
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
# as the locals hash.
@@ -21,18 +19,27 @@ module ActionView
_render_partial(options.merge(:partial => options[:layout]), &block)
elsif options.key?(:partial)
_render_partial(options)
- elsif options.key?(:once)
- _render_once(options)
else
_render_template(options)
end
- when :update
- update_page(&block)
else
_render_partial(:partial => options, :locals => locals)
end
end
+ # Render but returns a valid Rack body. If fibers are defined, we return
+ # a streaming body that renders the template piece by piece.
+ #
+ # Note that partials are not supported to be rendered with streaming,
+ # so in such cases, we just wrap them in an array.
+ def render_body(options)
+ if options.key?(:partial)
+ [_render_partial(options)]
+ else
+ StreamingTemplateRenderer.new(self).render(options)
+ end
+ end
+
# Returns the contents that are yielded to a layout, given a name or a block.
#
# You can think of a layout as a method that is called with a block. If the user calls
@@ -79,22 +86,23 @@ module ActionView
# Hello David
# </html>
#
- def _layout_for(*args, &block)
+ def _layout_for(*args)
+ name = args.first
+ name = :layout unless name.is_a?(Symbol)
+ @_view_flow.get(name).html_safe
+ end
+
+ # Handle layout for calls from partials that supports blocks.
+ def _block_layout_for(*args, &block)
name = args.first
- if name.is_a?(Symbol)
- @_content_for[name].html_safe
- elsif block
+ if !name.is_a?(Symbol) && block
capture(*args, &block)
else
- @_content_for[:layout].html_safe
+ _layout_for(*args)
end
end
- def _render_once(options) #:nodoc:
- _template_renderer.render_once(options)
- end
-
def _render_template(options) #:nodoc:
_template_renderer.render(options)
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 96d506fac5..6dfc4f68ae 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -126,17 +126,23 @@ module ActionView
@formats = Array.wrap(format).map { |f| f.is_a?(Mime::Type) ? f.ref : f }
end
+ # Returns if the underlying handler supports streaming. If so,
+ # a streaming buffer *may* be passed when it start rendering.
+ def supports_streaming?
+ handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
+ end
+
# Render a template. If the template was not compiled yet, it is done
# exactly before rendering.
#
# This method is instrumented as "!render_template.action_view". Notice that
# we use a bang in this instrumentation because you don't want to
# consume this in production. This is only slow if it's being listened to.
- def render(view, locals, &block)
+ def render(view, locals, buffer=nil, &block)
old_template, view._template = view._template, self
ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do
compile!(view)
- view.send(method_name, locals, &block)
+ view.send(method_name, locals, buffer, &block)
end
rescue Exception => e
handle_render_error(view, e)
@@ -167,28 +173,6 @@ module ActionView
end
end
- # Expires this template by setting his updated_at date to Jan 1st, 1970.
- def expire!
- @updated_at = Time.utc(1970)
- end
-
- # Receives a view context and renders a template exactly like self by using
- # the @virtual_path. It raises an error if no @virtual_path was given.
- def rerender(view)
- raise "A template needs to have a virtual path in order to be rerendered" unless @virtual_path
- name = @virtual_path.dup
- if name.sub!(/(^|\/)_([^\/]*)$/, '\1\2')
- view.render :partial => name
- else
- view.render :template => @virtual_path
- end
- end
-
- # Used to store template data by template handlers.
- def data
- @data ||= {}
- end
-
def inspect
@inspect ||=
if defined?(Rails.root)
@@ -274,13 +258,12 @@ module ActionView
end
end
- arity = @handler.respond_to?(:arity) ? @handler.arity : @handler.method(:call).arity
- code = arity.abs == 1 ? @handler.call(self) : @handler.call(self, view)
+ code = @handler.call(self)
# Make sure that the resulting String to be evalled is in the
# encoding of the code
source = <<-end_src
- def #{method_name}(local_assigns)
+ def #{method_name}(local_assigns, output_buffer)
_old_output_buffer = @output_buffer;#{locals_code};#{code}
ensure
@output_buffer = _old_output_buffer
diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb
index 4438199497..959afa734e 100644
--- a/actionpack/lib/action_view/template/handlers.rb
+++ b/actionpack/lib/action_view/template/handlers.rb
@@ -3,12 +3,10 @@ module ActionView #:nodoc:
class Template
module Handlers #:nodoc:
autoload :ERB, 'action_view/template/handlers/erb'
- autoload :RJS, 'action_view/template/handlers/rjs'
autoload :Builder, 'action_view/template/handlers/builder'
def self.extended(base)
base.register_default_template_handler :erb, ERB.new
- base.register_template_handler :rjs, RJS.new
base.register_template_handler :builder, Builder.new
end
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index a36837afc8..7e9e4e518a 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -1,28 +1,14 @@
require 'active_support/core_ext/class/attribute_accessors'
-require 'active_support/core_ext/string/output_safety'
require 'action_view/template'
require 'action_view/template/handler'
require 'erubis'
module ActionView
- class OutputBuffer < ActiveSupport::SafeBuffer
- def initialize(*)
- super
- encode! if encoding_aware?
- end
-
- def <<(value)
- super(value.to_s)
- end
- alias :append= :<<
- alias :safe_append= :safe_concat
- end
-
class Template
module Handlers
class Erubis < ::Erubis::Eruby
def add_preamble(src)
- src << "@output_buffer = ActionView::OutputBuffer.new;"
+ src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
end
def add_text(src, text)
@@ -55,7 +41,7 @@ module ActionView
class ERB
# Specify trim mode for the ERB compiler. Defaults to '-'.
- # See ERb documentation for suitable values.
+ # See ERB documentation for suitable values.
class_attribute :erb_trim_mode
self.erb_trim_mode = '-'
@@ -73,6 +59,10 @@ module ActionView
new.call(template)
end
+ def supports_streaming?
+ true
+ end
+
def handles_encoding?
true
end
diff --git a/actionpack/lib/action_view/template/handlers/rjs.rb b/actionpack/lib/action_view/template/handlers/rjs.rb
deleted file mode 100644
index 9d71059134..0000000000
--- a/actionpack/lib/action_view/template/handlers/rjs.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module ActionView
- module Template::Handlers
- class RJS
- # Default format used by RJS.
- class_attribute :default_format
- self.default_format = Mime::JS
-
- def call(template)
- "update_page do |page|;#{template.source}\nend"
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 4d999fb3b2..870897958a 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -5,6 +5,25 @@ require "action_view/template"
module ActionView
# = Action View Resolver
class Resolver
+ # Keeps all information about view path and builds virtual path.
+ class Path < String
+ attr_reader :name, :prefix, :partial, :virtual
+ alias_method :partial?, :partial
+
+ def initialize(name, prefix, partial)
+ @name, @prefix, @partial = name, prefix, partial
+ rebuild(@name, @prefix, @partial)
+ end
+
+ def rebuild(name, prefix, partial)
+ @virtual = ""
+ @virtual << "#{prefix}/" unless prefix.empty?
+ @virtual << (partial ? "_#{name}" : name)
+
+ self.replace(@virtual)
+ end
+ end
+
cattr_accessor :caching
self.caching = true
@@ -36,15 +55,12 @@ module ActionView
# because Resolver guarantees that the arguments are present and
# normalized.
def find_templates(name, prefix, partial, details)
- raise NotImplementedError
+ raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details) method"
end
# Helpers that builds a path. Useful for building virtual paths.
def build_path(name, prefix, partial)
- path = ""
- path << "#{prefix}/" unless prefix.empty?
- path << (partial ? "_#{name}" : name)
- path
+ Path.new(name, prefix, partial)
end
# Handles templates caching. If a key is given and caching is on
@@ -97,25 +113,24 @@ module ActionView
end
class PathResolver < Resolver
- EXTENSION_ORDER = [:locale, :formats, :handlers]
+ EXTENSIONS = [:locale, :formats, :handlers]
+ DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}"
+
+ def initialize(pattern=nil)
+ @pattern = pattern || DEFAULT_PATTERN
+ super()
+ end
private
def find_templates(name, prefix, partial, details)
path = build_path(name, prefix, partial)
- query(path, EXTENSION_ORDER.map { |ext| details[ext] }, details[:formats])
+ extensions = Hash[EXTENSIONS.map { |ext| [ext, details[ext]] }.flatten(0)]
+ query(path, extensions, details[:formats])
end
def query(path, exts, formats)
- query = File.join(@path, path)
-
- query << exts.map { |ext|
- "{#{ext.compact.map { |e| ".#{e}" }.join(',')},}"
- }.join
-
- query.gsub!(/\{\.html,/, "{.html,.text.html,")
- query.gsub!(/\{\.text,/, "{.text,.text.plain,")
-
+ query = build_query(path, exts)
templates = []
sanitizer = Hash.new { |h,k| h[k] = Dir["#{File.dirname(k)}/*"] }
@@ -126,12 +141,28 @@ module ActionView
contents = File.open(p, "rb") {|io| io.read }
templates << Template.new(contents, File.expand_path(p), handler,
- :virtual_path => path, :format => format, :updated_at => mtime(p))
+ :virtual_path => path.virtual, :format => format, :updated_at => mtime(p))
end
templates
end
+ # Helper for building query glob string based on resolver's pattern.
+ def build_query(path, exts)
+ query = @pattern.dup
+ query.gsub!(/\:prefix(\/)?/, path.prefix.empty? ? "" : "#{path.prefix}\\1") # prefix can be empty...
+ query.gsub!(/\:action/, path.partial? ? "_#{path.name}" : path.name)
+
+ exts.each { |ext, variants|
+ query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
+ }
+
+ query.gsub!('.{html,', '.{html,text.html,')
+ query.gsub!('.{text,', '.{text,text.plain,')
+
+ File.expand_path(query, @path)
+ end
+
# Returns the file mtime from the filesystem.
def mtime(p)
File.stat(p).mtime
@@ -149,11 +180,47 @@ module ActionView
end
end
- # A resolver that loads files from the filesystem.
+ # A resolver that loads files from the filesystem. It allows to set your own
+ # resolving pattern. Such pattern can be a glob string supported by some variables.
+ #
+ # ==== Examples
+ #
+ # Default pattern, loads views the same way as previous versions of rails, eg. when you're
+ # looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}`
+ #
+ # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}")
+ #
+ # This one allows you to keep files with different formats in seperated subdirectories,
+ # eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`,
+ # `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc.
+ #
+ # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{.:handlers,}")
+ #
+ # If you don't specify pattern then the default will be used.
+ #
+ # In order to use any of the customized resolvers above in a Rails application, you just need
+ # to configure ActionController::Base.view_paths in an initializer, for example:
+ #
+ # ActionController::Base.view_paths = FileSystemResolver.new(
+ # Rails.root.join("app/views"),
+ # ":prefix{/:locale}/:action{.:formats,}{.:handlers,}"
+ # )
+ #
+ # ==== Pattern format and variables
+ #
+ # Pattern have to be a valid glob string, and it allows you to use the
+ # following variables:
+ #
+ # * <tt>:prefix</tt> - usualy the controller path
+ # * <tt>:action</tt> - name of the action
+ # * <tt>:locale</tt> - possible locale versions
+ # * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
+ # * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
+ #
class FileSystemResolver < PathResolver
- def initialize(path)
+ def initialize(path, pattern=nil)
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
- super()
+ super(pattern)
@path = File.expand_path(path)
end
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 3e2ddffa16..5c74bf843a 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -121,7 +121,7 @@ module ActionView
# Support the selector assertions
#
# Need to experiment if this priority is the best one: rendered => output_buffer
- def response_from_page_or_rjs
+ def response_from_page
HTML::Document.new(@rendered.blank? ? @output_buffer : @rendered).root
end
diff --git a/actionpack/lib/action_view/testing/resolvers.rb b/actionpack/lib/action_view/testing/resolvers.rb
index 5c5cab7c7d..773dfcbb1d 100644
--- a/actionpack/lib/action_view/testing/resolvers.rb
+++ b/actionpack/lib/action_view/testing/resolvers.rb
@@ -8,8 +8,8 @@ module ActionView #:nodoc:
class FixtureResolver < PathResolver
attr_reader :hash
- def initialize(hash = {})
- super()
+ def initialize(hash = {}, pattern=nil)
+ super(pattern)
@hash = hash
end
@@ -21,8 +21,8 @@ module ActionView #:nodoc:
def query(path, exts, formats)
query = ""
- exts.each do |ext|
- query << '(' << ext.map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)'
+ EXTENSIONS.each do |ext|
+ query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)'
end
query = /^(#{Regexp.escape(path)})#{query}$/
@@ -32,9 +32,9 @@ module ActionView #:nodoc:
next unless _path =~ query
handler, format = extract_handler_and_format(_path, formats)
templates << Template.new(source, _path, handler,
- :virtual_path => $1, :format => format, :updated_at => updated_at)
+ :virtual_path => path.virtual, :format => format, :updated_at => updated_at)
end
-
+
templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
end
end
diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb
new file mode 100644
index 0000000000..9c10decd60
--- /dev/null
+++ b/actionpack/lib/sprockets/railtie.rb
@@ -0,0 +1,100 @@
+module Sprockets
+ class Railtie < Rails::Railtie
+ def self.using_coffee?
+ require 'coffee-script'
+ defined?(CoffeeScript)
+ rescue LoadError
+ false
+ end
+
+ def self.using_scss?
+ require 'sass'
+ defined?(Sass)
+ rescue LoadError
+ false
+ end
+
+ config.app_generators.javascript_engine :coffee if using_coffee?
+ config.app_generators.stylesheet_engine :scss if using_scss?
+
+ # Configure ActionController to use sprockets.
+ initializer "sprockets.set_configs", :after => "action_controller.set_configs" do |app|
+ ActiveSupport.on_load(:action_controller) do
+ self.use_sprockets = app.config.assets.enabled
+ end
+ end
+
+ # We need to configure this after initialization to ensure we collect
+ # paths from all engines. This hook is invoked exactly before routes
+ # are compiled.
+ config.after_initialize do |app|
+ assets = app.config.assets
+ next unless assets.enabled
+
+ app.assets = asset_environment(app)
+
+ ActiveSupport.on_load(:action_view) do
+ app.assets.context.instance_eval do
+ include ::ActionView::Helpers::SprocketsHelper
+ end
+ end
+
+ app.routes.append do
+ mount app.assets => assets.prefix
+ end
+
+ if config.action_controller.perform_caching
+ app.assets = app.assets.index
+ end
+ end
+
+ protected
+
+ def asset_environment(app)
+ require "sprockets"
+ assets = app.config.assets
+ env = Sprockets::Environment.new(app.root.to_s)
+ env.static_root = File.join(app.root.join("public"), assets.prefix)
+ env.paths.concat assets.paths
+ env.logger = Rails.logger
+ env.js_compressor = expand_js_compressor(assets.js_compressor)
+ env.css_compressor = expand_css_compressor(assets.css_compressor)
+ env
+ end
+
+ def expand_js_compressor(sym)
+ case sym
+ when :closure
+ require 'closure-compiler'
+ Closure::Compiler.new
+ when :uglifier
+ require 'uglifier'
+ Uglifier.new
+ when :yui
+ require 'yui/compressor'
+ YUI::JavaScriptCompressor.new
+ else
+ sym
+ end
+ end
+
+ def expand_css_compressor(sym)
+ case sym
+ when :scss
+ require 'sass'
+ compressor = Object.new
+ def compressor.compress(source)
+ Sass::Engine.new(source,
+ :syntax => :scss, :style => :compressed
+ ).render
+ end
+ compressor
+ when :yui
+ require 'yui/compressor'
+ YUI::JavaScriptCompressor.new(:munge => true)
+ else
+ sym
+ end
+ end
+ end
+end
diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb
index f63321c78b..878484eb57 100644
--- a/actionpack/test/controller/assert_select_test.rb
+++ b/actionpack/test/controller/assert_select_test.rb
@@ -34,13 +34,6 @@ class AssertSelectTest < ActionController::TestCase
@content = nil
end
- def rjs()
- render :update do |page|
- @update.call page
- end
- @update = nil
- end
-
def xml()
render :text=>@content, :layout=>false, :content_type=>Mime::XML
@content = nil
@@ -219,50 +212,6 @@ class AssertSelectTest < ActionController::TestCase
end
end
- # With single result.
- def test_assert_select_from_rjs_with_single_result
- render_rjs do |page|
- page.replace_html "test", "<div id=\"1\">foo</div>\n<div id=\"2\">foo</div>"
- end
- assert_select "div" do |elements|
- assert elements.size == 2
- assert_select "#1"
- assert_select "#2"
- end
- assert_select "div#?", /\d+/ do |elements|
- assert_select "#1"
- assert_select "#2"
- end
- end
-
- # With multiple results.
- def test_assert_select_from_rjs_with_multiple_results
- render_rjs do |page|
- page.replace_html "test", "<div id=\"1\">foo</div>"
- page.replace_html "test2", "<div id=\"2\">foo</div>"
- end
- assert_select "div" do |elements|
- assert elements.size == 2
- assert_select "#1"
- assert_select "#2"
- end
- end
-
- def test_assert_select_rjs_for_positioned_insert_should_fail_when_mixing_arguments
- render_rjs do |page|
- page.insert_html :top, "test1", "<div id=\"1\">foo</div>"
- page.insert_html :bottom, "test2", "<div id=\"2\">foo</div>"
- end
- assert_raise(Assertion) {assert_select_rjs :insert, :top, "test2"}
- end
-
- def test_assert_select_rjs_for_redirect_to
- render_rjs do |page|
- page.redirect_to '/'
- end
- assert_select_rjs :redirect, '/'
- end
-
def test_elect_with_xml_namespace_attributes
render_html %Q{<link xlink:href="http://nowhere.com"></link>}
assert_nothing_raised { assert_select "link[xlink:href=http://nowhere.com]" }
@@ -296,364 +245,6 @@ class AssertSelectTest < ActionController::TestCase
end
end
- # With one result.
- def test_css_select_from_rjs_with_single_result
- render_rjs do |page|
- page.replace_html "test", "<div id=\"1\">foo</div>\n<div id=\"2\">foo</div>"
- end
- assert_equal 2, css_select("div").size
- assert_equal 1, css_select("#1").size
- assert_equal 1, css_select("#2").size
- end
-
- # With multiple results.
- def test_css_select_from_rjs_with_multiple_results
- render_rjs do |page|
- page.replace_html "test", "<div id=\"1\">foo</div>"
- page.replace_html "test2", "<div id=\"2\">foo</div>"
- end
-
- assert_equal 2, css_select("div").size
- assert_equal 1, css_select("#1").size
- assert_equal 1, css_select("#2").size
- end
-
- #
- # Test assert_select_rjs.
- #
-
- # Test that we can pick up all statements in the result.
- def test_assert_select_rjs_picks_up_all_statements
- render_rjs do |page|
- page.replace "test", "<div id=\"1\">foo</div>"
- page.replace_html "test2", "<div id=\"2\">foo</div>"
- page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
- end
-
- found = false
- assert_select_rjs do
- assert_select "#1"
- assert_select "#2"
- assert_select "#3"
- found = true
- end
- assert found
- end
-
- # Test that we fail if there is nothing to pick.
- def test_assert_select_rjs_fails_if_nothing_to_pick
- render_rjs { }
- assert_raise(Assertion) { assert_select_rjs }
- end
-
- def test_assert_select_rjs_with_unicode
- # Test that non-ascii characters (which are converted into \uXXXX in RJS) are decoded correctly.
- render_rjs do |page|
- page.replace "test", "<div id=\"1\">\343\203\201\343\202\261\343\203\203\343\203\210</div>"
- end
- assert_select_rjs do
- str = "#1"
- assert_select str, :text => "\343\203\201\343\202\261\343\203\203\343\203\210"
- assert_select str, "\343\203\201\343\202\261\343\203\203\343\203\210"
- if str.respond_to?(:force_encoding)
- assert_select str, /\343\203\201..\343\203\210/u
- assert_raise(Assertion) { assert_select str, /\343\203\201.\343\203\210/u }
- else
- assert_select str, Regexp.new("\343\203\201..\343\203\210",0,'U')
- assert_raise(Assertion) { assert_select str, Regexp.new("\343\203\201.\343\203\210",0,'U') }
- end
- end
- end
-
- def test_assert_select_rjs_with_id
- # Test that we can pick up all statements in the result.
- render_rjs do |page|
- page.replace "test1", "<div id=\"1\">foo</div>"
- page.replace_html "test2", "<div id=\"2\">foo</div>"
- page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
- end
- assert_select_rjs "test1" do
- assert_select "div", 1
- assert_select "#1"
- end
- assert_select_rjs "test2" do
- assert_select "div", 1
- assert_select "#2"
- end
- assert_select_rjs "test3" do
- assert_select "div", 1
- assert_select "#3"
- end
- assert_raise(Assertion) { assert_select_rjs "test4" }
- end
-
- def test_assert_select_rjs_for_replace
- render_rjs do |page|
- page.replace "test1", "<div id=\"1\">foo</div>"
- page.replace_html "test2", "<div id=\"2\">foo</div>"
- page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
- end
- # Replace.
- assert_select_rjs :replace do
- assert_select "div", 1
- assert_select "#1"
- end
- assert_select_rjs :replace, "test1" do
- assert_select "div", 1
- assert_select "#1"
- end
- assert_raise(Assertion) { assert_select_rjs :replace, "test2" }
- # Replace HTML.
- assert_select_rjs :replace_html do
- assert_select "div", 1
- assert_select "#2"
- end
- assert_select_rjs :replace_html, "test2" do
- assert_select "div", 1
- assert_select "#2"
- end
- assert_raise(Assertion) { assert_select_rjs :replace_html, "test1" }
- end
-
- def test_assert_select_rjs_for_chained_replace
- render_rjs do |page|
- page['test1'].replace "<div id=\"1\">foo</div>"
- page['test2'].replace_html "<div id=\"2\">foo</div>"
- page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
- end
- # Replace.
- assert_select_rjs :chained_replace do
- assert_select "div", 1
- assert_select "#1"
- end
- assert_select_rjs :chained_replace, "test1" do
- assert_select "div", 1
- assert_select "#1"
- end
- assert_raise(Assertion) { assert_select_rjs :chained_replace, "test2" }
- # Replace HTML.
- assert_select_rjs :chained_replace_html do
- assert_select "div", 1
- assert_select "#2"
- end
- assert_select_rjs :chained_replace_html, "test2" do
- assert_select "div", 1
- assert_select "#2"
- end
- assert_raise(Assertion) { assert_select_rjs :replace_html, "test1" }
- end
-
- # Simple remove
- def test_assert_select_rjs_for_remove
- render_rjs do |page|
- page.remove "test1"
- end
-
- assert_select_rjs :remove, "test1"
- end
-
- def test_assert_select_rjs_for_remove_offers_useful_error_when_assertion_fails
- render_rjs do |page|
- page.remove "test_with_typo"
- end
-
- assert_select_rjs :remove, "test1"
-
- rescue Assertion => e
- assert_equal "No RJS statement that removes 'test1' was rendered.", e.message
- end
-
- def test_assert_select_rjs_for_remove_ignores_block
- render_rjs do |page|
- page.remove "test1"
- end
-
- assert_nothing_raised do
- assert_select_rjs :remove, "test1" do
- assert_select "p"
- end
- end
- end
-
- # Simple show
- def test_assert_select_rjs_for_show
- render_rjs do |page|
- page.show "test1"
- end
-
- assert_select_rjs :show, "test1"
- end
-
- def test_assert_select_rjs_for_show_offers_useful_error_when_assertion_fails
- render_rjs do |page|
- page.show "test_with_typo"
- end
-
- assert_select_rjs :show, "test1"
-
- rescue Assertion => e
- assert_equal "No RJS statement that shows 'test1' was rendered.", e.message
- end
-
- def test_assert_select_rjs_for_show_ignores_block
- render_rjs do |page|
- page.show "test1"
- end
-
- assert_nothing_raised do
- assert_select_rjs :show, "test1" do
- assert_select "p"
- end
- end
- end
-
- # Simple hide
- def test_assert_select_rjs_for_hide
- render_rjs do |page|
- page.hide "test1"
- end
-
- assert_select_rjs :hide, "test1"
- end
-
- def test_assert_select_rjs_for_hide_offers_useful_error_when_assertion_fails
- render_rjs do |page|
- page.hide "test_with_typo"
- end
-
- assert_select_rjs :hide, "test1"
-
- rescue Assertion => e
- assert_equal "No RJS statement that hides 'test1' was rendered.", e.message
- end
-
- def test_assert_select_rjs_for_hide_ignores_block
- render_rjs do |page|
- page.hide "test1"
- end
-
- assert_nothing_raised do
- assert_select_rjs :hide, "test1" do
- assert_select "p"
- end
- end
- end
-
- # Simple toggle
- def test_assert_select_rjs_for_toggle
- render_rjs do |page|
- page.toggle "test1"
- end
-
- assert_select_rjs :toggle, "test1"
- end
-
- def test_assert_select_rjs_for_toggle_offers_useful_error_when_assertion_fails
- render_rjs do |page|
- page.toggle "test_with_typo"
- end
-
- assert_select_rjs :toggle, "test1"
-
- rescue Assertion => e
- assert_equal "No RJS statement that toggles 'test1' was rendered.", e.message
- end
-
- def test_assert_select_rjs_for_toggle_ignores_block
- render_rjs do |page|
- page.toggle "test1"
- end
-
- assert_nothing_raised do
- assert_select_rjs :toggle, "test1" do
- assert_select "p"
- end
- end
- end
-
- # Non-positioned insert.
- def test_assert_select_rjs_for_nonpositioned_insert
- render_rjs do |page|
- page.replace "test1", "<div id=\"1\">foo</div>"
- page.replace_html "test2", "<div id=\"2\">foo</div>"
- page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
- end
- assert_select_rjs :insert_html do
- assert_select "div", 1
- assert_select "#3"
- end
- assert_select_rjs :insert_html, "test3" do
- assert_select "div", 1
- assert_select "#3"
- end
- assert_raise(Assertion) { assert_select_rjs :insert_html, "test1" }
- end
-
- # Positioned insert.
- def test_assert_select_rjs_for_positioned_insert
- render_rjs do |page|
- page.insert_html :top, "test1", "<div id=\"1\">foo</div>"
- page.insert_html :bottom, "test2", "<div id=\"2\">foo</div>"
- page.insert_html :before, "test3", "<div id=\"3\">foo</div>"
- page.insert_html :after, "test4", "<div id=\"4\">foo</div>"
- end
- assert_select_rjs :insert, :top do
- assert_select "div", 1
- assert_select "#1"
- end
- assert_select_rjs :insert, :bottom do
- assert_select "div", 1
- assert_select "#2"
- end
- assert_select_rjs :insert, :before do
- assert_select "div", 1
- assert_select "#3"
- end
- assert_select_rjs :insert, :after do
- assert_select "div", 1
- assert_select "#4"
- end
- assert_select_rjs :insert_html do
- assert_select "div", 4
- end
- end
-
- def test_assert_select_rjs_raise_errors
- assert_raise(ArgumentError) { assert_select_rjs(:destroy) }
- assert_raise(ArgumentError) { assert_select_rjs(:insert, :left) }
- end
-
- # Simple selection from a single result.
- def test_nested_assert_select_rjs_with_single_result
- render_rjs do |page|
- page.replace_html "test", "<div id=\"1\">foo</div>\n<div id=\"2\">foo</div>"
- end
-
- assert_select_rjs "test" do |elements|
- assert_equal 2, elements.size
- assert_select "#1"
- assert_select "#2"
- end
- end
-
- # Deal with two results.
- def test_nested_assert_select_rjs_with_two_results
- render_rjs do |page|
- page.replace_html "test", "<div id=\"1\">foo</div>"
- page.replace_html "test2", "<div id=\"2\">foo</div>"
- end
-
- assert_select_rjs "test" do |elements|
- assert_equal 1, elements.size
- assert_select "#1"
- end
-
- assert_select_rjs "test2" do |elements|
- assert_equal 1, elements.size
- assert_select "#2"
- end
- end
-
def test_feed_item_encoded
render_xml <<-EOF
<rss version="2.0">
@@ -728,11 +319,6 @@ EOF
get :html
end
- def render_rjs(&block)
- @controller.response_with(&block)
- get :rjs
- end
-
def render_xml(xml)
@controller.response_with = xml
get :xml
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 01f3e8f2b6..fada0c7748 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -713,17 +713,10 @@ class FunctionalCachingController < CachingController
end
end
- def js_fragment_cached_with_partial
- respond_to do |format|
- format.js
- end
- end
-
def formatted_fragment_cached
respond_to do |format|
format.html
format.xml
- format.js
end
end
@@ -770,13 +763,6 @@ CACHED
assert_match("Some cached content", @store.read('views/test.host/functional_caching/inline_fragment_cached'))
end
- def test_fragment_caching_in_rjs_partials
- xhr :get, :js_fragment_cached_with_partial
- assert_response :success
- assert_match(/Old fragment caching in a partial/, @response.body)
- assert_match("Old fragment caching in a partial", @store.read('views/test.host/functional_caching/js_fragment_cached_with_partial'))
- end
-
def test_html_formatted_fragment_caching
get :formatted_fragment_cached, :format => "html"
assert_response :success
diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb
index 9500c25a32..b12c798302 100644
--- a/actionpack/test/controller/content_type_test.rb
+++ b/actionpack/test/controller/content_type_test.rb
@@ -35,9 +35,6 @@ class OldContentTypeController < ActionController::Base
def render_default_for_builder
end
- def render_default_for_rjs
- end
-
def render_change_for_builder
response.content_type = Mime::HTML
render :action => "render_default_for_builder"
@@ -129,12 +126,6 @@ class ContentTypeTest < ActionController::TestCase
assert_equal "utf-8", @response.charset
end
- def test_default_for_rjs
- xhr :post, :render_default_for_rjs
- assert_equal Mime::JS, @response.content_type
- assert_equal "utf-8", @response.charset
- end
-
def test_change_for_builder
get :render_change_for_builder
assert_equal Mime::HTML, @response.content_type
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index 330fa276d0..9e44e8e088 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -505,6 +505,21 @@ class FilterTest < ActionController::TestCase
end
end
+ class ImplicitActionsController < ActionController::Base
+ before_filter :find_only, :only => :edit
+ before_filter :find_except, :except => :edit
+
+ private
+
+ def find_only
+ @only = 'Only'
+ end
+
+ def find_except
+ @except = 'Except'
+ end
+ end
+
def test_sweeper_should_not_block_rendering
response = test_process(SweeperTestController)
assert_equal 'hello world', response.body
@@ -783,6 +798,18 @@ class FilterTest < ActionController::TestCase
assert_equal("I rescued this: #<FilterTest::ErrorToRescue: Something made the bad noise.>", response.body)
end
+ def test_filters_obey_only_and_except_for_implicit_actions
+ test_process(ImplicitActionsController, 'show')
+ assert_equal 'Except', assigns(:except)
+ assert_nil assigns(:only)
+ assert_equal 'show', response.body
+
+ test_process(ImplicitActionsController, 'edit')
+ assert_equal 'Only', assigns(:only)
+ assert_nil assigns(:except)
+ assert_equal 'edit', response.body
+ end
+
private
def test_process(controller, action = "show")
@controller = controller.is_a?(Class) ? controller.new : controller
diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb
new file mode 100644
index 0000000000..9b69a2648e
--- /dev/null
+++ b/actionpack/test/controller/flash_hash_test.rb
@@ -0,0 +1,90 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ class FlashHashTest < ActiveSupport::TestCase
+ def setup
+ @hash = Flash::FlashHash.new
+ end
+
+ def test_set_get
+ @hash[:foo] = 'zomg'
+ assert_equal 'zomg', @hash[:foo]
+ end
+
+ def test_keys
+ assert_equal [], @hash.keys
+
+ @hash['foo'] = 'zomg'
+ assert_equal ['foo'], @hash.keys
+
+ @hash['bar'] = 'zomg'
+ assert_equal ['foo', 'bar'].sort, @hash.keys.sort
+ end
+
+ def test_update
+ @hash['foo'] = 'bar'
+ @hash.update('foo' => 'baz', 'hello' => 'world')
+
+ assert_equal 'baz', @hash['foo']
+ assert_equal 'world', @hash['hello']
+ end
+
+ def test_delete
+ @hash['foo'] = 'bar'
+ @hash.delete 'foo'
+
+ assert !@hash.key?('foo')
+ assert_nil @hash['foo']
+ end
+
+ def test_to_hash
+ @hash['foo'] = 'bar'
+ assert_equal({'foo' => 'bar'}, @hash.to_hash)
+
+ @hash.to_hash['zomg'] = 'aaron'
+ assert !@hash.key?('zomg')
+ assert_equal({'foo' => 'bar'}, @hash.to_hash)
+ end
+
+ def test_empty?
+ assert @hash.empty?
+ @hash['zomg'] = 'bears'
+ assert !@hash.empty?
+ @hash.clear
+ assert @hash.empty?
+ end
+
+ def test_each
+ @hash['hello'] = 'world'
+ @hash['foo'] = 'bar'
+
+ things = []
+ @hash.each do |k,v|
+ things << [k,v]
+ end
+
+ assert_equal([%w{ hello world }, %w{ foo bar }].sort, things.sort)
+ end
+
+ def test_replace
+ @hash['hello'] = 'world'
+ @hash.replace('omg' => 'aaron')
+ assert_equal({'omg' => 'aaron'}, @hash.to_hash)
+ end
+
+ def test_discard_no_args
+ @hash['hello'] = 'world'
+ @hash.discard
+ @hash.sweep
+ assert_equal({}, @hash.to_hash)
+ end
+
+ def test_discard_one_arg
+ @hash['hello'] = 'world'
+ @hash['omg'] = 'world'
+ @hash.discard 'hello'
+ @hash.sweep
+ assert_equal({'omg' => 'world'}, @hash.to_hash)
+ end
+ end
+end
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 3569a2f213..e19612eace 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -174,13 +174,13 @@ class FlashTest < ActionController::TestCase
assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed
assert_nil flash.discard(:unknown) # non existant key passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard()) # nothing passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard(nil)) # nothing passed
+ assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard().to_hash) # nothing passed
+ assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed
assert_equal(:foo_indeed, flash.keep(:foo)) # valid key passed
assert_nil flash.keep(:unknown) # non existant key passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep()) # nothing passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep(nil)) # nothing passed
+ assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep().to_hash) # nothing passed
+ assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed
end
def test_redirect_to_with_alert
@@ -214,11 +214,20 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
class TestController < ActionController::Base
+ def dont_set_flash
+ head :ok
+ end
+
def set_flash
flash["that"] = "hello"
head :ok
end
+ def set_flash_now
+ flash.now["that"] = "hello"
+ head :ok
+ end
+
def use_flash
render :inline => "flash: #{flash["that"]}"
end
@@ -245,6 +254,62 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
end
end
+ def test_setting_flash_raises_after_stream_back_to_client
+ with_test_route_set do
+ env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
+ get '/set_flash', nil, env
+ assert_raise(ActionDispatch::ClosedError) {
+ @request.flash['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_flash_does_not_raise_in_following_requests
+ with_test_route_set do
+ env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
+ get '/set_flash', nil, env
+ get '/set_flash', nil, env
+ end
+ end
+
+ def test_setting_flash_now_does_not_raise_in_following_requests
+ with_test_route_set do
+ env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
+ get '/set_flash_now', nil, env
+ get '/set_flash_now', nil, env
+ end
+ end
+
+ def test_setting_flash_raises_after_stream_back_to_client_even_with_an_empty_flash
+ with_test_route_set do
+ env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
+ get '/dont_set_flash', nil, env
+ assert_raise(ActionDispatch::ClosedError) {
+ @request.flash['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_flash_now_raises_after_stream_back_to_client
+ with_test_route_set do
+ env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
+ get '/set_flash_now', nil, env
+ assert_raise(ActionDispatch::ClosedError) {
+ @request.flash.now['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_flash_now_raises_after_stream_back_to_client_even_with_an_empty_flash
+ with_test_route_set do
+ env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
+ get '/dont_set_flash', nil, env
+ assert_raise(ActionDispatch::ClosedError) {
+ @request.flash.now['alert'] = 'alert'
+ }
+ end
+ end
+
private
# Overwrite get to send SessionSecret in env hash
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
new file mode 100644
index 0000000000..3e723e20d9
--- /dev/null
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -0,0 +1,83 @@
+require 'abstract_unit'
+
+class ForceSSLController < ActionController::Base
+ def banana
+ render :text => "monkey"
+ end
+
+ def cheeseburger
+ render :text => "sikachu"
+ end
+end
+
+class ForceSSLControllerLevel < ForceSSLController
+ force_ssl
+end
+
+class ForceSSLOnlyAction < ForceSSLController
+ force_ssl :only => :cheeseburger
+end
+
+class ForceSSLExceptAction < ForceSSLController
+ force_ssl :except => :banana
+end
+
+class ForceSSLControllerLevelTest < ActionController::TestCase
+ tests ForceSSLControllerLevel
+
+ def test_banana_redirects_to_https
+ get :banana
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_controller_level/banana", redirect_to_url
+ end
+
+ def test_cheeseburger_redirects_to_https
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_controller_level/cheeseburger", redirect_to_url
+ end
+end
+
+class ForceSSLOnlyActionTest < ActionController::TestCase
+ tests ForceSSLOnlyAction
+
+ def test_banana_not_redirects_to_https
+ get :banana
+ assert_response 200
+ end
+
+ def test_cheeseburger_redirects_to_https
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_only_action/cheeseburger", redirect_to_url
+ end
+end
+
+class ForceSSLExceptActionTest < ActionController::TestCase
+ tests ForceSSLExceptAction
+
+ def test_banana_not_redirects_to_https
+ get :banana
+ assert_response 200
+ end
+
+ def test_cheeseburger_redirects_to_https
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_except_action/cheeseburger", redirect_to_url
+ end
+end
+
+class ForceSSLExcludeDevelopmentTest < ActionController::TestCase
+ tests ForceSSLControllerLevel
+
+ def setup
+ Rails.env.stubs(:development?).returns(false)
+ end
+
+ def test_development_environment_not_redirects_to_https
+ Rails.env.stubs(:development?).returns(true)
+ get :banana
+ assert_response 200
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index 01c650a494..bd3e13e6fa 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -6,6 +6,8 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
before_filter :authenticate_with_request, :only => :display
before_filter :authenticate_long_credentials, :only => :show
+ http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search
+
def index
render :text => "Hello Secret"
end
@@ -17,6 +19,10 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
def show
render :text => 'Only for loooooong credentials'
end
+
+ def search
+ render :text => 'All inline'
+ end
private
@@ -104,6 +110,16 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
assert assigns(:logged_in)
assert_equal 'Definitely Maybe', @response.body
end
+
+ test "authenticate with class method" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials('David', 'Goliath')
+ get :search
+ assert_response :success
+
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials('David', 'WRONG!')
+ get :search
+ assert_response :unauthorized
+ end
private
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index f0d62b0b13..01dc2f2091 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -521,4 +521,12 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
get '/foo'
assert_raise(NameError) { missing_path }
end
+
+ test "process reuse the env we pass as argument" do
+ env = { :SERVER_NAME => 'server', 'action_dispatch.custom' => 'custom' }
+ get '/foo', nil, env
+ assert_equal :get, env[:method]
+ assert_equal 'server', env[:SERVER_NAME]
+ assert_equal 'custom', env['action_dispatch.custom']
+ end
end
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index 5debf96232..4a5e597500 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'controller/fake_models'
require 'active_support/core_ext/hash/conversions'
+require 'active_support/core_ext/object/inclusion'
class StarStarMimeController < ActionController::Base
layout nil
@@ -72,13 +73,12 @@ class RespondToController < ActionController::Base
def using_defaults
respond_to do |type|
type.html
- type.js
type.xml
end
end
def using_defaults_with_type_list
- respond_to(:html, :js, :xml)
+ respond_to(:html, :xml)
end
def made_for_content_type
@@ -129,7 +129,6 @@ class RespondToController < ActionController::Base
def all_types_with_layout
respond_to do |type|
type.html
- type.js
end
end
@@ -158,7 +157,7 @@ class RespondToController < ActionController::Base
protected
def set_layout
- if ["all_types_with_layout", "iphone_with_html_response_type"].include?(action_name)
+ if action_name.in?(["all_types_with_layout", "iphone_with_html_response_type"])
"respond_to/layouts/standard"
elsif action_name == "iphone_with_html_response_type_without_layout"
"respond_to/layouts/missing"
@@ -298,11 +297,6 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "text/html", @response.content_type
assert_equal 'Hello world!', @response.body
- @request.accept = "text/javascript"
- get :using_defaults
- assert_equal "text/javascript", @response.content_type
- assert_equal '$("body").visualEffect("highlight");', @response.body
-
@request.accept = "application/xml"
get :using_defaults
assert_equal "application/xml", @response.content_type
@@ -315,11 +309,6 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "text/html", @response.content_type
assert_equal 'Hello world!', @response.body
- @request.accept = "text/javascript"
- get :using_defaults_with_type_list
- assert_equal "text/javascript", @response.content_type
- assert_equal '$("body").visualEffect("highlight");', @response.body
-
@request.accept = "application/xml"
get :using_defaults_with_type_list
assert_equal "application/xml", @response.content_type
@@ -427,13 +416,6 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal 'HTML', @response.body
end
-
- def test_rjs_type_skips_layout
- @request.accept = "text/javascript"
- get :all_types_with_layout
- assert_equal 'RJS for all_types_with_layout', @response.body
- end
-
def test_html_type_with_layout
@request.accept = "text/html"
get :all_types_with_layout
@@ -443,9 +425,6 @@ class RespondToControllerTest < ActionController::TestCase
def test_xhr
xhr :get, :js_or_html
assert_equal 'JS', @response.body
-
- xhr :get, :using_defaults
- assert_equal '$("body").visualEffect("highlight");', @response.body
end
def test_custom_constant
@@ -558,6 +537,15 @@ class RespondWithController < ActionController::Base
respond_with(resource, :location => "http://test.host/", :status => :created)
end
+ def using_invalid_resource_with_template
+ respond_with(resource)
+ end
+
+ def using_options_with_template
+ @customer = resource
+ respond_with(@customer, :status => 123, :location => "http://test.host/")
+ end
+
def using_resource_with_responder
responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" }
respond_with(resource, :responder => responder)
@@ -633,11 +621,6 @@ class RespondWithControllerTest < ActionController::TestCase
end
def test_using_resource
- @request.accept = "text/javascript"
- get :using_resource
- assert_equal "text/javascript", @response.content_type
- assert_equal '$("body").visualEffect("highlight");', @response.body
-
@request.accept = "application/xml"
get :using_resource
assert_equal "application/xml", @response.content_type
@@ -953,6 +936,54 @@ class RespondWithControllerTest < ActionController::TestCase
assert_equal 201, @response.status
end
+ def test_using_resource_with_status_and_location_with_invalid_resource
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+
+ @request.accept = "text/xml"
+
+ post :using_resource_with_status_and_location
+ assert_equal errors.to_xml, @response.body
+ assert_equal 422, @response.status
+ assert_equal nil, @response.location
+
+ put :using_resource_with_status_and_location
+ assert_equal errors.to_xml, @response.body
+ assert_equal 422, @response.status
+ assert_equal nil, @response.location
+ end
+
+ def test_using_invalid_resource_with_template
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+
+ @request.accept = "text/xml"
+
+ post :using_invalid_resource_with_template
+ assert_equal errors.to_xml, @response.body
+ assert_equal 422, @response.status
+ assert_equal nil, @response.location
+
+ put :using_invalid_resource_with_template
+ assert_equal errors.to_xml, @response.body
+ assert_equal 422, @response.status
+ assert_equal nil, @response.location
+ end
+
+ def test_using_options_with_template
+ @request.accept = "text/xml"
+
+ post :using_options_with_template
+ assert_equal "<customer-name>david</customer-name>", @response.body
+ assert_equal 123, @response.status
+ assert_equal "http://test.host/", @response.location
+
+ put :using_options_with_template
+ assert_equal "<customer-name>david</customer-name>", @response.body
+ assert_equal 123, @response.status
+ assert_equal "http://test.host/", @response.location
+ end
+
def test_using_resource_with_responder
get :using_resource_with_responder
assert_equal "Resource name is david", @response.body
diff --git a/actionpack/test/controller/new_base/content_type_test.rb b/actionpack/test/controller/new_base/content_type_test.rb
index 8ba30944f5..4b70031c90 100644
--- a/actionpack/test/controller/new_base/content_type_test.rb
+++ b/actionpack/test/controller/new_base/content_type_test.rb
@@ -23,8 +23,7 @@ module ContentType
"content_type/implied/i_am_html_erb.html.erb" => "Hello world!",
"content_type/implied/i_am_xml_erb.xml.erb" => "<xml>Hello world!</xml>",
"content_type/implied/i_am_html_builder.html.builder" => "xml.p 'Hello'",
- "content_type/implied/i_am_xml_builder.xml.builder" => "xml.awesome 'Hello'",
- "content_type/implied/i_am_js_rjs.js.rjs" => "page.alert 'hello'"
+ "content_type/implied/i_am_xml_builder.xml.builder" => "xml.awesome 'Hello'"
)]
end
@@ -93,12 +92,6 @@ module ContentType
assert_header "Content-Type", "application/xml; charset=utf-8"
end
-
- test "sets Content-Type as text/javascript when rendering *.js" do
- get "/content_type/implied/i_am_js_rjs", "format" => "js"
-
- assert_header "Content-Type", "text/javascript; charset=utf-8"
- end
end
class ExplicitCharsetTest < Rack::TestCase
diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb
index 9f69d20329..3bb3016fdb 100644
--- a/actionpack/test/controller/new_base/render_implicit_action_test.rb
+++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb
@@ -3,8 +3,9 @@ require 'abstract_unit'
module RenderImplicitAction
class SimpleController < ::ApplicationController
self.view_paths = [ActionView::FixtureResolver.new(
- "render_implicit_action/simple/hello_world.html.erb" => "Hello world!",
- "render_implicit_action/simple/hyphen-ated.html.erb" => "Hello hyphen-ated!"
+ "render_implicit_action/simple/hello_world.html.erb" => "Hello world!",
+ "render_implicit_action/simple/hyphen-ated.html.erb" => "Hello hyphen-ated!",
+ "render_implicit_action/simple/not_implemented.html.erb" => "Not Implemented"
)]
def hello_world() end
@@ -24,5 +25,18 @@ module RenderImplicitAction
assert_body "Hello hyphen-ated!"
assert_status 200
end
+
+ test "render an action called not_implemented" do
+ get "/render_implicit_action/simple/not_implemented"
+
+ assert_body "Not Implemented"
+ assert_status 200
+ end
+
+ test "action_method? returns true for implicit actions" do
+ assert SimpleController.new.action_method?(:hello_world)
+ assert SimpleController.new.action_method?(:"hyphen-ated")
+ assert SimpleController.new.action_method?(:not_implemented)
+ end
end
end
diff --git a/actionpack/test/controller/new_base/render_layout_test.rb b/actionpack/test/controller/new_base/render_layout_test.rb
index bb2a953536..d3dcb5cad6 100644
--- a/actionpack/test/controller/new_base/render_layout_test.rb
+++ b/actionpack/test/controller/new_base/render_layout_test.rb
@@ -70,8 +70,8 @@ module ControllerLayouts
class MismatchFormatController < ::ApplicationController
self.view_paths = [ActionView::FixtureResolver.new(
"layouts/application.html.erb" => "<html><%= yield %></html>",
- "controller_layouts/mismatch_format/index.js.rjs" => "page[:test].ext",
- "controller_layouts/mismatch_format/implicit.rjs" => "page[:test].ext"
+ "controller_layouts/mismatch_format/index.xml.builder" => "xml.instruct!",
+ "controller_layouts/mismatch_format/implicit.builder" => "xml.instruct!"
)]
def explicit
@@ -81,15 +81,17 @@ module ControllerLayouts
class MismatchFormatTest < Rack::TestCase
testing ControllerLayouts::MismatchFormatController
+
+ XML_INSTRUCT = %Q(<?xml version="1.0" encoding="UTF-8"?>\n)
- test "if JS is selected, an HTML template is not also selected" do
- get :index, "format" => "js"
- assert_response "$(\"test\").ext();"
+ test "if XML is selected, an HTML template is not also selected" do
+ get :index, :format => "xml"
+ assert_response XML_INSTRUCT
end
- test "if JS is implicitly selected, an HTML template is not also selected" do
+ test "if XML is implicitly selected, an HTML template is not also selected" do
get :implicit
- assert_response "$(\"test\").ext();"
+ assert_response XML_INSTRUCT
end
test "if an HTML template is explicitly provides for a JS template, an error is raised" do
diff --git a/actionpack/test/controller/new_base/render_once_test.rb b/actionpack/test/controller/new_base/render_once_test.rb
deleted file mode 100644
index 175abf8a7e..0000000000
--- a/actionpack/test/controller/new_base/render_once_test.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-require 'abstract_unit'
-
-module RenderTemplate
- class RenderOnceController < ActionController::Base
- layout false
-
- RESOLVER = ActionView::FixtureResolver.new(
- "test/a.html.erb" => "a",
- "test/b.html.erb" => "<>",
- "test/c.html.erb" => "c",
- "test/one.html.erb" => "<%= render :once => 'result' %>",
- "test/two.html.erb" => "<%= render :once => 'result' %>",
- "test/three.html.erb" => "<%= render :once => 'result' %>",
- "test/result.html.erb" => "YES!",
- "other/result.html.erb" => "NO!",
- "layouts/test.html.erb" => "l<%= yield %>l"
- )
-
- self.view_paths = [RESOLVER]
-
- def _prefixes
- %w(test)
- end
-
- def multiple
- render :once => %w(a b c)
- end
-
- def once
- render :once => %w(one two three)
- end
-
- def duplicate
- render :once => %w(a a a)
- end
-
- def with_layout
- render :once => %w(a b c), :layout => "test"
- end
-
- def with_prefix
- render :once => "result", :prefixes => %w(other)
- end
-
- def with_nil_prefix
- render :once => "test/result", :prefixes => []
- end
- end
-
- module Tests
- def test_mutliple_arguments_get_all_rendered
- get :multiple
- assert_response "a\n<>\nc"
- end
-
- def test_referenced_templates_get_rendered_once
- get :once
- assert_response "YES!\n\n"
- end
-
- def test_duplicated_templates_get_rendered_once
- get :duplicate
- assert_response "a"
- end
-
- def test_layout_wraps_all_rendered_templates
- get :with_layout
- assert_response "la\n<>\ncl"
- end
-
- def test_with_prefix_option
- get :with_prefix
- assert_response "NO!"
- end
-
- def test_with_nil_prefix_option
- get :with_nil_prefix
- assert_response "YES!"
- end
- end
-
- class TestRenderOnce < Rack::TestCase
- testing RenderTemplate::RenderOnceController
- include Tests
- end
-end
diff --git a/actionpack/test/controller/new_base/render_rjs_test.rb b/actionpack/test/controller/new_base/render_rjs_test.rb
deleted file mode 100644
index 74bf865b54..0000000000
--- a/actionpack/test/controller/new_base/render_rjs_test.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-require 'abstract_unit'
-
-module RenderRjs
- class BasicController < ActionController::Base
- layout "application", :only => :index_respond_to
-
- self.view_paths = [ActionView::FixtureResolver.new(
- "layouts/application.html.erb" => "",
- "render_rjs/basic/index.js.rjs" => "page[:customer].replace_html render(:partial => 'customer')",
- "render_rjs/basic/index_html.js.rjs" => "page[:customer].replace_html :partial => 'customer'",
- "render_rjs/basic/index_no_js.js.erb" => "<%= render(:partial => 'developer') %>",
- "render_rjs/basic/_customer.js.erb" => "JS Partial",
- "render_rjs/basic/_customer.html.erb" => "HTML Partial",
- "render_rjs/basic/_developer.html.erb" => "HTML Partial",
- "render_rjs/basic/index_locale.js.rjs" => "page[:customer].replace_html :partial => 'customer'",
- "render_rjs/basic/_customer.da.html.erb" => "Danish HTML Partial",
- "render_rjs/basic/_customer.da.js.erb" => "Danish JS Partial"
- )]
-
- def index
- render
- end
-
- def index_respond_to
- respond_to do |format|
- format.js { render :action => "index_no_js" }
- end
- end
-
- def index_locale
- self.locale = :da
- end
- end
-
- class TestBasic < Rack::TestCase
- testing BasicController
-
- def setup
- @old_locale = I18n.locale
- end
-
- def teardown
- I18n.locale = @old_locale
- end
-
- test "rendering a partial in an RJS template should pick the JS template over the HTML one" do
- get :index, "format" => "js"
- assert_response("$(\"customer\").update(\"JS Partial\");")
- end
-
- test "rendering a partial in an RJS template should pick the HTML one if no JS is available" do
- get :index_no_js, "format" => "js"
- assert_response("HTML Partial")
- end
-
- test "rendering a partial in an RJS template should pick the HTML one if no JS is available on respond_to" do
- get :index_respond_to, "format" => "js"
- assert_response("HTML Partial")
- end
-
- test "replacing an element with a partial in an RJS template should pick the HTML template over the JS one" do
- get :index_html, "format" => "js"
- assert_response("$(\"customer\").update(\"HTML Partial\");")
- end
-
- test "replacing an element with a partial in an RJS template with a locale should pick the localed HTML template" do
- get :index_locale, "format" => "js"
- assert_response("$(\"customer\").update(\"Danish HTML Partial\");")
- end
- end
-end
diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb
new file mode 100644
index 0000000000..fed8d40b47
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_streaming_test.rb
@@ -0,0 +1,101 @@
+require 'abstract_unit'
+
+module RenderStreaming
+ class BasicController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "render_streaming/basic/hello_world.html.erb" => "Hello world",
+ "render_streaming/basic/boom.html.erb" => "<%= nil.invalid! %>",
+ "layouts/application.html.erb" => "<%= yield %>, I'm here!",
+ "layouts/boom.html.erb" => "<body class=\"<%= nil.invalid! %>\"<%= yield %></body>"
+ )]
+
+ layout "application"
+ stream :only => [:hello_world, :skip]
+
+ def hello_world
+ end
+
+ def layout_exception
+ render :action => "hello_world", :stream => true, :layout => "boom"
+ end
+
+ def template_exception
+ render :action => "boom", :stream => true
+ end
+
+ def skip
+ render :action => "hello_world", :stream => false
+ end
+
+ def explicit
+ render :action => "hello_world", :stream => true
+ end
+
+ def no_layout
+ render :action => "hello_world", :stream => true, :layout => false
+ end
+
+ def explicit_cache
+ headers["Cache-Control"] = "private"
+ render :action => "hello_world", :stream => true
+ end
+ end
+
+ class StreamingTest < Rack::TestCase
+ test "rendering with streaming enabled at the class level" do
+ get "/render_streaming/basic/hello_world"
+ assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n"
+ assert_streaming!
+ end
+
+ test "rendering with streaming given to render" do
+ get "/render_streaming/basic/explicit"
+ assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n"
+ assert_streaming!
+ end
+
+ test "rendering with streaming do not override explicit cache control given to render" do
+ get "/render_streaming/basic/explicit_cache"
+ assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n"
+ assert_streaming! "private"
+ end
+
+ test "rendering with streaming no layout" do
+ get "/render_streaming/basic/no_layout"
+ assert_body "b\r\nHello world\r\n0\r\n\r\n"
+ assert_streaming!
+ end
+
+ test "skip rendering with streaming at render level" do
+ get "/render_streaming/basic/skip"
+ assert_body "Hello world, I'm here!"
+ end
+
+ test "rendering with layout exception" do
+ get "/render_streaming/basic/layout_exception"
+ assert_body "d\r\n<body class=\"\r\n4e\r\n\"><script type=\"text/javascript\">window.location = \"/500.html\"</script></html>\r\n0\r\n\r\n"
+ assert_streaming!
+ end
+
+ test "rendering with template exception" do
+ get "/render_streaming/basic/template_exception"
+ assert_body "4e\r\n\"><script type=\"text/javascript\">window.location = \"/500.html\"</script></html>\r\n0\r\n\r\n"
+ assert_streaming!
+ end
+
+ test "do not stream on HTTP/1.0" do
+ get "/render_streaming/basic/hello_world", nil, "HTTP_VERSION" => "HTTP/1.0"
+ assert_body "Hello world, I'm here!"
+ assert_status 200
+ assert_equal "22", headers["Content-Length"]
+ assert_equal nil, headers["Transfer-Encoding"]
+ end
+
+ def assert_streaming!(cache="no-cache")
+ assert_status 200
+ assert_equal nil, headers["Content-Length"]
+ assert_equal "chunked", headers["Transfer-Encoding"]
+ assert_equal cache, headers["Cache-Control"]
+ end
+ end
+end if defined?(Fiber)
diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb
index d6062bfa8c..60468bf5c7 100644
--- a/actionpack/test/controller/new_base/render_test.rb
+++ b/actionpack/test/controller/new_base/render_test.rb
@@ -81,8 +81,7 @@ module Render
end
class TestOnlyRenderPublicActions < Rack::TestCase
- describe "Only public methods on actual controllers are callable actions"
-
+ # Only public methods on actual controllers are callable actions
test "raises an exception when a method of Object is called" do
assert_raises(AbstractController::ActionNotFound) do
get "/render/blank_render/clone", {}, "action_dispatch.show_exceptions" => false
diff --git a/actionpack/test/controller/render_js_test.rb b/actionpack/test/controller/render_js_test.rb
index 491c98a0fd..f070109b27 100644
--- a/actionpack/test/controller/render_js_test.rb
+++ b/actionpack/test/controller/render_js_test.rb
@@ -14,10 +14,6 @@ class RenderJSTest < ActionController::TestCase
render :js => "alert('hello')"
end
- def greeting
- # let's just rely on the template
- end
-
def show_partial
render :partial => 'partial'
end
@@ -31,11 +27,6 @@ class RenderJSTest < ActionController::TestCase
assert_equal "text/javascript", @response.content_type
end
- def test_render_with_default_from_accept_header
- xhr :get, :greeting
- assert_equal "$(\"body\").visualEffect(\"highlight\");", @response.body
- end
-
def test_should_render_js_partial
xhr :get, :show_partial, :format => 'js'
assert_equal 'partial js', @response.body
diff --git a/actionpack/test/controller/render_other_test.rb b/actionpack/test/controller/render_other_test.rb
index eda777e7a7..b5e74e373d 100644
--- a/actionpack/test/controller/render_other_test.rb
+++ b/actionpack/test/controller/render_other_test.rb
@@ -1,6 +1,4 @@
require 'abstract_unit'
-require 'controller/fake_models'
-require 'pathname'
ActionController.add_renderer :simon do |says, options|
self.content_type = Mime::TEXT
@@ -9,248 +7,13 @@ end
class RenderOtherTest < ActionController::TestCase
class TestController < ActionController::Base
- protect_from_forgery
-
- def self.controller_path
- 'test'
- end
-
- layout :determine_layout
-
- module RenderTestHelper
- def rjs_helper_method_from_module
- page.visual_effect :highlight
- end
- end
-
- helper RenderTestHelper
- helper do
- def rjs_helper_method(value)
- page.visual_effect :highlight, value
- end
- end
-
- def enum_rjs_test
- render :update do |page|
- page.select('.product').each do |value|
- page.rjs_helper_method_from_module
- page.rjs_helper_method(value)
- page.sortable(value, :url => { :action => "order" })
- page.draggable(value)
- end
- end
- end
-
- def render_explicit_html_template
- end
-
- def render_custom_code_rjs
- render :update, :status => 404 do |page|
- page.replace :foo, :partial => 'partial'
- end
- end
-
- def render_implicit_html_template
- end
-
- def render_js_with_explicit_template
- @project_id = 4
- render :template => 'test/delete_with_js'
- end
-
- def render_js_with_explicit_action_template
- @project_id = 4
- render :action => 'delete_with_js'
- end
-
- def delete_with_js
- @project_id = 4
- end
-
- def update_page
- render :update do |page|
- page.replace_html 'balance', '$37,000,000.00'
- page.visual_effect :highlight, 'balance'
- end
- end
-
- def update_page_with_instance_variables
- @money = '$37,000,000.00'
- @div_id = 'balance'
- render :update do |page|
- page.replace_html @div_id, @money
- page.visual_effect :highlight, @div_id
- end
- end
-
- def update_page_with_view_method
- render :update do |page|
- page.replace_html 'person', pluralize(2, 'person')
- end
- end
-
- def partial_as_rjs
- render :update do |page|
- page.replace :foo, :partial => 'partial'
- end
- end
-
- def respond_to_partial_as_rjs
- respond_to do |format|
- format.js do
- render :update do |page|
- page.replace :foo, :partial => 'partial'
- end
- end
- end
- end
-
- def render_alternate_default
- # For this test, the method "default_render" is overridden:
- @alternate_default_render = lambda do
- render :update do |page|
- page.replace :foo, :partial => 'partial'
- end
- end
- end
-
def render_simon_says
render :simon => "foo"
end
-
- private
- def default_render
- @alternate_default_render ||= nil
- if @alternate_default_render
- @alternate_default_render.call
- else
- super
- end
- end
-
- def determine_layout
- case action_name
- when "hello_world", "layout_test", "rendering_without_layout",
- "rendering_nothing_on_layout", "render_text_hello_world",
- "render_text_hello_world_with_layout",
- "hello_world_with_layout_false",
- "partial_only", "partial_only_with_layout",
- "accessing_params_in_template",
- "accessing_params_in_template_with_layout",
- "render_with_explicit_template",
- "render_with_explicit_string_template",
- "update_page", "update_page_with_instance_variables"
-
- "layouts/standard"
- when "action_talk_to_layout", "layout_overriding_layout"
- "layouts/talk_from_action"
- when "render_implicit_html_template_from_xhr_request"
- (request.xhr? ? 'layouts/xhr' : 'layouts/standard')
- end
- end
end
tests TestController
- def setup
- # 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 = Logger.new(nil)
-
- @request.host = "www.nextangle.com"
- end
-
- def test_enum_rjs_test
- ActiveSupport::SecureRandom.stubs(:base64).returns("asdf")
- get :enum_rjs_test
- body = %{
- $$(".product").each(function(value, index) {
- new Effect.Highlight(element,{});
- new Effect.Highlight(value,{});
- Sortable.create(value, {onUpdate:function(){new Ajax.Request('/render_other_test/test/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(value) + '&authenticity_token=' + encodeURIComponent('asdf')})}});
- new Draggable(value, {});
- });
- }.gsub(/^ /, '').strip
- assert_equal body, @response.body
- end
-
- def test_explicitly_rendering_an_html_template_with_implicit_html_template_renders_should_be_possible_from_an_rjs_template
- [:js, "js"].each do |format|
- assert_nothing_raised do
- get :render_explicit_html_template, :format => format
- assert_equal %(document.write("Hello world\\n");), @response.body
- end
- end
- end
-
- def test_render_custom_code_rjs
- get :render_custom_code_rjs
- assert_response 404
- assert_equal %(Element.replace("foo", "partial html");), @response.body
- end
-
- def test_render_in_an_rjs_template_should_pick_html_templates_when_available
- [:js, "js"].each do |format|
- assert_nothing_raised do
- get :render_implicit_html_template, :format => format
- assert_equal %(document.write("Hello world\\n");), @response.body
- end
- end
- end
-
- def test_render_rjs_template_explicitly
- get :render_js_with_explicit_template
- assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
- end
-
- def test_rendering_rjs_action_explicitly
- get :render_js_with_explicit_action_template
- assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
- end
-
- def test_render_rjs_with_default
- get :delete_with_js
- assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
- end
-
- def test_update_page
- get :update_page
- assert_template nil
- assert_equal 'text/javascript; charset=utf-8', @response.headers['Content-Type']
- assert_equal 2, @response.body.split($/).length
- end
-
- def test_update_page_with_instance_variables
- get :update_page_with_instance_variables
- assert_template nil
- assert_equal 'text/javascript; charset=utf-8', @response.headers["Content-Type"]
- assert_match(/balance/, @response.body)
- assert_match(/\$37/, @response.body)
- end
-
- def test_update_page_with_view_method
- get :update_page_with_view_method
- assert_template nil
- assert_equal 'text/javascript; charset=utf-8', @response.headers["Content-Type"]
- assert_match(/2 people/, @response.body)
- end
-
- def test_should_render_html_formatted_partial_with_rjs
- xhr :get, :partial_as_rjs
- assert_equal %(Element.replace("foo", "partial html");), @response.body
- end
-
- def test_should_render_html_formatted_partial_with_rjs_and_js_format
- xhr :get, :respond_to_partial_as_rjs
- assert_equal %(Element.replace("foo", "partial html");), @response.body
- end
-
- def test_should_render_with_alternate_default_render
- xhr :get, :render_alternate_default
- assert_equal %(Element.replace("foo", "partial html");), @response.body
- end
-
def test_using_custom_render_option
get :render_simon_says
assert_equal "Simon says: foo", @response.body
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index be492152f2..e62f3155c5 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -517,15 +517,6 @@ class TestController < ActionController::Base
render :partial => 'partial'
end
- def render_alternate_default
- # For this test, the method "default_render" is overridden:
- @alternate_default_render = lambda do
- render :update do |page|
- page.replace :foo, :partial => 'partial'
- end
- end
- end
-
def render_to_string_with_partial
@partial_only = render_to_string :partial => "partial_only"
@partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") }
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index d520b5e512..31f4bf3a76 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -172,13 +172,11 @@ end
class RequestForgeryProtectionControllerTest < ActionController::TestCase
include RequestForgeryProtectionTests
- test 'should emit a csrf-token meta tag' do
+ test 'should emit a csrf-param meta tag and a csrf-token meta tag' do
ActiveSupport::SecureRandom.stubs(:base64).returns(@token + '<=?')
get :meta
- assert_equal <<-METAS.strip_heredoc.chomp, @response.body
- <meta name="csrf-param" content="authenticity_token"/>
- <meta name="csrf-token" content="cf50faa3fe97702ca1ae&lt;=?"/>
- METAS
+ assert_select 'meta[name=?][content=?]', 'csrf-param', 'authenticity_token'
+ assert_select 'meta[name=?][content=?]', 'csrf-token', 'cf50faa3fe97702ca1ae&lt;=?'
end
end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index acb4617a60..6ea492cf8b 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/with_options'
+require 'active_support/core_ext/object/inclusion'
class ResourcesController < ActionController::Base
def index() render :nothing => true end
@@ -1292,7 +1293,7 @@ class ResourcesTest < ActionController::TestCase
def assert_resource_methods(expected, resource, action_method, method)
assert_equal expected.length, resource.send("#{action_method}_methods")[method].size, "#{resource.send("#{action_method}_methods")[method].inspect}"
expected.each do |action|
- assert resource.send("#{action_method}_methods")[method].include?(action),
+ assert action.in?(resource.send("#{action_method}_methods")[method])
"#{method} not in #{action_method} methods: #{resource.send("#{action_method}_methods")[method].inspect}"
end
end
@@ -1329,9 +1330,9 @@ class ResourcesTest < ActionController::TestCase
options = options.merge(:action => action.to_s)
path_options = { :path => path, :method => method }
- if Array(allowed).include?(action)
+ if action.in?(Array(allowed))
assert_recognizes options, path_options
- elsif Array(not_allowed).include?(action)
+ elsif action.in?(Array(not_allowed))
assert_not_recognizes options, path_options
end
end
diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb
index edda0d0a30..5896222a0a 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_test.rb
@@ -493,6 +493,18 @@ XML
)
end
+ def test_params_passing_with_frozen_values
+ assert_nothing_raised do
+ get :test_params, :frozen => 'icy'.freeze, :frozens => ['icy'.freeze].freeze
+ end
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {'controller' => 'test_test/test', 'action' => 'test_params',
+ 'frozen' => 'icy', 'frozens' => ['icy']},
+ parsed_params
+ )
+ end
+
def test_id_converted_to_string
get :test_params, :id => 20, :foo => Object.new
assert_kind_of String, @request.path_parameters['id']
diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb
index edfcb5cc4d..42356be1ea 100644
--- a/actionpack/test/controller/view_paths_test.rb
+++ b/actionpack/test/controller/view_paths_test.rb
@@ -131,6 +131,35 @@ class ViewLoadPathsTest < ActionController::TestCase
assert_equal "Hello overridden world!", @response.body
end
+ def test_decorate_view_paths_with_custom_resolver
+ decorator_class = Class.new(ActionView::PathResolver) do
+ def initialize(path_set)
+ @path_set = path_set
+ end
+
+ def find_all(*args)
+ @path_set.find_all(*args).collect do |template|
+ ::ActionView::Template.new(
+ "Decorated body",
+ template.identifier,
+ template.handler,
+ {
+ :virtual_path => template.virtual_path,
+ :format => template.formats
+ }
+ )
+ end
+ end
+ end
+
+ decorator = decorator_class.new(TestController.view_paths)
+ TestController.view_paths = ActionView::PathSet.new.push(decorator)
+
+ get :hello_world
+ assert_response :success
+ assert_equal "Decorated body", @response.body
+ end
+
def test_inheritance
original_load_paths = ActionController::Base.view_paths
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 39159fd629..ebc16694db 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -495,3 +495,99 @@ class CookiesTest < ActionController::TestCase
end
end
end
+
+class CookiesIntegrationTest < ActionDispatch::IntegrationTest
+ SessionKey = '_myapp_session'
+ SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
+
+ class TestController < ActionController::Base
+ def dont_set_cookies
+ head :ok
+ end
+
+ def set_cookies
+ cookies["that"] = "hello"
+ head :ok
+ end
+ end
+
+ def test_setting_cookies_raises_after_stream_back_to_client
+ with_test_route_set do
+ get '/set_cookies'
+ assert_raise(ActionDispatch::ClosedError) {
+ request.cookie_jar['alert'] = 'alert'
+ cookies['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_cookies_raises_after_stream_back_to_client_even_without_cookies
+ with_test_route_set do
+ get '/dont_set_cookies'
+ assert_raise(ActionDispatch::ClosedError) {
+ request.cookie_jar['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_permanent_cookies_raises_after_stream_back_to_client
+ with_test_route_set do
+ get '/set_cookies'
+ assert_raise(ActionDispatch::ClosedError) {
+ request.cookie_jar.permanent['alert'] = 'alert'
+ cookies['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_permanent_cookies_raises_after_stream_back_to_client_even_without_cookies
+ with_test_route_set do
+ get '/dont_set_cookies'
+ assert_raise(ActionDispatch::ClosedError) {
+ request.cookie_jar.permanent['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_signed_cookies_raises_after_stream_back_to_client
+ with_test_route_set do
+ get '/set_cookies'
+ assert_raise(ActionDispatch::ClosedError) {
+ request.cookie_jar.signed['alert'] = 'alert'
+ cookies['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_signed_cookies_raises_after_stream_back_to_client_even_without_cookies
+ with_test_route_set do
+ get '/dont_set_cookies'
+ assert_raise(ActionDispatch::ClosedError) {
+ request.cookie_jar.signed['alert'] = 'alert'
+ }
+ end
+ end
+
+ private
+
+ # Overwrite get to send SessionSecret in env hash
+ def get(path, parameters = nil, env = {})
+ env["action_dispatch.secret_token"] ||= SessionSecret
+ super
+ end
+
+ def with_test_route_set
+ with_routing do |set|
+ set.draw do
+ match ':action', :to => CookiesIntegrationTest::TestController
+ end
+
+ @app = self.class.build_app(set) do |middleware|
+ middleware.use ActionDispatch::Cookies
+ middleware.delete "ActionDispatch::ShowExceptions"
+ end
+
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
new file mode 100644
index 0000000000..b6c08ffc33
--- /dev/null
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -0,0 +1,88 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Routing
+ class MapperTest < ActiveSupport::TestCase
+ class FakeSet
+ attr_reader :routes
+
+ def initialize
+ @routes = []
+ end
+
+ def resources_path_names
+ {}
+ end
+
+ def request_class
+ ActionDispatch::Request
+ end
+
+ def add_route(*args)
+ routes << args
+ end
+
+ def conditions
+ routes.map { |x| x[1] }
+ end
+
+ def requirements
+ routes.map { |x| x[2] }
+ end
+ end
+
+ def test_initialize
+ Mapper.new FakeSet.new
+ end
+
+ def test_map_slash
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.match '/', :to => 'posts#index', :as => :main
+ assert_equal '/', fakeset.conditions.first[:path_info]
+ end
+
+ def test_map_more_slashes
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+
+ # FIXME: is this a desired behavior?
+ mapper.match '/one/two/', :to => 'posts#index', :as => :main
+ assert_equal '/one/two(.:format)', fakeset.conditions.first[:path_info]
+ end
+
+ def test_map_wildcard
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.match '/*path', :to => 'pages#show'
+ assert_equal '/*path(.:format)', fakeset.conditions.first[:path_info]
+ assert_equal(/.+?/, fakeset.requirements.first[:path])
+ end
+
+ def test_map_wildcard_with_other_element
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.match '/*path/foo/:bar', :to => 'pages#show'
+ assert_equal '/*path/foo/:bar(.:format)', fakeset.conditions.first[:path_info]
+ assert_nil fakeset.requirements.first[:path]
+ end
+
+ def test_map_wildcard_with_multiple_wildcard
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.match '/*foo/*bar', :to => 'pages#show'
+ assert_equal '/*foo/*bar(.:format)', fakeset.conditions.first[:path_info]
+ assert_nil fakeset.requirements.first[:foo]
+ assert_equal(/.+?/, fakeset.requirements.first[:bar])
+ end
+
+ def test_map_wildcard_with_format_false
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.match '/*path', :to => 'pages#show', :format => false
+ assert_equal '/*path', fakeset.conditions.first[:path_info]
+ assert_nil fakeset.requirements.first[:path]
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index 18f28deee4..b28a058250 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -69,6 +69,7 @@ module TestGenerationPrefix
# force draw
RailsApplication.routes
+ RailsApplication.routes.define_mounted_helper(:main_app)
class ::InsideEngineGeneratingController < ActionController::Base
include BlogEngine.routes.url_helpers
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 6f38714b2e..5abbaf74fe 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -33,22 +33,6 @@ class ResponseTest < ActiveSupport::TestCase
}, headers)
end
- test "streaming block" do
- @response.body = Proc.new do |response, output|
- 5.times { |n| output.write(n) }
- end
-
- status, headers, body = @response.to_a
- assert_equal 200, status
- assert_equal({
- "Content-Type" => "text/html; charset=utf-8"
- }, headers)
-
- parts = []
- body.each { |part| parts << part.to_s }
- assert_equal ["0", "1", "2", "3", "4"], parts
- end
-
test "content type" do
[204, 304].each do |c|
@response.status = c.to_s
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 5e5758a60e..ba7506721f 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -1,6 +1,7 @@
require 'erb'
require 'abstract_unit'
require 'controller/fake_controllers'
+require 'active_support/core_ext/object/inclusion'
class TestRoutingMapper < ActionDispatch::IntegrationTest
SprocketsApp = lambda { |env|
@@ -495,7 +496,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resources :todos, :id => /\d+/
end
- scope '/countries/:country', :constraints => lambda { |params, req| %[all France].include?(params[:country]) } do
+ scope '/countries/:country', :constraints => lambda { |params, req| params[:country].in?(["all", "France"]) } do
match '/', :to => 'countries#index'
match '/cities', :to => 'countries#cities'
end
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index 2a478c214f..e453dd11ce 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -7,6 +7,8 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
case req.path
when "/not_found"
raise ActionController::UnknownAction
+ when "/runtime_error"
+ raise RuntimeError
when "/method_not_allowed"
raise ActionController::MethodNotAllowed
when "/not_implemented"
@@ -121,4 +123,18 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
assert_response 404
assert_match(/AbstractController::ActionNotFound/, body)
end
+
+ test "show the controller name in the diagnostics template when controller name is present" do
+ @app = ProductionApp
+ get("/runtime_error", {}, {
+ 'action_dispatch.show_exceptions' => true,
+ 'action_dispatch.request.parameters' => {
+ 'action' => 'show',
+ 'id' => 'unknown',
+ 'controller' => 'featured_tiles'
+ }
+ })
+ assert_response 500
+ assert_match(/RuntimeError\n in FeaturedTilesController/, body)
+ end
end
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index 655745a848..2ebbed4414 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -47,35 +47,4 @@ class StaticTest < ActiveSupport::TestCase
end
include StaticTests
-end
-
-class MultipleDirectorisStaticTest < ActiveSupport::TestCase
- DummyApp = lambda { |env|
- [200, {"Content-Type" => "text/plain"}, ["Hello, World!"]]
- }
- App = ActionDispatch::Static.new(DummyApp,
- { "/" => "#{FIXTURE_LOAD_PATH}/public",
- "/blog" => "#{FIXTURE_LOAD_PATH}/blog_public",
- "/foo" => "#{FIXTURE_LOAD_PATH}/non_existing_dir"
- })
-
- def setup
- @app = App
- end
-
- include StaticTests
-
- test "serves files from other mounted directories" do
- assert_html "/blog/index.html", get("/blog/index.html")
- assert_html "/blog/index.html", get("/blog/index")
- assert_html "/blog/index.html", get("/blog/")
-
- assert_html "/blog/blog.html", get("/blog/blog/")
- assert_html "/blog/blog.html", get("/blog/blog.html")
- assert_html "/blog/blog.html", get("/blog/blog")
-
- assert_html "/blog/subdir/index.html", get("/blog/subdir/index.html")
- assert_html "/blog/subdir/index.html", get("/blog/subdir/")
- assert_html "/blog/subdir/index.html", get("/blog/subdir")
- end
-end
+end \ No newline at end of file
diff --git a/actionpack/test/fixtures/custom_pattern/another.html.erb b/actionpack/test/fixtures/custom_pattern/another.html.erb
new file mode 100644
index 0000000000..6d7f3bafbb
--- /dev/null
+++ b/actionpack/test/fixtures/custom_pattern/another.html.erb
@@ -0,0 +1 @@
+Hello custom patterns! \ No newline at end of file
diff --git a/actionpack/test/fixtures/custom_pattern/html/another.erb b/actionpack/test/fixtures/custom_pattern/html/another.erb
new file mode 100644
index 0000000000..dbd7e96ab6
--- /dev/null
+++ b/actionpack/test/fixtures/custom_pattern/html/another.erb
@@ -0,0 +1 @@
+Another template! \ No newline at end of file
diff --git a/actionpack/test/fixtures/custom_pattern/html/path.erb b/actionpack/test/fixtures/custom_pattern/html/path.erb
new file mode 100644
index 0000000000..6d7f3bafbb
--- /dev/null
+++ b/actionpack/test/fixtures/custom_pattern/html/path.erb
@@ -0,0 +1 @@
+Hello custom patterns! \ No newline at end of file
diff --git a/actionpack/test/fixtures/filter_test/implicit_actions/edit.html.erb b/actionpack/test/fixtures/filter_test/implicit_actions/edit.html.erb
new file mode 100644
index 0000000000..8491ab9f80
--- /dev/null
+++ b/actionpack/test/fixtures/filter_test/implicit_actions/edit.html.erb
@@ -0,0 +1 @@
+edit \ No newline at end of file
diff --git a/actionpack/test/fixtures/filter_test/implicit_actions/show.html.erb b/actionpack/test/fixtures/filter_test/implicit_actions/show.html.erb
new file mode 100644
index 0000000000..0a89cecf05
--- /dev/null
+++ b/actionpack/test/fixtures/filter_test/implicit_actions/show.html.erb
@@ -0,0 +1 @@
+show \ No newline at end of file
diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs
deleted file mode 100644
index 057f15e62f..0000000000
--- a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs
+++ /dev/null
@@ -1,6 +0,0 @@
-page.assign 'title', 'Hey'
-cache do
- page['element_1'].visual_effect :highlight
- page['element_2'].visual_effect :highlight
-end
-page.assign 'footer', 'Bye'
diff --git a/actionpack/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs b/actionpack/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs
deleted file mode 100644
index 248842c9da..0000000000
--- a/actionpack/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page.replace_html 'notices', :partial => 'partial' \ No newline at end of file
diff --git a/actionpack/test/fixtures/layouts/streaming.erb b/actionpack/test/fixtures/layouts/streaming.erb
new file mode 100644
index 0000000000..d3f896a6ca
--- /dev/null
+++ b/actionpack/test/fixtures/layouts/streaming.erb
@@ -0,0 +1,4 @@
+<%= yield :header -%>
+<%= yield -%>
+<%= yield :footer -%>
+<%= yield(:unknown).presence || "." -%> \ No newline at end of file
diff --git a/actionpack/test/fixtures/old_content_type/render_default_for_rjs.rjs b/actionpack/test/fixtures/old_content_type/render_default_for_rjs.rjs
deleted file mode 100644
index 8d614d04ad..0000000000
--- a/actionpack/test/fixtures/old_content_type/render_default_for_rjs.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page.alert 'hello world!' \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/all_types_with_layout.js.rjs b/actionpack/test/fixtures/respond_to/all_types_with_layout.js.rjs
deleted file mode 100644
index b7aec7c505..0000000000
--- a/actionpack/test/fixtures/respond_to/all_types_with_layout.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page << "RJS for all_types_with_layout" \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/using_defaults.js.rjs b/actionpack/test/fixtures/respond_to/using_defaults.js.rjs
deleted file mode 100644
index 469fcd8e15..0000000000
--- a/actionpack/test/fixtures/respond_to/using_defaults.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page[:body].visual_effect :highlight \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs b/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs
deleted file mode 100644
index 469fcd8e15..0000000000
--- a/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page[:body].visual_effect :highlight \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_with/using_invalid_resource_with_template.xml.erb b/actionpack/test/fixtures/respond_with/using_invalid_resource_with_template.xml.erb
new file mode 100644
index 0000000000..bf5869ed22
--- /dev/null
+++ b/actionpack/test/fixtures/respond_with/using_invalid_resource_with_template.xml.erb
@@ -0,0 +1 @@
+<content>I should not be displayed</content> \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_with/using_options_with_template.xml.erb b/actionpack/test/fixtures/respond_with/using_options_with_template.xml.erb
new file mode 100644
index 0000000000..b313017913
--- /dev/null
+++ b/actionpack/test/fixtures/respond_with/using_options_with_template.xml.erb
@@ -0,0 +1 @@
+<customer-name><%= @customer.name %></customer-name> \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_with/using_resource.js.rjs b/actionpack/test/fixtures/respond_with/using_resource.js.rjs
deleted file mode 100644
index 737c175a4e..0000000000
--- a/actionpack/test/fixtures/respond_with/using_resource.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page[:body].visual_effect :highlight
diff --git a/railties/lib/rails/generators/rails/app/templates/public/images/rails.png b/actionpack/test/fixtures/sprockets/app/images/logo.png
index d5edc04e65..d5edc04e65 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/images/rails.png
+++ b/actionpack/test/fixtures/sprockets/app/images/logo.png
Binary files differ
diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/application.js b/actionpack/test/fixtures/sprockets/app/javascripts/application.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/app/javascripts/application.js
diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/dir/xmlhr.js b/actionpack/test/fixtures/sprockets/app/javascripts/dir/xmlhr.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/app/javascripts/dir/xmlhr.js
diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/xmlhr.js b/actionpack/test/fixtures/sprockets/app/javascripts/xmlhr.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/app/javascripts/xmlhr.js
diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/application.css b/actionpack/test/fixtures/sprockets/app/stylesheets/application.css
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/app/stylesheets/application.css
diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/dir/style.css b/actionpack/test/fixtures/sprockets/app/stylesheets/dir/style.css
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/app/stylesheets/dir/style.css
diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/style.css b/actionpack/test/fixtures/sprockets/app/stylesheets/style.css
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/app/stylesheets/style.css
diff --git a/actionpack/test/fixtures/test/delete_with_js.rjs b/actionpack/test/fixtures/test/delete_with_js.rjs
deleted file mode 100644
index 4b75a955ad..0000000000
--- a/actionpack/test/fixtures/test/delete_with_js.rjs
+++ /dev/null
@@ -1,2 +0,0 @@
-page.remove 'person'
-page.visual_effect :highlight, "project-#{@project_id}"
diff --git a/actionpack/test/fixtures/test/enum_rjs_test.rjs b/actionpack/test/fixtures/test/enum_rjs_test.rjs
deleted file mode 100644
index e3004076a8..0000000000
--- a/actionpack/test/fixtures/test/enum_rjs_test.rjs
+++ /dev/null
@@ -1,6 +0,0 @@
-page.select('.product').each do |value|
- page.visual_effect :highlight
- page.visual_effect :highlight, value
- page.sortable(value, :url => { :action => "order" })
- page.draggable(value)
-end \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/greeting.js.rjs b/actionpack/test/fixtures/test/greeting.js.rjs
deleted file mode 100644
index 469fcd8e15..0000000000
--- a/actionpack/test/fixtures/test/greeting.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page[:body].visual_effect :highlight \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/nested_streaming.erb b/actionpack/test/fixtures/test/nested_streaming.erb
new file mode 100644
index 0000000000..55525e0c92
--- /dev/null
+++ b/actionpack/test/fixtures/test/nested_streaming.erb
@@ -0,0 +1,3 @@
+<%- content_for :header do -%>?<%- end -%>
+<%= render :template => "test/streaming" %>
+? \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/render_explicit_html_template.js.rjs b/actionpack/test/fixtures/test/render_explicit_html_template.js.rjs
deleted file mode 100644
index 4eb12fd6af..0000000000
--- a/actionpack/test/fixtures/test/render_explicit_html_template.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page.call "document.write", render(:partial => "one.html.erb")
diff --git a/actionpack/test/fixtures/test/render_implicit_html_template.js.rjs b/actionpack/test/fixtures/test/render_implicit_html_template.js.rjs
deleted file mode 100644
index 3d68041756..0000000000
--- a/actionpack/test/fixtures/test/render_implicit_html_template.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page.call "document.write", render(:partial => "one")
diff --git a/actionpack/test/fixtures/test/streaming.erb b/actionpack/test/fixtures/test/streaming.erb
new file mode 100644
index 0000000000..fb9b8b1ade
--- /dev/null
+++ b/actionpack/test/fixtures/test/streaming.erb
@@ -0,0 +1,3 @@
+<%- provide :header do -%>Yes, <%- end -%>
+this works
+<%- content_for :footer, " like a charm" -%>
diff --git a/actionpack/test/fixtures/test/streaming_buster.erb b/actionpack/test/fixtures/test/streaming_buster.erb
new file mode 100644
index 0000000000..4221d56dcc
--- /dev/null
+++ b/actionpack/test/fixtures/test/streaming_buster.erb
@@ -0,0 +1,3 @@
+<%= yield :foo -%>
+This won't look
+<% provide :unknown, " good." -%>
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 1bf748af14..2abc806e97 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -66,6 +66,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(auto_discovery_link_tag(:xml)) => %(<link href="http://www.example.com" rel="alternate" title="XML" type="application/xml" />),
%(auto_discovery_link_tag(:rss, :action => "feed")) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
%(auto_discovery_link_tag(:rss, "http://localhost/feed")) => %(<link href="http://localhost/feed" rel="alternate" title="RSS" type="application/rss+xml" />),
+ %(auto_discovery_link_tag(:rss, "//localhost/feed")) => %(<link href="//localhost/feed" rel="alternate" title="RSS" type="application/rss+xml" />),
%(auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"})) => %(<link href="http://www.example.com" rel="alternate" title="My RSS" type="application/rss+xml" />),
%(auto_discovery_link_tag(:rss, {}, {:title => "My RSS"})) => %(<link href="http://www.example.com" rel="alternate" title="My RSS" type="application/rss+xml" />),
%(auto_discovery_link_tag(nil, {}, {:type => "text/html"})) => %(<link href="http://www.example.com" rel="alternate" title="" type="text/html" />),
@@ -100,6 +101,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(javascript_include_tag("http://example.com/all")) => %(<script src="http://example.com/all" type="text/javascript"></script>),
%(javascript_include_tag("http://example.com/all.js")) => %(<script src="http://example.com/all.js" type="text/javascript"></script>),
+ %(javascript_include_tag("//example.com/all.js")) => %(<script src="//example.com/all.js" type="text/javascript"></script>),
}
StylePathToTag = {
@@ -129,6 +131,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(stylesheet_link_tag("http://www.example.com/styles/style")) => %(<link href="http://www.example.com/styles/style" media="screen" rel="stylesheet" type="text/css" />),
%(stylesheet_link_tag("http://www.example.com/styles/style.css")) => %(<link href="http://www.example.com/styles/style.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(stylesheet_link_tag("//www.example.com/styles/style.css")) => %(<link href="//www.example.com/styles/style.css" media="screen" rel="stylesheet" type="text/css" />),
}
ImagePathToTag = {
@@ -157,6 +160,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(image_tag("slash..png")) => %(<img alt="Slash." src="/images/slash..png" />),
%(image_tag(".pdf.png")) => %(<img alt=".pdf" src="/images/.pdf.png" />),
%(image_tag("http://www.rubyonrails.com/images/rails.png")) => %(<img alt="Rails" src="http://www.rubyonrails.com/images/rails.png" />),
+ %(image_tag("//www.rubyonrails.com/images/rails.png")) => %(<img alt="Rails" src="//www.rubyonrails.com/images/rails.png" />),
%(image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />),
%(image_tag("mouse.png", :mouseover => image_path("mouse_over.png"))) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />),
%(image_tag("mouse.png", :alt => nil)) => %(<img src="/images/mouse.png" />)
@@ -195,6 +199,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(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>)
}
@@ -217,6 +222,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(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" />),
}
def test_auto_discovery_link_tag
@@ -477,15 +483,6 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png")
end
- def test_env_asset_path
- @controller.config.asset_path = "/assets%s"
- def @controller.env; @_env ||= {} end
- @controller.env["action_dispatch.asset_path"] = "/omg%s"
-
- expected_path = "/assets/omg/images/rails.png"
- assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png")
- end
-
def test_proc_asset_id
@controller.config.asset_path = Proc.new do |asset_path|
"/assets.v12345#{asset_path}"
@@ -495,20 +492,6 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png")
end
- def test_env_proc_asset_path
- @controller.config.asset_path = Proc.new do |asset_path|
- "/assets.v12345#{asset_path}"
- end
-
- def @controller.env; @_env ||= {} end
- @controller.env["action_dispatch.asset_path"] = Proc.new do |asset_path|
- "/omg#{asset_path}"
- end
-
- expected_path = "/assets.v12345/omg/images/rails.png"
- assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png")
- end
-
def test_image_tag_interpreting_email_cid_correctly
# An inline image has no need for an alt tag to be automatically generated from the cid:
assert_equal '<img src="cid:thi%25%25sis@acontentid" />', image_tag("cid:thi%25%25sis@acontentid")
@@ -528,6 +511,10 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal %(<img alt="Rails" src="http://www.example.com/rails.png" />), image_tag("http://www.example.com/rails.png")
end
+ def test_should_skip_asset_id_on_scheme_relative_url
+ assert_equal %(<img alt="Rails" src="//www.example.com/rails.png" />), image_tag("//www.example.com/rails.png")
+ end
+
def test_should_use_preset_asset_id
ENV["RAILS_ASSET_ID"] = "4500"
assert_equal %(<img alt="Rails" src="/images/rails.png?4500" />), image_tag("rails.png")
@@ -1118,6 +1105,11 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
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"))
end
+ def test_should_ignore_asset_host_on_scheme_relative_url
+ @controller.config.asset_host = "http://assets.example.com"
+ assert_dom_equal(%(<link href="//bar.example.com/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag("//bar.example.com/stylesheets/style.css"))
+ end
+
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'))
diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb
index 03050485fa..a9a36e6e6b 100644
--- a/actionpack/test/template/capture_helper_test.rb
+++ b/actionpack/test/template/capture_helper_test.rb
@@ -4,7 +4,7 @@ class CaptureHelperTest < ActionView::TestCase
def setup
super
@av = ActionView::Base.new
- @_content_for = Hash.new {|h,k| h[k] = "" }
+ @_view_flow = ActionView::OutputFlow.new
end
def test_capture_captures_the_temporary_output_buffer_in_its_block
@@ -45,6 +45,20 @@ class CaptureHelperTest < ActionView::TestCase
assert ! content_for?(:something_else)
end
+ def test_provide
+ assert !content_for?(:title)
+ provide :title, "hi"
+ assert content_for?(:title)
+ assert_equal "hi", @_view_flow.get(:title)
+ provide :title, "<p>title</p>"
+ assert_equal "hi&lt;p&gt;title&lt;/p&gt;", @_view_flow.get(:title)
+
+ @_view_flow = ActionView::OutputFlow.new
+ provide :title, "hi"
+ provide :title, "<p>title</p>".html_safe
+ assert_equal "hi<p>title</p>", @_view_flow.get(:title)
+ end
+
def test_with_output_buffer_swaps_the_output_buffer_given_no_argument
assert_nil @av.output_buffer
buffer = @av.with_output_buffer do
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index aca2fef170..12d2410f49 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -53,12 +53,12 @@ class DateHelperTest < ActionView::TestCase
assert_equal "about 2 hours", distance_of_time_in_words(from, to + 89.minutes + 30.seconds)
assert_equal "about 24 hours", distance_of_time_in_words(from, to + 23.hours + 59.minutes + 29.seconds)
- # 1440..2529
+ # 1440..2519
assert_equal "1 day", distance_of_time_in_words(from, to + 23.hours + 59.minutes + 30.seconds)
assert_equal "1 day", distance_of_time_in_words(from, to + 41.hours + 59.minutes + 29.seconds)
- # 2530..43199
- assert_equal "2 days", distance_of_time_in_words(from, to + 42.hours + 59.minutes + 30.seconds)
+ # 2520..43199
+ assert_equal "2 days", distance_of_time_in_words(from, to + 41.hours + 59.minutes + 30.seconds)
assert_equal "3 days", distance_of_time_in_words(from, to + 2.days + 12.hours)
assert_equal "30 days", distance_of_time_in_words(from, to + 29.days + 23.hours + 59.minutes + 29.seconds)
@@ -2720,11 +2720,11 @@ class DateHelperTest < ActionView::TestCase
end
def test_time_tag_pubdate_option
- assert_match /<time.*pubdate="pubdate">.*<\/time>/, time_tag(Time.now, :pubdate => true)
+ assert_match(/<time.*pubdate="pubdate">.*<\/time>/, time_tag(Time.now, :pubdate => true))
end
def test_time_tag_with_given_text
- assert_match /<time.*>Right now<\/time>/, time_tag(Time.now, 'Right now')
+ assert_match(/<time.*>Right now<\/time>/, time_tag(Time.now, 'Right now'))
end
def test_time_tag_with_different_format
diff --git a/actionpack/test/template/erb_util_test.rb b/actionpack/test/template/erb_util_test.rb
index d1891094e8..30f6d1a213 100644
--- a/actionpack/test/template/erb_util_test.rb
+++ b/actionpack/test/template/erb_util_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/core_ext/object/inclusion'
class ErbUtilTest < Test::Unit::TestCase
include ERB::Util
@@ -29,7 +30,7 @@ class ErbUtilTest < Test::Unit::TestCase
def test_rest_in_ascii
(0..127).to_a.map {|int| int.chr }.each do |chr|
- next if %w(& " < >).include?(chr)
+ next if chr.in?('&"<>')
assert_equal chr, html_escape(chr)
end
end
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 359b078466..c25c850eb3 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'controller/fake_models'
+require 'active_support/core_ext/object/inclusion'
class FormHelperTest < ActionView::TestCase
tests ActionView::Helpers::FormHelper
@@ -23,7 +24,10 @@ class FormHelperTest < ActionView::TestCase
:helpers => {
:label => {
:post => {
- :body => "Write entire text here"
+ :body => "Write entire text here",
+ :color => {
+ :red => "Rojo"
+ }
}
}
}
@@ -140,6 +144,13 @@ class FormHelperTest < ActionView::TestCase
I18n.locale = old_locale
end
+ def test_label_with_locales_and_value
+ old_locale, I18n.locale = I18n.locale, :label
+ assert_dom_equal('<label for="post_color_red">Rojo</label>', label(:post, :color, :value => "red"))
+ ensure
+ I18n.locale = old_locale
+ end
+
def test_label_with_for_attribute_as_symbol
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, :for => "my_for"))
end
@@ -715,14 +726,31 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_with_method_as_part_of_html_options
+ form_for(@post, :url => '/', :html => { :id => 'create-post', :method => :delete }) 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", "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>" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_method
- form_for(@post, :url => '/', :html => { :id => 'create-post', :method => :put }) do |f|
+ form_for(@post, :url => '/', :method => :delete, :html => { :id => 'create-post' }) 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", "put") do
+ 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>" +
"<input name='post[secret]' type='hidden' value='0' />" +
@@ -1726,7 +1754,7 @@ class FormHelperTest < ActionView::TestCase
def snowman(method = nil)
txt = %{<div style="margin:0;padding:0;display:inline">}
txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
- if (method && !['get','post'].include?(method.to_s))
+ if method && !method.to_s.in?(['get', 'post'])
txt << %{<input name="_method" type="hidden" value="#{method}" />}
end
txt << %{</div>}
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 93ff7ba0fd..b92e1d9890 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'tzinfo'
+require 'active_support/core_ext/object/inclusion'
class Map < Hash
def category
@@ -82,7 +83,7 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_collection_options_with_proc_for_disabled
assert_dom_equal(
"<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" disabled=\"disabled\">Babe went home</option>\n<option value=\"Cabe\" disabled=\"disabled\">Cabe went home</option>",
- options_from_collection_for_select(dummy_posts, "author_name", "title", :disabled => lambda{|p| %w(Babe Cabe).include? p.author_name })
+ options_from_collection_for_select(dummy_posts, "author_name", "title", :disabled => lambda{|p| p.author_name.in?(["Babe", "Cabe"]) })
)
end
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index f8671f2980..656fa0356b 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/core_ext/object/inclusion'
class FormTagHelperTest < ActionView::TestCase
tests ActionView::Helpers::FormTagHelper
@@ -13,7 +14,7 @@ class FormTagHelperTest < ActionView::TestCase
txt = %{<div style="margin:0;padding:0;display:inline">}
txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
- if (method && !['get','post'].include?(method.to_s))
+ if method && !method.to_s.in?(['get','post'])
txt << %{<input name="_method" type="hidden" value="#{method}" />}
end
txt << %{</div>}
diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb
index 2e7484afaf..538e0e9874 100644
--- a/actionpack/test/template/javascript_helper_test.rb
+++ b/actionpack/test/template/javascript_helper_test.rb
@@ -27,6 +27,7 @@ class JavaScriptHelperTest < ActionView::TestCase
assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos'))
assert_equal %(backslash\\\\test), escape_javascript( %(backslash\\test) )
assert_equal %(dont <\\/close> tags), escape_javascript(%(dont </close> tags))
+ assert_equal %(dont <\\/close> tags), j(%(dont </close> tags))
end
def test_button_to_function
@@ -34,20 +35,6 @@ class JavaScriptHelperTest < ActionView::TestCase
button_to_function("Greeting", "alert('Hello world!')")
end
- def test_button_to_function_with_rjs_block
- html = button_to_function( "Greet me!" ) do |page|
- page.replace_html 'header', "<h1>Greetings</h1>"
- end
- assert_dom_equal %(<input type="button" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E&quot;);;" value="Greet me!" />), html
- end
-
- def test_button_to_function_with_rjs_block_and_options
- html = button_to_function( "Greet me!", :class => "greeter" ) do |page|
- page.replace_html 'header', "<h1>Greetings</h1>"
- end
- assert_dom_equal %(<input type="button" class="greeter" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C\/h1\\u003E&quot;);;" value="Greet me!" />), html
- end
-
def test_button_to_function_with_onclick
assert_dom_equal "<input onclick=\"alert('Goodbye World :('); alert('Hello world!');\" type=\"button\" value=\"Greeting\" />",
button_to_function("Greeting", "alert('Hello world!')", :onclick => "alert('Goodbye World :(')")
@@ -68,34 +55,11 @@ class JavaScriptHelperTest < ActionView::TestCase
link_to_function("Greeting", "alert('Hello world!')", :onclick => "confirm('Sanity!')")
end
- def test_link_to_function_with_rjs_block
- html = link_to_function( "Greet me!" ) do |page|
- page.replace_html 'header', "<h1>Greetings</h1>"
- end
- assert_dom_equal %(<a href="#" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E&quot;);; return false;">Greet me!</a>), html
- end
-
- def test_link_to_function_with_rjs_block_and_options
- html = link_to_function( "Greet me!", :class => "updater" ) do |page|
- page.replace_html 'header', "<h1>Greetings</h1>"
- end
- assert_dom_equal %(<a href="#" class="updater" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E&quot;);; return false;">Greet me!</a>), html
- end
-
- def test_link_to_function_with_href
+ def test_function_with_href
assert_dom_equal %(<a href="http://example.com/" onclick="alert('Hello world!'); return false;">Greeting</a>),
link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/')
end
- def test_link_to_function_with_inner_block_does_not_raise_exception
- html = link_to_function( "Greet me!" ) do |page|
- page.replace_html 'header', (content_tag :h1 do
- 'Greetings'
- end)
- end
- assert_dom_equal %(<a href="#" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E&quot;);; return false;">Greet me!</a>), html
- end
-
def test_javascript_tag
self.output_buffer = 'foo'
diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb
index 8d063e66b0..ff94cba59f 100644
--- a/actionpack/test/template/lookup_context_test.rb
+++ b/actionpack/test/template/lookup_context_test.rb
@@ -180,16 +180,6 @@ class LookupContextTest < ActiveSupport::TestCase
assert_not_equal template, old_template
end
-
- test "data can be stored in cached templates" do
- template = @lookup_context.find("hello_world", %w(test))
- template.data["cached"] = "data"
- assert_equal "Hello world!", template.source
-
- template = @lookup_context.find("hello_world", %w(test))
- assert_equal "data", template.data["cached"]
- assert_equal "Hello world!", template.source
- end
end
class LookupContextWithFalseCaching < ActiveSupport::TestCase
@@ -235,21 +225,6 @@ class LookupContextWithFalseCaching < ActiveSupport::TestCase
template = @lookup_context.find("foo", %w(test), true)
assert_equal "Foo", template.source
end
-
- test "data can be stored as long as template was not updated" do
- template = @lookup_context.find("foo", %w(test), true)
- template.data["cached"] = "data"
- assert_equal "Foo", template.source
-
- template = @lookup_context.find("foo", %w(test), true)
- assert_equal "data", template.data["cached"]
- assert_equal "Foo", template.source
-
- @resolver.hash["test/_foo.erb"][1] = Time.now.utc
- template = @lookup_context.find("foo", %w(test), true)
- assert_nil template.data["cached"]
- assert_equal "Foo", template.source
- end
end
class TestMissingTemplate < ActiveSupport::TestCase
diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index c1b4bab903..23a7e17e65 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -32,6 +32,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("555-1234", number_to_phone(5551234))
assert_equal("800-555-1212", number_to_phone(8005551212))
assert_equal("(800) 555-1212", number_to_phone(8005551212, {:area_code => true}))
+ assert_equal("", number_to_phone("", {:area_code => true}))
assert_equal("800 555 1212", number_to_phone(8005551212, {:delimiter => " "}))
assert_equal("(800) 555-1212 x 123", number_to_phone(8005551212, {:area_code => true, :extension => 123}))
assert_equal("800-555-1212", number_to_phone(8005551212, :extension => " "))
@@ -195,7 +196,9 @@ class NumberHelperTest < ActionView::TestCase
def test_number_to_human
assert_equal '-123', number_to_human(-123)
- assert_equal '0', number_to_human(0)
+ assert_equal '-0.5', number_to_human(-0.5)
+ assert_equal '0', number_to_human(0)
+ assert_equal '0.5', number_to_human(0.5)
assert_equal '123', number_to_human(123)
assert_equal '1.23 Thousand', number_to_human(1234)
assert_equal '12.3 Thousand', number_to_human(12345)
diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb
deleted file mode 100644
index a6aa848a00..0000000000
--- a/actionpack/test/template/prototype_helper_test.rb
+++ /dev/null
@@ -1,476 +0,0 @@
-require 'abstract_unit'
-require 'active_model'
-
-class Bunny < Struct.new(:Bunny, :id)
- extend ActiveModel::Naming
- include ActiveModel::Conversion
- def to_key() id ? [id] : nil end
-end
-
-class Author
- extend ActiveModel::Naming
- include ActiveModel::Conversion
-
- attr_reader :id
- def to_key() id ? [id] : nil end
- def save; @id = 1 end
- def new_record?; @id.nil? end
- def name
- @id.nil? ? 'new author' : "author ##{@id}"
- end
-end
-
-class Article
- extend ActiveModel::Naming
- include ActiveModel::Conversion
- attr_reader :id
- attr_reader :author_id
- def to_key() id ? [id] : nil end
- def save; @id = 1; @author_id = 1 end
- def new_record?; @id.nil? end
- def name
- @id.nil? ? 'new article' : "article ##{@id}"
- end
-end
-
-class Author::Nested < Author; end
-
-
-class PrototypeHelperBaseTest < ActionView::TestCase
- attr_accessor :formats, :output_buffer
-
- def update_details(details)
- @details = details
- yield if block_given?
- end
-
- def setup
- super
- @template = self
- end
-
- def url_for(options)
- if options.is_a?(String)
- options
- else
- url = "http://www.example.com/"
- url << options[:action].to_s if options and options[:action]
- url << "?a=#{options[:a]}" if options && options[:a]
- url << "&b=#{options[:b]}" if options && options[:a] && options[:b]
- url
- end
- end
-
- protected
- def request_forgery_protection_token
- nil
- end
-
- def protect_against_forgery?
- false
- end
-
- def create_generator
- block = Proc.new { |*args| yield(*args) if block_given? }
- JavaScriptGenerator.new self, &block
- end
-end
-
-class PrototypeHelperTest < PrototypeHelperBaseTest
- def _evaluate_assigns_and_ivars() end
-
- def setup
- @record = @author = Author.new
- @article = Article.new
- super
- end
-
- def test_update_page
- old_output_buffer = output_buffer
-
- block = Proc.new { |page| page.replace_html('foo', 'bar') }
- assert_equal create_generator(&block).to_s, update_page(&block)
-
- assert_equal old_output_buffer, output_buffer
- end
-
- def test_update_page_tag
- block = Proc.new { |page| page.replace_html('foo', 'bar') }
- assert_equal javascript_tag(create_generator(&block).to_s), update_page_tag(&block)
- end
-
- def test_update_page_tag_with_html_options
- block = Proc.new { |page| page.replace_html('foo', 'bar') }
- assert_equal javascript_tag(create_generator(&block).to_s, {:defer => 'true'}), update_page_tag({:defer => 'true'}, &block)
- end
-
- def test_remote_function
- res = remote_function(:url => authors_path, :with => "'author[name]='+$F('author_name')+'&author[dob]='+$F('author_dob')")
- assert_equal "new Ajax.Request('/authors', {asynchronous:true, evalScripts:true, parameters:'author[name]='+$F('author_name')+'&author[dob]='+$F('author_dob')})", res
- assert res.html_safe?
- end
-
- protected
- def author_path(record)
- "/authors/#{record.id}"
- end
-
- def authors_path
- "/authors"
- end
-
- def author_articles_path(author)
- "/authors/#{author.id}/articles"
- end
-
- def author_article_path(author, article)
- "/authors/#{author.id}/articles/#{article.id}"
- end
-end
-
-class JavaScriptGeneratorTest < PrototypeHelperBaseTest
- def setup
- super
- @generator = create_generator
- ActiveSupport.escape_html_entities_in_json = true
- end
-
- def teardown
- ActiveSupport.escape_html_entities_in_json = false
- end
-
- def _evaluate_assigns_and_ivars() end
-
- def test_insert_html_with_string
- assert_equal 'Element.insert("element", { top: "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E" });',
- @generator.insert_html(:top, 'element', '<p>This is a test</p>')
- assert_equal 'Element.insert("element", { bottom: "\\u003Cp\u003EThis is a test\\u003C/p\u003E" });',
- @generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
- assert_equal 'Element.insert("element", { before: "\\u003Cp\u003EThis is a test\\u003C/p\u003E" });',
- @generator.insert_html(:before, 'element', '<p>This is a test</p>')
- assert_equal 'Element.insert("element", { after: "\\u003Cp\u003EThis is a test\\u003C/p\u003E" });',
- @generator.insert_html(:after, 'element', '<p>This is a test</p>')
- end
-
- def test_replace_html_with_string
- assert_equal 'Element.update("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");',
- @generator.replace_html('element', '<p>This is a test</p>')
- end
-
- def test_replace_element_with_string
- assert_equal 'Element.replace("element", "\\u003Cdiv id=\"element\"\\u003E\\u003Cp\\u003EThis is a test\\u003C/p\\u003E\\u003C/div\\u003E");',
- @generator.replace('element', '<div id="element"><p>This is a test</p></div>')
- end
-
- def test_remove
- assert_equal 'Element.remove("foo");',
- @generator.remove('foo')
- assert_equal '["foo","bar","baz"].each(Element.remove);',
- @generator.remove('foo', 'bar', 'baz')
- end
-
- def test_show
- assert_equal 'Element.show("foo");',
- @generator.show('foo')
- assert_equal '["foo","bar","baz"].each(Element.show);',
- @generator.show('foo', 'bar', 'baz')
- end
-
- def test_hide
- assert_equal 'Element.hide("foo");',
- @generator.hide('foo')
- assert_equal '["foo","bar","baz"].each(Element.hide);',
- @generator.hide('foo', 'bar', 'baz')
- end
-
- def test_toggle
- assert_equal 'Element.toggle("foo");',
- @generator.toggle('foo')
- assert_equal '["foo","bar","baz"].each(Element.toggle);',
- @generator.toggle('foo', 'bar', 'baz')
- end
-
- def test_alert
- assert_equal 'alert("hello");', @generator.alert('hello')
- end
-
- def test_redirect_to
- assert_equal 'window.location.href = "http://www.example.com/welcome";',
- @generator.redirect_to(:action => 'welcome')
- assert_equal 'window.location.href = "http://www.example.com/welcome?a=b&c=d";',
- @generator.redirect_to("http://www.example.com/welcome?a=b&c=d")
- end
-
- def test_reload
- assert_equal 'window.location.reload();',
- @generator.reload
- end
-
- def test_delay
- @generator.delay(20) do
- @generator.hide('foo')
- end
-
- assert_equal "setTimeout(function() {\n;\nElement.hide(\"foo\");\n}, 20000);", @generator.to_s
- end
-
- def test_to_s
- @generator.insert_html(:top, 'element', '<p>This is a test</p>')
- @generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
- @generator.remove('foo', 'bar')
- @generator.replace_html('baz', '<p>This is a test</p>')
-
- assert_equal <<-EOS.chomp, @generator.to_s
-Element.insert("element", { top: "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E" });
-Element.insert("element", { bottom: "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E" });
-["foo","bar"].each(Element.remove);
-Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
- EOS
- end
-
- def test_element_access
- assert_equal %($("hello");), @generator['hello']
- end
-
- def test_element_access_on_records
- assert_equal %($("bunny_5");), @generator[Bunny.new(:id => 5)]
- assert_equal %($("new_bunny");), @generator[Bunny.new]
- end
-
- def test_element_proxy_one_deep
- @generator['hello'].hide
- assert_equal %($("hello").hide();), @generator.to_s
- end
-
- def test_element_proxy_variable_access
- @generator['hello']['style']
- assert_equal %($("hello").style;), @generator.to_s
- end
-
- def test_element_proxy_variable_access_with_assignment
- @generator['hello']['style']['color'] = 'red'
- assert_equal %($("hello").style.color = "red";), @generator.to_s
- end
-
- def test_element_proxy_assignment
- @generator['hello'].width = 400
- assert_equal %($("hello").width = 400;), @generator.to_s
- end
-
- def test_element_proxy_two_deep
- @generator['hello'].hide("first").clean_whitespace
- assert_equal %($("hello").hide("first").cleanWhitespace();), @generator.to_s
- end
-
- def test_select_access
- assert_equal %($$("div.hello");), @generator.select('div.hello')
- end
-
- def test_select_proxy_one_deep
- @generator.select('p.welcome b').first.hide
- assert_equal %($$("p.welcome b").first().hide();), @generator.to_s
- end
-
- def test_visual_effect
- assert_equal %(new Effect.Puff("blah",{});),
- @generator.visual_effect(:puff,'blah')
- end
-
- def test_visual_effect_toggle
- assert_equal %(Effect.toggle("blah",'appear',{});),
- @generator.visual_effect(:toggle_appear,'blah')
- end
-
- def test_sortable
- assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("blah")})}});),
- @generator.sortable('blah', :url => { :action => "order" })
- assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:false, evalScripts:true, parameters:Sortable.serialize("blah")})}});),
- @generator.sortable('blah', :url => { :action => "order" }, :type => :synchronous)
- end
-
- def test_draggable
- assert_equal %(new Draggable("blah", {});),
- @generator.draggable('blah')
- end
-
- def test_drop_receiving
- assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});),
- @generator.drop_receiving('blah', :url => { :action => "order" })
- assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:false, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});),
- @generator.drop_receiving('blah', :url => { :action => "order" }, :type => :synchronous)
- end
-
- def test_collection_first_and_last
- @generator.select('p.welcome b').first.hide()
- @generator.select('p.welcome b').last.show()
- assert_equal <<-EOS.strip, @generator.to_s
-$$("p.welcome b").first().hide();
-$$("p.welcome b").last().show();
- EOS
- end
-
- def test_collection_proxy_with_each
- @generator.select('p.welcome b').each do |value|
- value.remove_class_name 'selected'
- end
- @generator.select('p.welcome b').each do |value, index|
- @generator.visual_effect :highlight, value
- end
- assert_equal <<-EOS.strip, @generator.to_s
-$$("p.welcome b").each(function(value, index) {
-value.removeClassName("selected");
-});
-$$("p.welcome b").each(function(value, index) {
-new Effect.Highlight(value,{});
-});
- EOS
- end
-
- def test_collection_proxy_on_collect
- @generator.select('p').collect('a') { |para| para.show }
- @generator.select('p').collect { |para| para.hide }
- assert_equal <<-EOS.strip, @generator.to_s
-var a = $$("p").collect(function(value, index) {
-return value.show();
-});
-$$("p").collect(function(value, index) {
-return value.hide();
-});
- EOS
- @generator = create_generator
- end
-
- def test_collection_proxy_with_grep
- @generator.select('p').grep 'a', /^a/ do |value|
- @generator << '(value.className == "welcome")'
- end
- @generator.select('p').grep 'b', /b$/ do |value, index|
- @generator.call 'alert', value
- @generator << '(value.className == "welcome")'
- end
-
- assert_equal <<-EOS.strip, @generator.to_s
-var a = $$("p").grep(/^a/, function(value, index) {
-return (value.className == "welcome");
-});
-var b = $$("p").grep(/b$/, function(value, index) {
-alert(value);
-return (value.className == "welcome");
-});
- EOS
- end
-
- def test_collection_proxy_with_inject
- @generator.select('p').inject 'a', [] do |memo, value|
- @generator << '(value.className == "welcome")'
- end
- @generator.select('p').inject 'b', nil do |memo, value, index|
- @generator.call 'alert', memo
- @generator << '(value.className == "welcome")'
- end
-
- assert_equal <<-EOS.strip, @generator.to_s
-var a = $$("p").inject([], function(memo, value, index) {
-return (value.className == "welcome");
-});
-var b = $$("p").inject(null, function(memo, value, index) {
-alert(memo);
-return (value.className == "welcome");
-});
- EOS
- end
-
- def test_collection_proxy_with_pluck
- @generator.select('p').pluck('a', 'className')
- assert_equal %(var a = $$("p").pluck("className");), @generator.to_s
- end
-
- def test_collection_proxy_with_zip
- ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('a', [4, 5, 6], [7, 8, 9])
- ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('b', [4, 5, 6], [7, 8, 9]) do |array|
- @generator.call 'array.reverse'
- end
-
- assert_equal <<-EOS.strip, @generator.to_s
-var a = [1, 2, 3].zip([4,5,6], [7,8,9]);
-var b = [1, 2, 3].zip([4,5,6], [7,8,9], function(array) {
-return array.reverse();
-});
- EOS
- end
-
- def test_collection_proxy_with_find_all
- @generator.select('p').find_all 'a' do |value, index|
- @generator << '(value.className == "welcome")'
- end
-
- assert_equal <<-EOS.strip, @generator.to_s
-var a = $$("p").findAll(function(value, index) {
-return (value.className == "welcome");
-});
- EOS
- end
-
- def test_collection_proxy_with_in_groups_of
- @generator.select('p').in_groups_of('a', 3)
- @generator.select('p').in_groups_of('a', 3, 'x')
- assert_equal <<-EOS.strip, @generator.to_s
-var a = $$("p").inGroupsOf(3);
-var a = $$("p").inGroupsOf(3, "x");
- EOS
- end
-
- def test_collection_proxy_with_each_slice
- @generator.select('p').each_slice('a', 3)
- @generator.select('p').each_slice('a', 3) do |group, index|
- group.reverse
- end
-
- assert_equal <<-EOS.strip, @generator.to_s
-var a = $$("p").eachSlice(3);
-var a = $$("p").eachSlice(3, function(value, index) {
-return value.reverse();
-});
- EOS
- end
-
- def test_debug_rjs
- ActionView::Base.debug_rjs = true
- @generator['welcome'].replace_html 'Welcome'
- assert_equal "try {\n$(\"welcome\").update(\"Welcome\");\n} catch (e) { alert('RJS error:\\n\\n' + e.toString()); alert('$(\\\"welcome\\\").update(\\\"Welcome\\\");'); throw e }", @generator.to_s
- ensure
- ActionView::Base.debug_rjs = false
- end
-
- def test_literal
- literal = @generator.literal("function() {}")
- assert_equal "function() {}", ActiveSupport::JSON.encode(literal)
- assert_equal "", @generator.to_s
- end
-
- def test_class_proxy
- @generator.form.focus('my_field')
- assert_equal "Form.focus(\"my_field\");", @generator.to_s
- end
-
- def test_call_with_block
- @generator.call(:before)
- @generator.call(:my_method) do |p|
- p[:one].show
- p[:two].hide
- end
- @generator.call(:in_between)
- @generator.call(:my_method_with_arguments, true, "hello") do |p|
- p[:three].visual_effect(:highlight)
- end
- assert_equal "before();\nmy_method(function() { $(\"one\").show();\n$(\"two\").hide(); });\nin_between();\nmy_method_with_arguments(true, \"hello\", function() { $(\"three\").visualEffect(\"highlight\"); });", @generator.to_s
- end
-
- def test_class_proxy_call_with_block
- @generator.my_object.my_method do |p|
- p[:one].show
- p[:two].hide
- end
- assert_equal "MyObject.myMethod(function() { $(\"one\").show();\n$(\"two\").hide(); });", @generator.to_s
- end
-end
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 034fb6c210..d4e912c410 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -68,10 +68,6 @@ module RenderTestCases
assert_equal "The secret is in the sauce\n", @view.render(:file => "test/dot.directory/render_file_with_ivar")
end
- def test_render_update
- assert_equal 'alert("Hello, World!");', @view.render(:update) { |page| page.alert('Hello, World!') }
- end
-
def test_render_partial_from_default
assert_equal "only partial", @view.render("test/partial_only")
end
@@ -231,20 +227,11 @@ module RenderTestCases
"@output_buffer << 'source: #{template.source.inspect}'\n"
end
- WithViewHandler = lambda do |template, view|
- %'"#{template.class} #{view.class}"'
- end
-
def test_render_inline_with_render_from_to_proc
ActionView::Template.register_template_handler :ruby_handler, :source.to_proc
assert_equal '3', @view.render(:inline => "(1 + 2).to_s", :type => :ruby_handler)
end
- def test_render_inline_with_template_handler_with_view
- ActionView::Template.register_template_handler :with_view, WithViewHandler
- assert_equal 'ActionView::Template ActionView::Base', @view.render(:inline => "Hello, World!", :type => :with_view)
- end
-
def test_render_inline_with_compilable_custom_type
ActionView::Template.register_template_handler :foo, CustomHandler
assert_equal 'source: "Hello, World!"', @view.render(:inline => "Hello, World!", :type => :foo)
@@ -381,7 +368,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase
end
def test_render_utf8_template_with_incompatible_external_encoding
- with_external_encoding Encoding::SJIS do
+ with_external_encoding Encoding::SHIFT_JIS do
begin
result = @view.render(:file => "test/utf8.html.erb", :layouts => "layouts/yield")
flunk 'Should have raised incompatible encoding error'
@@ -392,7 +379,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase
end
def test_render_utf8_template_with_partial_with_incompatible_encoding
- with_external_encoding Encoding::SJIS do
+ with_external_encoding Encoding::SHIFT_JIS do
begin
result = @view.render(:file => "test/utf8_magic_with_bare_partial.html.erb", :layouts => "layouts/yield")
flunk 'Should have raised incompatible encoding error'
diff --git a/actionpack/test/template/resolver_patterns_test.rb b/actionpack/test/template/resolver_patterns_test.rb
new file mode 100644
index 0000000000..97b1bad055
--- /dev/null
+++ b/actionpack/test/template/resolver_patterns_test.rb
@@ -0,0 +1,31 @@
+require 'abstract_unit'
+
+class ResolverPatternsTest < ActiveSupport::TestCase
+ def setup
+ path = File.expand_path("../../fixtures/", __FILE__)
+ pattern = ":prefix/{:formats/,}:action{.:formats,}{.:handlers,}"
+ @resolver = ActionView::FileSystemResolver.new(path, pattern)
+ end
+
+ def test_should_return_empty_list_for_unknown_path
+ templates = @resolver.find_all("unknown", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]})
+ assert_equal [], templates, "expected an empty list of templates"
+ end
+
+ def test_should_return_template_for_declared_path
+ templates = @resolver.find_all("path", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]})
+ assert_equal 1, templates.size, "expected one template"
+ assert_equal "Hello custom patterns!", templates.first.source
+ assert_equal "custom_pattern/path", templates.first.virtual_path
+ assert_equal [:html], templates.first.formats
+ end
+
+ def test_should_return_all_templates_when_ambigous_pattern
+ templates = @resolver.find_all("another", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]})
+ assert_equal 2, templates.size, "expected two templates"
+ assert_equal "Another template!", templates[0].source
+ assert_equal "custom_pattern/another", templates[0].virtual_path
+ assert_equal "Hello custom patterns!", templates[1].source
+ assert_equal "custom_pattern/another", templates[1].virtual_path
+ end
+end
diff --git a/actionpack/test/template/scriptaculous_helper_test.rb b/actionpack/test/template/scriptaculous_helper_test.rb
deleted file mode 100644
index 233012bfdd..0000000000
--- a/actionpack/test/template/scriptaculous_helper_test.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-require 'abstract_unit'
-
-class ScriptaculousHelperTest < ActionView::TestCase
- tests ActionView::Helpers::ScriptaculousHelper
-
- def url_for(options)
- url = "http://www.example.com/"
- url << options[:action].to_s if options and options[:action]
- url
- end
-
- def test_effect
- assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect(:highlight, "posts")
- assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect("highlight", :posts)
- assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect(:highlight, :posts)
- assert_equal "new Effect.Fade(\"fademe\",{duration:4.0});", visual_effect(:fade, "fademe", :duration => 4.0)
- assert_equal "new Effect.Shake(element,{});", visual_effect(:shake)
- assert_equal "new Effect.DropOut(\"dropme\",{queue:'end'});", visual_effect(:drop_out, 'dropme', :queue => :end)
- assert_equal "new Effect.Highlight(\"status\",{endcolor:'#EEEEEE'});", visual_effect(:highlight, 'status', :endcolor => '#EEEEEE')
- assert_equal "new Effect.Highlight(\"status\",{restorecolor:'#500000', startcolor:'#FEFEFE'});", visual_effect(:highlight, 'status', :restorecolor => '#500000', :startcolor => '#FEFEFE')
-
- # chop the queue params into a comma separated list
- beginning, ending = 'new Effect.DropOut("dropme",{queue:{', '}});'
- ve = [
- visual_effect(:drop_out, 'dropme', :queue => {:position => "end", :scope => "test", :limit => 2}),
- visual_effect(:drop_out, 'dropme', :queue => {:scope => :list, :limit => 2}),
- visual_effect(:drop_out, 'dropme', :queue => {:position => :end, :scope => :test, :limit => 2})
- ].collect { |v| v[beginning.length..-ending.length-1].split(',') }
-
- assert ve[0].include?("limit:2")
- assert ve[0].include?("scope:'test'")
- assert ve[0].include?("position:'end'")
-
- assert ve[1].include?("limit:2")
- assert ve[1].include?("scope:'list'")
-
- assert ve[2].include?("limit:2")
- assert ve[2].include?("scope:'test'")
- assert ve[2].include?("position:'end'")
- end
-
- def test_toggle_effects
- assert_equal "Effect.toggle(\"posts\",'appear',{});", visual_effect(:toggle_appear, "posts")
- assert_equal "Effect.toggle(\"posts\",'slide',{});", visual_effect(:toggle_slide, "posts")
- assert_equal "Effect.toggle(\"posts\",'blind',{});", visual_effect(:toggle_blind, "posts")
- assert_equal "Effect.toggle(\"posts\",'appear',{});", visual_effect("toggle_appear", "posts")
- assert_equal "Effect.toggle(\"posts\",'slide',{});", visual_effect("toggle_slide", "posts")
- assert_equal "Effect.toggle(\"posts\",'blind',{});", visual_effect("toggle_blind", "posts")
- end
-
-
- def test_sortable_element
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}})\n//]]>\n</script>),
- sortable_element("mylist", :url => { :action => "order" })
- assert_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {constraint:'horizontal', onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}, tag:'div'})\n//]]>\n</script>),
- sortable_element("mylist", :tag => "div", :constraint => "horizontal", :url => { :action => "order" })
- assert_dom_equal %|<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {constraint:'horizontal', containment:['list1','list2'], onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}})\n//]]>\n</script>|,
- sortable_element("mylist", :containment => ['list1','list2'], :constraint => "horizontal", :url => { :action => "order" })
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {constraint:'horizontal', containment:'list1', onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}})\n//]]>\n</script>),
- sortable_element("mylist", :containment => 'list1', :constraint => "horizontal", :url => { :action => "order" })
- end
-
- def test_draggable_element
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable(\"product_13\", {})\n//]]>\n</script>),
- draggable_element("product_13")
- assert_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable(\"product_13\", {revert:true})\n//]]>\n</script>),
- draggable_element("product_13", :revert => true)
- end
-
- def test_drop_receiving_element
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {onDrop:function(element){new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
- drop_receiving_element("droptarget1")
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {accept:'products', onDrop:function(element){new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
- drop_receiving_element("droptarget1", :accept => 'products')
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {accept:'products', onDrop:function(element){new Ajax.Updater('infobox', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
- drop_receiving_element("droptarget1", :accept => 'products', :update => 'infobox')
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {accept:['tshirts','mugs'], onDrop:function(element){new Ajax.Updater('infobox', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
- drop_receiving_element("droptarget1", :accept => ['tshirts','mugs'], :update => 'infobox')
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add("droptarget1", {hoverclass:'dropready', onDrop:function(element){if (confirm('Are you sure?')) { new Ajax.Request('http://www.example.com/update_drop', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)}); }}})\n//]]>\n</script>),
- drop_receiving_element('droptarget1', :hoverclass=>'dropready', :url=>{:action=>'update_drop'}, :confirm => 'Are you sure?')
-
- end
- def protect_against_forgery?
- false
- end
-end
diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb
new file mode 100644
index 0000000000..8d3be09a4f
--- /dev/null
+++ b/actionpack/test/template/sprockets_helper_test.rb
@@ -0,0 +1,119 @@
+require 'abstract_unit'
+require 'sprockets'
+require 'mocha'
+
+module Rails; end
+
+class SprocketsHelperTest < ActionView::TestCase
+ tests ActionView::Helpers::SprocketsHelper
+
+ attr_accessor :assets
+
+ def setup
+ super
+
+ @controller = BasicController.new
+
+ @request = Class.new do
+ def protocol() 'http://' end
+ def ssl?() false end
+ def host_with_port() 'localhost' end
+ end.new
+
+ @controller.request = @request
+
+ @assets = Sprockets::Environment.new
+ @assets.paths << FIXTURES.join("sprockets/app/javascripts")
+ @assets.paths << FIXTURES.join("sprockets/app/stylesheets")
+ @assets.paths << FIXTURES.join("sprockets/app/images")
+
+ application = Object.new
+ Rails.stubs(:application).returns(application)
+ application.stubs(:config).returns(config)
+ application.stubs(:assets).returns(@assets)
+
+ config.perform_caching = true
+ end
+
+ def url_for(*args)
+ "http://www.example.com"
+ end
+
+ test "asset path" do
+ assert_equal "/assets/logo-9c0a079bdd7701d7e729bd956823d153.png",
+ asset_path("logo.png")
+
+ assert_equal "/images/logo",
+ asset_path("/images/logo")
+ assert_equal "/images/logo.gif",
+ asset_path("/images/logo.gif")
+
+ assert_equal "/dir/audio",
+ asset_path("/dir/audio")
+
+ assert_equal "http://www.example.com/video/play",
+ asset_path("http://www.example.com/video/play")
+ assert_equal "http://www.example.com/video/play.mp4",
+ asset_path("http://www.example.com/video/play.mp4")
+ end
+
+ test "javascript path" do
+ assert_equal "/assets/application-d41d8cd98f00b204e9800998ecf8427e.js",
+ asset_path(:application, "js")
+
+ assert_equal "/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js",
+ asset_path("xmlhr", "js")
+ assert_equal "/assets/dir/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js",
+ asset_path("dir/xmlhr.js", "js")
+
+ assert_equal "/dir/xmlhr.js",
+ asset_path("/dir/xmlhr", "js")
+
+ assert_equal "http://www.example.com/js/xmlhr",
+ asset_path("http://www.example.com/js/xmlhr", "js")
+ assert_equal "http://www.example.com/js/xmlhr.js",
+ asset_path("http://www.example.com/js/xmlhr.js", "js")
+ end
+
+ test "javascript include tag" do
+ assert_equal '<script src="/assets/application-d41d8cd98f00b204e9800998ecf8427e.js" type="text/javascript"></script>',
+ sprockets_javascript_include_tag(:application)
+
+ assert_equal '<script src="/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js" type="text/javascript"></script>',
+ sprockets_javascript_include_tag("xmlhr")
+ assert_equal '<script src="/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js" type="text/javascript"></script>',
+ sprockets_javascript_include_tag("xmlhr.js")
+ assert_equal '<script src="http://www.example.com/xmlhr" type="text/javascript"></script>',
+ sprockets_javascript_include_tag("http://www.example.com/xmlhr")
+ end
+
+ test "stylesheet path" do
+ assert_equal "/assets/application-d41d8cd98f00b204e9800998ecf8427e.css", asset_path(:application, "css")
+
+ assert_equal "/assets/style-d41d8cd98f00b204e9800998ecf8427e.css", asset_path("style", "css")
+ assert_equal "/assets/dir/style-d41d8cd98f00b204e9800998ecf8427e.css", asset_path("dir/style.css", "css")
+ assert_equal "/dir/style.css", asset_path("/dir/style.css", "css")
+
+ assert_equal "http://www.example.com/css/style",
+ asset_path("http://www.example.com/css/style", "css")
+ assert_equal "http://www.example.com/css/style.css",
+ asset_path("http://www.example.com/css/style.css", "css")
+ end
+
+ test "stylesheet link tag" do
+ assert_equal '<link href="/assets/application-d41d8cd98f00b204e9800998ecf8427e.css" media="screen" rel="stylesheet" type="text/css" />',
+ sprockets_stylesheet_link_tag(:application)
+
+ assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="screen" rel="stylesheet" type="text/css" />',
+ sprockets_stylesheet_link_tag("style")
+ assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="screen" rel="stylesheet" type="text/css" />',
+ sprockets_stylesheet_link_tag("style.css")
+
+ assert_equal '<link href="http://www.example.com/style.css" media="screen" rel="stylesheet" type="text/css" />',
+ sprockets_stylesheet_link_tag("http://www.example.com/style.css")
+ assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="all" rel="stylesheet" type="text/css" />',
+ sprockets_stylesheet_link_tag("style", :media => "all")
+ assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="print" rel="stylesheet" type="text/css" />',
+ sprockets_stylesheet_link_tag("style", :media => "print")
+ end
+end
diff --git a/actionpack/test/template/streaming_render_test.rb b/actionpack/test/template/streaming_render_test.rb
new file mode 100644
index 0000000000..4d69081570
--- /dev/null
+++ b/actionpack/test/template/streaming_render_test.rb
@@ -0,0 +1,105 @@
+# encoding: utf-8
+require 'abstract_unit'
+require 'controller/fake_models'
+
+class TestController < ActionController::Base
+end
+
+class FiberedTest < ActiveSupport::TestCase
+ def setup
+ view_paths = ActionController::Base.view_paths
+ @assigns = { :secret => 'in the sauce' }
+ @view = ActionView::Base.new(view_paths, @assigns)
+ @controller_view = TestController.new.view_context
+ end
+
+ def buffered_render(options)
+ body = @view.render_body(options)
+ string = ""
+ body.each do |piece|
+ string << piece
+ end
+ string
+ end
+
+ def test_streaming_works
+ content = []
+ body = @view.render_body(:template => "test/hello_world.erb", :layout => "layouts/yield")
+
+ body.each do |piece|
+ content << piece
+ end
+
+ assert_equal "<title>", content[0]
+ assert_equal "", content[1]
+ assert_equal "</title>\n", content[2]
+ assert_equal "Hello world!", content[3]
+ assert_equal "\n", content[4]
+ end
+
+ def test_render_file
+ assert_equal "Hello world!", buffered_render(:file => "test/hello_world.erb")
+ end
+
+ def test_render_file_with_locals
+ locals = { :secret => 'in the sauce' }
+ assert_equal "The secret is in the sauce\n", buffered_render(:file => "test/render_file_with_locals.erb", :locals => locals)
+ end
+
+ def test_render_partial
+ assert_equal "only partial", buffered_render(:partial => "test/partial_only")
+ end
+
+ def test_render_inline
+ assert_equal "Hello, World!", buffered_render(:inline => "Hello, World!")
+ end
+
+ def test_render_without_layout
+ assert_equal "Hello world!", buffered_render(:template => "test/hello_world")
+ end
+
+ def test_render_with_layout
+ assert_equal %(<title></title>\nHello world!\n),
+ buffered_render(:template => "test/hello_world.erb", :layout => "layouts/yield")
+ end
+
+ def test_render_with_layout_which_has_render_inline
+ assert_equal %(welcome\nHello world!\n),
+ buffered_render(:template => "test/hello_world.erb", :layout => "layouts/yield_with_render_inline_inside")
+ end
+
+ def test_render_with_layout_which_renders_another_partial
+ assert_equal %(partial html\nHello world!\n),
+ buffered_render(:template => "test/hello_world.erb", :layout => "layouts/yield_with_render_partial_inside")
+ end
+
+ def test_render_with_nested_layout
+ assert_equal %(<title>title</title>\n\n<div id="column">column</div>\n<div id="content">content</div>\n),
+ buffered_render(:template => "test/nested_layout.erb", :layout => "layouts/yield")
+ end
+
+ def test_render_with_file_in_layout
+ assert_equal %(\n<title>title</title>\n\n),
+ buffered_render(:template => "test/layout_render_file.erb")
+ end
+
+ def test_render_with_handler_without_streaming_support
+ assert_match "<p>This is grand!</p>", buffered_render(:template => "test/hello")
+ end
+
+ def test_render_with_streaming_multiple_yields_provide_and_content_for
+ assert_equal "Yes, \nthis works\n like a charm.",
+ buffered_render(:template => "test/streaming", :layout => "layouts/streaming")
+ end
+
+ def test_render_with_streaming_with_fake_yields_and_streaming_buster
+ assert_equal "This won't look\n good.",
+ buffered_render(:template => "test/streaming_buster", :layout => "layouts/streaming")
+ end
+
+ def test_render_with_nested_streaming_multiple_yields_provide_and_content_for
+ assert_equal "?Yes, \n\nthis works\n\n? like a charm.",
+ buffered_render(:template => "test/nested_streaming", :layout => "layouts/streaming")
+ end
+
+end if defined?(Fiber) \ No newline at end of file
diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb
index 3432a02c3c..5c655d5b69 100644
--- a/actionpack/test/template/template_test.rb
+++ b/actionpack/test/template/template_test.rb
@@ -113,44 +113,6 @@ class TestERBTemplate < ActiveSupport::TestCase
end
end
- def test_template_expire_sets_the_timestamp_to_1970
- @template = new_template("Hello", :updated_at => Time.utc(2010))
- assert_equal Time.utc(2010), @template.updated_at
- @template.expire!
- assert_equal Time.utc(1970), @template.updated_at
- end
-
- def test_template_rerender_renders_a_template_like_self
- @template = new_template("Hello", :virtual_path => "test/foo_bar")
- @context.expects(:render).with(:template => "test/foo_bar").returns("template")
- assert_equal "template", @template.rerender(@context)
- end
-
- def test_template_rerender_renders_a_root_template_like_self
- @template = new_template("Hello", :virtual_path => "foo_bar")
- @context.expects(:render).with(:template => "foo_bar").returns("template")
- assert_equal "template", @template.rerender(@context)
- end
-
- def test_template_rerender_renders_a_partial_like_self
- @template = new_template("Hello", :virtual_path => "test/_foo_bar")
- @context.expects(:render).with(:partial => "test/foo_bar").returns("partial")
- assert_equal "partial", @template.rerender(@context)
- end
-
- def test_template_rerender_renders_a_root_partial_like_self
- @template = new_template("Hello", :virtual_path => "_foo_bar")
- @context.expects(:render).with(:partial => "foo_bar").returns("partial")
- assert_equal "partial", @template.rerender(@context)
- end
-
- def test_rerender_raises_an_error_without_virtual_path
- @template = new_template("Hello", :virtual_path => nil)
- assert_raise RuntimeError do
- @template.rerender(@context)
- end
- end
-
if "ruby".encoding_aware?
def test_resulting_string_is_utf8
@template = new_template
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index d0d4286393..a4fcff5167 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -315,14 +315,20 @@ class TextHelperTest < ActionView::TestCase
end
end
- def test_auto_link_should_be_html_safe
+ def test_auto_link_should_not_be_html_safe
email_raw = 'santiago@wyeworks.com'
link_raw = 'http://www.rubyonrails.org'
- assert auto_link(nil).html_safe?
- assert auto_link('').html_safe?
- assert auto_link("#{link_raw} #{link_raw} #{link_raw}").html_safe?
- assert auto_link("hello #{email_raw}").html_safe?
+ assert !auto_link(nil).html_safe?, 'should not be html safe'
+ assert !auto_link('').html_safe?, 'should not be html safe'
+ assert !auto_link("#{link_raw} #{link_raw} #{link_raw}").html_safe?, 'should not be html safe'
+ assert !auto_link("hello #{email_raw}").html_safe?, 'should not be html safe'
+ end
+
+ def test_auto_link_email_address
+ email_raw = 'aaron@tenderlovemaking.com'
+ email_result = %{<a href="mailto:#{email_raw}">#{email_raw}</a>}
+ assert !auto_link_email_addresses(email_result).html_safe?, 'should not be html safe'
end
def test_auto_link
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
index 3082c7186a..03287fac7a 100644
--- a/activemodel/CHANGELOG
+++ b/activemodel/CHANGELOG
@@ -1,5 +1,14 @@
*Rails 3.1.0 (unreleased)*
+* Add support for proc or lambda as an option for InclusionValidator,
+ ExclusionValidator, and FormatValidator [Prem Sichanugrist]
+
+ You can now supply Proc, lambda, or anything that respond to #call in those
+ validations, and it will be called with current record as an argument.
+ That given proc or lambda must returns an object which respond to #include? for
+ InclusionValidator and ExclusionValidator, and returns a regular expression
+ object for FormatValidator.
+
* Added ActiveModel::SecurePassword to encapsulate dead-simple password usage with BCrypt encryption and salting [DHH]
* ActiveModel::AttributeMethods allows attributes to be defined on demand [Alexander Uvarov]
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index fec9c7ff8b..ce69c4a201 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -17,10 +17,8 @@ Gem::Specification.new do |s|
s.files = Dir['CHANGELOG', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
- s.has_rdoc = true
-
s.add_dependency('activesupport', version)
s.add_dependency('builder', '~> 3.0.0')
- s.add_dependency('i18n', '~> 0.5.0')
+ s.add_dependency('i18n', '~> 0.6.0beta1')
s.add_dependency('bcrypt-ruby', '~> 2.1.4')
end
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 2a99450a3d..6ee5e04267 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -56,6 +56,8 @@ module ActiveModel
module AttributeMethods
extend ActiveSupport::Concern
+ COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
+
included do
class_attribute :attribute_method_matchers, :instance_writer => false
self.attribute_method_matchers = []
@@ -106,11 +108,17 @@ module ActiveModel
if block_given?
sing.send :define_method, name, &block
else
- # use eval instead of a block to work around a memory leak in dev
- # mode in fcgi
- sing.class_eval <<-eorb, __FILE__, __LINE__ + 1
- def #{name}; #{value.nil? ? 'nil' : value.to_s.inspect}; end
- eorb
+ # If we can compile the method name, do it. Otherwise use define_method.
+ # This is an important *optimization*, please don't change it. define_method
+ # has slower dispatch and consumes more memory.
+ if name =~ COMPILABLE_REGEXP
+ sing.class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{name}; #{value.nil? ? 'nil' : value.to_s.inspect}; end
+ RUBY
+ else
+ value = value.to_s if value
+ sing.send(:define_method, name) { value }
+ end
end
end
@@ -229,8 +237,19 @@ module ActiveModel
def alias_attribute(new_name, old_name)
attribute_method_matchers.each do |matcher|
- define_method(matcher.method_name(new_name)) do |*args|
- send(matcher.method_name(old_name), *args)
+ matcher_new = matcher.method_name(new_name).to_s
+ matcher_old = matcher.method_name(old_name).to_s
+
+ if matcher_new =~ COMPILABLE_REGEXP && matcher_old =~ COMPILABLE_REGEXP
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{matcher_new}(*args)
+ send(:#{matcher_old}, *args)
+ end
+ RUBY
+ else
+ define_method(matcher_new) do |*args|
+ send(matcher_old, *args)
+ end
end
end
end
@@ -273,14 +292,25 @@ module ActiveModel
else
method_name = matcher.method_name(attr_name)
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
+ generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
if method_defined?('#{method_name}')
undef :'#{method_name}'
end
- define_method('#{method_name}') do |*args|
- send('#{matcher.method_missing_target}', '#{attr_name}', *args)
- end
- STR
+ RUBY
+
+ if method_name.to_s =~ COMPILABLE_REGEXP
+ generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{method_name}(*args)
+ send(:#{matcher.method_missing_target}, '#{attr_name}', *args)
+ end
+ RUBY
+ else
+ generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ define_method('#{method_name}') do |*args|
+ send('#{matcher.method_missing_target}', '#{attr_name}', *args)
+ end
+ RUBY
+ end
end
end
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index a479795d51..5ede78617a 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -8,7 +8,7 @@ module ActiveModel
# Provides a way to track changes in your object in the same way as
# Active Record does.
#
- # The requirements to implement ActiveModel::Dirty are to:
+ # The requirements for implementing ActiveModel::Dirty are:
#
# * <tt>include ActiveModel::Dirty</tt> in your object
# * Call <tt>define_attribute_methods</tt> passing each method you want to
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index c2f0228785..22ca3efa2b 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -278,25 +278,24 @@ module ActiveModel
# When using inheritance in your models, it will check all the inherited
# models too, but only if the model itself hasn't been found. Say you have
# <tt>class Admin < User; end</tt> and you wanted the translation for
- # the <tt>:blank</tt> error +message+ for the <tt>title</tt> +attribute+,
+ # the <tt>:blank</tt> error message for the <tt>title</tt> attribute,
# it looks for these translations:
#
- # <ol>
- # <li><tt>activemodel.errors.models.admin.attributes.title.blank</tt></li>
- # <li><tt>activemodel.errors.models.admin.blank</tt></li>
- # <li><tt>activemodel.errors.models.user.attributes.title.blank</tt></li>
- # <li><tt>activemodel.errors.models.user.blank</tt></li>
- # <li>any default you provided through the +options+ hash (in the activemodel.errors scope)</li>
- # <li><tt>activemodel.errors.messages.blank</tt></li>
- # <li><tt>errors.attributes.title.blank</tt></li>
- # <li><tt>errors.messages.blank</tt></li>
- # </ol>
+ # * <tt>activemodel.errors.models.admin.attributes.title.blank</tt>
+ # * <tt>activemodel.errors.models.admin.blank</tt>
+ # * <tt>activemodel.errors.models.user.attributes.title.blank</tt>
+ # * <tt>activemodel.errors.models.user.blank</tt>
+ # * any default you provided through the +options+ hash (in the <tt>activemodel.errors</tt> scope)
+ # * <tt>activemodel.errors.messages.blank</tt>
+ # * <tt>errors.attributes.title.blank</tt>
+ # * <tt>errors.messages.blank</tt>
+ #
def generate_message(attribute, type = :invalid, options = {})
type = options.delete(:message) if options[:message].is_a?(Symbol)
defaults = @base.class.lookup_ancestors.map do |klass|
- [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.attributes.#{attribute}.#{type}",
- :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.#{type}" ]
+ [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
+ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
end
defaults << options.delete(:message)
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index 957d1b9d70..b71ef4b22e 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -23,7 +23,7 @@ module ActiveModel
def test_to_key
assert model.respond_to?(:to_key), "The model should respond to to_key"
def model.persisted?() false end
- assert model.to_key.nil?
+ assert model.to_key.nil?, "to_key should return nil when `persisted?` returns false"
end
# == Responds to <tt>to_param</tt>
@@ -40,7 +40,7 @@ module ActiveModel
assert model.respond_to?(:to_param), "The model should respond to to_param"
def model.to_key() [1] end
def model.persisted?() false end
- assert model.to_param.nil?
+ assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false"
end
# == Responds to <tt>valid?</tt>
diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb
index be48415739..01eef762fd 100644
--- a/activemodel/lib/active_model/mass_assignment_security.rb
+++ b/activemodel/lib/active_model/mass_assignment_security.rb
@@ -24,10 +24,7 @@ module ActiveModel
# include ActiveModel::MassAssignmentSecurity
#
# attr_accessible :first_name, :last_name
- #
- # def self.admin_accessible_attributes
- # accessible_attributes + [ :plan_id ]
- # end
+ # attr_accessible :first_name, :last_name, :plan_id, :as => :admin
#
# def update
# ...
@@ -38,18 +35,17 @@ module ActiveModel
# protected
#
# def account_params
- # sanitize_for_mass_assignment(params[:account])
- # end
- #
- # def mass_assignment_authorizer
- # admin ? admin_accessible_attributes : super
+ # scope = admin ? :admin : :default
+ # sanitize_for_mass_assignment(params[:account], scope)
# end
#
# end
#
module ClassMethods
# Attributes named in this macro are protected from mass-assignment
- # whenever attributes are sanitized before assignment.
+ # whenever attributes are sanitized before assignment. A scope for the
+ # attributes is optional, if no scope is provided then :default is used.
+ # A scope can be defined by using the :as option.
#
# Mass-assignment to these attributes will simply be ignored, to assign
# to them you can use direct writer methods. This is meant to protect
@@ -60,36 +56,58 @@ module ActiveModel
# include ActiveModel::MassAssignmentSecurity
#
# attr_accessor :name, :credit_rating
- # attr_protected :credit_rating
#
- # def attributes=(values)
- # sanitize_for_mass_assignment(values).each do |k, v|
+ # attr_protected :credit_rating, :last_login
+ # attr_protected :last_login, :as => :admin
+ #
+ # def assign_attributes(values, options = {})
+ # sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
# send("#{k}=", v)
# end
# end
# end
#
+ # When using a :default scope :
+ #
# customer = Customer.new
- # customer.attributes = { "name" => "David", "credit_rating" => "Excellent" }
+ # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default)
# customer.name # => "David"
# customer.credit_rating # => nil
+ # customer.last_login # => nil
#
# customer.credit_rating = "Average"
# customer.credit_rating # => "Average"
#
+ # And using the :admin scope :
+ #
+ # customer = Customer.new
+ # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin)
+ # customer.name # => "David"
+ # customer.credit_rating # => "Excellent"
+ # customer.last_login # => nil
+ #
# To start from an all-closed default and enable attributes as needed,
# have a look at +attr_accessible+.
#
# Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_protected+
# to sanitize attributes won't provide sufficient protection.
- def attr_protected(*names)
- self._protected_attributes = self.protected_attributes + names
+ def attr_protected(*args)
+ options = args.extract_options!
+ scope = options[:as] || :default
+
+ self._protected_attributes = protected_attributes_configs.dup
+ self._protected_attributes[scope] = self.protected_attributes(scope) + args
+
self._active_authorizer = self._protected_attributes
end
# Specifies a white list of model attributes that can be set via
# mass-assignment.
#
+ # Like +attr_protected+, a scope for the attributes is optional,
+ # if no scope is provided then :default is used. A scope can be defined by
+ # using the :as option.
+ #
# This is the opposite of the +attr_protected+ macro: Mass-assignment
# will only set attributes in this list, to assign to the rest of
# attributes you can use direct writer methods. This is meant to protect
@@ -102,57 +120,90 @@ module ActiveModel
# include ActiveModel::MassAssignmentSecurity
#
# attr_accessor :name, :credit_rating
+ #
# attr_accessible :name
+ # attr_accessible :name, :credit_rating, :as => :admin
#
- # def attributes=(values)
- # sanitize_for_mass_assignment(values).each do |k, v|
+ # def assign_attributes(values, options = {})
+ # sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
# send("#{k}=", v)
# end
# end
# end
#
+ # When using a :default scope :
+ #
# customer = Customer.new
- # customer.attributes = { :name => "David", :credit_rating => "Excellent" }
+ # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default)
# customer.name # => "David"
# customer.credit_rating # => nil
#
# customer.credit_rating = "Average"
# customer.credit_rating # => "Average"
#
+ # And using the :admin scope :
+ #
+ # customer = Customer.new
+ # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin)
+ # customer.name # => "David"
+ # customer.credit_rating # => "Excellent"
+ #
# Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_accessible+
# to sanitize attributes won't provide sufficient protection.
- def attr_accessible(*names)
- self._accessible_attributes = self.accessible_attributes + names
+ def attr_accessible(*args)
+ options = args.extract_options!
+ scope = options[:as] || :default
+
+ self._accessible_attributes = accessible_attributes_configs.dup
+ self._accessible_attributes[scope] = self.accessible_attributes(scope) + args
+
self._active_authorizer = self._accessible_attributes
end
- def protected_attributes
- self._protected_attributes ||= BlackList.new(attributes_protected_by_default).tap do |w|
- w.logger = self.logger if self.respond_to?(:logger)
- end
+ def protected_attributes(scope = :default)
+ protected_attributes_configs[scope]
end
- def accessible_attributes
- self._accessible_attributes ||= WhiteList.new.tap { |w| w.logger = self.logger if self.respond_to?(:logger) }
+ def accessible_attributes(scope = :default)
+ accessible_attributes_configs[scope]
end
- def active_authorizer
- self._active_authorizer ||= protected_attributes
+ def active_authorizers
+ self._active_authorizer ||= protected_attributes_configs
end
+ alias active_authorizer active_authorizers
def attributes_protected_by_default
[]
end
+
+ private
+
+ def protected_attributes_configs
+ self._protected_attributes ||= begin
+ default_black_list = BlackList.new(attributes_protected_by_default).tap do |w|
+ w.logger = self.logger if self.respond_to?(:logger)
+ end
+ Hash.new(default_black_list)
+ end
+ end
+
+ def accessible_attributes_configs
+ self._accessible_attributes ||= begin
+ default_white_list = WhiteList.new.tap { |w| w.logger = self.logger if self.respond_to?(:logger) }
+ Hash.new(default_white_list)
+ end
+ end
end
protected
- def sanitize_for_mass_assignment(attributes)
- mass_assignment_authorizer.sanitize(attributes)
+ def sanitize_for_mass_assignment(attributes, scope = :default)
+ mass_assignment_authorizer(scope).sanitize(attributes)
end
- def mass_assignment_authorizer
- self.class.active_authorizer
+ def mass_assignment_authorizer(scope = :default)
+ self.class.active_authorizer[scope]
end
end
end
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index eb9b847509..315ccd40e9 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -4,7 +4,7 @@ require 'active_support/core_ext/module/introspection'
module ActiveModel
class Name < String
- attr_reader :singular, :plural, :element, :collection, :partial_path, :route_key, :param_key
+ attr_reader :singular, :plural, :element, :collection, :partial_path, :route_key, :param_key, :i18n_key
alias_method :cache_key, :collection
def initialize(klass, namespace = nil)
@@ -20,6 +20,7 @@ module ActiveModel
@partial_path = "#{@collection}/#{@element}".freeze
@param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze
@route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural).freeze
+ @i18n_key = self.underscore.to_sym
end
# Transform the model name into a more humane format, using I18n. By default,
@@ -33,7 +34,7 @@ module ActiveModel
@klass.respond_to?(:i18n_scope)
defaults = @klass.lookup_ancestors.map do |klass|
- klass.model_name.underscore.to_sym
+ klass.model_name.i18n_key
end
defaults << options[:default] if options[:default]
@@ -44,9 +45,10 @@ module ActiveModel
end
private
- def _singularize(str)
- ActiveSupport::Inflector.underscore(str).tr('/', '_')
- end
+
+ def _singularize(string, replacement='_')
+ ActiveSupport::Inflector.underscore(string).tr('/', replacement)
+ end
end
# == Active Model Naming
@@ -62,6 +64,9 @@ module ActiveModel
# BookCover.model_name # => "BookCover"
# BookCover.model_name.human # => "Book cover"
#
+ # BookCover.model_name.i18n_key # => "book_cover"
+ # BookModule::BookCover.model_name.i18n_key # => "book_module.book_cover"
+ #
# Providing the functionality that ActiveModel::Naming provides in your object
# is required to pass the Active Model Lint test. So either extending the provided
# method below, or rolling your own is required.
diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb
new file mode 100644
index 0000000000..b8aa9cc1e2
--- /dev/null
+++ b/activemodel/lib/active_model/observer_array.rb
@@ -0,0 +1,98 @@
+require 'set'
+
+module ActiveModel
+ # Stores the enabled/disabled state of individual observers for
+ # a particular model classes.
+ class ObserverArray < Array
+ INSTANCES = Hash.new do |hash, model_class|
+ hash[model_class] = new(model_class)
+ end
+
+ def self.for(model_class)
+ return nil unless model_class < ActiveModel::Observing
+ INSTANCES[model_class]
+ end
+
+ # returns false if:
+ # - the ObserverArray for the given model's class has the given observer
+ # in its disabled_observers set.
+ # - or that is the case at any level of the model's superclass chain.
+ def self.observer_enabled?(observer, model)
+ klass = model.class
+ observer_class = observer.class
+
+ loop do
+ break unless array = self.for(klass)
+ return false if array.disabled_observers.include?(observer_class)
+ klass = klass.superclass
+ end
+
+ true # observers are enabled by default
+ end
+
+ def disabled_observers
+ @disabled_observers ||= Set.new
+ end
+
+ attr_reader :model_class
+ def initialize(model_class, *args)
+ @model_class = model_class
+ super(*args)
+ end
+
+ def disable(*observers, &block)
+ set_enablement(false, observers, &block)
+ end
+
+ def enable(*observers, &block)
+ set_enablement(true, observers, &block)
+ end
+
+ private
+
+ def observer_class_for(observer)
+ return observer if observer.is_a?(Class)
+
+ if observer.respond_to?(:to_sym) # string/symbol
+ observer.to_s.camelize.constantize
+ else
+ raise ArgumentError, "#{observer} was not a class or a " +
+ "lowercase, underscored class name as expected."
+ end
+ end
+
+ def transaction
+ orig_disabled_observers = disabled_observers.dup
+
+ begin
+ yield
+ ensure
+ @disabled_observers = orig_disabled_observers
+ end
+ end
+
+ def set_enablement(enabled, observers)
+ if block_given?
+ transaction do
+ set_enablement(enabled, observers)
+ yield
+ end
+ else
+ observers = ActiveModel::Observer.all_observers if observers == [:all]
+ observers.each do |obs|
+ klass = observer_class_for(obs)
+
+ unless klass < ActiveModel::Observer
+ raise ArgumentError.new("#{obs} does not refer to a valid observer")
+ end
+
+ if enabled
+ disabled_observers.delete(klass)
+ else
+ disabled_observers << klass
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index ef36f80bec..e1a2ce218d 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -1,8 +1,10 @@
require 'singleton'
+require 'active_model/observer_array'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/remove_method'
require 'active_support/core_ext/string/inflections'
+require 'active_support/core_ext/enumerable'
module ActiveModel
module Observing
@@ -30,12 +32,12 @@ module ActiveModel
# +instantiate_observers+ is called during startup, and before
# each development request.
def observers=(*values)
- @observers = values.flatten
+ observers.replace(values.flatten)
end
# Gets the current observers.
def observers
- @observers ||= []
+ @observers ||= ObserverArray.for(self)
end
# Gets the current observer instances.
@@ -43,7 +45,7 @@ module ActiveModel
@observer_instances ||= []
end
- # Instantiate the global Active Record observers.
+ # Instantiate the global observers.
def instantiate_observers
observers.each { |o| instantiate_observer(o) }
end
@@ -76,7 +78,11 @@ module ActiveModel
elsif observer.respond_to?(:instance)
observer.instance
else
- raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
+ raise ArgumentError,
+ "#{observer} must be a lowercase, underscored class name (or an " +
+ "instance of the class itself) responding to the instance " +
+ "method. Example: Person.observers = :big_brother # calls " +
+ "BigBrother.instance"
end
end
@@ -197,6 +203,23 @@ module ActiveModel
nil
end
end
+
+ def subclasses
+ @subclasses ||= []
+ end
+
+ # List of all observer subclasses, sub-subclasses, etc.
+ # Necessary so we can disable or enable all observers.
+ def all_observers
+ subclasses.each_with_object(subclasses.dup) do |subclass, array|
+ array.concat(subclass.all_observers)
+ end
+ end
+ end
+
+ def self.inherited(subclass)
+ subclasses << subclass
+ super
end
# Start observing the declared classes and their subclasses.
@@ -210,7 +233,9 @@ module ActiveModel
# Send observed_method(object) if the method exists.
def update(observed_method, object) #:nodoc:
- send(observed_method, object) if respond_to?(observed_method)
+ if respond_to?(observed_method) && ObserverArray.observer_enabled?(self, object)
+ send(observed_method, object)
+ end
end
# Special method sent by the observed class when it is inherited.
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 957d0ddaaa..ee94ad66cf 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -31,11 +31,10 @@ module ActiveModel
# User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
def has_secure_password
attr_reader :password
- attr_accessor :password_confirmation
validates_confirmation_of :password
validates_presence_of :password_digest
-
+
include InstanceMethodsOnActivation
if respond_to?(:attributes_protected_by_default)
@@ -59,7 +58,9 @@ module ActiveModel
# Encrypts the password into the password_digest attribute.
def password=(unencrypted_password)
@password = unencrypted_password
- self.password_digest = BCrypt::Password.create(unencrypted_password)
+ unless unencrypted_password.blank?
+ self.password_digest = BCrypt::Password.create(unencrypted_password)
+ end
end
end
end
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index dbb76244e4..920a133159 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -44,7 +44,7 @@ module ActiveModel
# Specify +options+ with additional translating options.
def human_attribute_name(attribute, options = {})
defaults = lookup_ancestors.map do |klass|
- :"#{self.i18n_scope}.attributes.#{klass.model_name.underscore}.#{attribute}"
+ :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}"
end
defaults << :"attributes.#{attribute}"
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index e38e565d09..a85c23f725 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -1,18 +1,35 @@
+require 'active_support/core_ext/range.rb'
+
module ActiveModel
# == Active Model Exclusion Validator
module Validations
class ExclusionValidator < EachValidator
+ ERROR_MESSAGE = "An object with the method #include? or a proc or lambda is required, " <<
+ "and must be supplied as the :in option of the configuration hash"
+
def check_validity!
- raise ArgumentError, "An object with the method include? is required must be supplied as the " <<
- ":in option of the configuration hash" unless options[:in].respond_to?(:include?)
+ unless [:include?, :call].any? { |method| options[:in].respond_to?(method) }
+ raise ArgumentError, ERROR_MESSAGE
+ end
end
def validate_each(record, attribute, value)
- if options[:in].include?(value)
+ delimiter = options[:in]
+ exclusions = delimiter.respond_to?(:call) ? delimiter.call(record) : delimiter
+ if exclusions.send(inclusion_method(exclusions), value)
record.errors.add(attribute, :exclusion, options.except(:in).merge!(:value => value))
end
end
+
+ private
+
+ # In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the
+ # range for equality, so it may be slow for large ranges. The new <tt>Range#cover?</tt>
+ # uses the previous logic of comparing a value with the range endpoints.
+ def inclusion_method(enumerable)
+ enumerable.is_a?(Range) ? :cover? : :include?
+ end
end
module HelperMethods
@@ -22,10 +39,14 @@ module ActiveModel
# validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
# validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
# validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %{value} is not allowed"
+ # validates_exclusion_of :password, :in => lambda { |p| [p.username, p.first_name] }, :message => "should not be the same as your username or first name"
# end
#
# Configuration options:
# * <tt>:in</tt> - An enumerable object of items that the value shouldn't be part of.
+ # This can be supplied as a proc or lambda which returns an enumerable. If the enumerable
+ # is a range the test is performed with <tt>Range#cover?</tt>
+ # (backported in Active Support for 1.8), otherwise with <tt>include?</tt>.
# * <tt>:message</tt> - Specifies a custom error message (default is: "is reserved").
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index 541f53a834..6f23d492eb 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -4,10 +4,12 @@ module ActiveModel
module Validations
class FormatValidator < EachValidator
def validate_each(record, attribute, value)
- if options[:with] && value.to_s !~ options[:with]
- record.errors.add(attribute, :invalid, options.except(:with).merge!(:value => value))
- elsif options[:without] && value.to_s =~ options[:without]
- record.errors.add(attribute, :invalid, options.except(:without).merge!(:value => value))
+ if options[:with]
+ regexp = option_call(record, :with)
+ record_error(record, attribute, :with, value) if value.to_s !~ regexp
+ elsif options[:without]
+ regexp = option_call(record, :without)
+ record_error(record, attribute, :without, value) if value.to_s =~ regexp
end
end
@@ -16,12 +18,25 @@ module ActiveModel
raise ArgumentError, "Either :with or :without must be supplied (but not both)"
end
- if options[:with] && !options[:with].is_a?(Regexp)
- raise ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash"
- end
+ check_options_validity(options, :with)
+ check_options_validity(options, :without)
+ end
+
+ private
- if options[:without] && !options[:without].is_a?(Regexp)
- raise ArgumentError, "A regular expression must be supplied as the :without option of the configuration hash"
+ def option_call(record, name)
+ option = options[name]
+ option.respond_to?(:call) ? option.call(record) : option
+ end
+
+ def record_error(record, attribute, name, value)
+ record.errors.add(attribute, :invalid, options.except(name).merge!(:value => value))
+ end
+
+ def check_options_validity(options, name)
+ option = options[name]
+ if option && !option.is_a?(Regexp) && !option.respond_to?(:call)
+ raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}"
end
end
end
@@ -40,17 +55,26 @@ module ActiveModel
# validates_format_of :email, :without => /NOSPAM/
# end
#
+ # You can also provide a proc or lambda which will determine the regular expression that will be used to validate the attribute
+ #
+ # class Person < ActiveRecord::Base
+ # # Admin can have number as a first letter in their screen name
+ # validates_format_of :screen_name, :with => lambda{ |person| person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\Z/i : /\A[a-z][a-z0-9_\-]*\Z/i }
+ # end
+ #
# Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
#
- # You must pass either <tt>:with</tt> or <tt>:without</tt> as an option. In addition, both must be a regular expression,
- # or else an exception will be raised.
+ # You must pass either <tt>:with</tt> or <tt>:without</tt> as an option. In addition, both must be a regular expression
+ # or a proc or lambda, or else an exception will be raised.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
# * <tt>:with</tt> - Regular expression that if the attribute matches will result in a successful validation.
+ # This can be provided as a proc or lambda returning regular expression which will be called at runtime.
# * <tt>:without</tt> - Regular expression that if the attribute does not match will result in a successful validation.
+ # This can be provided as a proc or lambda returning regular expression which will be called at runtime.
# * <tt>:on</tt> - Specifies when this validation is active. Runs in all
# validation contexts by default (+nil+), other options are <tt>:create</tt>
# and <tt>:update</tt>.
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 92ac940f36..d32aebeb88 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -5,20 +5,30 @@ module ActiveModel
# == Active Model Inclusion Validator
module Validations
class InclusionValidator < EachValidator
+ ERROR_MESSAGE = "An object with the method #include? or a proc or lambda is required, " <<
+ "and must be supplied as the :in option of the configuration hash"
+
def check_validity!
- raise ArgumentError, "An object with the method include? is required must be supplied as the " <<
- ":in option of the configuration hash" unless options[:in].respond_to?(:include?)
+ unless [:include?, :call].any?{ |method| options[:in].respond_to?(method) }
+ raise ArgumentError, ERROR_MESSAGE
+ end
end
def validate_each(record, attribute, value)
- record.errors.add(attribute, :inclusion, options.except(:in).merge!(:value => value)) unless options[:in].send(include?, value)
+ delimiter = options[:in]
+ exclusions = delimiter.respond_to?(:call) ? delimiter.call(record) : delimiter
+ unless exclusions.send(inclusion_method(exclusions), value)
+ record.errors.add(attribute, :inclusion, options.except(:in).merge!(:value => value))
+ end
end
+ private
+
# In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the
# range for equality, so it may be slow for large ranges. The new <tt>Range#cover?</tt>
# uses the previous logic of comparing a value with the range endpoints.
- def include?
- options[:in].is_a?(Range) ? :cover? : :include?
+ def inclusion_method(enumerable)
+ enumerable.is_a?(Range) ? :cover? : :include?
end
end
@@ -29,11 +39,13 @@ module ActiveModel
# validates_inclusion_of :gender, :in => %w( m f )
# validates_inclusion_of :age, :in => 0..99
# validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %{value} is not included in the list"
+ # validates_inclusion_of :states, :in => lambda{ |person| STATES[person.country] }
# end
#
# Configuration options:
- # * <tt>:in</tt> - An enumerable object of available items.
- # If the enumerable is a range the test is performed with <tt>Range#cover?</tt>
+ # * <tt>:in</tt> - An enumerable object of available items. This can be
+ # supplied as a proc or lambda which returns an enumerable. If the enumerable
+ # is a range the test is performed with <tt>Range#cover?</tt>
# (backported in Active Support for 1.8), otherwise with <tt>include?</tt>.
# * <tt>:message</tt> - Specifies a custom error message (default is: "is not included in the list").
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index 7af6c83460..72735cfb89 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -43,7 +43,8 @@ module ActiveModel
value ||= [] if key == :maximum
- next if value && value.size.send(validity_check, check_value)
+ value_length = value.respond_to?(:length) ? value.length : value.to_s.length
+ next if value_length.send(validity_check, check_value)
errors_options = options.except(*RESERVED_OPTIONS)
errors_options[:count] = check_value
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 1c6123eb09..5304743389 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/array/wrap'
require "active_support/core_ext/module/anonymous"
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/object/inclusion'
module ActiveModel #:nodoc:
@@ -67,7 +68,7 @@ module ActiveModel #:nodoc:
#
# class TitleValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
- # record.errors[attribute] << 'must be Mr. Mrs. or Dr.' unless ['Mr.', 'Mrs.', 'Dr.'].include?(value)
+ # record.errors[attribute] << 'must be Mr. Mrs. or Dr.' unless value.in?(['Mr.', 'Mrs.', 'Dr.'])
# end
# end
#
@@ -120,7 +121,7 @@ module ActiveModel #:nodoc:
# Override this method in subclasses with validation logic, adding errors
# to the records +errors+ array where necessary.
def validate(record)
- raise NotImplementedError
+ raise NotImplementedError, "Subclasses must implement a validate(record) method."
end
end
@@ -156,7 +157,7 @@ module ActiveModel #:nodoc:
# Override this method in subclasses with the validation logic, adding
# errors to the records +errors+ array where necessary.
def validate_each(record, attribute, value)
- raise NotImplementedError
+ raise NotImplementedError, "Subclasses must implement a validate_each(record, attribute, value) method"
end
# Hook method that gets called by the initializer allowing verification
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index b001adb35a..022c6716bd 100644
--- a/activemodel/test/cases/attribute_methods_test.rb
+++ b/activemodel/test/cases/attribute_methods_test.rb
@@ -5,6 +5,12 @@ class ModelWithAttributes
attribute_method_suffix ''
+ class << self
+ define_method(:bar) do
+ 'original bar'
+ end
+ end
+
def attributes
{ :foo => 'value of foo' }
end
@@ -36,6 +42,27 @@ private
end
end
+class ModelWithWeirdNamesAttributes
+ include ActiveModel::AttributeMethods
+
+ attribute_method_suffix ''
+
+ class << self
+ define_method(:'c?d') do
+ 'original c?d'
+ end
+ end
+
+ def attributes
+ { :'a?b' => 'value of a?b' }
+ end
+
+private
+ def attribute(name)
+ attributes[name.to_sym]
+ end
+end
+
class AttributeMethodsTest < ActiveModel::TestCase
test 'unrelated classes should not share attribute method matchers' do
assert_not_equal ModelWithAttributes.send(:attribute_method_matchers),
@@ -49,6 +76,14 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_equal "value of foo", ModelWithAttributes.new.foo
end
+ test '#define_attribute_method generates attribute method with invalid identifier characters' do
+ ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b')
+ ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b')
+
+ assert_respond_to ModelWithWeirdNamesAttributes.new, :'a?b'
+ assert_equal "value of a?b", ModelWithWeirdNamesAttributes.new.send('a?b')
+ end
+
test '#define_attribute_methods generates attribute methods' do
ModelWithAttributes.define_attribute_methods([:foo])
@@ -58,15 +93,33 @@ class AttributeMethodsTest < ActiveModel::TestCase
test '#define_attribute_methods generates attribute methods with spaces in their names' do
ModelWithAttributesWithSpaces.define_attribute_methods([:'foo bar'])
-
+
assert_respond_to ModelWithAttributesWithSpaces.new, :'foo bar'
assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.send(:'foo bar')
end
-
+
+ test '#define_attr_method generates attribute method' do
+ ModelWithAttributes.define_attr_method(:bar, 'bar')
+
+ assert_respond_to ModelWithAttributes, :bar
+ assert_equal "original bar", ModelWithAttributes.original_bar
+ assert_equal "bar", ModelWithAttributes.bar
+ ModelWithAttributes.define_attr_method(:bar)
+ assert !ModelWithAttributes.bar
+ end
+
+ test '#define_attr_method generates attribute method with invalid identifier characters' do
+ ModelWithWeirdNamesAttributes.define_attr_method(:'c?d', 'c?d')
+
+ assert_respond_to ModelWithWeirdNamesAttributes, :'c?d'
+ assert_equal "original c?d", ModelWithWeirdNamesAttributes.send('original_c?d')
+ assert_equal "c?d", ModelWithWeirdNamesAttributes.send('c?d')
+ end
+
test '#alias_attribute works with attributes with spaces in their names' do
ModelWithAttributesWithSpaces.define_attribute_methods([:'foo bar'])
ModelWithAttributesWithSpaces.alias_attribute(:'foo_bar', :'foo bar')
-
+
assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.foo_bar
end
diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
index 015153ec7c..9a73a5ad91 100644
--- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
+++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require 'logger'
+require 'active_support/core_ext/object/inclusion'
class SanitizerTest < ActiveModel::TestCase
@@ -9,7 +10,7 @@ class SanitizerTest < ActiveModel::TestCase
attr_accessor :logger
def deny?(key)
- [ 'admin' ].include?(key)
+ key.in?(['admin'])
end
end
diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb
index f84e55e8d9..b22ce874ea 100644
--- a/activemodel/test/cases/mass_assignment_security_test.rb
+++ b/activemodel/test/cases/mass_assignment_security_test.rb
@@ -10,10 +10,27 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase
assert_equal expected, sanitized
end
+ def test_only_moderator_scope_attribute_accessible
+ user = SpecialUser.new
+ expected = { "name" => "John Smith", "email" => "john@smith.com" }
+ sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), :moderator)
+ assert_equal expected, sanitized
+
+ sanitized = user.sanitize_for_mass_assignment({ "name" => "John Smith", "email" => "john@smith.com", "admin" => true })
+ assert_equal({}, sanitized)
+ end
+
def test_attributes_accessible
user = Person.new
expected = { "name" => "John Smith", "email" => "john@smith.com" }
- sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true))
+ sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true))
+ assert_equal expected, sanitized
+ end
+
+ def test_admin_scoped_attributes_accessible
+ user = Person.new
+ expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true }
+ sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin)
assert_equal expected, sanitized
end
@@ -26,20 +43,30 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase
def test_mass_assignment_protection_inheritance
assert_blank LoosePerson.accessible_attributes
- assert_equal Set.new([ 'credit_rating', 'administrator']), LoosePerson.protected_attributes
+ assert_equal Set.new(['credit_rating', 'administrator']), LoosePerson.protected_attributes
+
+ assert_blank LoosePerson.accessible_attributes
+ assert_equal Set.new(['credit_rating']), LoosePerson.protected_attributes(:admin)
assert_blank LooseDescendant.accessible_attributes
- assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number']), LooseDescendant.protected_attributes
+ assert_equal Set.new(['credit_rating', 'administrator', 'phone_number']), LooseDescendant.protected_attributes
assert_blank LooseDescendantSecond.accessible_attributes
- assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number', 'name']), LooseDescendantSecond.protected_attributes,
+ assert_equal Set.new(['credit_rating', 'administrator', 'phone_number', 'name']), LooseDescendantSecond.protected_attributes,
'Running attr_protected twice in one class should merge the protections'
assert_blank TightPerson.protected_attributes - TightPerson.attributes_protected_by_default
- assert_equal Set.new([ 'name', 'address' ]), TightPerson.accessible_attributes
+ assert_equal Set.new(['name', 'address']), TightPerson.accessible_attributes
+
+ assert_blank TightPerson.protected_attributes(:admin) - TightPerson.attributes_protected_by_default
+ assert_equal Set.new(['name', 'address', 'admin']), TightPerson.accessible_attributes(:admin)
assert_blank TightDescendant.protected_attributes - TightDescendant.attributes_protected_by_default
- assert_equal Set.new([ 'name', 'address', 'phone_number' ]), TightDescendant.accessible_attributes
+ assert_equal Set.new(['name', 'address', 'phone_number']), TightDescendant.accessible_attributes
+
+ assert_blank TightDescendant.protected_attributes(:admin) - TightDescendant.attributes_protected_by_default
+ assert_equal Set.new(['name', 'address', 'admin', 'super_powers']), TightDescendant.accessible_attributes(:admin)
+
end
def test_mass_assignment_multiparameter_protector
diff --git a/activemodel/test/cases/observer_array_test.rb b/activemodel/test/cases/observer_array_test.rb
new file mode 100644
index 0000000000..215ca80bb4
--- /dev/null
+++ b/activemodel/test/cases/observer_array_test.rb
@@ -0,0 +1,122 @@
+require 'cases/helper'
+require 'models/observers'
+
+class ObserverArrayTest < ActiveModel::TestCase
+ def teardown
+ ORM.observers.enable :all
+ Budget.observers.enable :all
+ Widget.observers.enable :all
+ end
+
+ def assert_observer_notified(model_class, observer_class)
+ observer_class.instance.before_save_invocations.clear
+ model_instance = model_class.new
+ model_instance.save
+ assert_equal [model_instance], observer_class.instance.before_save_invocations
+ end
+
+ def assert_observer_not_notified(model_class, observer_class)
+ observer_class.instance.before_save_invocations.clear
+ model_instance = model_class.new
+ model_instance.save
+ assert_equal [], observer_class.instance.before_save_invocations
+ end
+
+ test "all observers are enabled by default" do
+ assert_observer_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "can disable individual observers using a class constant" do
+ ORM.observers.disable WidgetObserver
+
+ assert_observer_not_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "can disable individual observers using a symbol" do
+ ORM.observers.disable :budget_observer
+
+ assert_observer_notified Widget, WidgetObserver
+ assert_observer_not_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "can disable all observers using :all" do
+ ORM.observers.disable :all
+
+ assert_observer_not_notified Widget, WidgetObserver
+ assert_observer_not_notified Budget, BudgetObserver
+ assert_observer_not_notified Widget, AuditTrail
+ assert_observer_not_notified Budget, AuditTrail
+ end
+
+ test "can disable observers on individual models without affecting observers on other models" do
+ Widget.observers.disable :all
+
+ assert_observer_not_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_not_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "can disable observers for the duration of a block" do
+ yielded = false
+ ORM.observers.disable :budget_observer do
+ yielded = true
+ assert_observer_notified Widget, WidgetObserver
+ assert_observer_not_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ assert yielded
+ assert_observer_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "can enable observers for the duration of a block" do
+ yielded = false
+ Widget.observers.disable :all
+
+ Widget.observers.enable :all do
+ yielded = true
+ assert_observer_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ assert yielded
+ assert_observer_not_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_not_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "raises an appropriate error when a developer accidentally enables or disables the wrong class (i.e. Widget instead of WidgetObserver)" do
+ assert_raise ArgumentError do
+ ORM.observers.enable :widget
+ end
+
+ assert_raise ArgumentError do
+ ORM.observers.enable Widget
+ end
+
+ assert_raise ArgumentError do
+ ORM.observers.disable :widget
+ end
+
+ assert_raise ArgumentError do
+ ORM.observers.disable Widget
+ end
+ end
+end
+
diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb
index 63686843b6..99b1f407ae 100644
--- a/activemodel/test/cases/observing_test.rb
+++ b/activemodel/test/cases/observing_test.rb
@@ -43,6 +43,11 @@ class ObservingTest < ActiveModel::TestCase
assert ObservedModel.observers.include?(:bar), ":bar not in #{ObservedModel.observers.inspect}"
end
+ test "uses an ObserverArray so observers can be disabled" do
+ ObservedModel.observers = [:foo, :bar]
+ assert ObservedModel.observers.is_a?(ActiveModel::ObserverArray)
+ end
+
test "instantiates observer names passed as strings" do
ObservedModel.observers << 'foo_observer'
FooObserver.expects(:instance)
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 4a47a7a226..6950c3be1f 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -9,6 +9,18 @@ class SecurePasswordTest < ActiveModel::TestCase
@user = User.new
end
+ test "blank password" do
+ user = User.new
+ user.password = ''
+ assert !user.valid?, 'user should be invalid'
+ end
+
+ test "nil password" do
+ user = User.new
+ user.password = nil
+ assert !user.valid?, 'user should be invalid'
+ end
+
test "password must be present" do
assert !@user.valid?
assert_equal 1, @user.errors.size
@@ -33,13 +45,14 @@ class SecurePasswordTest < ActiveModel::TestCase
end
test "visitor#password_digest should be protected against mass assignment" do
- assert Visitor.active_authorizer.kind_of?(ActiveModel::MassAssignmentSecurity::BlackList)
- assert Visitor.active_authorizer.include?(:password_digest)
+ assert Visitor.active_authorizers[:default].kind_of?(ActiveModel::MassAssignmentSecurity::BlackList)
+ assert Visitor.active_authorizers[:default].include?(:password_digest)
end
test "Administrator's mass_assignment_authorizer should be WhiteList" do
- assert Administrator.active_authorizer.kind_of?(ActiveModel::MassAssignmentSecurity::WhiteList)
- assert !Administrator.active_authorizer.include?(:password_digest)
- assert Administrator.active_authorizer.include?(:name)
+ active_authorizer = Administrator.active_authorizers[:default]
+ assert active_authorizer.kind_of?(ActiveModel::MassAssignmentSecurity::WhiteList)
+ assert !active_authorizer.include?(:password_digest)
+ assert active_authorizer.include?(:name)
end
end
diff --git a/activemodel/test/cases/serializeration/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index 500a5c575f..500a5c575f 100644
--- a/activemodel/test/cases/serializeration/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
diff --git a/activemodel/test/cases/serializeration/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index b6a2f88667..b6a2f88667 100644
--- a/activemodel/test/cases/serializeration/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb
index be9d98d644..72a383f128 100644
--- a/activemodel/test/cases/validations/exclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/exclusion_validation_test.rb
@@ -42,4 +42,16 @@ class ExclusionValidationTest < ActiveModel::TestCase
ensure
Person.reset_callbacks(:validate)
end
+
+ def test_validates_exclusion_of_with_lambda
+ Topic.validates_exclusion_of :title, :in => lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) }
+
+ p = Topic.new
+ p.title = "elephant"
+ p.author_name = "sikachu"
+ assert p.invalid?
+
+ p.title = "wasabi"
+ assert p.valid?
+ end
end
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index 6c4fb36d52..73647efea5 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -98,6 +98,30 @@ class PresenceValidationTest < ActiveModel::TestCase
assert_raise(ArgumentError) { Topic.validates_format_of(:title, :without => "clearly not a regexp") }
end
+ def test_validates_format_of_with_lambda
+ Topic.validates_format_of :content, :with => lambda{ |topic| topic.title == "digit" ? /\A\d+\Z/ : /\A\S+\Z/ }
+
+ p = Topic.new
+ p.title = "digit"
+ p.content = "Pixies"
+ assert p.invalid?
+
+ p.content = "1234"
+ assert p.valid?
+ end
+
+ def test_validates_format_of_without_lambda
+ Topic.validates_format_of :content, :without => lambda{ |topic| topic.title == "characters" ? /\A\d+\Z/ : /\A\S+\Z/ }
+
+ p = Topic.new
+ p.title = "characters"
+ p.content = "1234"
+ assert p.invalid?
+
+ p.content = "Pixies"
+ assert p.valid?
+ end
+
def test_validates_format_of_for_ruby_class
Person.validates_format_of :karma, :with => /\A\d+\Z/
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
index 62f2ec785d..92c473de0e 100644
--- a/activemodel/test/cases/validations/inclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -74,4 +74,16 @@ class InclusionValidationTest < ActiveModel::TestCase
ensure
Person.reset_callbacks(:validate)
end
+
+ def test_validates_inclusion_of_with_lambda
+ Topic.validates_inclusion_of :title, :in => lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) }
+
+ p = Topic.new
+ p.title = "wasabi"
+ p.author_name = "sikachu"
+ assert p.invalid?
+
+ p.title = "elephant"
+ assert p.valid?
+ end
end
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index 1e6180a938..f02514639b 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -342,6 +342,17 @@ class LengthValidationTest < ActiveModel::TestCase
assert_equal ["Your essay must be at least 5 words."], t.errors[:content]
end
+ def test_validates_length_of_for_fixnum
+ Topic.validates_length_of(:approved, :is => 4)
+
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever", :approved => 1)
+ assert t.invalid?
+ assert t.errors[:approved].any?
+
+ t = Topic.new("title" => "uhohuhoh", "content" => "whatever", :approved => 1234)
+ assert t.valid?
+ end
+
def test_validates_length_of_for_ruby_class
Person.validates_length_of :karma, :minimum => 5
diff --git a/activemodel/test/models/mass_assignment_specific.rb b/activemodel/test/models/mass_assignment_specific.rb
index 2a8fe170c2..53b37369ff 100644
--- a/activemodel/test/models/mass_assignment_specific.rb
+++ b/activemodel/test/models/mass_assignment_specific.rb
@@ -5,9 +5,17 @@ class User
public :sanitize_for_mass_assignment
end
+class SpecialUser
+ include ActiveModel::MassAssignmentSecurity
+ attr_accessible :name, :email, :as => :moderator
+
+ public :sanitize_for_mass_assignment
+end
+
class Person
include ActiveModel::MassAssignmentSecurity
attr_accessible :name, :email
+ attr_accessible :name, :email, :admin, :as => :admin
public :sanitize_for_mass_assignment
end
@@ -32,6 +40,7 @@ end
class LoosePerson
include ActiveModel::MassAssignmentSecurity
attr_protected :credit_rating, :administrator
+ attr_protected :credit_rating, :as => :admin
end
class LooseDescendant < LoosePerson
@@ -46,6 +55,7 @@ end
class TightPerson
include ActiveModel::MassAssignmentSecurity
attr_accessible :name, :address
+ attr_accessible :name, :address, :admin, :as => :admin
def self.attributes_protected_by_default
["mobile_number"]
@@ -54,4 +64,5 @@ end
class TightDescendant < TightPerson
attr_accessible :phone_number
+ attr_accessible :super_powers, :as => :admin
end \ No newline at end of file
diff --git a/activemodel/test/models/observers.rb b/activemodel/test/models/observers.rb
new file mode 100644
index 0000000000..3729b3435e
--- /dev/null
+++ b/activemodel/test/models/observers.rb
@@ -0,0 +1,27 @@
+class ORM
+ include ActiveModel::Observing
+
+ def save
+ notify_observers :before_save
+ end
+
+ class Observer < ActiveModel::Observer
+ def before_save_invocations
+ @before_save_invocations ||= []
+ end
+
+ def before_save(record)
+ before_save_invocations << record
+ end
+ end
+end
+
+class Widget < ORM; end
+class Budget < ORM; end
+class WidgetObserver < ORM::Observer; end
+class BudgetObserver < ORM::Observer; end
+class AuditTrail < ORM::Observer
+ observe :widget, :budget
+end
+
+ORM.instantiate_observers
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 8b4e9a34cb..9ff29f1155 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,77 @@
*Rails 3.1.0 (unreleased)*
+* default_scope can take a block, lambda, or any other object which responds to `call` for lazy
+ evaluation:
+
+ default_scope { ... }
+ default_scope lambda { ... }
+ default_scope method(:foo)
+
+ This feature was originally implemented by Tim Morgan, but was then removed in favour of
+ defining a 'default_scope' class method, but has now been added back in by Jon Leighton.
+ The relevant lighthouse ticket is #1812.
+
+* Default scopes are now evaluated at the latest possible moment, to avoid problems where
+ scopes would be created which would implicitly contain the default scope, which would then
+ be impossible to get rid of via Model.unscoped.
+
+ Note that this means that if you are inspecting the internal structure of an
+ ActiveRecord::Relation, it will *not* contain the default scope, though the resulting
+ query will do. You can get a relation containing the default scope by calling
+ ActiveRecord#with_default_scope, though this is not part of the public API.
+
+ [Jon Leighton]
+
+* Calling 'default_scope' multiple times in a class (including when a superclass calls
+ 'default_scope') is deprecated. The current behavior is that this will merge the default
+ scopes together:
+
+ class Post < ActiveRecord::Base # Rails 3.1
+ default_scope where(:published => true)
+ default_scope where(:hidden => false)
+ # The default scope is now: where(:published => true, :hidden => false)
+ end
+
+ In Rails 3.2, the behavior will be changed to overwrite previous scopes:
+
+ class Post < ActiveRecord::Base # Rails 3.2
+ default_scope where(:published => true)
+ default_scope where(:hidden => false)
+ # The default scope is now: where(:hidden => false)
+ end
+
+ If you wish to merge default scopes in special ways, it is recommended to define your default
+ scope as a class method and use the standard techniques for sharing code (inheritance, mixins,
+ etc.):
+
+ class Post < ActiveRecord::Base
+ def self.default_scope
+ where(:published => true).where(:hidden => false)
+ end
+ end
+
+ [Jon Leighton]
+
+* PostgreSQL adapter only supports PostgreSQL version 8.2 and higher.
+
+* ConnectionManagement middleware is changed to clean up the connection pool
+ after the rack body has been flushed.
+
+* Added an update_column method on ActiveRecord. This new method updates a given attribute on an object, skipping validations and callbacks.
+ It is recommended to use #update_attribute unless you are sure you do not want to execute any callback, including the modification of
+ the updated_at column. It should not be called on new records.
+ Example:
+
+ User.first.update_column(:name, "sebastian") # => true
+
+ [Sebastian Martinez]
+
+* Associations with a :through option can now use *any* association as the
+ through or source association, including other associations which have a
+ :through option and has_and_belongs_to_many associations
+
+ [Jon Leighton]
+
* The configuration for the current database connection is now accessible via
ActiveRecord::Base.connection_config. [fxn]
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index b1df24844a..c3cd76a714 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -17,7 +17,6 @@ Gem::Specification.new do |s|
s.files = Dir['CHANGELOG', 'README.rdoc', 'examples/**/*', 'lib/**/*']
s.require_path = 'lib'
- s.has_rdoc = true
s.extra_rdoc_files = %w( README.rdoc )
s.rdoc_options.concat ['--main', 'README.rdoc']
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index e91cbd7f33..08fb6bf3c4 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -52,14 +52,6 @@ module ActiveRecord
end
end
- class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc:
- def initialize(reflection)
- through_reflection = reflection.through_reflection
- source_reflection = reflection.source_reflection
- super("Invalid source reflection macro :#{source_reflection.macro}#{" :through" if source_reflection.options[:through]} for has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}. Use :source to specify the source reflection.")
- end
- end
-
class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
@@ -78,6 +70,12 @@ module ActiveRecord
end
end
+ class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc
+ def initialize(owner, reflection)
+ super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
+ end
+ end
+
class HasAndBelongsToManyAssociationWithPrimaryKeyError < ActiveRecordError #:nodoc:
def initialize(reflection)
super("Primary key is not allowed in a has_and_belongs_to_many join table (#{reflection.options[:join_table]}).")
@@ -142,8 +140,11 @@ module ActiveRecord
autoload :HasAndBelongsToMany, 'active_record/associations/builder/has_and_belongs_to_many'
end
- autoload :Preloader, 'active_record/associations/preloader'
- autoload :JoinDependency, 'active_record/associations/join_dependency'
+ autoload :Preloader, 'active_record/associations/preloader'
+ autoload :JoinDependency, 'active_record/associations/join_dependency'
+ autoload :AssociationScope, 'active_record/associations/association_scope'
+ autoload :AliasTracker, 'active_record/associations/alias_tracker'
+ autoload :JoinHelper, 'active_record/associations/join_helper'
# Clears out the association cache.
def clear_association_cache #:nodoc:
@@ -548,6 +549,49 @@ module ActiveRecord
# belongs_to :tag, :inverse_of => :taggings
# end
#
+ # === Nested Associations
+ #
+ # You can actually specify *any* association with the <tt>:through</tt> option, including an
+ # association which has a <tt>:through</tt> option itself. For example:
+ #
+ # class Author < ActiveRecord::Base
+ # has_many :posts
+ # has_many :comments, :through => :posts
+ # has_many :commenters, :through => :comments
+ # end
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :comments
+ # end
+ #
+ # class Comment < ActiveRecord::Base
+ # belongs_to :commenter
+ # end
+ #
+ # @author = Author.first
+ # @author.commenters # => People who commented on posts written by the author
+ #
+ # An equivalent way of setting up this association this would be:
+ #
+ # class Author < ActiveRecord::Base
+ # has_many :posts
+ # has_many :commenters, :through => :posts
+ # end
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :comments
+ # has_many :commenters, :through => :comments
+ # end
+ #
+ # class Comment < ActiveRecord::Base
+ # belongs_to :commenter
+ # end
+ #
+ # When using nested association, you will not be able to modify the association because there
+ # is not enough information to know what modification to make. For example, if you tried to
+ # add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the
+ # intermediate <tt>Post</tt> and <tt>Comment</tt> objects.
+ #
# === Polymorphic Associations
#
# Polymorphic associations on models are not restricted on what types of models they
@@ -1068,10 +1112,10 @@ module ActiveRecord
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# [:through]
- # Specifies a join model through which to perform the query. Options for <tt>:class_name</tt>,
+ # Specifies an association through which to perform the query. This can be any other type
+ # of association, including other <tt>:through</tt> associations. Options for <tt>:class_name</tt>,
# <tt>:primary_key</tt> and <tt>:foreign_key</tt> are ignored, as the association uses the
- # source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>,
- # <tt>has_one</tt> or <tt>has_many</tt> association on the join model.
+ # source reflection.
#
# If the association on the join model is a +belongs_to+, the collection can be modified
# and the records on the <tt>:through</tt> model will be automatically created and removed
@@ -1198,10 +1242,10 @@ module ActiveRecord
# you want to do a join but not include the joined columns. Do not forget to include the
# primary and foreign keys, otherwise it will raise an error.
# [:through]
- # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>
- # and <tt>:foreign_key</tt> are ignored, as the association uses the source reflection. You
- # can only use a <tt>:through</tt> query through a <tt>has_one</tt> or <tt>belongs_to</tt>
- # association on the join model.
+ # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>,
+ # <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the
+ # source reflection. You can only use a <tt>:through</tt> query through a <tt>has_one</tt>
+ # or <tt>belongs_to</tt> association on the join model.
# [:source]
# Specifies the source association name used by <tt>has_one :through</tt> queries.
# Only use it if the name cannot be inferred from the association.
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
new file mode 100644
index 0000000000..634dee2289
--- /dev/null
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -0,0 +1,85 @@
+require 'active_support/core_ext/string/conversions'
+
+module ActiveRecord
+ module Associations
+ # Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
+ # ActiveRecord::Associations::ThroughAssociationScope
+ class AliasTracker # :nodoc:
+ attr_reader :aliases, :table_joins
+
+ # table_joins is an array of arel joins which might conflict with the aliases we assign here
+ def initialize(table_joins = [])
+ @aliases = Hash.new
+ @table_joins = table_joins
+ end
+
+ def aliased_table_for(table_name, aliased_name = nil)
+ table_alias = aliased_name_for(table_name, aliased_name)
+
+ if table_alias == table_name
+ Arel::Table.new(table_name)
+ else
+ Arel::Table.new(table_name).alias(table_alias)
+ end
+ end
+
+ def aliased_name_for(table_name, aliased_name = nil)
+ aliased_name ||= table_name
+
+ initialize_count_for(table_name) if aliases[table_name].nil?
+
+ if aliases[table_name].zero?
+ # If it's zero, we can have our table_name
+ aliases[table_name] = 1
+ table_name
+ else
+ # Otherwise, we need to use an alias
+ aliased_name = connection.table_alias_for(aliased_name)
+
+ initialize_count_for(aliased_name) if aliases[aliased_name].nil?
+
+ # Update the count
+ aliases[aliased_name] += 1
+
+ if aliases[aliased_name] > 1
+ "#{truncate(aliased_name)}_#{aliases[aliased_name]}"
+ else
+ aliased_name
+ end
+ end
+ end
+
+ def pluralize(table_name)
+ ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
+ end
+
+ private
+
+ def initialize_count_for(name)
+ aliases[name] = 0
+
+ unless Arel::Table === table_joins
+ # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
+ quoted_name = connection.quote_table_name(name).downcase
+
+ aliases[name] += table_joins.map { |join|
+ # Table names + table aliases
+ join.left.downcase.scan(
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
+ ).size
+ }.sum
+ end
+
+ aliases[name]
+ end
+
+ def truncate(name)
+ name[0..connection.table_alias_length-3]
+ end
+
+ def connection
+ ActiveRecord::Base.connection
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 25b4b9d90d..687b668634 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/array/wrap'
+require 'active_support/core_ext/object/inclusion'
module ActiveRecord
module Associations
@@ -93,23 +94,9 @@ module ActiveRecord
# by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
# actually gets built.
def construct_scope
- @association_scope = association_scope if klass
- end
-
- def association_scope
- scope = klass.unscoped
- scope = scope.create_with(creation_attributes)
- scope = scope.apply_finder_options(options.slice(:readonly, :include))
- scope = scope.where(interpolate(options[:conditions]))
- if select = select_value
- scope = scope.select(select)
+ if klass
+ @association_scope = AssociationScope.new(self).scope
end
- scope = scope.extending(*Array.wrap(options[:extend]))
- scope.where(construct_owner_conditions)
- end
-
- def aliased_table
- klass.arel_table
end
# Set the inverse association, if possible
@@ -174,42 +161,24 @@ module ActiveRecord
end
end
- def select_value
- options[:select]
- end
-
- # Implemented by (some) subclasses
def creation_attributes
- { }
- end
-
- # Returns a hash linking the owner to the association represented by the reflection
- def construct_owner_attributes(reflection = reflection)
attributes = {}
- if reflection.macro == :belongs_to
- attributes[reflection.association_primary_key] = owner[reflection.foreign_key]
- else
+
+ if reflection.macro.in?([:has_one, :has_many]) && !options[:through]
attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
if reflection.options[:as]
attributes[reflection.type] = owner.class.base_class.name
end
end
- attributes
- end
- # Builds an array of arel nodes from the owner attributes hash
- def construct_owner_conditions(table = aliased_table, reflection = reflection)
- conditions = construct_owner_attributes(reflection).map do |attr, value|
- table[attr].eq(value)
- end
- table.create_and(conditions)
+ attributes
end
# Sets the owner attributes on the given record
def set_owner_attributes(record)
if owner.persisted?
- construct_owner_attributes.each { |key, value| record[key] = value }
+ creation_attributes.each { |key, value| record[key] = value }
end
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
new file mode 100644
index 0000000000..ab102b2b8f
--- /dev/null
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -0,0 +1,120 @@
+module ActiveRecord
+ module Associations
+ class AssociationScope #:nodoc:
+ include JoinHelper
+
+ attr_reader :association, :alias_tracker
+
+ delegate :klass, :owner, :reflection, :interpolate, :to => :association
+ delegate :chain, :conditions, :options, :source_options, :active_record, :to => :reflection
+
+ def initialize(association)
+ @association = association
+ @alias_tracker = AliasTracker.new
+ end
+
+ def scope
+ scope = klass.unscoped
+ scope = scope.extending(*Array.wrap(options[:extend]))
+
+ # 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, :order, :limit, :joins, :group, :having, :offset))
+
+ if options[:through] && !options[:include]
+ scope = scope.includes(source_options[:include])
+ end
+
+ if select = select_value
+ scope = scope.select(select)
+ end
+
+ add_constraints(scope)
+ end
+
+ private
+
+ def select_value
+ select_value = options[:select]
+
+ if reflection.collection?
+ select_value ||= options[:uniq] && "DISTINCT #{reflection.quoted_table_name}.*"
+ end
+
+ if reflection.macro == :has_and_belongs_to_many
+ select_value ||= reflection.klass.arel_table[Arel.star]
+ end
+
+ select_value
+ end
+
+ def add_constraints(scope)
+ tables = construct_tables
+
+ chain.each_with_index do |reflection, i|
+ table, foreign_table = tables.shift, tables.first
+
+ if reflection.source_macro == :has_and_belongs_to_many
+ join_table = tables.shift
+
+ scope = scope.joins(join(
+ join_table,
+ table[reflection.active_record_primary_key].
+ eq(join_table[reflection.association_foreign_key])
+ ))
+
+ table, foreign_table = join_table, tables.first
+ end
+
+ if reflection.source_macro == :belongs_to
+ key = reflection.association_primary_key
+ foreign_key = reflection.foreign_key
+ else
+ key = reflection.foreign_key
+ foreign_key = reflection.active_record_primary_key
+ end
+
+ if reflection == chain.last
+ scope = scope.where(table[key].eq(owner[foreign_key]))
+
+ conditions[i].each do |condition|
+ if options[:through] && condition.is_a?(Hash)
+ condition = { table.name => condition }
+ end
+
+ scope = scope.where(interpolate(condition))
+ end
+ else
+ constraint = table[key].eq(foreign_table[foreign_key])
+ join = join(foreign_table, constraint)
+
+ scope = scope.joins(join)
+
+ unless conditions[i].empty?
+ scope = scope.where(sanitize(conditions[i], table))
+ end
+ end
+ end
+
+ scope
+ end
+
+ def alias_suffix
+ reflection.name
+ end
+
+ def table_name_for(reflection)
+ if reflection == self.reflection
+ # If this is a polymorphic belongs_to, we want to get the klass from the
+ # association because it depends on the polymorphic_type attribute of
+ # the owner
+ klass.table_name
+ else
+ reflection.table_name
+ end
+ end
+
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 964e7fddc8..f6d26840c2 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/inclusion'
+
module ActiveRecord::Associations::Builder
class BelongsTo < SingularAssociation #:nodoc:
self.macro = :belongs_to
@@ -65,7 +67,7 @@ module ActiveRecord::Associations::Builder
def configure_dependency
if options[:dependent]
- unless [:destroy, :delete].include?(options[:dependent])
+ unless options[:dependent].in?([:destroy, :delete])
raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{options[:dependent].inspect})"
end
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index e40b32826a..4b48757da7 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -7,24 +7,24 @@ module ActiveRecord::Associations::Builder
def build
reflection = super
check_validity(reflection)
- redefine_destroy
+ define_after_destroy_method
reflection
end
private
- def redefine_destroy
- # Don't use a before_destroy callback since users' before_destroy
- # callbacks will be executed after the association is wiped out.
+ def define_after_destroy_method
name = self.name
- model.send(:include, Module.new {
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def destroy # def destroy
- super # super
- #{name}.clear # posts.clear
- end # end
- RUBY
- })
+ model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1)
+ def #{after_destroy_method_name}
+ association(#{name.to_sym.inspect}).delete_all
+ end
+ eoruby
+ model.after_destroy after_destroy_method_name
+ end
+
+ def after_destroy_method_name
+ "has_and_belongs_to_many_after_destroy_for_#{name}"
end
# TODO: These checks should probably be moved into the Reflection, and we should not be
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index 77bb66228d..ecbc70888f 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/inclusion'
+
module ActiveRecord::Associations::Builder
class HasMany < CollectionAssociation #:nodoc:
self.macro = :has_many
@@ -14,7 +16,7 @@ module ActiveRecord::Associations::Builder
def configure_dependency
if options[:dependent]
- unless [:destroy, :delete_all, :nullify, :restrict].include?(options[:dependent])
+ unless options[:dependent].in?([:destroy, :delete_all, :nullify, :restrict])
raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, " \
":nullify or :restrict (#{options[:dependent].inspect})"
end
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index 07ba5d088e..88c0d3e90f 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/inclusion'
+
module ActiveRecord::Associations::Builder
class HasOne < SingularAssociation #:nodoc:
self.macro = :has_one
@@ -27,7 +29,7 @@ module ActiveRecord::Associations::Builder
def configure_dependency
if options[:dependent]
- unless [:destroy, :delete, :nullify, :restrict].include?(options[:dependent])
+ unless options[:dependent].in?([:destroy, :delete, :nullify, :restrict])
raise ArgumentError, "The :dependent option expects either :destroy, :delete, " \
":nullify or :restrict (#{options[:dependent].inspect})"
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index f3761bd2c7..33a184d48d 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -21,14 +21,7 @@ module ActiveRecord
attr_reader :proxy
def initialize(owner, reflection)
- # When scopes are created via method_missing on the proxy, they are stored so that
- # any records fetched from the database are kept around for future use.
- @scopes_cache = Hash.new do |hash, method|
- hash[method] = { }
- end
-
super
-
@proxy = CollectionProxy.new(self)
end
@@ -74,7 +67,6 @@ module ActiveRecord
def reset
@loaded = false
@target = []
- @scopes_cache.clear
end
def select(select = nil)
@@ -327,15 +319,6 @@ module ActiveRecord
end
end
- def cached_scope(method, args)
- @scopes_cache[method][args] ||= scoped.readonly(nil).send(method, *args)
- end
-
- def association_scope
- options = reflection.options.slice(:order, :limit, :joins, :group, :having, :offset)
- super.apply_finder_options(options)
- end
-
def load_target
if find_target?
targets = []
@@ -373,14 +356,6 @@ module ActiveRecord
private
- def select_value
- super || uniq_select_value
- end
-
- def uniq_select_value
- options[:uniq] && "DISTINCT #{reflection.quoted_table_name}.*"
- end
-
def custom_counter_sql
if options[:counter_sql]
interpolate(options[:counter_sql])
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index cf77d770c9..388173c1fb 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -82,8 +82,6 @@ module ActiveRecord
end
end
- elsif @association.klass.scopes[method]
- @association.cached_scope(method, args)
else
scoped.readonly(nil).send(method, *args, &block)
end
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index 028630977d..217213808b 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -26,10 +26,6 @@ module ActiveRecord
record
end
- def association_scope
- super.joins(construct_joins)
- end
-
private
def count_records
@@ -48,24 +44,6 @@ module ActiveRecord
end
end
- def construct_joins
- right = join_table
- left = reflection.klass.arel_table
-
- condition = left[reflection.klass.primary_key].eq(
- right[reflection.association_foreign_key])
-
- right.create_join(right, right.create_on(condition))
- end
-
- def construct_owner_conditions
- super(join_table)
- end
-
- def select_value
- super || reflection.klass.arel_table[Arel.star]
- end
-
def invertible_for?(record)
false
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index cebf3e477a..78c5c4b870 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -94,8 +94,6 @@ module ActiveRecord
end
end
end
-
- alias creation_attributes construct_owner_attributes
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index acac68fda5..9d2b29685b 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -34,7 +34,9 @@ module ActiveRecord
end
def insert_record(record, validate = true)
+ ensure_not_nested
return if record.new_record? && !record.save(:validate => validate)
+
through_record(record).save!
update_counter(1)
record
@@ -59,6 +61,8 @@ module ActiveRecord
end
def build_record(attributes)
+ ensure_not_nested
+
record = super(attributes)
inverse = source_reflection.inverse_of
@@ -93,6 +97,8 @@ module ActiveRecord
end
def delete_records(records, method)
+ ensure_not_nested
+
through = owner.association(through_reflection.name)
scope = through.scoped.where(construct_join_attributes(*records))
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index e13f97125f..7134dc85c8 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/inclusion'
+
module ActiveRecord
# = Active Record Belongs To Has One Association
module Associations
@@ -39,14 +41,8 @@ module ActiveRecord
end
end
- def association_scope
- super.order(options[:order])
- end
-
private
- alias creation_attributes construct_owner_attributes
-
# The reason that the save param for replace is false, if for create (not just build),
# is because the setting of the foreign keys is actually handled by the scoping when
# the record is instantiated, and so they are set straight away and do not need to be
@@ -56,7 +52,7 @@ module ActiveRecord
end
def remove_target!(method)
- if [:delete, :destroy].include?(method)
+ if method.in?([:delete, :destroy])
target.send(method)
else
nullify_owner_attributes(target)
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index d76d729303..fdf8ae1453 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -12,6 +12,8 @@ module ActiveRecord
private
def create_through_record(record)
+ ensure_not_nested
+
through_proxy = owner.association(through_reflection.name)
through_record = through_proxy.send(:load_target)
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index c7c3cf521c..504f25271c 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -5,18 +5,16 @@ module ActiveRecord
autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
- attr_reader :join_parts, :reflections, :table_aliases, :active_record
+ attr_reader :join_parts, :reflections, :alias_tracker, :active_record
def initialize(base, associations, joins)
- @active_record = base
- @table_joins = joins
- @join_parts = [JoinBase.new(base)]
- @associations = {}
- @reflections = []
- @table_aliases = Hash.new do |h,name|
- h[name] = count_aliases_from_table_joins(name.downcase)
- end
- @table_aliases[base.table_name] = 1
+ @active_record = base
+ @table_joins = joins
+ @join_parts = [JoinBase.new(base)]
+ @associations = {}
+ @reflections = []
+ @alias_tracker = AliasTracker.new(joins)
+ @alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
build(associations)
end
@@ -45,20 +43,6 @@ module ActiveRecord
}.flatten
end
- def count_aliases_from_table_joins(name)
- return 0 if Arel::Table === @table_joins
-
- # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
- quoted_name = active_record.connection.quote_table_name(name).downcase
-
- @table_joins.map { |join|
- # Table names + table aliases
- join.left.downcase.scan(
- /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
- ).size
- }.sum
- end
-
def instantiate(rows)
primary_key = join_base.aliased_primary_key
parents = {}
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 ebe39c35fe..0a666598ed 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -2,6 +2,8 @@ module ActiveRecord
module Associations
class JoinDependency # :nodoc:
class JoinAssociation < JoinPart # :nodoc:
+ include JoinHelper
+
# The reflection of the association represented
attr_reader :reflection
@@ -18,10 +20,15 @@ module ActiveRecord
attr_accessor :join_type
# These implement abstract methods from the superclass
- attr_reader :aliased_prefix, :aliased_table_name
+ attr_reader :aliased_prefix
+
+ attr_reader :tables
- delegate :options, :through_reflection, :source_reflection, :to => :reflection
+ delegate :options, :through_reflection, :source_reflection, :chain, :to => :reflection
delegate :table, :table_name, :to => :parent, :prefix => :parent
+ delegate :alias_tracker, :to => :join_dependency
+
+ alias :alias_suffix :parent_table_name
def initialize(reflection, join_dependency, parent = nil)
reflection.check_validity!
@@ -37,14 +44,7 @@ module ActiveRecord
@parent = parent
@join_type = Arel::InnerJoin
@aliased_prefix = "t#{ join_dependency.join_parts.size }"
-
- # This must be done eagerly upon initialisation because the alias which is produced
- # depends on the state of the join dependency, but we want it to work the same way
- # every time.
- allocate_aliases
- @table = Arel::Table.new(
- table_name, :as => aliased_table_name, :engine => arel_engine
- )
+ @tables = construct_tables.reverse
end
def ==(other)
@@ -60,219 +60,90 @@ module ActiveRecord
end
def join_to(relation)
- send("join_#{reflection.macro}_to", relation)
- end
-
- def join_relation(joining_relation)
- self.join_type = Arel::OuterJoin
- joining_relation.joins(self)
- end
-
- attr_reader :table
- # More semantic name given we are talking about associations
- alias_method :target_table, :table
+ tables = @tables.dup
+ foreign_table = parent_table
+
+ # The chain starts with the target table, but we want to end with it here (makes
+ # more sense in this context), so we reverse
+ chain.reverse.each_with_index do |reflection, i|
+ table = tables.shift
+
+ case reflection.source_macro
+ when :belongs_to
+ key = reflection.association_primary_key
+ foreign_key = reflection.foreign_key
+ when :has_and_belongs_to_many
+ # Join the join table first...
+ relation.from(join(
+ table,
+ table[reflection.foreign_key].
+ eq(foreign_table[reflection.active_record_primary_key])
+ ))
+
+ foreign_table, table = table, tables.shift
+
+ key = reflection.association_primary_key
+ foreign_key = reflection.association_foreign_key
+ else
+ key = reflection.foreign_key
+ foreign_key = reflection.active_record_primary_key
+ end
- protected
+ constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
- def aliased_table_name_for(name, suffix = nil)
- aliases = @join_dependency.table_aliases
+ relation.from(join(table, constraint))
- if aliases[name] != 0 # We need an alias
- connection = active_record.connection
+ unless conditions[i].empty?
+ relation.where(sanitize(conditions[i], table))
+ end
- name = connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
- aliases[name] += 1
- name = name[0, connection.table_alias_length-3] + "_#{aliases[name]}" if aliases[name] > 1
- else
- aliases[name] += 1
+ # The current table in this iteration becomes the foreign table in the next
+ foreign_table = table
end
- name
+ relation
end
- def pluralize(table_name)
- ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
- end
+ def build_constraint(reflection, table, key, foreign_table, foreign_key)
+ constraint = table[key].eq(foreign_table[foreign_key])
- private
-
- def allocate_aliases
- @aliased_table_name = aliased_table_name_for(table_name)
-
- if reflection.macro == :has_and_belongs_to_many
- @aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
- elsif [:has_many, :has_one].include?(reflection.macro) && reflection.options[:through]
- @aliased_join_table_name = aliased_table_name_for(reflection.through_reflection.klass.table_name, "_join")
- end
- end
-
- def process_conditions(conditions, table_name)
- if conditions.respond_to?(:to_proc)
- conditions = instance_eval(&conditions)
+ if reflection.klass.finder_needs_type_condition?
+ constraint = table.create_and([
+ constraint,
+ reflection.klass.send(:type_condition, table)
+ ])
end
- Arel.sql(sanitize_sql(conditions, table_name))
+ constraint
end
- def sanitize_sql(condition, table_name)
- active_record.send(:sanitize_sql, condition, table_name)
+ def join_relation(joining_relation)
+ self.join_type = Arel::OuterJoin
+ joining_relation.joins(self)
end
- def join_target_table(relation, condition)
- conditions = [condition]
-
- # If the target table is an STI model then we must be sure to only include records of
- # its type and its sub-types.
- unless active_record.descends_from_active_record?
- sti_column = target_table[active_record.inheritance_column]
- subclasses = active_record.descendants
- sti_condition = sti_column.eq(active_record.sti_name)
-
- conditions << subclasses.inject(sti_condition) { |attr,subclass|
- attr.or(sti_column.eq(subclass.sti_name))
- }
- end
-
- # If the reflection has conditions, add them
- if options[:conditions]
- conditions << process_conditions(options[:conditions], aliased_table_name)
- end
-
- ands = relation.create_and(conditions)
-
- join = relation.create_join(
- target_table,
- relation.create_on(ands),
- join_type)
-
- relation.from join
+ def table
+ tables.last
end
- def join_has_and_belongs_to_many_to(relation)
- join_table = Arel::Table.new(
- options[:join_table]
- ).alias(@aliased_join_table_name)
-
- fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key
- klass_fk = options[:association_foreign_key] || reflection.klass.to_s.foreign_key
-
- relation = relation.join(join_table, join_type)
- relation = relation.on(
- join_table[fk].
- eq(parent_table[reflection.active_record.primary_key])
- )
-
- join_target_table(
- relation,
- target_table[reflection.klass.primary_key].
- eq(join_table[klass_fk])
- )
+ def aliased_table_name
+ table.table_alias || table.name
end
- def join_has_many_to(relation)
- if reflection.options[:through]
- join_has_many_through_to(relation)
- elsif reflection.options[:as]
- join_has_many_polymorphic_to(relation)
- else
- foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
- primary_key = options[:primary_key] || parent.primary_key
-
- join_target_table(
- relation,
- target_table[foreign_key].
- eq(parent_table[primary_key])
- )
- end
+ def conditions
+ @conditions ||= reflection.conditions.reverse
end
- alias :join_has_one_to :join_has_many_to
-
- def join_has_many_through_to(relation)
- join_table = Arel::Table.new(
- through_reflection.klass.table_name
- ).alias @aliased_join_table_name
- jt_conditions = []
- first_key = second_key = nil
+ private
- if through_reflection.macro == :belongs_to
- jt_primary_key = through_reflection.foreign_key
- jt_foreign_key = through_reflection.association_primary_key
+ def interpolate(conditions)
+ if conditions.respond_to?(:to_proc)
+ instance_eval(&conditions)
else
- jt_primary_key = through_reflection.active_record_primary_key
- jt_foreign_key = through_reflection.foreign_key
-
- if through_reflection.options[:as] # has_many :through against a polymorphic join
- jt_conditions <<
- join_table["#{through_reflection.options[:as]}_type"].
- eq(parent.active_record.base_class.name)
- end
- end
-
- case source_reflection.macro
- when :has_many
- second_key = options[:foreign_key] || primary_key
-
- if source_reflection.options[:as]
- first_key = "#{source_reflection.options[:as]}_id"
- else
- first_key = through_reflection.klass.base_class.to_s.foreign_key
- end
-
- unless through_reflection.klass.descends_from_active_record?
- jt_conditions <<
- join_table[through_reflection.active_record.inheritance_column].
- eq(through_reflection.klass.sti_name)
- end
- when :belongs_to
- first_key = primary_key
-
- if reflection.options[:source_type]
- second_key = source_reflection.association_foreign_key
-
- jt_conditions <<
- join_table[reflection.source_reflection.foreign_type].
- eq(reflection.options[:source_type])
- else
- second_key = source_reflection.foreign_key
- end
+ conditions
end
-
- jt_conditions <<
- parent_table[jt_primary_key].
- eq(join_table[jt_foreign_key])
-
- if through_reflection.options[:conditions]
- jt_conditions << process_conditions(through_reflection.options[:conditions], aliased_table_name)
- end
-
- relation = relation.join(join_table, join_type).on(*jt_conditions)
-
- join_target_table(
- relation,
- target_table[first_key].eq(join_table[second_key])
- )
end
- def join_has_many_polymorphic_to(relation)
- join_target_table(
- relation,
- target_table["#{reflection.options[:as]}_id"].
- eq(parent_table[parent.primary_key]).and(
- target_table["#{reflection.options[:as]}_type"].
- eq(parent.active_record.base_class.name))
- )
- end
-
- def join_belongs_to_to(relation)
- foreign_key = options[:foreign_key] || reflection.foreign_key
- primary_key = options[:primary_key] || reflection.klass.primary_key
-
- join_target_table(
- relation,
- target_table[primary_key].eq(parent_table[foreign_key])
- )
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
index 3279e56e7d..2b1d888a9a 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
@@ -22,7 +22,7 @@ module ActiveRecord
end
def aliased_table
- Arel::Nodes::TableAlias.new aliased_table_name, table
+ Arel::Nodes::TableAlias.new table, aliased_table_name
end
def ==(other)
diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb
new file mode 100644
index 0000000000..eae546e76e
--- /dev/null
+++ b/activerecord/lib/active_record/associations/join_helper.rb
@@ -0,0 +1,56 @@
+module ActiveRecord
+ module Associations
+ # Helper class module which gets mixed into JoinDependency::JoinAssociation and AssociationScope
+ module JoinHelper #:nodoc:
+
+ def join_type
+ Arel::InnerJoin
+ end
+
+ private
+
+ def construct_tables
+ tables = []
+ chain.each do |reflection|
+ tables << alias_tracker.aliased_table_for(
+ table_name_for(reflection),
+ table_alias_for(reflection, reflection != self.reflection)
+ )
+
+ if reflection.source_macro == :has_and_belongs_to_many
+ tables << alias_tracker.aliased_table_for(
+ (reflection.source_reflection || reflection).options[:join_table],
+ table_alias_for(reflection, true)
+ )
+ end
+ end
+ tables
+ end
+
+ def table_name_for(reflection)
+ reflection.table_name
+ end
+
+ def table_alias_for(reflection, join = false)
+ name = alias_tracker.pluralize(reflection.name)
+ name << "_#{alias_suffix}"
+ name << "_join" if join
+ name
+ end
+
+ 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/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index d630fc4c63..ad6374d09a 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -19,8 +19,9 @@ module ActiveRecord
source_reflection.name, options
).run
- through_records.each do |owner, owner_through_records|
- owner_through_records.map! { |r| r.send(source_reflection.name) }.flatten!
+ through_records.each do |owner, records|
+ records.map! { |r| r.send(source_reflection.name) }.flatten!
+ records.compact!
end
end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 0d8e45adb5..4edbe216be 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -37,7 +37,7 @@ module ActiveRecord
# Implemented by subclasses
def replace(record)
- raise NotImplementedError
+ raise NotImplementedError, "Subclasses must implement a replace(record) method"
end
def set_new_record(record)
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index e1d60ccb17..e6ab628719 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -3,79 +3,24 @@ module ActiveRecord
module Associations
module ThroughAssociation #:nodoc:
- delegate :source_options, :through_options, :source_reflection, :through_reflection, :to => :reflection
+ delegate :source_reflection, :through_reflection, :chain, :to => :reflection
protected
+ # We merge in these scopes for two reasons:
+ #
+ # 1. To get the default_scope conditions for any of the other reflections in the chain
+ # 2. To get the type conditions for any STI models in the chain
def target_scope
- super.merge(through_reflection.klass.scoped)
- end
-
- def association_scope
- scope = super.joins(construct_joins)
- scope = add_conditions(scope)
- unless options[:include]
- scope = scope.includes(source_options[:include])
+ scope = super
+ chain[1..-1].each do |reflection|
+ scope = scope.merge(reflection.klass.scoped)
end
scope
end
private
- # This scope affects the creation of the associated records (not the join records). At the
- # moment we only support creating on a :through association when the source reflection is a
- # belongs_to. Thus it's not necessary to set a foreign key on the associated record(s), so
- # this scope has can legitimately be empty.
- def creation_attributes
- { }
- end
-
- def aliased_through_table
- name = through_reflection.table_name
-
- reflection.table_name == name ?
- through_reflection.klass.arel_table.alias(name + "_join") :
- through_reflection.klass.arel_table
- end
-
- def construct_owner_conditions
- super(aliased_through_table, through_reflection)
- end
-
- def construct_joins
- right = aliased_through_table
- left = reflection.klass.arel_table
-
- conditions = []
-
- if source_reflection.macro == :belongs_to
- reflection_primary_key = source_reflection.association_primary_key
- source_primary_key = source_reflection.foreign_key
-
- if options[:source_type]
- column = source_reflection.foreign_type
- conditions <<
- right[column].eq(options[:source_type])
- end
- else
- reflection_primary_key = source_reflection.foreign_key
- source_primary_key = source_reflection.active_record_primary_key
-
- if source_options[:as]
- column = "#{source_options[:as]}_type"
- conditions <<
- left[column].eq(through_reflection.klass.name)
- end
- end
-
- conditions <<
- left[reflection_primary_key].eq(right[source_primary_key])
-
- right.create_join(
- right,
- right.create_on(right.create_and(conditions)))
- end
-
# Construct attributes for :through pointing to owner and associate. This is used by the
# methods which create and delete records on the association.
#
@@ -112,37 +57,8 @@ module ActiveRecord
end
end
- # The reason that we are operating directly on the scope here (rather than passing
- # back some arel conditions to be added to the scope) is because scope.where([x, y])
- # has a different meaning to scope.where(x).where(y) - the first version might
- # perform some substitution if x is a string.
- def add_conditions(scope)
- unless through_reflection.klass.descends_from_active_record?
- scope = scope.where(through_reflection.klass.send(:type_condition))
- end
-
- scope = scope.where(interpolate(source_options[:conditions]))
- scope.where(through_conditions)
- end
-
- # If there is a hash of conditions then we make sure the keys are scoped to the
- # through table name if left ambiguous.
- def through_conditions
- conditions = interpolate(through_options[:conditions])
-
- if conditions.is_a?(Hash)
- Hash[conditions.map { |key, value|
- unless value.is_a?(Hash) || key.to_s.include?('.')
- key = aliased_through_table.name + '.' + key.to_s
- end
-
- [key, value]
- }]
- else
- conditions
- end
- end
-
+ # Note: this does not capture all cases, for example it would be crazy to try to
+ # properly support stale-checking for nested associations.
def stale_state
if through_reflection.macro == :belongs_to
owner[through_reflection.foreign_key].to_s
@@ -153,6 +69,12 @@ module ActiveRecord
through_reflection.macro == :belongs_to &&
!owner[through_reflection.foreign_key].nil?
end
+
+ def ensure_not_nested
+ if reflection.nested?
+ raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
+ end
+ 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 fcdd31ddea..5f06452247 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -17,6 +17,11 @@ module ActiveRecord
@primary_key ||= reset_primary_key
end
+ # Returns a quoted version of the primary key name, used to construct SQL statements.
+ def quoted_primary_key
+ @quoted_primary_key ||= connection.quote_column_name(primary_key)
+ end
+
def reset_primary_key #:nodoc:
key = self == base_class ? get_primary_key(base_class.name) :
base_class.primary_key
@@ -43,7 +48,12 @@ module ActiveRecord
end
attr_accessor :original_primary_key
- attr_writer :primary_key
+
+ # Attribute writer for the primary key column
+ def primary_key=(value)
+ @quoted_primary_key = nil
+ @primary_key = value
+ end
# Sets the name of the primary key column to use to the given value,
# or (if the value is nil or false) to the value returned by the given
@@ -53,6 +63,7 @@ module ActiveRecord
# set_primary_key "sysid"
# end
def set_primary_key(value = nil, &block)
+ @quoted_primary_key = nil
@primary_key ||= ''
self.original_primary_key = @primary_key
value &&= value.to_s
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index ab86d8bad1..aef99e3129 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -43,7 +43,7 @@ module ActiveRecord
end
if attr_name == primary_key && attr_name != "id"
- define_read_method(:id, attr_name, columns_hash[attr_name])
+ define_read_method('id', attr_name, columns_hash[attr_name])
end
end
@@ -59,7 +59,9 @@ module ActiveRecord
end
# Define an attribute reader method. Cope with nil column.
- def define_read_method(symbol, attr_name, column)
+ # method_name is the same as attr_name except when a non-standard primary key is used,
+ # we still define #id as an accessor for the key
+ def define_read_method(method_name, attr_name, column)
cast_code = column.type_cast_code('v')
access_code = "(v=@attributes['#{attr_name}']) && #{cast_code}"
@@ -70,16 +72,38 @@ module ActiveRecord
if cache_attribute?(attr_name)
access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
end
- generated_attribute_methods.module_eval("def _#{symbol}; #{access_code}; end; alias #{symbol} _#{symbol}", __FILE__, __LINE__)
+
+ # Where possible, generate the method by evalling a string, as this will result in
+ # faster accesses because it avoids the block eval and then string eval incurred
+ # by the second branch.
+ #
+ # The second, slower, branch is necessary to support instances where the database
+ # returns columns with extra stuff in (like 'my_column(omg)').
+ if method_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__
+ def _#{method_name}
+ #{access_code}
+ end
+
+ alias #{method_name} _#{method_name}
+ STR
+ else
+ generated_attribute_methods.module_eval do
+ define_method("_#{method_name}") { eval(access_code) }
+ alias_method(method_name, "_#{method_name}")
+ end
+ end
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)
- send "_#{attr_name}"
- rescue NoMethodError
- _read_attribute attr_name
+ if respond_to? "_#{attr_name}"
+ send "_#{attr_name}" if @attributes.has_key?(attr_name.to_s)
+ else
+ _read_attribute attr_name
+ end
end
def _read_attribute(attr_name)
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 76218d2a73..62a3cfa9a5 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/object/inclusion'
module ActiveRecord
module AttributeMethods
@@ -21,9 +22,9 @@ module ActiveRecord
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}(reload = false)
+ def _#{attr_name}
cached = @attributes_cache['#{attr_name}']
- return cached if cached && !reload
+ return cached if cached
time = _read_attribute('#{attr_name}')
@attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
end
@@ -41,12 +42,13 @@ module ActiveRecord
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.dup unless original_time.nil?
+ 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}, (time || original_time))
+ write_attribute(:#{attr_name}, original_time)
+ @attributes_cache["#{attr_name}"] = time
end
EOV
generated_attribute_methods.module_eval(method_body, __FILE__, line)
@@ -57,7 +59,7 @@ module ActiveRecord
private
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)
+ time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && column.type.in?([:datetime, :timestamp])
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index 6a593a7e0e..c77a3ac145 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -10,7 +10,13 @@ module ActiveRecord
module ClassMethods
protected
def define_method_attribute=(attr_name)
- generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
+ if attr_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
+ generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
+ else
+ generated_attribute_methods.send(:define_method, "#{attr_name}=") do |new_value|
+ write_attribute(attr_name, new_value)
+ end
+ end
end
end
@@ -26,6 +32,7 @@ module ActiveRecord
@attributes[attr_name] = value
end
end
+ alias_method :raw_write_attribute, :write_attribute
private
# Handle *= for method_missing.
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index a5e1c91f47..04c12f86b6 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -425,8 +425,8 @@ module ActiveRecord #:nodoc:
self.store_full_sti_class = true
# Stores the default scope for the class
- class_attribute :default_scoping, :instance_writer => false
- self.default_scoping = []
+ class_attribute :default_scopes, :instance_writer => false
+ self.default_scopes = []
# Returns a hash of all the attributes that have been specified for serialization as
# keys and their class restriction as values.
@@ -437,9 +437,10 @@ module ActiveRecord #:nodoc:
self._attr_readonly = []
class << self # Class methods
- delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
+ delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
+ delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
- delegate :select, :group, :order, :except, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
+ delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
@@ -869,7 +870,9 @@ module ActiveRecord #:nodoc:
# Returns a scope for this class without taking into account the default_scope.
#
# class Post < ActiveRecord::Base
- # default_scope :published => true
+ # def self.default_scope
+ # where :published => true
+ # end
# end
#
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
@@ -891,13 +894,8 @@ module ActiveRecord #:nodoc:
block_given? ? relation.scoping { yield } : relation
end
- def scoped_methods #:nodoc:
- key = :"#{self}_scoped_methods"
- Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
- end
-
def before_remove_const #:nodoc:
- reset_scoped_methods
+ self.current_scope = nil
end
# Specifies how the record is loaded by +Marshal+.
@@ -973,8 +971,8 @@ module ActiveRecord #:nodoc:
relation
end
- def type_condition
- sti_column = arel_table[inheritance_column.to_sym]
+ def type_condition(table = arel_table)
+ sti_column = table[inheritance_column.to_sym]
sti_names = ([self] + descendants).map { |model| model.sti_name }
sti_column.in(sti_names)
@@ -995,7 +993,7 @@ module ActiveRecord #:nodoc:
if parent < ActiveRecord::Base && !parent.abstract_class?
contained = parent.table_name
contained = contained.singularize if parent.pluralize_table_names
- contained << '_'
+ contained += '_'
end
"#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{table_name_suffix}"
else
@@ -1019,7 +1017,7 @@ module ActiveRecord #:nodoc:
super unless all_attributes_exists?(attribute_names)
if match.finder?
options = arguments.extract_options!
- relation = options.any? ? construct_finder_arel(options, current_scoped_methods) : scoped
+ relation = options.any? ? scoped(options) : scoped
relation.send :find_by_attributes, match, attribute_names, *arguments
elsif match.instantiator?
scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
@@ -1108,43 +1106,47 @@ module ActiveRecord #:nodoc:
# end
#
# *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+.
- def with_scope(method_scoping = {}, action = :merge, &block)
- method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
+ def with_scope(scope = {}, action = :merge, &block)
+ # If another Active Record class has been passed in, get its current scope
+ scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope)
+
+ previous_scope = self.current_scope
- if method_scoping.is_a?(Hash)
+ if scope.is_a?(Hash)
# Dup first and second level of hash (method and params).
- method_scoping = method_scoping.dup
- method_scoping.each do |method, params|
- method_scoping[method] = params.dup unless params == true
+ scope = scope.dup
+ scope.each do |method, params|
+ scope[method] = params.dup unless params == true
end
- method_scoping.assert_valid_keys([ :find, :create ])
- relation = construct_finder_arel(method_scoping[:find] || {})
+ scope.assert_valid_keys([ :find, :create ])
+ relation = construct_finder_arel(scope[:find] || {})
+ relation.default_scoped = true unless action == :overwrite
- if current_scoped_methods && current_scoped_methods.create_with_value && method_scoping[:create]
+ if previous_scope && previous_scope.create_with_value && scope[:create]
scope_for_create = if action == :merge
- current_scoped_methods.create_with_value.merge(method_scoping[:create])
+ previous_scope.create_with_value.merge(scope[:create])
else
- method_scoping[:create]
+ scope[:create]
end
relation = relation.create_with(scope_for_create)
else
- scope_for_create = method_scoping[:create]
- scope_for_create ||= current_scoped_methods.create_with_value if current_scoped_methods
+ scope_for_create = scope[:create]
+ scope_for_create ||= previous_scope.create_with_value if previous_scope
relation = relation.create_with(scope_for_create) if scope_for_create
end
- method_scoping = relation
+ scope = relation
end
- method_scoping = current_scoped_methods.merge(method_scoping) if current_scoped_methods && action == :merge
+ scope = previous_scope.merge(scope) if previous_scope && action == :merge
- self.scoped_methods << method_scoping
+ self.current_scope = scope
begin
yield
ensure
- self.scoped_methods.pop
+ self.current_scope = previous_scope
end
end
@@ -1167,14 +1169,24 @@ MSG
with_scope(method_scoping, :overwrite, &block)
end
- # Sets the default options for the model. The format of the
- # <tt>options</tt> argument is the same as in find.
+ def current_scope #:nodoc:
+ Thread.current[:"#{self}_current_scope"]
+ end
+
+ def current_scope=(scope) #:nodoc:
+ Thread.current[:"#{self}_current_scope"] = scope
+ end
+
+ # Use this macro in your model to set a default scope for all operations on
+ # the model.
#
# class Person < ActiveRecord::Base
# default_scope order('last_name, first_name')
# end
#
- # <tt>default_scope</tt> is also applied while creating/building a record. It is not
+ # Person.all # => SELECT * FROM people ORDER BY last_name, first_name
+ #
+ # The <tt>default_scope</tt> is also applied while creating/building a record. It is not
# applied while updating a record.
#
# class Article < ActiveRecord::Base
@@ -1183,23 +1195,73 @@ MSG
#
# Article.new.published # => true
# Article.create.published # => true
- def default_scope(options = {})
- reset_scoped_methods
- default_scoping = self.default_scoping.dup
- self.default_scoping = default_scoping << construct_finder_arel(options, default_scoping.pop)
- end
+ #
+ # You can also use <tt>default_scope</tt> with a block, in order to have it lazily evaluated:
+ #
+ # class Article < ActiveRecord::Base
+ # default_scope { where(:published_at => Time.now - 1.week) }
+ # end
+ #
+ # (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
+ # macro, and it will be called when building the default scope.)
+ #
+ # If you need to do more complex things with a default scope, you can alternatively
+ # define it as a class method:
+ #
+ # class Article < ActiveRecord::Base
+ # def self.default_scope
+ # # Should return a scope, you can call 'super' here etc.
+ # end
+ # end
+ def default_scope(scope = {})
+ if default_scopes.length != 0
+ ActiveSupport::Deprecation.warn <<-WARN
+Calling 'default_scope' multiple times in a class (including when a superclass calls 'default_scope') is deprecated. The current behavior is that this will merge the default scopes together:
+
+class Post < ActiveRecord::Base # Rails 3.1
+ default_scope where(:published => true)
+ default_scope where(:hidden => false)
+ # The default scope is now: where(:published => true, :hidden => false)
+end
- def current_scoped_methods #:nodoc:
- method = scoped_methods.last
- if method.respond_to?(:call)
- relation.scoping { method.call }
- else
- method
+In Rails 3.2, the behavior will be changed to overwrite previous scopes:
+
+class Post < ActiveRecord::Base # Rails 3.2
+ default_scope where(:published => true)
+ default_scope where(:hidden => false)
+ # The default scope is now: where(:hidden => false)
+end
+
+If you wish to merge default scopes in special ways, it is recommended to define your default scope as a class method and use the standard techniques for sharing code (inheritance, mixins, etc.):
+
+class Post < ActiveRecord::Base
+ def self.default_scope
+ where(:published => true).where(:hidden => false)
+ end
+end
+ WARN
end
+
+ scope = Proc.new if block_given?
+ self.default_scopes = default_scopes.dup << scope
end
- def reset_scoped_methods #:nodoc:
- Thread.current[:"#{self}_scoped_methods"] = nil
+ def build_default_scope #:nodoc:
+ if method(:default_scope).owner != Base.singleton_class
+ # Use relation.scoping to ensure we ignore whatever the current value of
+ # self.current_scope may be.
+ relation.scoping { default_scope }
+ elsif default_scopes.any?
+ default_scopes.inject(relation) do |default_scope, scope|
+ if scope.is_a?(Hash)
+ default_scope.apply_finder_options(scope)
+ elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
+ default_scope.merge(scope.call)
+ else
+ default_scope.merge(scope)
+ end
+ end
+ end
end
# Returns the class type of the record using the current module as a prefix. So descendants of
@@ -1559,11 +1621,11 @@ MSG
# Allows you to set all the attributes at once by passing in a hash with keys
# matching the attribute names (which again matches the column names).
#
- # If +guard_protected_attributes+ is true (the default), then sensitive
- # attributes can be protected from this form of mass-assignment by using
- # the +attr_protected+ macro. Or you can alternatively specify which
- # attributes *can* be accessed with the +attr_accessible+ macro. Then all the
- # attributes not included in that won't be allowed to be mass-assigned.
+ # If any attributes are protected by either +attr_protected+ or
+ # +attr_accessible+ then only settable attributes will be assigned.
+ #
+ # The +guard_protected_attributes+ argument is now deprecated, use
+ # the +assign_attributes+ method if you want to bypass mass-assignment security.
#
# class User < ActiveRecord::Base
# attr_protected :is_admin
@@ -1573,15 +1635,59 @@ MSG
# user.attributes = { :username => 'Phusion', :is_admin => true }
# user.username # => "Phusion"
# user.is_admin? # => false
+ def attributes=(new_attributes, guard_protected_attributes = nil)
+ unless guard_protected_attributes.nil?
+ message = "the use of 'guard_protected_attributes' will be removed from the next major release of rails, " +
+ "if you want to bypass mass-assignment security then look into using assign_attributes"
+ ActiveSupport::Deprecation.warn(message)
+ end
+
+ return unless new_attributes.is_a?(Hash)
+
+ guard_protected_attributes ||= true
+ if guard_protected_attributes
+ assign_attributes(new_attributes)
+ else
+ assign_attributes(new_attributes, :without_protection => true)
+ end
+ end
+
+ # Allows you to set all the attributes for a particular mass-assignment
+ # security scope by passing in a hash of attributes with keys matching
+ # the attribute names (which again matches the column names) and the scope
+ # name using the :as option.
+ #
+ # To bypass mass-assignment security you can use the :without_protection => true
+ # option.
+ #
+ # class User < ActiveRecord::Base
+ # attr_accessible :name
+ # attr_accessible :name, :is_admin, :as => :admin
+ # end
+ #
+ # user = User.new
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true })
+ # user.name # => "Josh"
+ # user.is_admin? # => false
#
- # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
+ # user = User.new
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
+ # user.name # => "Josh"
# user.is_admin? # => true
- def attributes=(new_attributes, guard_protected_attributes = true)
- return unless new_attributes.is_a?(Hash)
+ #
+ # user = User.new
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
+ # user.name # => "Josh"
+ # user.is_admin? # => true
+ def assign_attributes(new_attributes, options = {})
attributes = new_attributes.stringify_keys
+ scope = options[:as] || :default
multi_parameter_attributes = []
- attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes
+
+ unless options[:without_protection]
+ attributes = sanitize_for_mass_assignment(attributes, scope)
+ end
attributes.each do |k, v|
if k.include?("(")
@@ -1915,11 +2021,8 @@ MSG
end
def populate_with_current_scope_attributes
- if scope = self.class.send(:current_scoped_methods)
- create_with = scope.scope_for_create
- create_with.each { |att,value|
- respond_to?("#{att}=") && send("#{att}=", value)
- }
+ self.class.scoped.scope_for_create.each do |att,value|
+ respond_to?("#{att}=") && send("#{att}=", value)
end
end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 86d58df99b..a175bf003c 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -214,6 +214,24 @@ module ActiveRecord
# needs to be aware of it because an ordinary +save+ will raise such exception
# instead of quietly returning +false+.
#
+ # == Debugging callbacks
+ #
+ # The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. ActiveModel Callbacks support
+ # <tt>:before</tt>, <tt>:after</tt> and <tt>:around</tt> as values for the <tt>kind</tt> property. The <tt>kind</tt> property
+ # defines what part of the chain the callback runs in.
+ #
+ # To find all callbacks in the before_save callback chain:
+ #
+ # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }
+ #
+ # Returns an array of callback objects that form the before_save chain.
+ #
+ # To further check if the before_save chain contains a proc defined as <tt>rest_when_dead</tt> use the <tt>filter</tt> property of the callback object:
+ #
+ # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead)
+ #
+ # Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model.
+ #
module Callbacks
extend ActiveSupport::Concern
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 4297c26413..b4db1eed18 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -151,6 +151,12 @@ module ActiveRecord
@reserved_connections[current_connection_id] ||= checkout
end
+ # Check to see if there is an active connection in this connection
+ # pool.
+ def active_connection?
+ @reserved_connections.key? current_connection_id
+ end
+
# Signal that the thread is finished with the current connection.
# #release_connection releases the connection-thread association
# and returns the connection to the pool.
@@ -346,6 +352,12 @@ module ActiveRecord
@connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec)
end
+ # Returns true if there are any active connections among the connection
+ # pools that the ConnectionHandler is managing.
+ def active_connections?
+ connection_pools.values.any? { |pool| pool.active_connection? }
+ end
+
# Returns any connections in use by the current thread back to the pool,
# and also returns connections to the pool cached by threads that are no
# longer alive.
@@ -405,18 +417,40 @@ module ActiveRecord
end
class ConnectionManagement
+ class Proxy # :nodoc:
+ attr_reader :body, :testing
+
+ def initialize(body, testing = false)
+ @body = body
+ @testing = testing
+ end
+
+ def each(&block)
+ body.each(&block)
+ end
+
+ def close
+ body.close if body.respond_to?(:close)
+
+ # 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
+ end
+
def initialize(app)
@app = app
end
def call(env)
- @app.call(env)
- ensure
- # Don't return connection (and perform implicit rollback) if
- # this request is a part of integration test
- unless env.key?("rack.test")
- ActiveRecord::Base.clear_active_connections!
- end
+ testing = env.key?('rack.test')
+
+ status, headers, body = @app.call(env)
+
+ [status, headers, Proxy.new(body, testing)]
+ rescue
+ ActiveRecord::Base.clear_active_connections! unless testing
+ raise
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index d88720c8bf..bcd3abc08d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -116,7 +116,11 @@ module ActiveRecord
connection_handler.remove_connection(klass)
end
- delegate :clear_active_connections!, :clear_reloadable_connections!,
+ def clear_active_connections!
+ connection_handler.clear_active_connections!
+ end
+
+ delegate :clear_reloadable_connections!,
:clear_all_connections!,:verify_active_connections!, :to => :connection_handler
end
end
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 5c1ce173c8..70da9d5f1e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -55,9 +55,25 @@ module ActiveRecord
def exec_query(sql, name = 'SQL', binds = [])
end
+ # Executes insert +sql+ statement in the context of this connection using
+ # +binds+ as the bind substitutes. +name+ is the logged along with
+ # the executed +sql+ statement.
+ def exec_insert(sql, name, binds)
+ exec_query(sql, name, binds)
+ end
+
# Returns the last auto-generated ID from the affected table.
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
- insert_sql(sql, name, pk, id_value, sequence_name)
+ #
+ # +id_value+ will be returned unless the value is nil, in
+ # which case the database will attempt to calculate the last inserted
+ # id and return that value.
+ #
+ # If the next id was calculated in advance (as in Oracle), it should be
+ # passed in as +id_value+.
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
+ sql, binds = sql_for_insert(sql, 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.
@@ -237,7 +253,6 @@ module ActiveRecord
# add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
# generates
# SELECT * FROM suppliers LIMIT 10 OFFSET 50
-
def add_limit_offset!(sql, options)
if limit = options[:limit]
sql << " LIMIT #{sanitize_limit(limit)}"
@@ -361,6 +376,15 @@ module ActiveRecord
end
end
end
+
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
+ [sql, binds]
+ end
+
+ def last_inserted_id(result)
+ row = result.rows.first
+ row && row.first
+ 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 7489e88eef..3de850ec9e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -35,7 +35,43 @@ module ActiveRecord
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
else
- "'#{quote_string(value.to_yaml)}'"
+ "'#{quote_string(YAML.dump(value))}'"
+ end
+ end
+
+ # Cast a +value+ to a type that the database understands. For example,
+ # SQLite does not understand dates, so this method will convert a Date
+ # to a String.
+ def type_cast(value, column)
+ return value.id if value.respond_to?(:quoted_id)
+
+ case value
+ when String, ActiveSupport::Multibyte::Chars
+ value = value.to_s
+ return value unless column
+
+ case column.type
+ when :binary then value
+ when :integer then value.to_i
+ when :float then value.to_f
+ else
+ value
+ end
+
+ when true, false
+ if column && column.type == :integer
+ value ? 1 : 0
+ else
+ value ? 't' : 'f'
+ end
+ # BigDecimals need to be put in a non-normalized form and quoted.
+ when nil then nil
+ when BigDecimal then value.to_s('F')
+ when Numeric then value
+ when Date, Time then quoted_date(value)
+ when Symbol then value.to_s
+ else
+ YAML.dump(value)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 3ec7dd02a4..8bae50885f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -279,12 +279,11 @@ module ActiveRecord
raise NotImplementedError, "change_column is not implemented"
end
- # Sets a new default value for a column. If you want to set the default
- # value to +NULL+, you are out of luck. You need to
- # DatabaseStatements#execute the appropriate SQL statement yourself.
+ # Sets a new default value for a column.
# ===== Examples
# change_column_default(:suppliers, :qualification, 'new')
# change_column_default(:accounts, :authorized, 1)
+ # change_column_default(:users, :email, nil)
def change_column_default(table_name, column_name, default)
raise NotImplementedError, "change_column_default is not implemented"
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 0f44baa2fe..468a2b106b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -105,7 +105,7 @@ module ActiveRecord
# Returns a bind substitution value given a +column+ and list of current
# +binds+
- def substitute_for(column, binds)
+ def substitute_at(column, index)
Arel.sql '?'
end
@@ -203,6 +203,10 @@ module ActiveRecord
def release_savepoint
end
+ def case_sensitive_modifier(node)
+ node
+ end
+
def current_savepoint_name
"active_record_#{open_transactions}"
end
@@ -219,7 +223,9 @@ module ActiveRecord
rescue Exception => e
message = "#{e.class.name}: #{e.message}: #{sql}"
@logger.debug message if @logger
- raise translate_exception(e, message)
+ exception = translate_exception(e, message)
+ exception.set_backtrace e.backtrace
+ raise exception
end
def translate_exception(e, message)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 7bad511c64..cf68ddc2da 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,5 +1,6 @@
# encoding: utf-8
+gem 'mysql2', '~> 0.3.0'
require 'mysql2'
module ActiveRecord
@@ -282,6 +283,17 @@ module ActiveRecord
end
alias :create :insert_sql
+ def exec_insert(sql, name, binds)
+ binds = binds.dup
+
+ # Pretend to support bind parameters
+ execute sql.gsub('?') { quote(*binds.shift.reverse) }, name
+ end
+
+ def last_inserted_id(result)
+ @connection.last_id
+ end
+
def update_sql(sql, name = nil)
super
@connection.affected_rows
@@ -528,6 +540,11 @@ module ActiveRecord
def case_sensitive_equality_operator
"= BINARY"
end
+ deprecate :case_sensitive_equality_operator
+
+ def case_sensitive_modifier(node)
+ Arel::Nodes::Bin.new(node)
+ end
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
where_sql
@@ -587,8 +604,26 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
- def select(sql, name = nil)
- execute(sql, name).each(:as => :hash)
+ def select(sql, name = nil, binds = [])
+ exec_query(sql, name, binds).to_a
+ end
+
+ def exec_query(sql, name = 'SQL', binds = [])
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
+
+ log(sql, name, binds) do
+ begin
+ result = @connection.query(sql)
+ rescue ActiveRecord::StatementInvalid => exception
+ if exception.message.split(":").first =~ /Packets out of order/
+ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
+ else
+ raise
+ end
+ end
+
+ ActiveRecord::Result.new(result.fields, result.to_a)
+ end
end
def supports_views?
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 368c5b2023..2c05ff21f9 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -196,6 +196,7 @@ module ActiveRecord
@connection_options, @config = connection_options, config
@quoted_column_names, @quoted_table_names = {}, {}
@statements = {}
+ @client_encoding = nil
connect
end
@@ -207,16 +208,18 @@ module ActiveRecord
true
end
- # Returns +true+ when the connection adapter supports prepared statement
- # caching, otherwise returns +false+
+ # Returns +true+, since this connection adapter supports prepared statement
+ # caching.
def supports_statement_cache?
true
end
+ # Returns true.
def supports_migrations? #:nodoc:
true
end
+ # Returns true.
def supports_primary_key? #:nodoc:
true
end
@@ -243,6 +246,12 @@ module ActiveRecord
end
end
+ def type_cast(value, column)
+ return super unless value == true || value == false
+
+ value ? 1 : 0
+ end
+
def quote_column_name(name) #:nodoc:
@quoted_column_names[name] ||= "`#{name}`"
end
@@ -301,6 +310,8 @@ module ActiveRecord
connect
end
+ # Disconnects from the database if already connected. Otherwise, this
+ # method does nothing.
def disconnect!
@connection.close rescue nil
end
@@ -323,6 +334,7 @@ module ActiveRecord
rows
end
+ # Clears the prepared statements cache.
def clear_cache!
@statements.values.each do |cache|
cache[:stmt].close
@@ -330,6 +342,63 @@ module ActiveRecord
@statements.clear
end
+ if "<3".respond_to?(:encode)
+ # Taken from here:
+ # https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb
+ # Author: TOMITA Masahiro <tommy@tmtm.org>
+ ENCODINGS = {
+ "armscii8" => nil,
+ "ascii" => Encoding::US_ASCII,
+ "big5" => Encoding::Big5,
+ "binary" => Encoding::ASCII_8BIT,
+ "cp1250" => Encoding::Windows_1250,
+ "cp1251" => Encoding::Windows_1251,
+ "cp1256" => Encoding::Windows_1256,
+ "cp1257" => Encoding::Windows_1257,
+ "cp850" => Encoding::CP850,
+ "cp852" => Encoding::CP852,
+ "cp866" => Encoding::IBM866,
+ "cp932" => Encoding::Windows_31J,
+ "dec8" => nil,
+ "eucjpms" => Encoding::EucJP_ms,
+ "euckr" => Encoding::EUC_KR,
+ "gb2312" => Encoding::EUC_CN,
+ "gbk" => Encoding::GBK,
+ "geostd8" => nil,
+ "greek" => Encoding::ISO_8859_7,
+ "hebrew" => Encoding::ISO_8859_8,
+ "hp8" => nil,
+ "keybcs2" => nil,
+ "koi8r" => Encoding::KOI8_R,
+ "koi8u" => Encoding::KOI8_U,
+ "latin1" => Encoding::ISO_8859_1,
+ "latin2" => Encoding::ISO_8859_2,
+ "latin5" => Encoding::ISO_8859_9,
+ "latin7" => Encoding::ISO_8859_13,
+ "macce" => Encoding::MacCentEuro,
+ "macroman" => Encoding::MacRoman,
+ "sjis" => Encoding::SHIFT_JIS,
+ "swe7" => nil,
+ "tis620" => Encoding::TIS_620,
+ "ucs2" => Encoding::UTF_16BE,
+ "ujis" => Encoding::EucJP_ms,
+ "utf8" => Encoding::UTF_8,
+ "utf8mb4" => Encoding::UTF_8,
+ }
+ else
+ ENCODINGS = Hash.new { |h,k| h[k] = k }
+ end
+
+ # Get the client encoding for this database
+ def client_encoding
+ return @client_encoding if @client_encoding
+
+ result = exec_query(
+ "SHOW VARIABLES WHERE Variable_name = 'character_set_client'",
+ 'SCHEMA')
+ @client_encoding = ENCODINGS[result.rows.last.last]
+ end
+
def exec_query(sql, name = 'SQL', binds = [])
log(sql, name, binds) do
result = nil
@@ -345,7 +414,7 @@ module ActiveRecord
end
stmt.execute(*binds.map { |col, val|
- col ? col.type_cast(val) : val
+ type_cast(val, col)
})
if metadata = stmt.result_metadata
cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
@@ -363,6 +432,10 @@ module ActiveRecord
end
end
+ def last_inserted_id(result)
+ @connection.insert_id
+ end
+
def exec_without_stmt(sql, name = 'SQL') # :nodoc:
# Some queries, like SHOW CREATE TABLE don't work through the prepared
# statement API. For those queries, we need to use this method. :'(
@@ -486,6 +559,10 @@ module ActiveRecord
end
end
+ # Drops a MySQL database.
+ #
+ # Example:
+ # drop_database 'sebastian_development'
def drop_database(name) #:nodoc:
execute "DROP DATABASE IF EXISTS `#{name}`"
end
@@ -504,14 +581,28 @@ module ActiveRecord
show_variable 'collation_database'
end
- def tables(name = nil) #:nodoc:
+ def tables(name = nil, database = nil) #:nodoc:
tables = []
- result = execute("SHOW TABLES", name)
+ result = execute(["SHOW TABLES", database].compact.join(' IN '), 'SCHEMA')
result.each { |field| tables << field[0] }
result.free
tables
end
+ def table_exists?(name)
+ return true if super
+
+ name = name.to_s
+ schema, table = name.split('.', 2)
+
+ unless table # A table was provided without a schema
+ table = schema
+ schema = nil
+ end
+
+ tables(nil, schema).include? table
+ end
+
def drop_table(table_name, options = {})
super(table_name, options)
end
@@ -537,7 +628,7 @@ module ActiveRecord
def columns(table_name, name = nil)#:nodoc:
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
columns = []
- result = execute(sql)
+ result = execute(sql, 'SCHEMA')
result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
result.free
columns
@@ -624,7 +715,7 @@ module ActiveRecord
# Returns a table's primary key and belonging sequence.
def pk_and_sequence_for(table) #:nodoc:
keys = []
- result = execute("describe #{quote_table_name(table)}")
+ result = execute("describe #{quote_table_name(table)}", 'SCHEMA')
result.each_hash do |h|
keys << h["Field"]if h["Key"] == "PRI"
end
@@ -641,6 +732,11 @@ module ActiveRecord
def case_sensitive_equality_operator
"= BINARY"
end
+ deprecate :case_sensitive_equality_operator
+
+ def case_sensitive_modifier(node)
+ Arel::Nodes::Bin.new(node)
+ end
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
where_sql
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 576450bc3a..0c2afc180b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1,6 +1,9 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_support/core_ext/kernel/requires'
require 'active_support/core_ext/object/blank'
+
+# Make sure we're using pg high enough for PGResult#values
+gem 'pg', '~> 0.11'
require 'pg'
module ActiveRecord
@@ -95,6 +98,9 @@ module ActiveRecord
# XML type
when 'xml'
:xml
+ # tsvector type
+ when 'tsvector'
+ :tsvector
# Arrays
when /^\D+\[\]$/
:string
@@ -186,6 +192,11 @@ module ActiveRecord
options = args.extract_options!
column(args[0], 'xml', options)
end
+
+ def tsvector(*args)
+ options = args.extract_options!
+ column(args[0], 'tsvector', options)
+ end
end
ADAPTER_NAME = 'PostgreSQL'
@@ -203,7 +214,8 @@ module ActiveRecord
:date => { :name => "date" },
:binary => { :name => "bytea" },
:boolean => { :name => "boolean" },
- :xml => { :name => "xml" }
+ :xml => { :name => "xml" },
+ :tsvector => { :name => "tsvector" }
}
# Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -211,8 +223,8 @@ module ActiveRecord
ADAPTER_NAME
end
- # Returns +true+ when the connection adapter supports prepared statement
- # caching, otherwise returns +false+
+ # Returns +true+, since this connection adapter supports prepared statement
+ # caching.
def supports_statement_cache?
true
end
@@ -229,9 +241,15 @@ module ActiveRecord
@statements = {}
connect
- @local_tz = execute('SHOW TIME ZONE').first["TimeZone"]
+
+ if postgresql_version < 80200
+ raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
+ end
+
+ @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
end
+ # Clears the prepared statements cache.
def clear_cache!
@statements.each_value do |value|
@connection.query "DEALLOCATE #{value}"
@@ -270,7 +288,8 @@ module ActiveRecord
super
end
- # Close the connection.
+ # Disconnects from the database if already connected. Otherwise, this
+ # method does nothing.
def disconnect!
clear_cache!
@connection.close rescue nil
@@ -293,13 +312,13 @@ module ActiveRecord
# Enable standard-conforming strings if available.
def set_standard_conforming_strings
old, self.client_min_messages = client_min_messages, 'panic'
- execute('SET standard_conforming_strings = on') rescue nil
+ execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
ensure
self.client_min_messages = old
end
def supports_insert_with_returning?
- postgresql_version >= 80200
+ true
end
def supports_ddl_transactions?
@@ -310,10 +329,9 @@ module ActiveRecord
true
end
- # Returns the configured supported identifier length supported by PostgreSQL,
- # or report the default of 63 on PostgreSQL 7.x.
+ # Returns the configured supported identifier length supported by PostgreSQL
def table_alias_length
- @table_alias_length ||= (postgresql_version >= 80000 ? query('SHOW max_identifier_length')[0][0].to_i : 63)
+ @table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
end
# QUOTING ==================================================
@@ -356,6 +374,18 @@ module ActiveRecord
end
end
+ def type_cast(value, column)
+ return super unless column
+
+ case value
+ when String
+ return super unless 'bytea' == column.sql_type
+ { :value => value, :format => 1 }
+ else
+ super
+ end
+ end
+
# Quotes strings for use in SQL input.
def quote_string(s) #:nodoc:
@connection.escape(s)
@@ -404,7 +434,7 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
def supports_disable_referential_integrity?() #:nodoc:
- postgresql_version >= 80100
+ true
end
def disable_referential_integrity #:nodoc:
@@ -427,34 +457,16 @@ module ActiveRecord
end
# Executes an INSERT query and returns the new record's ID
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
# Extract the table from the insert sql. Yuck.
- table = sql.split(" ", 4)[2].gsub('"', '')
-
- # Try an insert with 'returning id' if available (PG >= 8.2)
- if supports_insert_with_returning?
- pk, sequence_name = *pk_and_sequence_for(table) unless pk
- if pk
- id = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
- clear_query_cache
- return id
- end
- end
+ _, table = extract_schema_and_table(sql.split(" ", 4)[2])
- # Otherwise, insert then grab last_insert_id.
- if insert_id = super
- insert_id
- else
- # If neither pk nor sequence name is given, look them up.
- unless pk || sequence_name
- pk, sequence_name = *pk_and_sequence_for(table)
- end
+ pk ||= primary_key(table)
- # If a pk is given, fallback to default sequence name.
- # Don't fetch last insert id for a table without a pk.
- if pk && sequence_name ||= default_sequence_name(table, pk)
- last_insert_id(table, sequence_name)
- end
+ if pk
+ select_value("#{sql} RETURNING #{quote_column_name(pk)}")
+ else
+ super
end
end
alias :create :insert
@@ -462,42 +474,43 @@ module ActiveRecord
# create a 2D array representing the result set
def result_as_array(res) #:nodoc:
# check if we have any binary column and if they need escaping
- unescape_col = []
- res.nfields.times do |j|
- unescape_col << res.ftype(j)
+ ftypes = Array.new(res.nfields) do |i|
+ [i, res.ftype(i)]
end
- ary = []
- res.ntuples.times do |i|
- ary << []
- res.nfields.times do |j|
- data = res.getvalue(i,j)
- case unescape_col[j]
-
- # unescape string passed BYTEA field (OID == 17)
- when BYTEA_COLUMN_TYPE_OID
- data = unescape_bytea(data) if String === data
-
- # If this is a money type column and there are any currency symbols,
- # then strip them off. Indeed it would be prettier to do this in
- # PostgreSQLColumn.string_to_decimal but would break form input
- # fields that call value_before_type_cast.
- when MONEY_COLUMN_TYPE_OID
- # Because money output is formatted according to the locale, there are two
- # cases to consider (note the decimal separators):
- # (1) $12,345,678.12
- # (2) $12.345.678,12
- case data
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
- data.gsub!(/[^-\d.]/, '')
- when /^-?\D+[\d.]+,\d{2}$/ # (2)
- data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
- end
+ rows = res.values
+ return rows unless ftypes.any? { |_, x|
+ x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
+ }
+
+ typehash = ftypes.group_by { |_, type| type }
+ binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
+ monies = typehash[MONEY_COLUMN_TYPE_OID] || []
+
+ rows.each do |row|
+ # unescape string passed BYTEA field (OID == 17)
+ binaries.each do |index, _|
+ row[index] = unescape_bytea(row[index])
+ end
+
+ # If this is a money type column and there are any currency symbols,
+ # then strip them off. Indeed it would be prettier to do this in
+ # PostgreSQLColumn.string_to_decimal but would break form input
+ # fields that call value_before_type_cast.
+ monies.each do |index, _|
+ data = row[index]
+ # 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 data
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
+ data.gsub!(/[^-\d.]/, '')
+ when /^-?\D+[\d.]+,\d{2}$/ # (2)
+ data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
end
- ary[i] << data
end
end
- return ary
end
@@ -525,8 +538,8 @@ module ActiveRecord
end
end
- def substitute_for(column, current_values)
- Arel.sql("$#{current_values.length + 1}")
+ def substitute_at(column, index)
+ Arel.sql("$#{index + 1}")
end
def exec_query(sql, name = 'SQL', binds = [])
@@ -544,7 +557,7 @@ module ActiveRecord
# Clear the queue
@connection.get_last_result
@connection.send_query_prepared(key, binds.map { |col, val|
- col ? col.type_cast(val) : val
+ type_cast(val, col)
})
@connection.block
result = @connection.get_last_result
@@ -554,6 +567,18 @@ module ActiveRecord
end
end
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
+ unless pk
+ _, table = extract_schema_and_table(sql.split(" ", 4)[2])
+
+ pk = primary_key(table)
+ end
+
+ sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
+
+ [sql, binds]
+ end
+
# Executes an UPDATE query and returns the number of affected tuples.
def update_sql(sql, name = nil)
super.cmd_tuples
@@ -627,25 +652,17 @@ module ActiveRecord
execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
end
- # Drops a PostgreSQL database
+ # Drops a PostgreSQL database.
#
# Example:
# drop_database 'matt_development'
def drop_database(name) #:nodoc:
- if postgresql_version >= 80200
- execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
- else
- begin
- execute "DROP DATABASE #{quote_table_name(name)}"
- rescue ActiveRecord::StatementInvalid
- @logger.warn "#{name} database doesn't exist." if @logger
- end
- end
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
end
# Returns the list of all tables in the schema search path or a specified schema.
def tables(name = nil)
- query(<<-SQL, name).map { |row| row[0] }
+ query(<<-SQL, 'SCHEMA').map { |row| row[0] }
SELECT tablename
FROM pg_tables
WHERE schemaname = ANY (current_schemas(false))
@@ -653,7 +670,21 @@ module ActiveRecord
end
def table_exists?(name)
- name = name.to_s
+ schema, table = extract_schema_and_table(name.to_s)
+
+ binds = [[nil, table.gsub(/(^"|"$)/,'')]]
+ binds << [nil, schema] if schema
+
+ exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0
+ SELECT COUNT(*)
+ FROM pg_tables
+ WHERE tablename = $1
+ #{schema ? "AND schemaname = $2" : ''}
+ SQL
+ end
+
+ # Extracts the table and schema name from +name+
+ def extract_schema_and_table(name)
schema, table = name.split('.', 2)
unless table # A table was provided without a schema
@@ -665,13 +696,7 @@ module ActiveRecord
table = name
schema = nil
end
-
- query(<<-SQL).first[0].to_i > 0
- SELECT COUNT(*)
- FROM pg_tables
- WHERE tablename = '#{table.gsub(/(^"|"$)/,'')}'
- #{schema ? "AND schemaname = '#{schema}'" : ''}
- SQL
+ [schema, table]
end
# Returns the list of all indexes for a table.
@@ -748,37 +773,47 @@ module ActiveRecord
# Returns the current client message level.
def client_min_messages
- query('SHOW client_min_messages')[0][0]
+ query('SHOW client_min_messages', 'SCHEMA')[0][0]
end
# Set the client message level.
def client_min_messages=(level)
- execute("SET client_min_messages TO '#{level}'")
+ execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
end
# Returns the sequence name for a table's primary key or some other specified key.
def default_sequence_name(table_name, pk = nil) #:nodoc:
- default_pk, default_seq = pk_and_sequence_for(table_name)
- default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
+ serial_sequence(table_name, pk || 'id').split('.').last
+ rescue ActiveRecord::StatementInvalid
+ "#{table_name}_#{pk || 'id'}_seq"
+ end
+
+ def serial_sequence(table, column)
+ result = exec_query(<<-eosql, 'SCHEMA', [[nil, table], [nil, column]])
+ SELECT pg_get_serial_sequence($1, $2)
+ eosql
+ result.rows.first.first
end
# Resets the sequence of a table's primary key to the maximum value.
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
unless pk and sequence
default_pk, default_sequence = pk_and_sequence_for(table)
+
pk ||= default_pk
sequence ||= default_sequence
end
- if pk
- if sequence
- quoted_sequence = quote_column_name(sequence)
- select_value <<-end_sql, 'Reset sequence'
- SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
- end_sql
- else
- @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
- end
+ if @logger && pk && !sequence
+ @logger.warn "#{table} has primary key #{pk} with no default sequence"
+ end
+
+ if pk && sequence
+ quoted_sequence = quote_column_name(sequence)
+
+ select_value <<-end_sql, 'Reset sequence'
+ SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
+ end_sql
end
end
@@ -786,7 +821,7 @@ 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, 'PK and serial sequence').rows.first
+ result = exec_query(<<-end_sql, 'SCHEMA').rows.first
SELECT attr.attname, seq.relname
FROM pg_class seq,
pg_attribute attr,
@@ -803,28 +838,6 @@ module ActiveRecord
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
end_sql
- 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
-
# [primary_key, sequence]
[result.first, result.last]
rescue
@@ -833,8 +846,21 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table)
- pk_and_sequence = pk_and_sequence_for(table)
- pk_and_sequence && pk_and_sequence.first
+ row = exec_query(<<-end_sql, 'SCHEMA', [[nil, table]]).rows.first
+ SELECT DISTINCT(attr.attname)
+ FROM pg_attribute attr,
+ pg_depend dep,
+ pg_namespace name,
+ pg_constraint cons
+ WHERE 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 = $1::regclass
+ end_sql
+
+ row && row.first
end
# Renames a table.
@@ -848,38 +874,14 @@ module ActiveRecord
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
add_column_options!(add_column_sql, options)
- begin
- execute add_column_sql
- rescue ActiveRecord::StatementInvalid => e
- raise e if postgresql_version > 80000
-
- execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}")
- change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
- change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
- end
+ execute add_column_sql
end
# Changes the column of a table.
def change_column(table_name, column_name, type, options = {})
quoted_table_name = quote_table_name(table_name)
- begin
- execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- rescue ActiveRecord::StatementInvalid => e
- raise e if postgresql_version > 80000
- # This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
- begin
- begin_db_transaction
- tmp_column_name = "#{column_name}_ar_tmp"
- add_column(table_name, tmp_column_name, type, options)
- execute "UPDATE #{quoted_table_name} SET #{quote_column_name(tmp_column_name)} = CAST(#{quote_column_name(column_name)} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
- remove_column(table_name, column_name)
- rename_column(table_name, tmp_column_name, column_name)
- commit_db_transaction
- rescue
- rollback_db_transaction
- end
- end
+ execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
@@ -942,10 +944,7 @@ module ActiveRecord
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}" }
- # Return a DISTINCT ON() clause that's distinct on the columns we want but includes
- # all the required columns for the ORDER BY to work properly.
- sql = "DISTINCT ON (#{columns}) #{columns}, "
- sql << order_columns * ', '
+ "DISTINCT #{columns}, #{order_columns * ', '}"
end
protected
@@ -1031,15 +1030,16 @@ module ActiveRecord
# If using Active Record's time zone support configure the connection to return
# TIMESTAMP WITH ZONE types in UTC.
if ActiveRecord::Base.default_timezone == :utc
- execute("SET time zone 'UTC'")
+ execute("SET time zone 'UTC'", 'SCHEMA')
elsif @local_tz
- execute("SET time zone '#{@local_tz}'")
+ execute("SET time zone '#{@local_tz}'", 'SCHEMA')
end
end
# Returns the current ID of a table's sequence.
- def last_insert_id(table, sequence_name) #:nodoc:
- Integer(select_value("SELECT currval('#{sequence_name}')"))
+ def last_insert_id(sequence_name) #:nodoc:
+ r = exec_query("SELECT currval($1)", 'SQL', [[nil, sequence_name]])
+ Integer(r.rows.first.first)
end
# Executes a SELECT query and returns the results, performing any data type
@@ -1075,7 +1075,7 @@ module ActiveRecord
# - format_type includes the column size constraint, e.g. varchar(50)
# - ::regclass is a function that gives the id for a table name
def column_definitions(table_name) #:nodoc:
- exec_query(<<-end_sql).rows
+ exec_query(<<-end_sql, 'SCHEMA').rows
SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
@@ -1101,4 +1101,3 @@ module ActiveRecord
end
end
end
-
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index c2cd9e8d5e..c3a7b039ff 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -34,6 +34,14 @@ module ActiveRecord
module ConnectionAdapters #:nodoc:
class SQLite3Adapter < SQLiteAdapter # :nodoc:
+ def quote(value, column = nil)
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
+ s = column.class.string_to_binary(value).unpack("H*")[0]
+ "x'#{s}'"
+ else
+ super
+ end
+ end
# Returns the current database encoding format as a string, eg: 'UTF-8'
def encoding
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 9ee6b88ab6..ed5006dcec 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -66,16 +66,18 @@ module ActiveRecord
sqlite_version >= '3.6.8'
end
- # Returns +true+ when the connection adapter supports prepared statement
- # caching, otherwise returns +false+
+ # Returns true, since this connection adapter supports prepared statement
+ # caching.
def supports_statement_cache?
true
end
+ # Returns true.
def supports_migrations? #:nodoc:
true
end
+ # Returns true.
def supports_primary_key? #:nodoc:
true
end
@@ -88,12 +90,15 @@ module ActiveRecord
sqlite_version >= '3.1.6'
end
+ # Disconnects from the database if already connected. Otherwise, this
+ # method does nothing.
def disconnect!
super
clear_cache!
@connection.close rescue nil
end
+ # Clears the prepared statements cache.
def clear_cache!
@statements.clear
end
@@ -165,7 +170,7 @@ module ActiveRecord
cols = cache[:cols] ||= stmt.columns
stmt.reset!
stmt.bind_params binds.map { |col, val|
- col ? col.type_cast(val) : val
+ type_cast(val, col)
}
end
@@ -173,6 +178,10 @@ module ActiveRecord
end
end
+ def last_inserted_id(result)
+ @connection.last_insert_row_id
+ end
+
def execute(sql, name = nil) #:nodoc:
log(sql, name) { @connection.execute(sql) }
end
@@ -188,7 +197,8 @@ module ActiveRecord
end
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
- super || @connection.last_insert_row_id
+ super
+ id_value || @connection.last_insert_row_id
end
alias :create :insert_sql
@@ -222,7 +232,7 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
- def tables(name = nil) #:nodoc:
+ def tables(name = 'SCHEMA') #:nodoc:
sql = <<-SQL
SELECT name
FROM sqlite_master
@@ -346,7 +356,7 @@ module ActiveRecord
end
def table_structure(table_name)
- structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})").to_hash
+ structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
structure
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index d523c643ba..0939ec2626 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -173,10 +173,10 @@ class FixturesFileNotFound < StandardError; end
# traversed in the database to create the fixture hash and/or instance variables. This is expensive for
# large sets of fixtured data.
#
-# = Dynamic fixtures with ERb
+# = Dynamic fixtures with ERB
#
# Some times you don't care about the content of the fixtures as much as you care about the volume. In these cases, you can
-# mix ERb in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like:
+# mix ERB in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like:
#
# <% for i in 1..1000 %>
# fix_<%= i %>:
@@ -186,7 +186,7 @@ class FixturesFileNotFound < StandardError; end
#
# This will create 1000 very simple YAML fixtures.
#
-# Using ERb, you can also inject dynamic values into your fixtures with inserts like <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
+# Using ERB, you can also inject dynamic values into your fixtures with inserts like <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
# This is however a feature to be used with some caution. The point of fixtures are that they're
# stable units of predictable sample data. If you feel that you need to inject dynamic values, then
# perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb
index d18b2b0a54..95a8e5cff7 100644
--- a/activerecord/lib/active_record/identity_map.rb
+++ b/activerecord/lib/active_record/identity_map.rb
@@ -50,7 +50,14 @@ module ActiveRecord
def get(klass, primary_key)
obj = repository[klass.symbolized_base_class][primary_key]
- obj.is_a?(klass) ? obj : nil
+ if obj.is_a?(klass)
+ if ActiveRecord::Base.logger
+ ActiveRecord::Base.logger.debug "#{klass} with ID = #{primary_key} loaded from Identity Map"
+ end
+ obj
+ else
+ nil
+ end
end
def add(record)
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 6b2b1ebafe..9a31675782 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -23,7 +23,7 @@ module ActiveRecord
# p2.first_name = "should fail"
# p2.save # Raises a ActiveRecord::StaleObjectError
#
- # Optimistic locking will also check for stale data when objects are destroyed. Example:
+ # Optimistic locking will also check for stale data when objects are destroyed. Example:
#
# p1 = Person.find(1)
# p2 = Person.find(1)
diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb
index 557b277d6b..862cf8f72a 100644
--- a/activerecord/lib/active_record/locking/pessimistic.rb
+++ b/activerecord/lib/active_record/locking/pessimistic.rb
@@ -9,9 +9,8 @@ module ActiveRecord
# Account.find(1, :lock => true)
#
# Pass <tt>:lock => 'some locking clause'</tt> to give a database-specific locking clause
- # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'.
+ # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
#
- # Example:
# Account.transaction do
# # select * from accounts where name = 'shugo' limit 1 for update
# shugo = Account.where("name = 'shugo'").lock(true).first
@@ -24,6 +23,7 @@ module ActiveRecord
#
# You can also use ActiveRecord::Base#lock! method to lock one record by id.
# This may be better if you don't need to lock every row. Example:
+ #
# Account.transaction do
# # select * from accounts where ...
# accounts = Account.where(...).all
@@ -44,7 +44,7 @@ module ActiveRecord
module Pessimistic
# Obtain a row lock on this record. Reloads the record to obtain the requested
# lock. Pass an SQL locking clause to append the end of the SELECT statement
- # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
+ # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
# the locked record.
def lock!(lock = true)
reload(:lock => lock) if persisted?
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index afadbf03ef..d31e321440 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -23,6 +23,9 @@ module ActiveRecord
return unless logger.debug?
payload = event.payload
+
+ return if 'SCHEMA' == payload[:name]
+
name = '%s (%.1fms)' % [payload[:name], event.duration]
sql = payload[:sql].squeeze(' ')
binds = nil
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index d291632260..588f52be44 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -9,11 +9,6 @@ module ActiveRecord
module NamedScope
extend ActiveSupport::Concern
- included do
- class_attribute :scopes
- self.scopes = {}
- end
-
module ClassMethods
# Returns an anonymous \scope.
#
@@ -35,7 +30,13 @@ module ActiveRecord
if options
scoped.apply_finder_options(options)
else
- current_scoped_methods ? relation.merge(current_scoped_methods) : relation.clone
+ if current_scope
+ current_scope.clone
+ else
+ scope = relation.clone
+ scope.default_scoped = true
+ scope
+ end
end
end
@@ -50,6 +51,14 @@ module ActiveRecord
# The above calls to <tt>scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
# in effect, represents the query <tt>Shirt.where(:color => 'red')</tt>.
#
+ # Note that this is simply 'syntactic sugar' for defining an actual class method:
+ #
+ # class Shirt < ActiveRecord::Base
+ # def self.red
+ # where(:color => 'red')
+ # end
+ # end
+ #
# Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it
# resembles the association object constructed by a <tt>has_many</tt> declaration. For instance,
# you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>, <tt>Shirt.red.where(:size => 'small')</tt>.
@@ -76,11 +85,31 @@ module ActiveRecord
# Named \scopes can also be procedural:
#
# class Shirt < ActiveRecord::Base
- # scope :colored, lambda {|color| where(:color => color) }
+ # scope :colored, lambda { |color| where(:color => color) }
# end
#
# In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
#
+ # On Ruby 1.9 you can use the 'stabby lambda' syntax:
+ #
+ # scope :colored, ->(color) { where(:color => color) }
+ #
+ # Note that scopes defined with \scope will be evaluated when they are defined, rather than
+ # when they are used. For example, the following would be incorrect:
+ #
+ # class Post < ActiveRecord::Base
+ # scope :recent, where('published_at >= ?', Time.now - 1.week)
+ # end
+ #
+ # The example above would be 'frozen' to the <tt>Time.now</tt> value when the <tt>Post</tt>
+ # class was defined, and so the resultant SQL query would always be the same. The correct
+ # way to do this would be via a lambda, which will re-evaluate the scope each time
+ # it is called:
+ #
+ # class Post < ActiveRecord::Base
+ # scope :recent, lambda { where('published_at >= ?', Time.now - 1.week) }
+ # end
+ #
# Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
#
# class Shirt < ActiveRecord::Base
@@ -99,6 +128,29 @@ module ActiveRecord
#
# Article.published.new.published # => true
# Article.published.create.published # => true
+ #
+ # Class methods on your model are automatically available
+ # on scopes. Assuming the following setup:
+ #
+ # class Article < ActiveRecord::Base
+ # scope :published, where(:published => true)
+ # scope :featured, where(:featured => true)
+ #
+ # def self.latest_article
+ # order('published_at desc').first
+ # end
+ #
+ # def self.titles
+ # map(&:title)
+ # end
+ #
+ # end
+ #
+ # We are able to call the methods like this:
+ #
+ # Article.published.featured.latest_article
+ # Article.featured.titles
+
def scope(name, scope_options = {})
name = name.to_sym
valid_scope_name?(name)
@@ -106,27 +158,20 @@ module ActiveRecord
scope_proc = lambda do |*args|
options = scope_options.respond_to?(:call) ? scope_options.call(*args) : scope_options
+ options = scoped.apply_finder_options(options) if options.is_a?(Hash)
- relation = if options.is_a?(Hash)
- scoped.apply_finder_options(options)
- elsif options
- scoped.merge(options)
- else
- scoped
- end
+ relation = scoped.merge(options)
extension ? relation.extending(extension) : relation
end
- self.scopes = self.scopes.merge name => scope_proc
-
- singleton_class.send(:redefine_method, name, &scopes[name])
+ singleton_class.send(:redefine_method, name, &scope_proc)
end
protected
def valid_scope_name?(name)
- if !scopes[name] && respond_to?(name, true)
+ if respond_to?(name, true)
logger.warn "Creating scope :#{name}. " \
"Overwriting existing method #{self.name}.#{name}."
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 522c0cfc9f..08b27b6a8e 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -403,12 +403,6 @@ module ActiveRecord
unless reject_new_record?(association_name, attributes)
association.build(attributes.except(*UNASSIGNABLE_KEYS))
end
- elsif existing_records.count == 0 #Existing record but not yet associated
- existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
- if !call_reject_if(association_name, attributes)
- association.send(:add_record_to_target_with_callbacks, existing_record) if !association.loaded?
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
- end
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
unless association.loaded? || call_reject_if(association_name, attributes)
# Make sure we are operating on the actual object which is in the association's
@@ -452,6 +446,7 @@ module ActiveRecord
end
def call_reject_if(association_name, attributes)
+ return false if has_destroy_flag?(attributes)
case callback = self.nested_attributes_options[association_name][:reject_if]
when Symbol
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index df7b22080c..787ac977e0 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -119,25 +119,44 @@ module ActiveRecord
save(:validate => false)
end
+ # Updates a single attribute of an object, without calling save.
+ #
+ # * Validation is skipped.
+ # * Callbacks are skipped.
+ # * updated_at/updated_on column is not updated if that column is available.
+ #
+ def update_column(name, value)
+ name = name.to_s
+ raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
+ raise ActiveRecordError, "can not update on a new record object" unless persisted?
+ raw_write_attribute(name, value)
+ self.class.update_all({ name => value }, self.class.primary_key => id) == 1
+ end
+
# Updates the attributes of the model from the passed-in hash and saves the
# record, all wrapped in a transaction. If the object is invalid, the saving
# will fail and false will be returned.
- def update_attributes(attributes)
+ #
+ # When updating model attributes, mass-assignment security protection is respected.
+ # If no +:as+ option is supplied then the +:default+ scope will be used.
+ # If you want to bypass the protection given by +attr_protected+ and
+ # +attr_accessible+ then you can do so using the +:without_protection+ option.
+ def update_attributes(attributes, options = {})
# The following transaction covers any possible database side-effects of the
# attributes assignment. For example, setting the IDs of a child collection.
with_transaction_returning_status do
- self.attributes = attributes
+ self.assign_attributes(attributes, options)
save
end
end
# Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
# of +save+, so an exception is raised if the record is invalid.
- def update_attributes!(attributes)
+ def update_attributes!(attributes, options = {})
# The following transaction covers any possible database side-effects of the
# attributes assignment. For example, setting the IDs of a child collection.
with_transaction_returning_status do
- self.attributes = attributes
+ self.assign_attributes(attributes, options)
save!
end
end
@@ -270,17 +289,9 @@ module ActiveRecord
# Creates a record with values matching those of the instance attributes
# and returns its id.
def create
- if id.nil? && connection.prefetch_primary_key?(self.class.table_name)
- self.id = connection.next_sequence_value(self.class.sequence_name)
- end
-
attributes_values = arel_attributes_values(!id.nil?)
- new_id = if attributes_values.empty?
- self.class.unscoped.insert connection.empty_insert_statement_value
- else
- self.class.unscoped.insert attributes_values
- end
+ new_id = self.class.unscoped.insert attributes_values
self.id ||= new_id
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index cace6f0cc0..d38588519b 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -50,6 +50,9 @@ module ActiveRecord
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
+ if app.config.active_record.delete(:whitelist_attributes)
+ attr_accessible(nil)
+ end
app.config.active_record.each do |k,v|
send "#{k}=", v
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index ff36814684..7d76d7a19f 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/inclusion'
+
db_namespace = namespace :db do
task :load_config => :rails_env do
require 'active_record'
@@ -68,7 +70,13 @@ db_namespace = namespace :db do
@charset = ENV['CHARSET'] || 'utf8'
@collation = ENV['COLLATION'] || 'utf8_unicode_ci'
creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)}
- error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error
+ if config['adapter'] =~ /jdbc/
+ #FIXME After Jdbcmysql gives this class
+ require 'active_record/railties/jdbcmysql_error'
+ error_class = ArJdbcMySQL::Error
+ else
+ error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error
+ end
access_denied_error = 1045
begin
ActiveRecord::Base.establish_connection(config.merge('database' => nil))
@@ -92,7 +100,7 @@ db_namespace = namespace :db do
$stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['charset']
end
end
- when 'postgresql'
+ when /postgresql/
@encoding = config['encoding'] || ENV['CHARSET'] || 'utf8'
begin
ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
@@ -135,7 +143,7 @@ db_namespace = namespace :db do
end
def local_database?(config, &block)
- if %w( 127.0.0.1 localhost ).include?(config['host']) || config['host'].blank?
+ if config['host'].in?(["127.0.0.1", "localhost"]) || config['host'].blank?
yield
else
$stderr.puts "This task only modifies local databases. #{config['database']} is on a remote host."
diff --git a/activerecord/lib/active_record/railties/jdbcmysql_error.rb b/activerecord/lib/active_record/railties/jdbcmysql_error.rb
new file mode 100644
index 0000000000..6b9af2a0cb
--- /dev/null
+++ b/activerecord/lib/active_record/railties/jdbcmysql_error.rb
@@ -0,0 +1,16 @@
+#FIXME Remove if ArJdbcMysql will give.
+module ArJdbcMySQL
+ class Error < StandardError
+ attr_accessor :error_number, :sql_state
+
+ def initialize msg
+ super
+ @error_number = nil
+ @sql_state = nil
+ end
+
+ # Mysql gem compatibility
+ alias_method :errno, :error_number
+ alias_method :error, :message
+ end
+end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 5de08953f9..bcba85d7a4 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/module/deprecation'
+require 'active_support/core_ext/object/inclusion'
module ActiveRecord
# = Active Record Reflection
@@ -163,7 +164,7 @@ module ActiveRecord
def initialize(macro, name, options, active_record)
super
- @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
+ @collection = macro.in?([:has_many, :has_and_belongs_to_many])
end
# Returns a new, unsaved instance of the associated class. +options+ will
@@ -262,16 +263,30 @@ module ActiveRecord
end
def through_reflection
- false
- end
-
- def through_reflection_foreign_key
+ nil
end
def source_reflection
nil
end
+ # A chain of reflections from this one back to the owner. For more see the explanation in
+ # ThroughReflection.
+ def chain
+ [self]
+ end
+
+ # An array of arrays of conditions. Each item in the outside array corresponds to a reflection
+ # in the #chain. The inside arrays are simply conditions (and each condition may itself be
+ # a hash, array, arel predicate, etc...)
+ def conditions
+ conditions = [options[:conditions]].compact
+ conditions << { type => active_record.base_class.name } if options[:as]
+ [conditions]
+ end
+
+ alias :source_macro :macro
+
def has_inverse?
@options[:inverse_of]
end
@@ -363,7 +378,7 @@ module ActiveRecord
# Holds all the meta-data about a :through association as it was specified
# in the Active Record class.
class ThroughReflection < AssociationReflection #:nodoc:
- delegate :association_primary_key, :foreign_type, :to => :source_reflection
+ delegate :foreign_key, :foreign_type, :association_foreign_key, :active_record_primary_key, :to => :source_reflection
# Gets the source of the through reflection. It checks both a singularized
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
@@ -392,6 +407,88 @@ module ActiveRecord
@through_reflection ||= active_record.reflect_on_association(options[:through])
end
+ # Returns an array of reflections which are involved in this association. Each item in the
+ # array corresponds to a table which will be part of the query for this association.
+ #
+ # The chain is built by recursively calling #chain on the source reflection and the through
+ # reflection. The base case for the recursion is a normal association, which just returns
+ # [self] as its #chain.
+ def chain
+ @chain ||= begin
+ chain = source_reflection.chain + through_reflection.chain
+ chain[0] = self # Use self so we don't lose the information from :source_type
+ chain
+ end
+ end
+
+ # Consider the following example:
+ #
+ # class Person
+ # has_many :articles
+ # has_many :comment_tags, :through => :articles
+ # end
+ #
+ # class Article
+ # has_many :comments
+ # has_many :comment_tags, :through => :comments, :source => :tags
+ # end
+ #
+ # class Comment
+ # has_many :tags
+ # end
+ #
+ # There may be conditions on Person.comment_tags, Article.comment_tags and/or Comment.tags,
+ # but only Comment.tags will be represented in the #chain. So this method creates an array
+ # of conditions corresponding to the chain. Each item in the #conditions array corresponds
+ # to an item in the #chain, and is itself an array of conditions from an arbitrary number
+ # of relevant reflections, plus any :source_type or polymorphic :as constraints.
+ def conditions
+ @conditions ||= begin
+ conditions = source_reflection.conditions
+
+ # Add to it the conditions from this reflection if necessary.
+ conditions.first << options[:conditions] if options[:conditions]
+
+ through_conditions = through_reflection.conditions
+
+ if options[:source_type]
+ through_conditions.first << { foreign_type => options[:source_type] }
+ end
+
+ # Recursively fill out the rest of the array from the through reflection
+ conditions += through_conditions
+
+ # And return
+ conditions
+ end
+ end
+
+ # The macro used by the source association
+ def source_macro
+ source_reflection.source_macro
+ end
+
+ # A through association is nested iff there would be more than one join table
+ def nested?
+ chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
+ end
+
+ # We want to use the klass from this reflection, rather than just delegate straight to
+ # the source_reflection, because the source_reflection may be polymorphic. We still
+ # need to respect the source_reflection's :primary_key option, though.
+ def association_primary_key
+ @association_primary_key ||= begin
+ # Get the "actual" source reflection if the immediate source reflection has a
+ # source reflection itself
+ source_reflection = self.source_reflection
+ while source_reflection.source_reflection
+ source_reflection = source_reflection.source_reflection
+ end
+
+ source_reflection.options[:primary_key] || klass.primary_key
+ end
+ end
+
# Gets an array of possible <tt>:through</tt> source reflection names:
#
# [:singularized, :pluralized]
@@ -429,10 +526,6 @@ module ActiveRecord
raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
end
- unless [:belongs_to, :has_many, :has_one].include?(source_reflection.macro) && source_reflection.options[:through].nil?
- raise HasManyThroughSourceAssociationMacroError.new(self)
- end
-
if macro == :has_one && through_reflection.collection?
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
end
@@ -440,14 +533,6 @@ module ActiveRecord
check_validity_of_inverse!
end
- def through_reflection_primary_key
- through_reflection.belongs_to? ? through_reflection.klass.primary_key : through_reflection.foreign_key
- end
-
- def through_reflection_foreign_key
- through_reflection.foreign_key if through_reflection.belongs_to?
- end
-
private
def derive_class_name
# get the class_name of the belongs_to association of the through reflection
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 5af20bf38b..8e5f66ec1d 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -6,23 +6,25 @@ module ActiveRecord
JoinOperation = Struct.new(:relation, :join_class, :on)
ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind]
- SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from]
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from, :reorder]
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
# These are explicitly delegated to improve performance (avoids method_missing)
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
- delegate :table_name, :primary_key, :to => :klass
+ delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :to => :klass
attr_reader :table, :klass, :loaded
- attr_accessor :extensions
+ attr_accessor :extensions, :default_scoped
alias :loaded? :loaded
+ alias :default_scoped? :default_scoped
def initialize(klass, table)
@klass, @table = klass, table
@implicit_readonly = nil
@loaded = false
+ @default_scoped = false
SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
(ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
@@ -30,22 +32,46 @@ module ActiveRecord
end
def insert(values)
- im = arel.compile_insert values
- im.into @table
-
primary_key_value = nil
if primary_key && Hash === values
primary_key_value = values[values.keys.find { |k|
k.name == primary_key
}]
+
+ if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
+ primary_key_value = connection.next_sequence_value(klass.sequence_name)
+ values[klass.arel_table[klass.primary_key]] = primary_key_value
+ end
+ end
+
+ im = arel.create_insert
+ im.into @table
+
+ conn = @klass.connection
+
+ substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
+ binds = substitutes.map do |arel_attr, value|
+ [@klass.columns_hash[arel_attr.name], value]
+ end
+
+ substitutes.each_with_index do |tuple, i|
+ tuple[1] = conn.substitute_at(binds[i][0], i)
end
- @klass.connection.insert(
+ if values.empty? # empty insert
+ im.values = Arel.sql(connection.empty_insert_statement_value)
+ else
+ im.insert substitutes
+ end
+
+ conn.insert(
im.to_sql,
'SQL',
primary_key,
- primary_key_value)
+ primary_key_value,
+ nil,
+ binds)
end
def new(*args, &block)
@@ -110,7 +136,10 @@ module ActiveRecord
# Returns true if there are no records.
def empty?
- loaded? ? @records.empty? : count.zero?
+ return @records.empty? if loaded?
+
+ c = count
+ c.respond_to?(:zero?) ? c.zero? : c.empty?
end
def any?
@@ -140,12 +169,7 @@ module ActiveRecord
# Please check unscoped if you want to remove all previous scopes (including
# the default_scope) during the execution of a block.
def scoping
- @klass.scoped_methods << self
- begin
- yield
- ensure
- @klass.scoped_methods.pop
- end
+ @klass.send(:with_scope, self, :overwrite) { yield }
end
# Updates all records with details given if they match a set of conditions supplied, limits and order can
@@ -180,6 +204,7 @@ module ActiveRecord
# # The same idea applies to limit and order
# Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(:author => 'David')
def update_all(updates, conditions = nil, options = {})
+ IdentityMap.repository[symbolized_base_class].clear if IdentityMap.enabled?
if conditions || options.present?
where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
else
@@ -308,6 +333,7 @@ module ActiveRecord
# If you need to destroy dependent associations or call your <tt>before_*</tt> or
# +after_destroy+ callbacks, use the +destroy_all+ method instead.
def delete_all(conditions = nil)
+ IdentityMap.repository[symbolized_base_class] = {} if IdentityMap.enabled?
if conditions
where(conditions).delete_all
else
@@ -339,6 +365,7 @@ module ActiveRecord
# # Delete multiple rows
# Todo.delete([2,3,4])
def delete(id_or_array)
+ IdentityMap.remove_by_id(self.symbolized_base_class, id_or_array) if IdentityMap.enabled?
where(primary_key => id_or_array).delete_all
end
@@ -360,7 +387,7 @@ module ActiveRecord
end
def where_values_hash
- equalities = @where_values.grep(Arel::Nodes::Equality).find_all { |node|
+ equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node|
node.left.relation.name == table_name
}
@@ -388,13 +415,20 @@ module ActiveRecord
to_a.inspect
end
+ def with_default_scope #:nodoc:
+ if default_scoped?
+ default_scope = @klass.send(:build_default_scope)
+ default_scope ? default_scope.merge(self) : self
+ else
+ self
+ end
+ end
+
protected
def method_missing(method, *args, &block)
if Array.method_defined?(method)
to_a.send(method, *args, &block)
- elsif @klass.scopes[method]
- merge(@klass.send(method, *args, &block))
elsif @klass.respond_to?(method)
scoping { @klass.send(method, *args, &block) }
elsif arel.respond_to?(method)
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index bf5a60f458..d52b84179f 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -83,7 +83,7 @@ module ActiveRecord
private
def batch_order
- "#{table_name}.#{primary_key} ASC"
+ "#{quoted_table_name}.#{quoted_primary_key} ASC"
end
end
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index c1842b1a96..869eebfa34 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -196,24 +196,22 @@ module ActiveRecord
end
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
- column = aggregate_column(column_name)
-
# Postgresql doesn't like ORDER BY when there are no GROUP BY
relation = except(:order)
- select_value = operation_over_aggregate_column(column, operation, distinct)
- relation.select_values = [select_value]
+ if operation == "count" && (relation.limit_value || relation.offset_value)
+ # Shortcut when limit is zero.
+ return 0 if relation.limit_value == 0
- query_builder = relation.arel
+ query_builder = build_count_subquery(relation, column_name, distinct)
+ else
+ column = aggregate_column(column_name)
- if operation == "count"
- limit = relation.limit_value
- offset = relation.offset_value
+ select_value = operation_over_aggregate_column(column, operation, distinct)
- unless limit && offset
- query_builder.limit = nil
- query_builder.offset = nil
- end
+ relation.select_values = [select_value]
+
+ query_builder = relation.arel
end
type_cast_calculated_value(@klass.connection.select_value(query_builder.to_sql), column_for(column_name), operation)
@@ -312,5 +310,18 @@ module ActiveRecord
select if select !~ /(,|\*)/
end
end
+
+ def build_count_subquery(relation, column_name, distinct)
+ column_alias = Arel.sql('count_column')
+ subquery_alias = Arel.sql('subquery_for_count')
+
+ aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
+ relation.select_values = [aliased_column]
+ subquery = relation.arel.as(subquery_alias)
+
+ sm = Arel::SelectManager.new relation.engine
+ select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
+ sm.project(select_value).from(subquery)
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 426000fde1..57c9921ea8 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -123,6 +123,12 @@ module ActiveRecord
end
end
+ # Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found. Note that <tt>first!</tt> accepts no arguments.
+ def first!
+ first or raise RecordNotFound
+ end
+
# A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
# same arguments to this method as you can to <tt>find(:last)</tt>.
def last(*args)
@@ -137,6 +143,12 @@ module ActiveRecord
end
end
+ # Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found. Note that <tt>last!</tt> accepts no arguments.
+ def last!
+ last or raise RecordNotFound
+ end
+
# A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the
# same arguments to this method as you can to <tt>find(:all)</tt>.
def all(*args)
@@ -171,7 +183,9 @@ module ActiveRecord
def exists?(id = nil)
id = id.id if ActiveRecord::Base === id
- relation = select("1").limit(1)
+ join_dependency = construct_join_dependency_for_association_find
+ relation = construct_relation_for_association_find(join_dependency)
+ relation = relation.except(:select).select("1").limit(1)
case id
when Array, Hash
@@ -180,14 +194,13 @@ module ActiveRecord
relation = relation.where(table[primary_key].eq(id)) if id
end
- relation.first ? true : false
+ connection.select_value(relation.to_sql) ? true : false
end
protected
def find_with_associations
- including = (@eager_load_values + @includes_values).uniq
- join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
+ join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
rows = connection.select_all(relation.to_sql, 'SQL', relation.bind_values)
join_dependency.instantiate(rows)
@@ -195,6 +208,11 @@ module ActiveRecord
[]
end
+ def construct_join_dependency_for_association_find
+ including = (@eager_load_values + @includes_values).uniq
+ ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
+ end
+
def construct_relation_for_association_calculations
including = (@eager_load_values + @includes_values).uniq
join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
@@ -228,6 +246,8 @@ module ActiveRecord
orders = relation.order_values
values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
+ relation = relation.dup
+
ids_array = relation.select(values).collect {|row| row[primary_key]}
ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
end
@@ -259,8 +279,8 @@ module ActiveRecord
unless record
record = @klass.new do |r|
- r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty?
- r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty?
+ r.assign_attributes(protected_attributes_for_create)
+ r.assign_attributes(unprotected_attributes_for_create, :without_protection => true)
end
yield(record) if block_given?
record.save if match.instantiator == :create
@@ -291,9 +311,18 @@ module ActiveRecord
def find_one(id)
id = id.id if ActiveRecord::Base === id
+ if IdentityMap.enabled? && where_values.blank? &&
+ limit_value.blank? && order_values.blank? &&
+ includes_values.blank? && preload_values.blank? &&
+ readonly_value.nil? && joins_values.blank? &&
+ !@klass.locking_enabled? &&
+ record = IdentityMap.get(@klass, id)
+ return record
+ end
+
column = columns_hash[primary_key]
- substitute = connection.substitute_for(column, @bind_values)
+ substitute = connection.substitute_at(column, @bind_values.length)
relation = where(table[primary_key].eq(substitute))
relation.bind_values = [[column, id]]
record = relation.first
@@ -325,8 +354,8 @@ module ActiveRecord
if result.size == expected_size
result
else
- conditions = arel.wheres.map { |x| x.value }.join(', ')
- conditions = " [WHERE #{conditions}]" if conditions.present?
+ conditions = arel.where_sql
+ conditions = " [#{conditions}]" if conditions
error = "Couldn't find all #{@klass.name.pluralize} with IDs "
error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index cd1d7108b3..94aa999715 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -8,7 +8,8 @@ module ActiveRecord
attr_accessor :includes_values, :eager_load_values, :preload_values,
:select_values, :group_values, :order_values, :joins_values,
:where_values, :having_values, :bind_values,
- :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value
+ :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value,
+ :from_value, :reorder_value
def includes(*args)
args.reject! {|a| a.blank? }
@@ -62,6 +63,14 @@ module ActiveRecord
relation
end
+ def reorder(*args)
+ return self if args.blank?
+
+ relation = clone
+ relation.reorder_value = args.flatten
+ relation
+ end
+
def joins(*args)
return self if args.compact.blank?
@@ -159,7 +168,7 @@ module ActiveRecord
end
def arel
- @arel ||= build_arel
+ @arel ||= with_default_scope.build_arel
end
def build_arel
@@ -176,7 +185,8 @@ module ActiveRecord
arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
- arel.order(*@order_values.uniq.reject{|o| o.blank?}) unless @order_values.empty?
+ order = @reorder_value ? @reorder_value : @order_values
+ arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
build_select(arel, @select_values.uniq)
@@ -261,7 +271,7 @@ module ActiveRecord
)
join_nodes.each do |join|
- join_dependency.table_aliases[join.left.name.downcase] = 1
+ join_dependency.alias_tracker.aliased_name_for(join.left.name.downcase)
end
join_dependency.graft(*stashed_association_joins)
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 4150e36a9a..69706b5ead 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -8,6 +8,8 @@ module ActiveRecord
merged_relation = clone
+ r = r.with_default_scope if r.default_scoped? && r.klass != klass
+
Relation::ASSOCIATION_METHODS.each do |method|
value = r.send(:"#{method}_values")
@@ -70,6 +72,7 @@ module ActiveRecord
#
def except(*skips)
result = self.class.new(@klass, table)
+ result.default_scoped = default_scoped
((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - skips).each do |method|
result.send(:"#{method}_values=", send(:"#{method}_values"))
@@ -79,6 +82,9 @@ module ActiveRecord
result.send(:"#{method}_value=", send(:"#{method}_value"))
end
+ # Apply scope extension modules
+ result.send(:apply_modules, extensions)
+
result
end
@@ -91,6 +97,7 @@ module ActiveRecord
#
def only(*onlies)
result = self.class.new(@klass, table)
+ result.default_scoped = default_scoped
((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) & onlies).each do |method|
result.send(:"#{method}_values=", send(:"#{method}_values"))
@@ -100,6 +107,9 @@ module ActiveRecord
result.send(:"#{method}_value=", send(:"#{method}_value"))
end
+ # Apply scope extension modules
+ result.send(:apply_modules, extensions)
+
result
end
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 0465b21e88..243012f88c 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -1,9 +1,9 @@
module ActiveRecord
###
- # This class encapsulates a Result returned from calling +exec+ on any
+ # This class encapsulates a Result returned from calling +exec_query+ on any
# database connection adapter. For example:
#
- # x = ActiveRecord::Base.connection.exec('SELECT * FROM foo')
+ # x = ActiveRecord::Base.connection.exec_query('SELECT * FROM foo')
# x # => #<ActiveRecord::Result:0xdeadbeef>
class Result
include Enumerable
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 60d4c256c4..d363f36108 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -270,6 +270,7 @@ module ActiveRecord
def rolledback!(force_restore_state = false) #:nodoc:
run_callbacks :rollback
ensure
+ IdentityMap.remove(self) if IdentityMap.enabled?
restore_transaction_record_state(force_restore_state)
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index a96796f9ff..4db4105389 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -14,6 +14,7 @@ module ActiveRecord
def validate_each(record, attribute, value)
finder_class = find_finder_class_for(record)
+ table = finder_class.arel_table
coder = record.class.serialized_attributes[attribute.to_s]
@@ -21,21 +22,15 @@ module ActiveRecord
value = coder.dump value
end
- sql, params = mount_sql_and_params(finder_class, record.class.quoted_table_name, attribute, value)
-
- relation = finder_class.unscoped.where(sql, *params)
+ relation = build_relation(finder_class, table, attribute, value)
+ relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.send(:id))) if record.persisted?
Array.wrap(options[:scope]).each do |scope_item|
scope_value = record.send(scope_item)
- relation = relation.where(scope_item => scope_value)
- end
-
- if record.persisted?
- # TODO : This should be in Arel
- relation = relation.where("#{record.class.quoted_table_name}.#{record.class.primary_key} <> ?", record.send(:id))
+ relation = relation.and(table[scope_item].eq(scope_value))
end
- if relation.exists?
+ if finder_class.unscoped.where(relation).exists?
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
end
end
@@ -57,27 +52,19 @@ module ActiveRecord
class_hierarchy.detect { |klass| !klass.abstract_class? }
end
- def mount_sql_and_params(klass, table_name, attribute, value) #:nodoc:
+ def build_relation(klass, table, attribute, value) #:nodoc:
column = klass.columns_hash[attribute.to_s]
+ value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text?
- operator = if value.nil?
- "IS ?"
- elsif column.text?
- value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s
- "#{klass.connection.case_sensitive_equality_operator} ?"
- else
- "= ?"
- end
-
- sql_attribute = "#{table_name}.#{klass.connection.quote_column_name(attribute)}"
-
- if value.nil? || (options[:case_sensitive] || !column.text?)
- sql = "#{sql_attribute} #{operator}"
+ if !options[:case_sensitive] && value && column.text?
+ # will use SQL LOWER function before comparison
+ relation = table[attribute].lower.eq(table.lower(value))
else
- sql = "LOWER(#{sql_attribute}) = LOWER(?)"
+ value = klass.connection.case_sensitive_modifier(value)
+ relation = table[attribute].eq(value)
end
- [sql, [value]]
+ relation
end
end
@@ -173,10 +160,17 @@ module ActiveRecord
# This technique is also known as optimistic concurrency control:
# http://en.wikipedia.org/wiki/Optimistic_concurrency_control
#
- # Active Record currently provides no way to distinguish unique
- # index constraint errors from other types of database errors, so you
- # will have to parse the (database-specific) exception message to detect
- # such a case.
+ # The bundled ActiveRecord::ConnectionAdapters distinguish unique index
+ # constraint errors from other types of database errors by throwing an
+ # ActiveRecord::RecordNotUnique exception.
+ # For other adapters you will have to parse the (database-specific) exception
+ # message to detect such a case.
+ # The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
+ # * ActiveRecord::ConnectionAdapters::MysqlAdapter
+ # * ActiveRecord::ConnectionAdapters::Mysql2Adapter
+ # * ActiveRecord::ConnectionAdapters::SQLiteAdapter
+ # * ActiveRecord::ConnectionAdapters::SQLite3Adapter
+ # * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
#
def validates_uniqueness_of(*attr_names)
validates_with UniquenessValidator, _merge_attributes(attr_names)
diff --git a/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb b/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb
index afcda2a98a..90923f6e74 100644
--- a/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb
@@ -1,4 +1,5 @@
require 'rails/generators/active_record'
+require 'active_support/core_ext/object/inclusion'
module ActiveRecord
module Generators
@@ -13,7 +14,7 @@ module ActiveRecord
def session_table_name
current_table_name = ActiveRecord::SessionStore::Session.table_name
- if ["sessions", "session"].include?(current_table_name)
+ if current_table_name.in?(["sessions", "session"])
current_table_name = (ActiveRecord::Base.pluralize_table_names ? 'session'.pluralize : 'session')
end
current_table_name
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index eb3f8143e7..eee771ecff 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -44,7 +44,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
def test_bind_value_substitute
- bind_param = @connection.substitute_for('foo', [])
+ bind_param = @connection.substitute_at('foo', 0)
assert_equal Arel.sql('?'), bind_param
end
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
new file mode 100644
index 0000000000..146b77a95c
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -0,0 +1,69 @@
+# encoding: utf-8
+
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class MysqlAdapterTest < ActiveRecord::TestCase
+ def setup
+ @conn = ActiveRecord::Base.connection
+ @conn.exec_query('drop table if exists ex')
+ @conn.exec_query(<<-eosql)
+ CREATE TABLE `ex` (
+ `id` int(11) DEFAULT NULL auto_increment PRIMARY KEY,
+ `number` integer,
+ `data` varchar(255))
+ eosql
+ end
+
+ def test_client_encoding
+ if "<3".respond_to?(:encoding)
+ assert_equal Encoding::UTF_8, @conn.client_encoding
+ else
+ assert_equal 'utf8', @conn.client_encoding
+ end
+ end
+
+ def test_exec_insert_number
+ insert(@conn, 'number' => 10)
+
+ result = @conn.exec_query('SELECT number FROM ex WHERE number = 10')
+
+ assert_equal 1, result.rows.length
+ assert_equal 10, result.rows.last.last
+ end
+
+ def test_exec_insert_string
+ str = 'いただきます!'
+ insert(@conn, 'number' => 10, 'data' => str)
+
+ result = @conn.exec_query('SELECT number, data FROM ex WHERE number = 10')
+
+ value = result.rows.last.last
+
+ if "<3".respond_to?(:encoding)
+ # FIXME: this should probably be inside the mysql AR adapter?
+ value.force_encoding(@conn.client_encoding)
+
+ # The strings in this file are utf-8, so transcode to utf-8
+ value.encode!(Encoding::UTF_8)
+ end
+
+ assert_equal str, value
+ end
+
+ private
+ def insert(ctx, data)
+ binds = data.map { |name, value|
+ [ctx.columns('ex').find { |x| x.name == name }, value]
+ }
+ columns = binds.map(&:first).map(&:name)
+
+ sql = "INSERT INTO ex (#{columns.join(", ")})
+ VALUES (#{(['?'] * columns.length).join(', ')})"
+
+ ctx.exec_insert(sql, 'SQL', binds)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb
new file mode 100644
index 0000000000..9673e2bb46
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb
@@ -0,0 +1,26 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class MysqlAdapter
+ class QuotingTest < ActiveRecord::TestCase
+ def setup
+ @conn = ActiveRecord::Base.connection
+ end
+
+ def test_type_cast_true
+ c = Column.new(nil, 1, 'boolean')
+ assert_equal 1, @conn.type_cast(true, nil)
+ assert_equal 1, @conn.type_cast(true, c)
+ end
+
+ def test_type_cast_false
+ c = Column.new(nil, 1, 'boolean')
+ assert_equal 0, @conn.type_cast(false, nil)
+ assert_equal 0, @conn.type_cast(false, c)
+ end
+ end
+ end
+ end
+end
+
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
new file mode 100644
index 0000000000..c6c1d1dad5
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/schema_test.rb
@@ -0,0 +1,36 @@
+require "cases/helper"
+require 'models/post'
+require 'models/comment'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class MysqlSchemaTest < ActiveRecord::TestCase
+ fixtures :posts
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ db = Post.connection_pool.spec.config[:database]
+ table = Post.table_name
+ @db_name = db
+
+ @omgpost = Class.new(Post) do
+ set_table_name "#{db}.#{table}"
+ def self.name; 'Post'; end
+ end
+ end
+
+ def test_schema
+ assert @omgpost.find(:first)
+ end
+
+ def test_table_exists?
+ name = @omgpost.table_name
+ assert @connection.table_exists?(name), "#{name} table should exist"
+ end
+
+ def test_table_exists_wrong_schema
+ assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")
+ end
+ end if current_adapter?(:MysqlAdapter)
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 5bb8fa2f93..ce08e4c6a7 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -3,6 +3,9 @@ require "cases/helper"
class PostgresqlArray < ActiveRecord::Base
end
+class PostgresqlTsvector < ActiveRecord::Base
+end
+
class PostgresqlMoney < ActiveRecord::Base
end
@@ -34,6 +37,9 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
@connection.execute("INSERT INTO postgresql_arrays (commission_by_quarter, nicknames) VALUES ( '{35000,21000,18000,17000}', '{foo,bar,baz}' )")
@first_array = PostgresqlArray.find(1)
+ @connection.execute("INSERT INTO postgresql_tsvectors (text_vector) VALUES (' ''text'' ''vector'' ')")
+ @first_tsvector = PostgresqlTsvector.find(1)
+
@connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('567.89'::money)")
@connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('-567.89'::money)")
@first_money = PostgresqlMoney.find(1)
@@ -62,6 +68,10 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal :string, @first_array.column_for_attribute(:nicknames).type
end
+ def test_data_type_of_tsvector_types
+ assert_equal :tsvector, @first_tsvector.column_for_attribute(:text_vector).type
+ end
+
def test_data_type_of_money_types
assert_equal :decimal, @first_money.column_for_attribute(:wealth).type
end
@@ -95,11 +105,26 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal '{foo,bar,baz}', @first_array.nicknames
end
+ def test_tsvector_values
+ assert_equal "'text' 'vector'", @first_tsvector.text_vector
+ end
+
def test_money_values
assert_equal 567.89, @first_money.wealth
assert_equal(-567.89, @second_money.wealth)
end
+ def test_update_tsvector
+ new_text_vector = "'new' 'text' 'vector'"
+ assert @first_tsvector.text_vector = new_text_vector
+ assert @first_tsvector.save
+ assert @first_tsvector.reload
+ assert @first_tsvector.text_vector = new_text_vector
+ assert @first_tsvector.save
+ assert @first_tsvector.reload
+ assert_equal @first_tsvector.text_vector, new_text_vector
+ end
+
def test_number_values
assert_equal 123.456, @first_number.single
assert_equal 123456.789, @first_number.double
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index b0a4a4e39d..7c49236854 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -1,3 +1,4 @@
+# encoding: utf-8
require "cases/helper"
module ActiveRecord
@@ -6,7 +7,52 @@ module ActiveRecord
def setup
@connection = ActiveRecord::Base.connection
@connection.exec_query('drop table if exists ex')
- @connection.exec_query('create table ex(id serial primary key, data character varying(255))')
+ @connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))')
+ end
+
+ def test_serial_sequence
+ assert_equal 'public.accounts_id_seq',
+ @connection.serial_sequence('accounts', 'id')
+
+ assert_raises(ActiveRecord::StatementInvalid) do
+ @connection.serial_sequence('zomg', 'id')
+ end
+ end
+
+ def test_default_sequence_name
+ assert_equal 'accounts_id_seq',
+ @connection.default_sequence_name('accounts', 'id')
+
+ assert_equal 'accounts_id_seq',
+ @connection.default_sequence_name('accounts')
+ end
+
+ def test_default_sequence_name_bad_table
+ assert_equal 'zomg_id_seq',
+ @connection.default_sequence_name('zomg', 'id')
+
+ assert_equal 'zomg_id_seq',
+ @connection.default_sequence_name('zomg')
+ end
+
+ def test_exec_insert_number
+ insert(@connection, 'number' => 10)
+
+ result = @connection.exec_query('SELECT number FROM ex WHERE number = 10')
+
+ assert_equal 1, result.rows.length
+ assert_equal "10", result.rows.last.last
+ end
+
+ def test_exec_insert_string
+ str = 'いただきます!'
+ insert(@connection, 'number' => 10, 'data' => str)
+
+ result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10')
+
+ value = result.rows.last.last
+
+ assert_equal str, value
end
def test_table_alias_length
@@ -56,13 +102,28 @@ module ActiveRecord
assert_equal [['1', 'foo']], result.rows
end
- def test_substitute_for
- bind = @connection.substitute_for(nil, [])
+ def test_substitute_at
+ bind = @connection.substitute_at(nil, 0)
assert_equal Arel.sql('$1'), bind
- bind = @connection.substitute_for(nil, [nil])
+ bind = @connection.substitute_at(nil, 1)
assert_equal Arel.sql('$2'), bind
end
+
+ private
+ def insert(ctx, data)
+ binds = data.map { |name, value|
+ [ctx.columns('ex').find { |x| x.name == name }, value]
+ }
+ columns = binds.map(&:first).map(&:name)
+
+ bind_subs = columns.length.times.map { |x| "$#{x + 1}" }
+
+ sql = "INSERT INTO ex (#{columns.join(", ")})
+ VALUES (#{bind_subs.join(', ')})"
+
+ ctx.exec_insert(sql, 'SQL', binds)
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
new file mode 100644
index 0000000000..172055f15c
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -0,0 +1,25 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class PostgreSQLAdapter
+ class QuotingTest < ActiveRecord::TestCase
+ def setup
+ @conn = ActiveRecord::Base.connection
+ end
+
+ def test_type_cast_true
+ c = Column.new(nil, 1, 'boolean')
+ assert_equal 't', @conn.type_cast(true, nil)
+ assert_equal 't', @conn.type_cast(true, c)
+ end
+
+ def test_type_cast_false
+ c = Column.new(nil, 1, 'boolean')
+ assert_equal 'f', @conn.type_cast(false, nil)
+ assert_equal 'f', @conn.type_cast(false, c)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb b/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb
deleted file mode 100644
index ce0b2f5f5b..0000000000
--- a/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb
+++ /dev/null
@@ -1,210 +0,0 @@
-require "cases/helper"
-
-module ActiveRecord
- module ConnectionAdapters
- class SQLiteAdapterTest < ActiveRecord::TestCase
- def setup
- @ctx = Base.sqlite3_connection :database => ':memory:',
- :adapter => 'sqlite3',
- :timeout => nil
- @ctx.execute <<-eosql
- CREATE TABLE items (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer
- )
- eosql
- end
-
- def test_execute
- @ctx.execute "INSERT INTO items (number) VALUES (10)"
- records = @ctx.execute "SELECT * FROM items"
- assert_equal 1, records.length
-
- record = records.first
- assert_equal 10, record['number']
- assert_equal 1, record['id']
- end
-
- def test_quote_string
- assert_equal "''", @ctx.quote_string("'")
- end
-
- def test_insert_sql
- 2.times do |i|
- rv = @ctx.insert_sql "INSERT INTO items (number) VALUES (#{i})"
- assert_equal(i + 1, rv)
- end
-
- records = @ctx.execute "SELECT * FROM items"
- assert_equal 2, records.length
- end
-
- def test_insert_sql_logged
- sql = "INSERT INTO items (number) VALUES (10)"
- name = "foo"
-
- assert_logged([[sql, name]]) do
- @ctx.insert_sql sql, name
- end
- end
-
- def test_insert_id_value_returned
- sql = "INSERT INTO items (number) VALUES (10)"
- idval = 'vuvuzela'
- id = @ctx.insert_sql sql, nil, nil, idval
- assert_equal idval, id
- end
-
- def test_select_rows
- 2.times do |i|
- @ctx.create "INSERT INTO items (number) VALUES (#{i})"
- end
- rows = @ctx.select_rows 'select number, id from items'
- assert_equal [[0, 1], [1, 2]], rows
- end
-
- def test_select_rows_logged
- sql = "select * from items"
- name = "foo"
-
- assert_logged([[sql, name]]) do
- @ctx.select_rows sql, name
- end
- end
-
- def test_transaction
- count_sql = 'select count(*) from items'
-
- @ctx.begin_db_transaction
- @ctx.create "INSERT INTO items (number) VALUES (10)"
-
- assert_equal 1, @ctx.select_rows(count_sql).first.first
- @ctx.rollback_db_transaction
- assert_equal 0, @ctx.select_rows(count_sql).first.first
- end
-
- def test_tables
- assert_equal %w{ items }, @ctx.tables
-
- @ctx.execute <<-eosql
- CREATE TABLE people (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer
- )
- eosql
- assert_equal %w{ items people }.sort, @ctx.tables.sort
- end
-
- def test_tables_logs_name
- name = "hello"
- assert_logged [[name]] do
- @ctx.tables(name)
- assert_not_nil @ctx.logged.first.shift
- end
- end
-
- def test_columns
- columns = @ctx.columns('items').sort_by { |x| x.name }
- assert_equal 2, columns.length
- assert_equal %w{ id number }.sort, columns.map { |x| x.name }
- assert_equal [nil, nil], columns.map { |x| x.default }
- assert_equal [true, true], columns.map { |x| x.null }
- end
-
- def test_columns_with_default
- @ctx.execute <<-eosql
- CREATE TABLE columns_with_default (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer default 10
- )
- eosql
- column = @ctx.columns('columns_with_default').find { |x|
- x.name == 'number'
- }
- assert_equal 10, column.default
- end
-
- def test_columns_with_not_null
- @ctx.execute <<-eosql
- CREATE TABLE columns_with_default (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer not null
- )
- eosql
- column = @ctx.columns('columns_with_default').find { |x|
- x.name == 'number'
- }
- assert !column.null, "column should not be null"
- end
-
- def test_indexes_logs
- intercept_logs_on @ctx
- assert_difference('@ctx.logged.length') do
- @ctx.indexes('items')
- end
- assert_match(/items/, @ctx.logged.last.first)
- end
-
- def test_no_indexes
- assert_equal [], @ctx.indexes('items')
- end
-
- def test_index
- @ctx.add_index 'items', 'id', :unique => true, :name => 'fun'
- index = @ctx.indexes('items').find { |idx| idx.name == 'fun' }
-
- assert_equal 'items', index.table
- assert index.unique, 'index is unique'
- assert_equal ['id'], index.columns
- end
-
- def test_non_unique_index
- @ctx.add_index 'items', 'id', :name => 'fun'
- index = @ctx.indexes('items').find { |idx| idx.name == 'fun' }
- assert !index.unique, 'index is not unique'
- end
-
- def test_compound_index
- @ctx.add_index 'items', %w{ id number }, :name => 'fun'
- index = @ctx.indexes('items').find { |idx| idx.name == 'fun' }
- assert_equal %w{ id number }.sort, index.columns.sort
- end
-
- def test_primary_key
- assert_equal 'id', @ctx.primary_key('items')
-
- @ctx.execute <<-eosql
- CREATE TABLE foos (
- internet integer PRIMARY KEY AUTOINCREMENT,
- number integer not null
- )
- eosql
- assert_equal 'internet', @ctx.primary_key('foos')
- end
-
- def test_no_primary_key
- @ctx.execute 'CREATE TABLE failboat (number integer not null)'
- assert_nil @ctx.primary_key('failboat')
- end
-
- private
-
- def assert_logged logs
- intercept_logs_on @ctx
- yield
- assert_equal logs, @ctx.logged
- end
-
- def intercept_logs_on ctx
- @ctx.extend(Module.new {
- attr_accessor :logged
- def log sql, name
- @logged << [sql, name]
- yield
- end
- })
- @ctx.logged = []
- end
- end
- end
-end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
new file mode 100644
index 0000000000..0d9db92447
--- /dev/null
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -0,0 +1,93 @@
+require "cases/helper"
+require 'bigdecimal'
+require 'yaml'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class SQLiteAdapter
+ class QuotingTest < ActiveRecord::TestCase
+ def setup
+ @conn = Base.sqlite3_connection :database => ':memory:',
+ :adapter => 'sqlite3',
+ :timeout => 100
+ end
+
+ def test_type_cast_symbol
+ assert_equal 'foo', @conn.type_cast(:foo, nil)
+ end
+
+ def test_type_cast_date
+ date = Date.today
+ expected = @conn.quoted_date(date)
+ assert_equal expected, @conn.type_cast(date, nil)
+ end
+
+ def test_type_cast_time
+ time = Time.now
+ expected = @conn.quoted_date(time)
+ assert_equal expected, @conn.type_cast(time, nil)
+ end
+
+ def test_type_cast_numeric
+ assert_equal 10, @conn.type_cast(10, nil)
+ assert_equal 2.2, @conn.type_cast(2.2, nil)
+ end
+
+ def test_type_cast_nil
+ assert_equal nil, @conn.type_cast(nil, nil)
+ end
+
+ def test_type_cast_true
+ c = Column.new(nil, 1, 'int')
+ assert_equal 't', @conn.type_cast(true, nil)
+ assert_equal 1, @conn.type_cast(true, c)
+ end
+
+ def test_type_cast_false
+ c = Column.new(nil, 1, 'int')
+ assert_equal 'f', @conn.type_cast(false, nil)
+ assert_equal 0, @conn.type_cast(false, c)
+ end
+
+ def test_type_cast_string
+ assert_equal '10', @conn.type_cast('10', nil)
+
+ c = Column.new(nil, 1, 'int')
+ assert_equal 10, @conn.type_cast('10', c)
+
+ c = Column.new(nil, 1, 'float')
+ assert_equal 10.1, @conn.type_cast('10.1', c)
+
+ c = Column.new(nil, 1, 'binary')
+ assert_equal '10.1', @conn.type_cast('10.1', c)
+
+ c = Column.new(nil, 1, 'date')
+ assert_equal '10.1', @conn.type_cast('10.1', c)
+ end
+
+ def test_type_cast_bigdecimal
+ bd = BigDecimal.new '10.0'
+ assert_equal bd.to_s('F'), @conn.type_cast(bd, nil)
+ end
+
+ def test_type_cast_unknown
+ obj = Class.new.new
+ assert_equal YAML.dump(obj), @conn.type_cast(obj, nil)
+ end
+
+ def test_quoted_id
+ quoted_id_obj = Class.new {
+ def quoted_id
+ "'zomg'"
+ end
+
+ def id
+ 10
+ end
+ }.new
+ assert_equal 10, @conn.type_cast(quoted_id_obj, nil)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index b8abdface4..6ff04e3eb3 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -1,12 +1,34 @@
+# encoding: utf-8
require "cases/helper"
module ActiveRecord
module ConnectionAdapters
class SQLite3AdapterTest < ActiveRecord::TestCase
+ class DualEncoding < ActiveRecord::Base
+ end
+
def setup
@conn = Base.sqlite3_connection :database => ':memory:',
:adapter => 'sqlite3',
:timeout => 100
+ @conn.execute <<-eosql
+ CREATE TABLE items (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer
+ )
+ eosql
+ end
+
+ def test_exec_insert
+ column = @conn.columns('items').find { |col| col.name == 'number' }
+ vals = [[column, 10]]
+ @conn.exec_insert('insert into items (number) VALUES (?)', 'SQL', vals)
+
+ result = @conn.exec_query(
+ 'select number from items where number = ?', 'SQL', vals)
+
+ assert_equal 1, result.rows.length
+ assert_equal 10, result.rows.first.first
end
def test_primary_key_returns_nil_for_no_pk
@@ -58,7 +80,7 @@ module ActiveRecord
end
def test_bind_value_substitute
- bind_param = @conn.substitute_for('foo', [])
+ bind_param = @conn.substitute_at('foo', 0)
assert_equal Arel.sql('?'), bind_param
end
@@ -102,6 +124,213 @@ module ActiveRecord
assert_equal [[1, 'foo']], result.rows
end
+
+ def test_quote_binary_column_escapes_it
+ return unless "<3".respond_to?(:encode)
+
+ DualEncoding.connection.execute(<<-eosql)
+ CREATE TABLE dual_encodings (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ name string,
+ data binary
+ )
+ eosql
+ str = "\x80".force_encoding("ASCII-8BIT")
+ binary = DualEncoding.new :name => 'いただきます!', :data => str
+ binary.save!
+ assert_equal str, binary.data
+ end
+
+ def test_execute
+ @conn.execute "INSERT INTO items (number) VALUES (10)"
+ records = @conn.execute "SELECT * FROM items"
+ assert_equal 1, records.length
+
+ record = records.first
+ assert_equal 10, record['number']
+ assert_equal 1, record['id']
+ end
+
+ def test_quote_string
+ assert_equal "''", @conn.quote_string("'")
+ end
+
+ def test_insert_sql
+ 2.times do |i|
+ rv = @conn.insert_sql "INSERT INTO items (number) VALUES (#{i})"
+ assert_equal(i + 1, rv)
+ end
+
+ records = @conn.execute "SELECT * FROM items"
+ assert_equal 2, records.length
+ end
+
+ def test_insert_sql_logged
+ sql = "INSERT INTO items (number) VALUES (10)"
+ name = "foo"
+
+ assert_logged([[sql, name, []]]) do
+ @conn.insert_sql sql, name
+ end
+ end
+
+ def test_insert_id_value_returned
+ sql = "INSERT INTO items (number) VALUES (10)"
+ idval = 'vuvuzela'
+ id = @conn.insert_sql sql, nil, nil, idval
+ assert_equal idval, id
+ end
+
+ def test_select_rows
+ 2.times do |i|
+ @conn.create "INSERT INTO items (number) VALUES (#{i})"
+ end
+ rows = @conn.select_rows 'select number, id from items'
+ assert_equal [[0, 1], [1, 2]], rows
+ end
+
+ def test_select_rows_logged
+ sql = "select * from items"
+ name = "foo"
+
+ assert_logged([[sql, name, []]]) do
+ @conn.select_rows sql, name
+ end
+ end
+
+ def test_transaction
+ count_sql = 'select count(*) from items'
+
+ @conn.begin_db_transaction
+ @conn.create "INSERT INTO items (number) VALUES (10)"
+
+ assert_equal 1, @conn.select_rows(count_sql).first.first
+ @conn.rollback_db_transaction
+ assert_equal 0, @conn.select_rows(count_sql).first.first
+ end
+
+ def test_tables
+ assert_equal %w{ items }, @conn.tables
+
+ @conn.execute <<-eosql
+ CREATE TABLE people (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer
+ )
+ eosql
+ assert_equal %w{ items people }.sort, @conn.tables.sort
+ end
+
+ def test_tables_logs_name
+ name = "hello"
+ assert_logged [[name, []]] do
+ @conn.tables(name)
+ assert_not_nil @conn.logged.first.shift
+ end
+ end
+
+ def test_columns
+ columns = @conn.columns('items').sort_by { |x| x.name }
+ assert_equal 2, columns.length
+ assert_equal %w{ id number }.sort, columns.map { |x| x.name }
+ assert_equal [nil, nil], columns.map { |x| x.default }
+ assert_equal [true, true], columns.map { |x| x.null }
+ end
+
+ def test_columns_with_default
+ @conn.execute <<-eosql
+ CREATE TABLE columns_with_default (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer default 10
+ )
+ eosql
+ column = @conn.columns('columns_with_default').find { |x|
+ x.name == 'number'
+ }
+ assert_equal 10, column.default
+ end
+
+ def test_columns_with_not_null
+ @conn.execute <<-eosql
+ CREATE TABLE columns_with_default (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer not null
+ )
+ eosql
+ column = @conn.columns('columns_with_default').find { |x|
+ x.name == 'number'
+ }
+ assert !column.null, "column should not be null"
+ end
+
+ def test_indexes_logs
+ intercept_logs_on @conn
+ assert_difference('@conn.logged.length') do
+ @conn.indexes('items')
+ end
+ assert_match(/items/, @conn.logged.last.first)
+ end
+
+ def test_no_indexes
+ assert_equal [], @conn.indexes('items')
+ end
+
+ def test_index
+ @conn.add_index 'items', 'id', :unique => true, :name => 'fun'
+ index = @conn.indexes('items').find { |idx| idx.name == 'fun' }
+
+ assert_equal 'items', index.table
+ assert index.unique, 'index is unique'
+ assert_equal ['id'], index.columns
+ end
+
+ def test_non_unique_index
+ @conn.add_index 'items', 'id', :name => 'fun'
+ index = @conn.indexes('items').find { |idx| idx.name == 'fun' }
+ assert !index.unique, 'index is not unique'
+ end
+
+ def test_compound_index
+ @conn.add_index 'items', %w{ id number }, :name => 'fun'
+ index = @conn.indexes('items').find { |idx| idx.name == 'fun' }
+ assert_equal %w{ id number }.sort, index.columns.sort
+ end
+
+ def test_primary_key
+ assert_equal 'id', @conn.primary_key('items')
+
+ @conn.execute <<-eosql
+ CREATE TABLE foos (
+ internet integer PRIMARY KEY AUTOINCREMENT,
+ number integer not null
+ )
+ eosql
+ assert_equal 'internet', @conn.primary_key('foos')
+ end
+
+ def test_no_primary_key
+ @conn.execute 'CREATE TABLE failboat (number integer not null)'
+ assert_nil @conn.primary_key('failboat')
+ end
+
+ private
+
+ def assert_logged logs
+ intercept_logs_on @conn
+ yield
+ assert_equal logs, @conn.logged
+ end
+
+ def intercept_logs_on ctx
+ @conn.extend(Module.new {
+ attr_accessor :logged
+ def log sql, name, binds = []
+ @logged << [sql, name, binds]
+ yield
+ end
+ })
+ @conn.logged = []
+ end
end
end
end
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index 0742e311d9..39e8a7960a 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -15,17 +15,17 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_with_cascaded_two_levels
authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id")
- assert_equal 2, authors.size
+ assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
- assert_equal 1, authors[1].posts.size
+ assert_equal 3, authors[1].posts.size
assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
end
def test_eager_association_loading_with_cascaded_two_levels_and_one_level
authors = Author.find(:all, :include=>[{:posts=>:comments}, :categorizations], :order=>"authors.id")
- assert_equal 2, authors.size
+ assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
- assert_equal 1, authors[1].posts.size
+ assert_equal 3, authors[1].posts.size
assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
assert_equal 1, authors[0].categorizations.size
assert_equal 2, authors[1].categorizations.size
@@ -51,24 +51,24 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
categories = Category.joins(:categorizations).includes([{:posts=>:comments}, :authors])
assert_nothing_raised do
- assert_equal 2, categories.count
- assert_equal 2, categories.all.uniq.size # Must uniq since instantiating with inner joins will get dupes
+ assert_equal 3, categories.count
+ assert_equal 3, categories.all.uniq.size # Must uniq since instantiating with inner joins will get dupes
end
end
def test_cascaded_eager_association_loading_with_duplicated_includes
categories = Category.includes(:categorizations).includes(:categorizations => :author).where("categorizations.id is not null")
assert_nothing_raised do
- assert_equal 2, categories.count
- assert_equal 2, categories.all.size
+ assert_equal 3, categories.count
+ assert_equal 3, categories.all.size
end
end
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")
assert_nothing_raised do
- assert_equal 2, categories.count
- assert_equal 2, categories.all.size
+ assert_equal 3, categories.count
+ assert_equal 3, categories.all.size
end
end
@@ -81,15 +81,15 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations
authors = Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id")
- assert_equal 2, authors.size
+ assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
- assert_equal 1, authors[1].posts.size
+ assert_equal 3, authors[1].posts.size
assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
end
def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference
authors = Author.find(:all, :include=>{:posts=>[:comments, :author]}, :order=>"authors.id")
- assert_equal 2, authors.size
+ assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
assert_equal authors(:david).name, authors[0].name
assert_equal [authors(:david).name], authors[0].posts.collect{|post| post.author.name}.uniq
@@ -157,9 +157,9 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_where_first_level_returns_nil
authors = Author.find(:all, :include => {:post_about_thinking => :comments}, :order => 'authors.id DESC')
- assert_equal [authors(:mary), authors(:david)], authors
+ assert_equal [authors(:bob), authors(:mary), authors(:david)], authors
assert_no_queries do
- authors[1].post_about_thinking.comments.first
+ authors[2].post_about_thinking.comments.first
end
end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 48406a9b98..9bc7910fc6 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -68,8 +68,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_with_ordering
list = Post.find(:all, :include => :comments, :order => "posts.id DESC")
- [:eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments,
- :authorless, :thinking, :welcome
+ [:other_by_mary, :other_by_bob, :misc_by_mary, :misc_by_bob, :eager_other,
+ :sti_habtm, :sti_post_and_comments, :sti_comments, :authorless, :thinking, :welcome
].each_with_index do |post, index|
assert_equal posts(post), list[index]
end
@@ -97,25 +97,25 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_preloading_has_many_in_multiple_queries_with_more_ids_than_database_can_handle
Post.connection.expects(:in_clause_length).at_least_once.returns(5)
posts = Post.find(:all, :include=>:comments)
- assert_equal 7, posts.size
+ assert_equal 11, posts.size
end
def test_preloading_has_many_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle
Post.connection.expects(:in_clause_length).at_least_once.returns(nil)
posts = Post.find(:all, :include=>:comments)
- assert_equal 7, posts.size
+ assert_equal 11, posts.size
end
def test_preloading_habtm_in_multiple_queries_with_more_ids_than_database_can_handle
Post.connection.expects(:in_clause_length).at_least_once.returns(5)
posts = Post.find(:all, :include=>:categories)
- assert_equal 7, posts.size
+ assert_equal 11, posts.size
end
def test_preloading_habtm_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle
Post.connection.expects(:in_clause_length).at_least_once.returns(nil)
posts = Post.find(:all, :include=>:categories)
- assert_equal 7, posts.size
+ assert_equal 11, posts.size
end
def test_load_associated_records_in_one_query_when_adapter_has_no_limit
@@ -170,6 +170,16 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal [comment], category.posts[0].comments
end
end
+
+ def test_associations_loaded_for_all_records
+ post = Post.create!(:title => 'foo', :body => "I like cars!")
+ comment = SpecialComment.create!(:body => 'Come on!', :post => post)
+ first_category = Category.create! :name => 'First!', :posts => [post]
+ second_category = Category.create! :name => 'Second!', :posts => [post]
+
+ categories = Category.where(:id => [first_category.id, second_category.id]).includes(:posts => :special_comments)
+ assert_equal categories.map { |category| category.posts.first.special_comments.loaded? }, [true, true]
+ end
def test_finding_with_includes_on_has_many_association_with_same_include_includes_only_once
author_id = authors(:david).id
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 dc382c3007..f4d14853d3 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
@@ -604,7 +604,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
project = SpecialProject.create("name" => "Special Project")
assert developer.save
developer.projects << project
- developer.update_attribute("name", "Bruza")
+ developer.update_column("name", "Bruza")
assert_equal 1, Developer.connection.select_value(<<-end_sql).to_i
SELECT count(*) FROM developers_projects
WHERE project_id = #{project.id}
@@ -642,12 +642,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_find_grouped
all_posts_from_category1 = Post.find(:all, :conditions => "category_id = 1", :joins => :categories)
grouped_posts_of_category1 = Post.find(:all, :conditions => "category_id = 1", :group => "author_id", :select => 'count(posts.id) as posts_count', :joins => :categories)
- assert_equal 4, all_posts_from_category1.size
- assert_equal 1, grouped_posts_of_category1.size
+ assert_equal 5, all_posts_from_category1.size
+ assert_equal 2, grouped_posts_of_category1.size
end
def test_find_scoped_grouped
- assert_equal 4, categories(:general).posts_grouped_by_title.size
+ assert_equal 5, categories(:general).posts_grouped_by_title.size
assert_equal 1, categories(:technology).posts_grouped_by_title.size
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index ad774eb9ce..007f11b535 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -70,16 +70,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
# would be convenient), because this would cause that scope to be applied to any callbacks etc.
def test_build_and_create_should_not_happen_within_scope
car = cars(:honda)
- original_scoped_methods = Bulb.scoped_methods
+ scoped_count = car.foo_bulbs.scoped.where_values.count
- bulb = car.bulbs.build
- assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+ bulb = car.foo_bulbs.build
+ assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
- bulb = car.bulbs.create
- assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+ bulb = car.foo_bulbs.create
+ assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
- bulb = car.bulbs.create!
- assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+ bulb = car.foo_bulbs.create!
+ assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
end
def test_no_sql_should_be_fired_if_association_already_loaded
@@ -639,7 +639,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_deleting_updates_counter_cache_with_dependent_delete_all
post = posts(:welcome)
- post.update_attribute(:taggings_with_delete_all_count, post.taggings_count)
+ post.update_column(:taggings_with_delete_all_count, post.taggings_count)
assert_difference "post.reload.taggings_with_delete_all_count", -1 do
post.taggings_with_delete_all.delete(post.taggings_with_delete_all.first)
@@ -648,7 +648,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_deleting_updates_counter_cache_with_dependent_destroy
post = posts(:welcome)
- post.update_attribute(:taggings_with_destroy_count, post.taggings_count)
+ post.update_column(:taggings_with_destroy_count, post.taggings_count)
assert_difference "post.reload.taggings_with_destroy_count", -1 do
post.taggings_with_destroy.delete(post.taggings_with_destroy.first)
@@ -787,7 +787,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = Firm.find(:first)
# break the vanilla firm_id foreign key
assert_equal 2, firm.clients.count
- firm.clients.first.update_attribute(:firm_id, nil)
+ firm.clients.first.update_column(:firm_id, nil)
assert_equal 1, firm.clients(true).count
assert_equal 1, firm.clients_using_primary_key_with_delete_all.count
old_record = firm.clients_using_primary_key_with_delete_all.first
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 efdecd4b09..70a4e06dbe 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -17,9 +17,10 @@ require 'models/developer'
require 'models/subscriber'
require 'models/book'
require 'models/subscription'
-require 'models/categorization'
-require 'models/category'
require 'models/essay'
+require 'models/category'
+require 'models/owner'
+require 'models/categorization'
require 'models/member'
require 'models/membership'
require 'models/club'
@@ -27,7 +28,7 @@ require 'models/club'
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags,
:owners, :pets, :toys, :jobs, :references, :companies, :members, :author_addresses,
- :subscribers, :books, :subscriptions, :developers, :categorizations
+ :subscribers, :books, :subscriptions, :developers, :categorizations, :essays
# Dummies to force column loads so query counts are clean.
def setup
@@ -43,7 +44,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_associate_existing
- assert_queries(2) { posts(:thinking); people(:david) }
+ posts(:thinking); people(:david) # Warm cache
assert_queries(1) do
posts(:thinking).people << people(:david)
@@ -285,7 +286,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_update_counter_caches_on_delete_with_dependent_destroy
post = posts(:welcome)
tag = post.tags.create!(:name => 'doomed')
- post.update_attribute(:tags_with_destroy_count, post.tags.count)
+ post.update_column(:tags_with_destroy_count, post.tags.count)
assert_difference ['post.reload.taggings_count', 'post.reload.tags_with_destroy_count'], -1 do
posts(:welcome).tags_with_destroy.delete(tag)
@@ -295,7 +296,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_update_counter_caches_on_delete_with_dependent_nullify
post = posts(:welcome)
tag = post.tags.create!(:name => 'doomed')
- post.update_attribute(:tags_with_nullify_count, post.tags.count)
+ post.update_column(:tags_with_nullify_count, post.tags.count)
assert_no_difference 'post.reload.taggings_count' do
assert_difference 'post.reload.tags_with_nullify_count', -1 do
@@ -656,6 +657,25 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert author.comments.include?(comment)
end
+ def test_has_many_through_polymorphic_with_primary_key_option
+ assert_equal [categories(:general)], authors(:david).essay_categories
+
+ authors = Author.joins(:essay_categories).where('categories.id' => categories(:general).id)
+ assert_equal authors(:david), authors.first
+
+ assert_equal [owners(:blackbeard)], authors(:david).essay_owners
+
+ authors = Author.joins(:essay_owners).where("owners.name = 'blackbeard'")
+ assert_equal authors(:david), authors.first
+ end
+
+ def test_has_many_through_with_primary_key_option
+ assert_equal [categories(:general)], authors(:david).essay_categories_2
+
+ authors = Author.joins(:essay_categories_2).where('categories.id' => categories(:general).id)
+ assert_equal authors(:david), authors.first
+ end
+
def test_size_of_through_association_should_increase_correctly_when_has_many_association_is_added
post = posts(:thinking)
readers = post.readers.size
@@ -679,10 +699,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_joining_has_many_through_belongs_to
- posts = Post.joins(:author_categorizations).
+ posts = Post.joins(:author_categorizations).order('posts.id').
where('categorizations.id' => categorizations(:mary_thinking_sti).id)
- assert_equal [posts(:eager_other)], posts
+ assert_equal [posts(:eager_other), posts(:misc_by_mary), posts(:other_by_mary)], posts
end
def test_select_chosen_fields_only
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index c1dad5e246..f3c96ccbe6 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -165,16 +165,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_build_and_create_should_not_happen_within_scope
pirate = pirates(:blackbeard)
- original_scoped_methods = Bulb.scoped_methods.dup
+ scoped_count = pirate.association(:foo_bulb).scoped.where_values.count
- bulb = pirate.build_bulb
- assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+ bulb = pirate.build_foo_bulb
+ assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
- bulb = pirate.create_bulb
- assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+ bulb = pirate.create_foo_bulb
+ assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
- bulb = pirate.create_bulb!
- assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+ bulb = pirate.create_foo_bulb!
+ assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
end
def test_create_association
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 bfc5ddc747..968025ade8 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -9,13 +9,16 @@ require 'models/member_detail'
require 'models/minivan'
require 'models/dashboard'
require 'models/speedometer'
+require 'models/category'
require 'models/author'
+require 'models/essay'
+require 'models/owner'
require 'models/post'
require 'models/comment'
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans,
- :dashboards, :speedometers, :authors, :posts, :comments
+ :dashboards, :speedometers, :authors, :posts, :comments, :categories, :essays, :owners
def setup
@member = members(:groucho)
@@ -87,12 +90,12 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_with_conditions_eager_loading
# conditions on the through table
assert_equal clubs(:moustache_club), Member.find(@member.id, :include => :favourite_club).favourite_club
- memberships(:membership_of_favourite_club).update_attribute(:favourite, false)
+ memberships(:membership_of_favourite_club).update_column(:favourite, false)
assert_equal nil, Member.find(@member.id, :include => :favourite_club).reload.favourite_club
# conditions on the source table
assert_equal clubs(:moustache_club), Member.find(@member.id, :include => :hairy_club).hairy_club
- clubs(:moustache_club).update_attribute(:name, "Association of Clean-Shaven Persons")
+ clubs(:moustache_club).update_column(:name, "Association of Clean-Shaven Persons")
assert_equal nil, Member.find(@member.id, :include => :hairy_club).reload.hairy_club
end
@@ -242,6 +245,25 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_has_one_through_polymorphic_with_primary_key_option
+ assert_equal categories(:general), authors(:david).essay_category
+
+ authors = Author.joins(:essay_category).where('categories.id' => categories(:general).id)
+ assert_equal authors(:david), authors.first
+
+ assert_equal owners(:blackbeard), authors(:david).essay_owner
+
+ authors = Author.joins(:essay_owner).where("owners.name = 'blackbeard'")
+ assert_equal authors(:david), authors.first
+ end
+
+ def test_has_one_through_with_primary_key_option
+ assert_equal categories(:general), authors(:david).essay_category_2
+
+ authors = Author.joins(:essay_category_2).where('categories.id' => categories(:general).id)
+ assert_equal authors(:david), authors.first
+ end
+
def test_has_one_through_with_default_scope_on_join_model
assert_equal posts(:welcome).comments.order('id').first, authors(:david).comment_on_first_post
end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 19303fef9f..49a1c117bc 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -1,7 +1,9 @@
require "cases/helper"
+require 'active_support/core_ext/object/inclusion'
require 'models/tag'
require 'models/tagging'
require 'models/post'
+require 'models/rating'
require 'models/item'
require 'models/comment'
require 'models/author'
@@ -160,7 +162,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_delete_polymorphic_has_many_with_delete_all
assert_equal 1, posts(:welcome).taggings.count
- posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDeleteAll'
+ posts(:welcome).taggings.first.update_column :taggable_type, 'PostWithHasManyDeleteAll'
post = find_post_with_dependency(1, :has_many, :taggings, :delete_all)
old_count = Tagging.count
@@ -171,7 +173,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_delete_polymorphic_has_many_with_destroy
assert_equal 1, posts(:welcome).taggings.count
- posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDestroy'
+ posts(:welcome).taggings.first.update_column :taggable_type, 'PostWithHasManyDestroy'
post = find_post_with_dependency(1, :has_many, :taggings, :destroy)
old_count = Tagging.count
@@ -182,7 +184,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_delete_polymorphic_has_many_with_nullify
assert_equal 1, posts(:welcome).taggings.count
- posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyNullify'
+ posts(:welcome).taggings.first.update_column :taggable_type, 'PostWithHasManyNullify'
post = find_post_with_dependency(1, :has_many, :taggings, :nullify)
old_count = Tagging.count
@@ -193,7 +195,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_delete_polymorphic_has_one_with_destroy
assert posts(:welcome).tagging
- posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneDestroy'
+ posts(:welcome).tagging.update_column :taggable_type, 'PostWithHasOneDestroy'
post = find_post_with_dependency(1, :has_one, :tagging, :destroy)
old_count = Tagging.count
@@ -204,7 +206,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_delete_polymorphic_has_one_with_nullify
assert posts(:welcome).tagging
- posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneNullify'
+ posts(:welcome).tagging.update_column :taggable_type, 'PostWithHasOneNullify'
post = find_post_with_dependency(1, :has_one, :tagging, :nullify)
old_count = Tagging.count
@@ -288,7 +290,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_going_through_join_model_with_custom_foreign_key
- assert_equal [], posts(:thinking).authors
+ assert_equal [authors(:bob)], posts(:thinking).authors
assert_equal [authors(:mary)], posts(:authorless).authors
end
@@ -305,7 +307,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_with_custom_primary_key_on_has_many_source
- assert_equal [authors(:david)], posts(:thinking).authors_using_custom_pk
+ assert_equal [authors(:david), authors(:bob)], posts(:thinking).authors_using_custom_pk.order('authors.id')
end
def test_both_scoped_and_explicit_joins_should_be_respected
@@ -399,14 +401,6 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
end
- def test_has_many_through_has_many_through
- assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tags }
- end
-
- def test_has_many_through_habtm
- assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).post_categories }
- end
-
def test_eager_load_has_many_through_has_many
author = Author.find :first, :conditions => ['name = ?', 'David'], :include => :comments, :order => 'comments.id'
SpecialComment.new; VerySpecialComment.new
@@ -460,7 +454,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert saved_post.tags.include?(new_tag)
assert new_tag.persisted?
- assert saved_post.reload.tags(true).include?(new_tag)
+ assert new_tag.in?(saved_post.reload.tags(true))
new_post = Post.new(:title => "Association replacmenet works!", :body => "You best believe it.")
@@ -473,7 +467,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
new_post.save!
assert new_post.persisted?
- assert new_post.reload.tags(true).include?(saved_tag)
+ assert saved_tag.in?(new_post.reload.tags(true))
assert !posts(:thinking).tags.build.persisted?
assert !posts(:thinking).tags.new.persisted?
@@ -522,10 +516,10 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_collection_size_uses_counter_cache_if_it_exists
- author = authors(:david)
- author.stubs(:_read_attribute).with('comments_count').returns(100)
- assert_equal 100, author.comments.size
- assert !author.comments.loaded?
+ c = categories(:general)
+ c.categorizations_count = 100
+ assert_equal 100, c.categorizations.size
+ assert !c.categorizations.loaded?
end
def test_adding_junk_to_has_many_through_should_raise_type_mismatch
@@ -714,7 +708,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
# create dynamic Post models to allow different dependency options
def find_post_with_dependency(post_id, association, association_name, dependency)
class_name = "PostWith#{association.to_s.classify}#{dependency.to_s.classify}"
- Post.find(post_id).update_attribute :type, class_name
+ Post.find(post_id).update_column :type, class_name
klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
klass.set_table_name 'posts'
klass.send(association, association_name, :as => :taggable, :dependent => dependency)
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
new file mode 100644
index 0000000000..dd450a2a8e
--- /dev/null
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -0,0 +1,546 @@
+require "cases/helper"
+require 'models/author'
+require 'models/post'
+require 'models/person'
+require 'models/reference'
+require 'models/job'
+require 'models/reader'
+require 'models/comment'
+require 'models/tag'
+require 'models/tagging'
+require 'models/subscriber'
+require 'models/book'
+require 'models/subscription'
+require 'models/rating'
+require 'models/member'
+require 'models/member_detail'
+require 'models/member_type'
+require 'models/sponsor'
+require 'models/club'
+require 'models/organization'
+require 'models/category'
+require 'models/categorization'
+require 'models/membership'
+require 'models/essay'
+
+class NestedThroughAssociationsTest < ActiveRecord::TestCase
+ fixtures :authors, :books, :posts, :subscriptions, :subscribers, :tags, :taggings,
+ :people, :readers, :references, :jobs, :ratings, :comments, :members, :member_details,
+ :member_types, :sponsors, :clubs, :organizations, :categories, :categories_posts,
+ :categorizations, :memberships, :essays
+
+ # Through associations can either use the has_many or has_one macros.
+ #
+ # has_many
+ # - Source reflection can be has_many, has_one, belongs_to or has_and_belongs_to_many
+ # - Through reflection can be has_many, has_one, belongs_to or has_and_belongs_to_many
+ #
+ # has_one
+ # - Source reflection can be has_one or belongs_to
+ # - Through reflection can be has_one or belongs_to
+ #
+ # Additionally, the source reflection and/or through reflection may be subject to
+ # polymorphism and/or STI.
+ #
+ # When testing these, we need to make sure it works via loading the association directly, or
+ # joining the association, or including the association. We also need to ensure that associations
+ # are readonly where relevant.
+
+ # has_many through
+ # Source: has_many through
+ # Through: has_many
+ def test_has_many_through_has_many_with_has_many_through_source_reflection
+ general = tags(:general)
+ assert_equal [general, general], authors(:david).tags
+ end
+
+ def test_has_many_through_has_many_with_has_many_through_source_reflection_preload
+ authors = assert_queries(5) { Author.includes(:tags).to_a }
+ general = tags(:general)
+
+ assert_no_queries do
+ assert_equal [general, general], authors.first.tags
+ end
+ end
+
+ def test_has_many_through_has_many_with_has_many_through_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Author.where('tags.id' => tags(:general).id),
+ [authors(:david)], :tags
+ )
+
+ # This ensures that the polymorphism of taggings is being observed correctly
+ authors = Author.joins(:tags).where('taggings.taggable_type' => 'FakeModel')
+ assert authors.empty?
+ end
+
+ # has_many through
+ # Source: has_many
+ # Through: has_many through
+ def test_has_many_through_has_many_through_with_has_many_source_reflection
+ luke, david = subscribers(:first), subscribers(:second)
+ assert_equal [luke, david, david], authors(:david).subscribers.order('subscribers.nick')
+ end
+
+ def test_has_many_through_has_many_through_with_has_many_source_reflection_preload
+ luke, david = subscribers(:first), subscribers(:second)
+ authors = assert_queries(4) { Author.includes(:subscribers).to_a }
+ assert_no_queries do
+ assert_equal [luke, david, david], authors.first.subscribers.sort_by(&:nick)
+ end
+ end
+
+ def test_has_many_through_has_many_through_with_has_many_source_reflection_preload_via_joins
+ # All authors with subscribers where one of the subscribers' nick is 'alterself'
+ assert_includes_and_joins_equal(
+ Author.where('subscribers.nick' => 'alterself'),
+ [authors(:david)], :subscribers
+ )
+ end
+
+ # has_many through
+ # Source: has_one through
+ # Through: has_one
+ def test_has_many_through_has_one_with_has_one_through_source_reflection
+ assert_equal [member_types(:founding)], members(:groucho).nested_member_types
+ end
+
+ def test_has_many_through_has_one_with_has_one_through_source_reflection_preload
+ members = assert_queries(4) { Member.includes(:nested_member_types).to_a }
+ founding = member_types(:founding)
+ assert_no_queries do
+ assert_equal [founding], members.first.nested_member_types
+ end
+ end
+
+ def test_has_many_through_has_one_with_has_one_through_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Member.where('member_types.id' => member_types(:founding).id),
+ [members(:groucho)], :nested_member_types
+ )
+ end
+
+ # has_many through
+ # Source: has_one
+ # Through: has_one through
+ def test_has_many_through_has_one_through_with_has_one_source_reflection
+ assert_equal [sponsors(:moustache_club_sponsor_for_groucho)], members(:groucho).nested_sponsors
+ end
+
+ def test_has_many_through_has_one_through_with_has_one_source_reflection_preload
+ members = assert_queries(4) { Member.includes(:nested_sponsors).to_a }
+ mustache = sponsors(:moustache_club_sponsor_for_groucho)
+ assert_no_queries do
+ assert_equal [mustache], members.first.nested_sponsors
+ end
+ end
+
+ def test_has_many_through_has_one_through_with_has_one_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Member.where('sponsors.id' => sponsors(:moustache_club_sponsor_for_groucho).id),
+ [members(:groucho)], :nested_sponsors
+ )
+ end
+
+ # has_many through
+ # Source: has_many through
+ # Through: has_one
+ def test_has_many_through_has_one_with_has_many_through_source_reflection
+ groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy)
+
+ assert_equal [groucho_details, other_details],
+ members(:groucho).organization_member_details.order('member_details.id')
+ end
+
+ def test_has_many_through_has_one_with_has_many_through_source_reflection_preload
+ members = assert_queries(4) { Member.includes(:organization_member_details).to_a.sort_by(&:id) }
+ groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy)
+
+ assert_no_queries do
+ assert_equal [groucho_details, other_details], members.first.organization_member_details.sort_by(&:id)
+ end
+ end
+
+ def test_has_many_through_has_one_with_has_many_through_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Member.where('member_details.id' => member_details(:groucho).id).order('member_details.id'),
+ [members(:groucho), members(:some_other_guy)], :organization_member_details
+ )
+
+ members = Member.joins(:organization_member_details).
+ where('member_details.id' => 9)
+ assert members.empty?
+ end
+
+ # has_many through
+ # Source: has_many
+ # Through: has_one through
+ def test_has_many_through_has_one_through_with_has_many_source_reflection
+ groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy)
+
+ assert_equal [groucho_details, other_details],
+ members(:groucho).organization_member_details_2.order('member_details.id')
+ end
+
+ def test_has_many_through_has_one_through_with_has_many_source_reflection_preload
+ members = assert_queries(4) { Member.includes(:organization_member_details_2).to_a.sort_by(&:id) }
+ groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy)
+
+ assert_no_queries do
+ assert_equal [groucho_details, other_details], members.first.organization_member_details_2.sort_by(&:id)
+ end
+ end
+
+ def test_has_many_through_has_one_through_with_has_many_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Member.where('member_details.id' => member_details(:groucho).id).order('member_details.id'),
+ [members(:groucho), members(:some_other_guy)], :organization_member_details_2
+ )
+
+ members = Member.joins(:organization_member_details_2).
+ where('member_details.id' => 9)
+ assert members.empty?
+ end
+
+ # has_many through
+ # Source: has_and_belongs_to_many
+ # Through: has_many
+ def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection
+ general, cooking = categories(:general), categories(:cooking)
+
+ assert_equal [general, cooking], authors(:bob).post_categories.order('categories.id')
+ end
+
+ def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection_preload
+ authors = assert_queries(3) { Author.includes(:post_categories).to_a.sort_by(&:id) }
+ general, cooking = categories(:general), categories(:cooking)
+
+ assert_no_queries do
+ assert_equal [general, cooking], authors[2].post_categories.sort_by(&:id)
+ end
+ end
+
+ def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Author.where('categories.id' => categories(:cooking).id),
+ [authors(:bob)], :post_categories
+ )
+ end
+
+ # has_many through
+ # Source: has_many
+ # Through: has_and_belongs_to_many
+ def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection
+ greetings, more = comments(:greetings), comments(:more_greetings)
+
+ assert_equal [greetings, more], categories(:technology).post_comments.order('comments.id')
+ end
+
+ def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload
+ categories = assert_queries(3) { Category.includes(:post_comments).to_a.sort_by(&:id) }
+ greetings, more = comments(:greetings), comments(:more_greetings)
+
+ assert_no_queries do
+ assert_equal [greetings, more], categories[1].post_comments.sort_by(&:id)
+ end
+ end
+
+ def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Category.where('comments.id' => comments(:more_greetings).id).order('comments.id'),
+ [categories(:general), categories(:technology)], :post_comments
+ )
+ end
+
+ # has_many through
+ # Source: has_many through a habtm
+ # Through: has_many through
+ def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection
+ greetings, more = comments(:greetings), comments(:more_greetings)
+
+ assert_equal [greetings, more], authors(:bob).category_post_comments.order('comments.id')
+ end
+
+ def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload
+ authors = assert_queries(5) { Author.includes(:category_post_comments).to_a.sort_by(&:id) }
+ greetings, more = comments(:greetings), comments(:more_greetings)
+
+ assert_no_queries do
+ assert_equal [greetings, more], authors[2].category_post_comments.sort_by(&:id)
+ end
+ end
+
+ def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Author.where('comments.id' => comments(:does_it_hurt).id).order('authors.id'),
+ [authors(:david), authors(:mary)], :category_post_comments
+ )
+ end
+
+ # has_many through
+ # Source: belongs_to
+ # Through: has_many through
+ def test_has_many_through_has_many_through_with_belongs_to_source_reflection
+ assert_equal [tags(:general), tags(:general)], authors(:david).tagging_tags
+ end
+
+ def test_has_many_through_has_many_through_with_belongs_to_source_reflection_preload
+ authors = assert_queries(5) { Author.includes(:tagging_tags).to_a }
+ general = tags(:general)
+
+ assert_no_queries do
+ assert_equal [general, general], authors.first.tagging_tags
+ end
+ end
+
+ def test_has_many_through_has_many_through_with_belongs_to_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Author.where('tags.id' => tags(:general).id),
+ [authors(:david)], :tagging_tags
+ )
+ end
+
+ # has_many through
+ # Source: has_many through
+ # Through: belongs_to
+ def test_has_many_through_belongs_to_with_has_many_through_source_reflection
+ welcome_general, thinking_general = taggings(:welcome_general), taggings(:thinking_general)
+
+ assert_equal [welcome_general, thinking_general],
+ categorizations(:david_welcome_general).post_taggings.order('taggings.id')
+ end
+
+ def test_has_many_through_belongs_to_with_has_many_through_source_reflection_preload
+ categorizations = assert_queries(4) { Categorization.includes(:post_taggings).to_a.sort_by(&:id) }
+ welcome_general, thinking_general = taggings(:welcome_general), taggings(:thinking_general)
+
+ assert_no_queries do
+ assert_equal [welcome_general, thinking_general], categorizations.first.post_taggings.sort_by(&:id)
+ end
+ end
+
+ def test_has_many_through_belongs_to_with_has_many_through_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Categorization.where('taggings.id' => taggings(:welcome_general).id).order('taggings.id'),
+ [categorizations(:david_welcome_general)], :post_taggings
+ )
+ end
+
+ # has_one through
+ # Source: has_one through
+ # Through: has_one
+ def test_has_one_through_has_one_with_has_one_through_source_reflection
+ assert_equal member_types(:founding), members(:groucho).nested_member_type
+ end
+
+ def test_has_one_through_has_one_with_has_one_through_source_reflection_preload
+ members = assert_queries(4) { Member.includes(:nested_member_type).to_a.sort_by(&:id) }
+ founding = member_types(:founding)
+
+ assert_no_queries do
+ assert_equal founding, members.first.nested_member_type
+ end
+ end
+
+ def test_has_one_through_has_one_with_has_one_through_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Member.where('member_types.id' => member_types(:founding).id),
+ [members(:groucho)], :nested_member_type
+ )
+ end
+
+ # has_one through
+ # Source: belongs_to
+ # Through: has_one through
+ def test_has_one_through_has_one_through_with_belongs_to_source_reflection
+ assert_equal categories(:general), members(:groucho).club_category
+ end
+
+ def test_has_one_through_has_one_through_with_belongs_to_source_reflection_preload
+ members = assert_queries(4) { Member.includes(:club_category).to_a.sort_by(&:id) }
+ general = categories(:general)
+
+ assert_no_queries do
+ assert_equal general, members.first.club_category
+ end
+ end
+
+ def test_has_one_through_has_one_through_with_belongs_to_source_reflection_preload_via_joins
+ assert_includes_and_joins_equal(
+ Member.where('categories.id' => categories(:technology).id),
+ [members(:blarpy_winkup)], :club_category
+ )
+ end
+
+ def test_distinct_has_many_through_a_has_many_through_association_on_source_reflection
+ author = authors(:david)
+ assert_equal [tags(:general)], author.distinct_tags
+ end
+
+ def test_distinct_has_many_through_a_has_many_through_association_on_through_reflection
+ author = authors(:david)
+ assert_equal [subscribers(:first), subscribers(:second)],
+ author.distinct_subscribers.order('subscribers.nick')
+ end
+
+ def test_nested_has_many_through_with_a_table_referenced_multiple_times
+ author = authors(:bob)
+ assert_equal [posts(:misc_by_bob), posts(:misc_by_mary), posts(:other_by_bob), posts(:other_by_mary)],
+ author.similar_posts.sort_by(&:id)
+
+ # Mary and Bob both have posts in misc, but they are the only ones.
+ authors = Author.joins(:similar_posts).where('posts.id' => posts(:misc_by_bob).id)
+ assert_equal [authors(:mary), authors(:bob)], authors.uniq.sort_by(&:id)
+
+ # Check the polymorphism of taggings is being observed correctly (in both joins)
+ authors = Author.joins(:similar_posts).where('taggings.taggable_type' => 'FakeModel')
+ assert authors.empty?
+ authors = Author.joins(:similar_posts).where('taggings_authors_join.taggable_type' => 'FakeModel')
+ assert authors.empty?
+ end
+
+ def test_has_many_through_with_foreign_key_option_on_through_reflection
+ assert_equal [posts(:welcome), posts(:authorless)], people(:david).agents_posts.order('posts.id')
+ assert_equal [authors(:david)], references(:david_unicyclist).agents_posts_authors
+
+ references = Reference.joins(:agents_posts_authors).where('authors.id' => authors(:david).id)
+ assert_equal [references(:david_unicyclist)], references
+ end
+
+ def test_has_many_through_with_foreign_key_option_on_source_reflection
+ assert_equal [people(:michael), people(:susan)], jobs(:unicyclist).agents.order('people.id')
+
+ jobs = Job.joins(:agents)
+ assert_equal [jobs(:unicyclist), jobs(:unicyclist)], jobs
+ end
+
+ def test_has_many_through_with_sti_on_through_reflection
+ ratings = posts(:sti_comments).special_comments_ratings.sort_by(&:id)
+ assert_equal [ratings(:special_comment_rating), ratings(:sub_special_comment_rating)], ratings
+
+ # Ensure STI is respected in the join
+ scope = Post.joins(:special_comments_ratings).where(:id => posts(:sti_comments).id)
+ assert scope.where("comments.type" => "Comment").empty?
+ assert !scope.where("comments.type" => "SpecialComment").empty?
+ assert !scope.where("comments.type" => "SubSpecialComment").empty?
+ end
+
+ def test_has_many_through_with_sti_on_nested_through_reflection
+ taggings = posts(:sti_comments).special_comments_ratings_taggings
+ assert_equal [taggings(:special_comment_rating)], taggings
+
+ scope = Post.joins(:special_comments_ratings_taggings).where(:id => posts(:sti_comments).id)
+ assert scope.where("comments.type" => "Comment").empty?
+ assert !scope.where("comments.type" => "SpecialComment").empty?
+ end
+
+ def test_nested_has_many_through_writers_should_raise_error
+ david = authors(:david)
+ subscriber = subscribers(:first)
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ david.subscribers = [subscriber]
+ end
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ david.subscriber_ids = [subscriber.id]
+ end
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ david.subscribers << subscriber
+ end
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ david.subscribers.delete(subscriber)
+ end
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ david.subscribers.clear
+ end
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ david.subscribers.build
+ end
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ david.subscribers.create
+ end
+ end
+
+ def test_nested_has_one_through_writers_should_raise_error
+ groucho = members(:groucho)
+ founding = member_types(:founding)
+
+ assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ groucho.nested_member_type = founding
+ end
+ end
+
+ def test_nested_has_many_through_with_conditions_on_through_associations
+ assert_equal [tags(:blue)], authors(:bob).misc_post_first_blue_tags
+ end
+
+ def test_nested_has_many_through_with_conditions_on_through_associations_preload
+ assert Author.where('tags.id' => 100).joins(:misc_post_first_blue_tags).empty?
+
+ authors = assert_queries(3) { Author.includes(:misc_post_first_blue_tags).to_a.sort_by(&:id) }
+ blue = tags(:blue)
+
+ assert_no_queries do
+ assert_equal [blue], authors[2].misc_post_first_blue_tags
+ end
+ end
+
+ 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'),
+ [authors(:bob)], :misc_post_first_blue_tags
+ )
+ end
+
+ def test_nested_has_many_through_with_conditions_on_source_associations
+ assert_equal [tags(:blue)], authors(:bob).misc_post_first_blue_tags_2
+ end
+
+ def test_nested_has_many_through_with_conditions_on_source_associations_preload
+ authors = assert_queries(4) { Author.includes(:misc_post_first_blue_tags_2).to_a.sort_by(&:id) }
+ blue = tags(:blue)
+
+ assert_no_queries do
+ assert_equal [blue], authors[2].misc_post_first_blue_tags_2
+ end
+ end
+
+ 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'),
+ [authors(:bob)], :misc_post_first_blue_tags_2
+ )
+ end
+
+ def test_nested_has_many_through_with_foreign_key_option_on_the_source_reflection_through_reflection
+ assert_equal [categories(:general)], organizations(:nsa).author_essay_categories
+
+ organizations = Organization.joins(:author_essay_categories).
+ where('categories.id' => categories(:general).id)
+ assert_equal [organizations(:nsa)], organizations
+
+ assert_equal categories(:general), organizations(:nsa).author_owned_essay_category
+
+ organizations = Organization.joins(:author_owned_essay_category).
+ where('categories.id' => categories(:general).id)
+ assert_equal [organizations(:nsa)], organizations
+ end
+
+ private
+
+ def assert_includes_and_joins_equal(query, expected, association)
+ actual = assert_queries(1) { query.joins(association).to_a.uniq }
+ assert_equal expected, actual
+
+ 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 47b8e48582..04f628a398 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -66,7 +66,7 @@ class AssociationsTest < ActiveRecord::TestCase
ship = Ship.create!(:name => "The good ship Dollypop")
part = ship.parts.create!(:name => "Mast")
part.mark_for_destruction
- ShipPart.find(part.id).update_attribute(:name, 'Deck')
+ ShipPart.find(part.id).update_column(:name, 'Deck')
ship.parts.send(:load_target)
assert_equal 'Deck', ship.parts[0].name
end
@@ -170,7 +170,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
david = developers(:david)
assert !david.projects.loaded?
- david.update_attribute(:created_at, Time.now)
+ david.update_column(:created_at, Time.now)
assert !david.projects.loaded?
end
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index d0a9028264..3641031d12 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'active_support/core_ext/object/inclusion'
module ActiveRecord
module AttributeMethods
@@ -41,13 +42,13 @@ module ActiveRecord
instance = @klass.new
@klass.column_names.each do |name|
- assert ! instance.methods.map(&:to_s).include?(name)
+ assert !name.in?(instance.methods.map(&:to_s))
end
@klass.define_attribute_methods
@klass.column_names.each do |name|
- assert(instance.methods.map(&:to_s).include?(name), "#{name} is not defined")
+ assert name.in?(instance.methods.map(&:to_s)), "#{name} is not defined"
end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index dfacf58da8..5074ae50ab 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'active_support/core_ext/object/inclusion'
require 'models/minimalistic'
require 'models/developer'
require 'models/auto_id'
@@ -9,6 +10,7 @@ require 'models/company'
require 'models/category'
require 'models/reply'
require 'models/contact'
+require 'models/keyboard'
class AttributeMethodsTest < ActiveRecord::TestCase
fixtures :topics, :developers, :companies, :computers
@@ -76,6 +78,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_respond_to?
topic = Topic.find(1)
assert_respond_to topic, "title"
+ assert_respond_to topic, "_title"
assert_respond_to topic, "title?"
assert_respond_to topic, "title="
assert_respond_to topic, :title
@@ -87,6 +90,16 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert !topic.respond_to?(:nothingness)
end
+ def test_respond_to_with_custom_primary_key
+ keyboard = Keyboard.create
+ assert_not_nil keyboard.key_number
+ assert_equal keyboard.key_number, keyboard.id
+ assert keyboard.respond_to?('key_number')
+ assert keyboard.respond_to?('_key_number')
+ assert keyboard.respond_to?('id')
+ assert keyboard.respond_to?('_id')
+ end
+
# Syck calls respond_to? before actually calling initialize
def test_respond_to_with_allocated_object
topic = Topic.allocate
@@ -118,22 +131,39 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def test_read_attributes_before_type_cast_on_datetime
- developer = Developer.find(:first)
- if current_adapter?(:Mysql2Adapter, :OracleAdapter)
- # Mysql2 and Oracle adapters keep the value in Time instance
- assert_equal developer.created_at.to_s(:db), developer.attributes_before_type_cast["created_at"].to_s(:db)
- else
- assert_equal developer.created_at.to_s(:db), developer.attributes_before_type_cast["created_at"].to_s
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = @target.new
+
+ record.written_on = "345643456"
+ assert_equal "345643456", record.written_on_before_type_cast
+ assert_equal nil, record.written_on
+
+ record.written_on = "2009-10-11 12:13:14"
+ assert_equal "2009-10-11 12:13:14", record.written_on_before_type_cast
+ assert_equal Time.zone.parse("2009-10-11 12:13:14"), record.written_on
+ assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
end
+ end
- developer.created_at = "345643456"
+ def test_read_attributes_after_type_cast_on_datetime
+ tz = "Pacific Time (US & Canada)"
+
+ in_time_zone tz do
+ record = @target.new
- assert_equal developer.created_at_before_type_cast, "345643456"
- assert_equal developer.created_at, nil
+ date_string = "2011-03-24"
+ time = Time.zone.parse date_string
- developer.created_at = "2010-03-21 21:23:32"
- assert_equal developer.created_at_before_type_cast, "2010-03-21 21:23:32"
- assert_equal developer.created_at, Time.parse("2010-03-21 21:23:32")
+ record.written_on = date_string
+ assert_equal date_string, record.written_on_before_type_cast
+ assert_equal time, record.written_on
+ assert_equal ActiveSupport::TimeZone[tz], record.written_on.time_zone
+
+ record.save
+ record.reload
+
+ assert_equal time, record.written_on
+ end
end
def test_hash_content
@@ -621,7 +651,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def time_related_columns_on_topic
- Topic.columns.select { |c| [:time, :date, :datetime, :timestamp].include?(c.type) }
+ Topic.columns.select { |c| c.type.in?([:time, :date, :datetime, :timestamp]) }
end
def serialized_columns_on_topic
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index d03fc68a11..5ee3b2d776 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -18,7 +18,7 @@ require 'models/comment'
require 'models/minimalistic'
require 'models/warehouse_thing'
require 'models/parrot'
-require 'models/loose_person'
+require 'models/person'
require 'models/edge'
require 'models/joke'
require 'rexml/document'
@@ -45,6 +45,8 @@ class ReadonlyTitlePost < Post
attr_readonly :title
end
+class Weird < ActiveRecord::Base; end
+
class Boolean < ActiveRecord::Base; end
class BasicsTest < ActiveRecord::TestCase
@@ -170,7 +172,7 @@ class BasicsTest < ActiveRecord::TestCase
with_active_record_default_timezone :utc do
time = Time.local(2000)
topic = Topic.create('written_on' => time)
- saved_time = Topic.find(topic.id).written_on
+ saved_time = Topic.find(topic.id).reload.written_on
assert_equal time, saved_time
assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "EST"], time.to_a
assert_equal [0, 0, 5, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a
@@ -184,7 +186,7 @@ class BasicsTest < ActiveRecord::TestCase
Time.use_zone 'Central Time (US & Canada)' do
time = Time.zone.local(2000)
topic = Topic.create('written_on' => time)
- saved_time = Topic.find(topic.id).written_on
+ saved_time = Topic.find(topic.id).reload.written_on
assert_equal time, saved_time
assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a
assert_equal [0, 0, 6, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a
@@ -197,7 +199,7 @@ class BasicsTest < ActiveRecord::TestCase
with_env_tz 'America/New_York' do
time = Time.utc(2000)
topic = Topic.create('written_on' => time)
- saved_time = Topic.find(topic.id).written_on
+ saved_time = Topic.find(topic.id).reload.written_on
assert_equal time, saved_time
assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], time.to_a
assert_equal [0, 0, 19, 31, 12, 1999, 5, 365, false, "EST"], saved_time.to_a
@@ -210,7 +212,7 @@ class BasicsTest < ActiveRecord::TestCase
Time.use_zone 'Central Time (US & Canada)' do
time = Time.zone.local(2000)
topic = Topic.create('written_on' => time)
- saved_time = Topic.find(topic.id).written_on
+ saved_time = Topic.find(topic.id).reload.written_on
assert_equal time, saved_time
assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a
assert_equal [0, 0, 1, 1, 1, 2000, 6, 1, false, "EST"], saved_time.to_a
@@ -477,6 +479,22 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "changed", post.body
end
+ def test_non_valid_identifier_column_name
+ weird = Weird.create('a$b' => 'value')
+ weird.reload
+ assert_equal 'value', weird.send('a$b')
+
+ weird.update_column('a$b', 'value2')
+ weird.reload
+ assert_equal 'value2', weird.send('a$b')
+ end
+
+ def test_attributes_guard_protected_attributes_is_deprecated
+ attributes = { "title" => "An amazing title" }
+ topic = Topic.new
+ assert_deprecated { topic.send(:attributes=, attributes, false) }
+ end
+
def test_multiparameter_attributes_on_date
attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" }
topic = Topic.find(1)
@@ -883,7 +901,7 @@ class BasicsTest < ActiveRecord::TestCase
assert g.save
# Reload and check that we have all the geometric attributes.
- h = Geometric.find(g.id)
+ h = ActiveRecord::IdentityMap.without { Geometric.find(g.id) }
assert_equal '(5,6.1)', h.a_point
assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
@@ -911,7 +929,7 @@ class BasicsTest < ActiveRecord::TestCase
assert g.save
# Reload and check that we have all the geometric attributes.
- h = Geometric.find(g.id)
+ h = ActiveRecord::IdentityMap.without { Geometric.find(g.id) }
assert_equal '(5,6.1)', h.a_point
assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
@@ -1036,7 +1054,7 @@ class BasicsTest < ActiveRecord::TestCase
topic = Topic.new(:content => myobj)
assert topic.save
Topic.serialize(:content, Hash)
- assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).content }
+ assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).reload.content }
ensure
Topic.serialize(:content)
end
@@ -1614,18 +1632,14 @@ class BasicsTest < ActiveRecord::TestCase
assert_not_equal c1, c2
end
- def test_default_scope_is_reset
+ def test_current_scope_is_reset
Object.const_set :UnloadablePost, Class.new(ActiveRecord::Base)
- UnloadablePost.table_name = 'posts'
- UnloadablePost.class_eval do
- default_scope order('posts.comments_count ASC')
- end
- UnloadablePost.scoped_methods # make Thread.current[:UnloadablePost_scoped_methods] not nil
+ UnloadablePost.send(:current_scope=, UnloadablePost.scoped)
UnloadablePost.unloadable
- assert_not_nil Thread.current[:UnloadablePost_scoped_methods]
+ assert_not_nil Thread.current[:UnloadablePost_current_scope]
ActiveSupport::Dependencies.remove_unloadable_constants!
- assert_nil Thread.current[:UnloadablePost_scoped_methods]
+ assert_nil Thread.current[:UnloadablePost_current_scope]
ensure
Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost)
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 6f65fb96d1..6620464d6a 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -25,7 +25,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_each_should_execute_if_id_is_in_select
- assert_queries(4) do
+ assert_queries(6) do
Post.find_each(:select => "id, title, type", :batch_size => 2) do |post|
assert_kind_of Post, post
end
@@ -83,4 +83,14 @@ class EachTest < ActiveRecord::TestCase
Post.find_in_batches(:batch_size => post_count + 1) {|batch| assert_kind_of Array, batch }
end
end
+
+ def test_find_in_batches_should_quote_batch_order
+ c = Post.connection
+ assert_sql(/ORDER BY #{c.quote_table_name('posts')}.#{c.quote_column_name('id')}/) do
+ Post.find_in_batches(:batch_size => 1) do |batch|
+ assert_kind_of Array, batch
+ assert_kind_of Post, batch.first
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb
index 8545ba97cc..06c14cb108 100644
--- a/activerecord/test/cases/binary_test.rb
+++ b/activerecord/test/cases/binary_test.rb
@@ -1,3 +1,4 @@
+# encoding: utf-8
require "cases/helper"
# Without using prepared statements, it makes no sense to test
@@ -9,6 +10,24 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter)
class BinaryTest < ActiveRecord::TestCase
FIXTURES = %w(flowers.jpg example.log)
+ def test_mixed_encoding
+ str = "\x80"
+ str.force_encoding('ASCII-8BIT') if str.respond_to?(:force_encoding)
+
+ binary = Binary.new :name => 'いただきます!', :data => str
+ binary.save!
+ binary.reload
+ assert_equal str, binary.data
+
+ name = binary.name
+
+ # Mysql adapter doesn't properly encode things, so we have to do it
+ if current_adapter?(:MysqlAdapter)
+ name.force_encoding('UTF-8') if name.respond_to?(:force_encoding)
+ end
+ assert_equal 'いただきます!', name
+ end
+
def test_load_save
Binary.delete_all
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 19383bb06b..3652255c38 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -33,7 +33,7 @@ module ActiveRecord
# FIXME: use skip with minitest
return unless @connection.supports_statement_cache?
- sub = @connection.substitute_for(@pk, [])
+ sub = @connection.substitute_at(@pk, 0)
binds = [[@pk, 1]]
sql = "select * from topics where id = #{sub}"
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index caf07a7357..654c4c9010 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -65,7 +65,7 @@ class CalculationsTest < ActiveRecord::TestCase
c = Account.sum(:credit_limit, :group => :firm_id)
[1,6,2].each { |firm_id| assert c.keys.include?(firm_id) }
end
-
+
def test_should_group_by_multiple_fields
c = Account.count(:all, :group => ['firm_id', :credit_limit])
[ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) }
@@ -109,27 +109,42 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [2, 6], c.keys.compact
end
- def test_limit_with_offset_is_kept
+ def test_limit_should_apply_before_count
+ accounts = Account.limit(3).where('firm_id IS NOT NULL')
+
+ assert_equal 3, accounts.count(:firm_id)
+ assert_equal 3, accounts.select(:firm_id).count
+ end
+
+ def test_count_should_shortcut_with_limit_zero
+ accounts = Account.limit(0)
+
+ assert_no_queries { assert_equal 0, accounts.count }
+ end
+
+ def test_limit_is_kept
return if current_adapter?(:OracleAdapter)
- queries = assert_sql { Account.limit(1).offset(1).count }
+ queries = assert_sql { Account.limit(1).count }
assert_equal 1, queries.length
assert_match(/LIMIT/, queries.first)
- assert_match(/OFFSET/, queries.first)
end
- def test_offset_without_limit_removes_offset
+ def test_offset_is_kept
+ return if current_adapter?(:OracleAdapter)
+
queries = assert_sql { Account.offset(1).count }
assert_equal 1, queries.length
- assert_no_match(/LIMIT/, queries.first)
- assert_no_match(/OFFSET/, queries.first)
+ assert_match(/OFFSET/, queries.first)
end
- def test_limit_without_offset_removes_limit
- queries = assert_sql { Account.limit(1).count }
+ def test_limit_with_offset_is_kept
+ return if current_adapter?(:OracleAdapter)
+
+ queries = assert_sql { Account.limit(1).offset(1).count }
assert_equal 1, queries.length
- assert_no_match(/LIMIT/, queries.first)
- assert_no_match(/OFFSET/, queries.first)
+ assert_match(/LIMIT/, queries.first)
+ assert_match(/OFFSET/, queries.first)
end
def test_no_limit_no_offset
@@ -311,8 +326,8 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_count_scoped_select_with_options
Account.update_all("credit_limit = NULL")
- Account.last.update_attribute('credit_limit', 49)
- Account.first.update_attribute('credit_limit', 51)
+ Account.last.update_column('credit_limit', 49)
+ Account.first.update_column('credit_limit', 51)
assert_equal 1, Account.scoped(:select => "credit_limit").count(:conditions => ['credit_limit >= 50'])
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
new file mode 100644
index 0000000000..abf317768f
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -0,0 +1,33 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class ConnectionHandlerTest < ActiveRecord::TestCase
+ def setup
+ @handler = ConnectionHandler.new
+ @handler.establish_connection 'america', Base.connection_pool.spec
+ @klass = Struct.new(:name).new('america')
+ end
+
+ def test_retrieve_connection
+ assert @handler.retrieve_connection(@klass)
+ end
+
+ def test_active_connections?
+ assert !@handler.active_connections?
+ assert @handler.retrieve_connection(@klass)
+ assert @handler.active_connections?
+ @handler.clear_active_connections!
+ assert !@handler.active_connections?
+ end
+
+ def test_retrieve_connection_pool_with_ar_base
+ assert_nil @handler.retrieve_connection_pool(ActiveRecord::Base)
+ end
+
+ def test_retrieve_connection_pool
+ assert_not_nil @handler.retrieve_connection_pool(@klass)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index c535119972..85871aebdf 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -1,25 +1,82 @@
require "cases/helper"
-class ConnectionManagementTest < ActiveRecord::TestCase
- def setup
- @env = {}
- @app = stub('App')
- @management = ActiveRecord::ConnectionAdapters::ConnectionManagement.new(@app)
-
- @connections_cleared = false
- ActiveRecord::Base.stubs(:clear_active_connections!).with { @connections_cleared = true }
- end
+module ActiveRecord
+ module ConnectionAdapters
+ class ConnectionManagementTest < ActiveRecord::TestCase
+ class App
+ attr_reader :calls
+ def initialize
+ @calls = []
+ end
- test "clears active connections after each call" do
- @app.expects(:call).with(@env)
- @management.call(@env)
- assert @connections_cleared
- end
+ def call(env)
+ @calls << env
+ [200, {}, ['hi mom']]
+ end
+ end
+
+ def setup
+ @env = {}
+ @app = App.new
+ @management = ConnectionManagement.new(@app)
+
+ # make sure we have an active connection
+ assert ActiveRecord::Base.connection
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
+
+ def test_app_delegation
+ manager = ConnectionManagement.new(@app)
+
+ manager.call @env
+ assert_equal [@env], @app.calls
+ end
+
+ def test_connections_are_active_after_call
+ @management.call(@env)
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
+
+ def test_body_responds_to_each
+ _, _, body = @management.call(@env)
+ bits = []
+ body.each { |bit| bits << bit }
+ assert_equal ['hi mom'], bits
+ end
+
+ def test_connections_are_cleared_after_body_close
+ _, _, body = @management.call(@env)
+ body.close
+ assert !ActiveRecord::Base.connection_handler.active_connections?
+ end
+
+ def test_active_connections_are_not_cleared_on_body_close_during_test
+ @env['rack.test'] = true
+ _, _, body = @management.call(@env)
+ body.close
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
+
+ def test_connections_closed_if_exception
+ app = Class.new(App) { def call(env); raise; end }.new
+ explosive = ConnectionManagement.new(app)
+ assert_raises(RuntimeError) { explosive.call(@env) }
+ assert !ActiveRecord::Base.connection_handler.active_connections?
+ end
+
+ def test_connections_not_closed_if_exception_and_test
+ @env['rack.test'] = true
+ app = Class.new(App) { def call(env); raise; end }.new
+ explosive = ConnectionManagement.new(app)
+ assert_raises(RuntimeError) { explosive.call(@env) }
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
- test "doesn't clear active connections when running in a test case" do
- @env['rack.test'] = true
- @app.expects(:call).with(@env)
- @management.call(@env)
- assert !@connections_cleared
+ test "doesn't clear active connections when running in a test case" do
+ @env['rack.test'] = true
+ @management.call(@env)
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
+ end
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 7ac14fa8d6..f92f4e62c5 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -18,6 +18,14 @@ module ActiveRecord
end
end
+ def test_active_connection?
+ assert !@pool.active_connection?
+ assert @pool.connection
+ assert @pool.active_connection?
+ @pool.release_connection
+ assert !@pool.active_connection?
+ end
+
def test_pool_caches_columns
columns = @pool.columns['posts']
assert_equal columns, @pool.columns['posts']
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index deaf5252db..b3a281d960 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'active_support/core_ext/object/inclusion'
require 'models/default'
require 'models/entrant'
@@ -94,7 +95,7 @@ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
assert_equal 0, klass.columns_hash['zero'].default
assert !klass.columns_hash['zero'].null
# 0 in MySQL 4, nil in 5.
- assert [0, nil].include?(klass.columns_hash['omit'].default)
+ assert klass.columns_hash['omit'].default.in?([0, nil])
assert !klass.columns_hash['omit'].null
assert_raise(ActiveRecord::StatementInvalid) { klass.create! }
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index a6738fb654..b1ce846218 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -413,7 +413,7 @@ class DirtyTest < ActiveRecord::TestCase
with_partial_updates(Topic) do
Topic.create!(:author_name => 'Bill', :content => {:a => "a"})
topic = Topic.select('id, author_name').first
- topic.update_attribute :author_name, 'John'
+ topic.update_column :author_name, 'John'
topic = Topic.first
assert_not_nil topic.content
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index c35590b84b..655437318f 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -74,6 +74,11 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ def test_exists_does_not_instantiate_records
+ Developer.expects(:instantiate).never
+ Developer.exists?
+ end
+
def test_find_by_array_of_one_id
assert_kind_of(Array, Topic.find([ 1 ]))
assert_equal(1, Topic.find([ 1 ]).length)
@@ -124,11 +129,13 @@ class FinderTest < ActiveRecord::TestCase
def test_find_all_with_limit_and_offset_and_multiple_order_clauses
first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0
second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3
- last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6
+ third_three_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6
+ last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 9
assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] }
assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] }
- assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] }
+ assert_equal [[2,7],[2,9],[2,11]], third_three_posts.map { |p| [p.author_id, p.id] }
+ assert_equal [[3,8],[3,10]], last_posts.map { |p| [p.author_id, p.id] }
end
@@ -189,6 +196,46 @@ class FinderTest < ActiveRecord::TestCase
assert_nil Topic.where("title = 'The Second Topic of the day!'").first
end
+ def test_first_bang_present
+ assert_nothing_raised do
+ assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").first!
+ end
+ end
+
+ def test_first_bang_missing
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.where("title = 'This title does not exist'").first!
+ end
+ end
+
+ def test_model_class_responds_to_first_bang
+ assert Topic.first!
+ Topic.delete_all
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.first!
+ end
+ end
+
+ def test_last_bang_present
+ assert_nothing_raised do
+ assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").last!
+ end
+ end
+
+ def test_last_bang_missing
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.where("title = 'This title does not exist'").last!
+ end
+ end
+
+ def test_model_class_responds_to_last_bang
+ assert_equal topics(:fourth), Topic.last!
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.delete_all
+ Topic.last!
+ end
+ end
+
def test_unexisting_record_exception_handling
assert_raise(ActiveRecord::RecordNotFound) {
Topic.find(1).parent
@@ -200,9 +247,10 @@ class FinderTest < ActiveRecord::TestCase
def test_find_only_some_columns
topic = Topic.find(1, :select => "author_name")
assert_raise(ActiveModel::MissingAttributeError) {topic.title}
+ assert_nil topic.read_attribute("title")
assert_equal "David", topic.author_name
assert !topic.attribute_present?("title")
- #assert !topic.respond_to?("title")
+ assert !topic.attribute_present?(:title)
assert topic.attribute_present?("author_name")
assert_respond_to topic, "author_name"
end
diff --git a/activerecord/test/cases/habtm_destroy_order_test.rb b/activerecord/test/cases/habtm_destroy_order_test.rb
index 15598392e2..f2b91d977e 100644
--- a/activerecord/test/cases/habtm_destroy_order_test.rb
+++ b/activerecord/test/cases/habtm_destroy_order_test.rb
@@ -13,5 +13,39 @@ class HabtmDestroyOrderTest < ActiveRecord::TestCase
sicp.destroy
end
end
+ assert !sicp.destroyed?
+ end
+
+ test "not destroying a student with lessons leaves student<=>lesson association intact" do
+ # test a normal before_destroy doesn't destroy the habtm joins
+ begin
+ sicp = Lesson.new(:name => "SICP")
+ ben = Student.new(:name => "Ben Bitdiddle")
+ # add a before destroy to student
+ Student.class_eval do
+ before_destroy do
+ raise ActiveRecord::Rollback unless lessons.empty?
+ end
+ end
+ ben.lessons << sicp
+ ben.save!
+ ben.destroy
+ assert !ben.reload.lessons.empty?
+ ensure
+ # get rid of it so Student is still like it was
+ Student.reset_callbacks(:destroy)
+ end
+ end
+
+ test "not destroying a lesson with students leaves student<=>lesson association intact" do
+ # test a more aggressive before_destroy doesn't destroy the habtm joins and still throws the exception
+ sicp = Lesson.new(:name => "SICP")
+ ben = Student.new(:name => "Ben Bitdiddle")
+ sicp.students << ben
+ sicp.save!
+ assert_raises LessonError do
+ sicp.destroy
+ end
+ assert !sicp.reload.students.empty?
end
end
diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb
index 399d6c26df..199e59657d 100644
--- a/activerecord/test/cases/identity_map_test.rb
+++ b/activerecord/test/cases/identity_map_test.rb
@@ -90,6 +90,13 @@ class IdentityMapTest < ActiveRecord::TestCase
)
end
+ def test_queries_are_not_executed_when_finding_by_id
+ Post.find 1
+ assert_no_queries do
+ Post.find 1
+ end
+ end
+
##############################################################################
# Tests checking if IM is functioning properly on more advanced finds #
# and associations #
@@ -144,7 +151,7 @@ class IdentityMapTest < ActiveRecord::TestCase
s = Subscriber.find('swistak')
- assert_equal({'name' => ["Raczkowski Marcin", "Swistak Sreberkowiec"]}, swistak.changes)
+ assert_equal({"name"=>["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes)
assert_equal("Swistak Sreberkowiec", swistak.name)
end
@@ -159,8 +166,8 @@ class IdentityMapTest < ActiveRecord::TestCase
s = Subscriber.find('swistak')
assert_equal("Swistak Sreberkowiec", swistak.name)
- assert_equal({}, swistak.changes)
- assert !swistak.name_changed?
+ assert_equal({"name"=>["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes)
+ assert swistak.name_changed?
end
def test_has_many_associations
@@ -207,31 +214,31 @@ class IdentityMapTest < ActiveRecord::TestCase
def test_find_with_preloaded_associations
assert_queries(2) do
- posts = Post.preload(:comments)
+ posts = Post.preload(:comments).order('posts.id')
assert posts.first.comments.first
end
# With IM we'll retrieve post object from previous query, it'll have comments
# already preloaded from first call
assert_queries(1) do
- posts = Post.preload(:comments).to_a
+ posts = Post.preload(:comments).order('posts.id')
assert posts.first.comments.first
end
assert_queries(2) do
- posts = Post.preload(:author)
+ posts = Post.preload(:author).order('posts.id')
assert posts.first.author
end
# With IM we'll retrieve post object from previous query, it'll have comments
# already preloaded from first call
assert_queries(1) do
- posts = Post.preload(:author).to_a
+ posts = Post.preload(:author).order('posts.id')
assert posts.first.author
end
assert_queries(1) do
- posts = Post.preload(:author, :comments).to_a
+ posts = Post.preload(:author, :comments).order('posts.id')
assert posts.first.author
assert posts.first.comments.first
end
@@ -239,22 +246,22 @@ class IdentityMapTest < ActiveRecord::TestCase
def test_find_with_included_associations
assert_queries(2) do
- posts = Post.includes(:comments)
+ posts = Post.includes(:comments).order('posts.id')
assert posts.first.comments.first
end
assert_queries(1) do
- posts = Post.scoped.includes(:comments)
+ posts = Post.scoped.includes(:comments).order('posts.id')
assert posts.first.comments.first
end
assert_queries(2) do
- posts = Post.includes(:author)
+ posts = Post.includes(:author).order('posts.id')
assert posts.first.author
end
assert_queries(1) do
- posts = Post.includes(:author, :comments).to_a
+ posts = Post.includes(:author, :comments).order('posts.id')
assert posts.first.author
assert posts.first.comments.first
end
@@ -381,6 +388,15 @@ class IdentityMapTest < ActiveRecord::TestCase
assert_not_nil post.title
end
+ def test_log
+ log = StringIO.new
+ ActiveRecord::Base.logger = Logger.new(log)
+ ActiveRecord::Base.logger.level = Logger::DEBUG
+ Post.find 1
+ Post.find 1
+ assert_match(/Post with ID = 1 loaded from Identity Map/, log.string)
+ end
+
# Currently AR is not allowing changing primary key (see Persistence#update)
# So we ignore it. If this changes, this test needs to be uncommented.
# def test_updating_of_pkey
diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb
index 5da7f9e1b9..8664d63e8f 100644
--- a/activerecord/test/cases/json_serialization_test.rb
+++ b/activerecord/test/cases/json_serialization_test.rb
@@ -181,7 +181,11 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
def test_should_allow_except_option_for_list_of_authors
ActiveRecord::Base.include_root_in_json = false
authors = [@david, @mary]
- assert_equal %([{"id":1},{"id":2}]), ActiveSupport::JSON.encode(authors, :except => [:name, :author_address_id, :author_address_extra_id])
+ encoded = ActiveSupport::JSON.encode(authors, :except => [
+ :name, :author_address_id, :author_address_extra_id,
+ :organization_id, :owned_essay_id
+ ])
+ assert_equal %([{"id":1},{"id":2}]), encoded
ensure
ActiveRecord::Base.include_root_in_json = true
end
@@ -196,7 +200,7 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
)
['"name":"David"', '"posts":[', '{"id":1}', '{"id":2}', '{"id":4}',
- '{"id":5}', '{"id":6}', '"name":"Mary"', '"posts":[{"id":7}]'].each do |fragment|
+ '{"id":5}', '{"id":6}', '"name":"Mary"', '"posts":[', '{"id":7}', '{"id":9}'].each do |fragment|
assert json.include?(fragment), json
end
end
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index cbaaca764b..5f55299065 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -8,6 +8,8 @@ class LogSubscriberTest < ActiveRecord::TestCase
def setup
@old_logger = ActiveRecord::Base.logger
+ @using_identity_map = ActiveRecord::IdentityMap.enabled?
+ ActiveRecord::IdentityMap.enabled = false
super
ActiveRecord::LogSubscriber.attach_to(:active_record)
end
@@ -16,12 +18,40 @@ class LogSubscriberTest < ActiveRecord::TestCase
super
ActiveRecord::LogSubscriber.log_subscribers.pop
ActiveRecord::Base.logger = @old_logger
+ ActiveRecord::IdentityMap.enabled = @using_identity_map
end
def set_logger(logger)
ActiveRecord::Base.logger = logger
end
+ def test_schema_statements_are_ignored
+ event = Struct.new(:duration, :payload)
+
+ logger = Class.new(ActiveRecord::LogSubscriber) {
+ attr_accessor :debugs
+
+ def initialize
+ @debugs = []
+ super
+ end
+
+ def debug message
+ @debugs << message
+ end
+ }.new
+ assert_equal 0, logger.debugs.length
+
+ logger.sql(event.new(0, { :sql => 'hi mom!' }))
+ assert_equal 1, logger.debugs.length
+
+ logger.sql(event.new(0, { :sql => 'hi mom!', :name => 'foo' }))
+ assert_equal 2, logger.debugs.length
+
+ logger.sql(event.new(0, { :sql => 'hi mom!', :name => 'SCHEMA' }))
+ assert_equal 2, logger.debugs.length
+ end
+
def test_basic_query_logging
Developer.all
wait
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index 025ec1d3fa..2c051bff84 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -3,6 +3,7 @@ require 'models/company'
require 'models/subscriber'
require 'models/keyboard'
require 'models/task'
+require 'models/person'
class MassAssignmentSecurityTest < ActiveRecord::TestCase
@@ -30,6 +31,66 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
end
end
+ def test_assign_attributes_uses_default_scope_when_no_scope_is_provided
+ p = LoosePerson.new
+ p.assign_attributes(attributes_hash)
+
+ assert_equal nil, p.id
+ assert_equal 'Josh', p.first_name
+ assert_equal 'm', p.gender
+ assert_equal nil, p.comments
+ end
+
+ def test_assign_attributes_skips_mass_assignment_security_protection_when_without_protection_is_used
+ p = LoosePerson.new
+ p.assign_attributes(attributes_hash, :without_protection => true)
+
+ assert_equal 5, p.id
+ assert_equal 'Josh', p.first_name
+ assert_equal 'm', p.gender
+ assert_equal 'rides a sweet bike', p.comments
+ end
+
+ def test_assign_attributes_with_default_scope_and_attr_protected_attributes
+ p = LoosePerson.new
+ p.assign_attributes(attributes_hash, :as => :default)
+
+ assert_equal nil, p.id
+ assert_equal 'Josh', p.first_name
+ assert_equal 'm', p.gender
+ assert_equal nil, p.comments
+ end
+
+ def test_assign_attributes_with_admin_scope_and_attr_protected_attributes
+ p = LoosePerson.new
+ p.assign_attributes(attributes_hash, :as => :admin)
+
+ assert_equal nil, p.id
+ assert_equal 'Josh', p.first_name
+ assert_equal 'm', p.gender
+ assert_equal 'rides a sweet bike', p.comments
+ end
+
+ def test_assign_attributes_with_default_scope_and_attr_accessible_attributes
+ p = TightPerson.new
+ p.assign_attributes(attributes_hash, :as => :default)
+
+ assert_equal nil, p.id
+ assert_equal 'Josh', p.first_name
+ assert_equal 'm', p.gender
+ assert_equal nil, p.comments
+ end
+
+ def test_assign_attributes_with_admin_scope_and_attr_accessible_attributes
+ p = TightPerson.new
+ p.assign_attributes(attributes_hash, :as => :admin)
+
+ assert_equal nil, p.id
+ assert_equal 'Josh', p.first_name
+ assert_equal 'm', p.gender
+ assert_equal 'rides a sweet bike', p.comments
+ end
+
def test_protection_against_class_attribute_writers
[:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names,
:default_timezone, :schema_format, :lock_optimistically, :record_timestamps].each do |method|
@@ -40,4 +101,14 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
end
end
+ private
+
+ def attributes_hash
+ {
+ :id => 5,
+ :first_name => 'Josh',
+ :gender => 'm',
+ :comments => 'rides a sweet bike'
+ }
+ end
end \ No newline at end of file
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 7e8383da9e..7f0f007a70 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -249,22 +249,21 @@ class MethodScopingTest < ActiveRecord::TestCase
end
def test_scoped_with_duck_typing
- scoping = Struct.new(:method_scoping).new(:find => { :conditions => ["name = ?", 'David'] })
+ scoping = Struct.new(:current_scope).new(:find => { :conditions => ["name = ?", 'David'] })
Developer.send(:with_scope, scoping) do
assert_equal %w(David), Developer.find(:all).map { |d| d.name }
end
end
def test_ensure_that_method_scoping_is_correctly_restored
- scoped_methods = Developer.instance_eval('current_scoped_methods')
-
begin
Developer.send(:with_scope, :find => { :conditions => "name = 'Jamis'" }) do
raise "an exception"
end
rescue
end
- assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods')
+
+ assert !Developer.scoped.where_values.include?("name = 'Jamis'")
end
end
@@ -509,14 +508,15 @@ class NestedScopingTest < ActiveRecord::TestCase
def test_ensure_that_method_scoping_is_correctly_restored
Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- scoped_methods = Developer.instance_eval('current_scoped_methods')
begin
Developer.send(:with_scope, :find => { :conditions => "name = 'Maiha'" }) do
raise "an exception"
end
rescue
end
- assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods')
+
+ assert Developer.scoped.where_values.include?("name = 'David'")
+ assert !Developer.scoped.where_values.include?("name = 'Maiha'")
end
end
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index fb050c3e52..8fd1fc2577 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -58,17 +58,6 @@ class NamedScopeTest < ActiveRecord::TestCase
assert Topic.approved.respond_to?(:tables_in_string, true)
end
- def test_subclasses_inherit_scopes
- assert Topic.scopes.include?(:base)
-
- assert Reply.scopes.include?(:base)
- assert_equal Reply.find(:all), Reply.base
- end
-
- def test_classes_dont_inherit_subclasses_scopes
- assert !ActiveRecord::Base.scopes.include?(:base)
- end
-
def test_scopes_with_options_limit_finds_to_those_matching_the_criteria_specified
assert !Topic.find(:all, :conditions => {:approved => true}).empty?
@@ -246,6 +235,12 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_no_queries { assert topics.any? }
end
+ def test_model_class_should_respond_to_any
+ assert Topic.any?
+ Topic.delete_all
+ assert !Topic.any?
+ end
+
def test_many_should_not_load_results
topics = Topic.base
assert_queries(2) do
@@ -280,6 +275,15 @@ class NamedScopeTest < ActiveRecord::TestCase
assert Topic.base.many?
end
+ def test_model_class_should_respond_to_many
+ Topic.delete_all
+ assert !Topic.many?
+ Topic.create!
+ assert !Topic.many?
+ Topic.create!
+ assert Topic.many?
+ end
+
def test_should_build_on_top_of_scope
topic = Topic.approved.build({})
assert topic.approved
@@ -425,26 +429,31 @@ class NamedScopeTest < ActiveRecord::TestCase
end
end
+ # Note: these next two are kinda odd because they are essentially just testing that the
+ # query cache works as it should, but they are here for legacy reasons as they was previously
+ # a separate cache on association proxies, and these show that that is not necessary.
def test_scopes_are_cached_on_associations
post = posts(:welcome)
- assert_equal post.comments.containing_the_letter_e.object_id, post.comments.containing_the_letter_e.object_id
-
- post.comments.containing_the_letter_e.all # force load
- assert_no_queries { post.comments.containing_the_letter_e.all }
+ Post.cache do
+ assert_queries(1) { post.comments.containing_the_letter_e.all }
+ assert_no_queries { post.comments.containing_the_letter_e.all }
+ end
end
def test_scopes_with_arguments_are_cached_on_associations
post = posts(:welcome)
- one = post.comments.limit_by(1).all
- assert_equal 1, one.size
+ Post.cache do
+ one = assert_queries(1) { post.comments.limit_by(1).all }
+ assert_equal 1, one.size
- two = post.comments.limit_by(2).all
- assert_equal 2, two.size
+ two = assert_queries(1) { post.comments.limit_by(2).all }
+ assert_equal 2, two.size
- assert_no_queries { post.comments.limit_by(1).all }
- assert_no_queries { post.comments.limit_by(2).all }
+ assert_no_queries { post.comments.limit_by(1).all }
+ assert_no_queries { post.comments.limit_by(2).all }
+ end
end
def test_scopes_are_reset_on_association_reload
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index c57ab7ed28..6568eb1d18 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -131,6 +131,14 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
assert_equal 'photography', interest.reload.topic
end
+ def test_destroy_works_independent_of_reject_if
+ Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true }, :allow_destroy => true
+ man = Man.create(:name => "Jon")
+ interest = man.interests.create(:topic => 'the ladies')
+ man.update_attributes({:interests_attributes => { :_destroy => "1", :id => interest.id } })
+ assert man.reload.interests.empty?
+ end
+
def test_has_many_association_updating_a_single_record
Man.accepts_nested_attributes_for(:interests)
man = Man.create(:name => 'John')
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 8ca9d626d1..b066575af8 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -12,7 +12,7 @@ require 'models/minimalistic'
require 'models/warehouse_thing'
require 'models/parrot'
require 'models/minivan'
-require 'models/loose_person'
+require 'models/person'
require 'rexml/document'
require 'active_support/core_ext/exception'
@@ -389,6 +389,92 @@ class PersistencesTest < ActiveRecord::TestCase
assert_not_equal prev_month, developer.updated_at
end
+ def test_update_column
+ topic = Topic.find(1)
+ topic.update_column("approved", true)
+ assert topic.approved?
+ topic.reload
+ assert topic.approved?
+
+ topic.update_column(:approved, false)
+ assert !topic.approved?
+ topic.reload
+ assert !topic.approved?
+ end
+
+ def test_update_column_should_not_use_setter_method
+ dev = Developer.find(1)
+ dev.instance_eval { def salary=(value); write_attribute(:salary, value * 2); end }
+
+ dev.update_column(:salary, 80000)
+ assert_equal 80000, dev.salary
+
+ dev.reload
+ assert_equal 80000, dev.salary
+ end
+
+ def test_update_column_should_raise_exception_if_new_record
+ topic = Topic.new
+ assert_raises(ActiveRecord::ActiveRecordError) { topic.update_column("approved", false) }
+ end
+
+ def test_update_column_should_not_leave_the_object_dirty
+ topic = Topic.find(1)
+ topic.update_attribute("content", "Have a nice day")
+
+ topic.reload
+ topic.update_column(:content, "You too")
+ assert_equal [], topic.changed
+
+ topic.reload
+ topic.update_column("content", "Have a nice day")
+ assert_equal [], topic.changed
+ end
+
+ def test_update_column_with_model_having_primary_key_other_than_id
+ minivan = Minivan.find('m1')
+ new_name = 'sebavan'
+
+ minivan.update_column(:name, new_name)
+ assert_equal new_name, minivan.name
+ end
+
+ def test_update_column_for_readonly_attribute
+ minivan = Minivan.find('m1')
+ prev_color = minivan.color
+ assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_column(:color, 'black') }
+ assert_equal prev_color, minivan.color
+ end
+
+ def test_update_column_should_not_modify_updated_at
+ developer = Developer.find(1)
+ prev_month = Time.now.prev_month
+
+ developer.update_column(:updated_at, prev_month)
+ assert_equal prev_month, developer.updated_at
+
+ developer.update_column(:salary, 80001)
+ assert_equal prev_month, developer.updated_at
+
+ developer.reload
+ assert_equal prev_month.to_i, developer.updated_at.to_i
+ end
+
+ def test_update_column_with_one_changed_and_one_updated
+ t = Topic.order('id').limit(1).first
+ title, author_name = t.title, t.author_name
+ t.author_name = 'John'
+ t.update_column(:title, 'super_title')
+ assert_equal 'John', t.author_name
+ assert_equal 'super_title', t.title
+ assert t.changed?, "topic should have changed"
+ assert t.author_name_changed?, "author_name should have changed"
+
+ t.reload
+ assert_equal author_name, t.author_name
+ assert_equal 'super_title', t.title
+ end
+
def test_update_attributes
topic = Topic.find(1)
assert !topic.approved?
@@ -405,6 +491,26 @@ class PersistencesTest < ActiveRecord::TestCase
assert_equal "The First Topic", topic.title
end
+ def test_update_attributes_as_admin
+ person = TightPerson.create({ "first_name" => 'Joshua' })
+ person.update_attributes({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :as => :admin)
+ person.reload
+
+ assert_equal 'Josh', person.first_name
+ assert_equal 'm', person.gender
+ assert_equal 'from NZ', person.comments
+ end
+
+ def test_update_attributes_without_protection
+ person = TightPerson.create({ "first_name" => 'Joshua' })
+ person.update_attributes({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :without_protection => true)
+ person.reload
+
+ assert_equal 'Josh', person.first_name
+ assert_equal 'm', person.gender
+ assert_equal 'from NZ', person.comments
+ end
+
def test_update_attributes!
Reply.validates_presence_of(:title)
reply = Reply.find(2)
@@ -426,6 +532,26 @@ class PersistencesTest < ActiveRecord::TestCase
Reply.reset_callbacks(:validate)
end
+ def test_update_attributes_with_bang_as_admin
+ person = TightPerson.create({ "first_name" => 'Joshua' })
+ person.update_attributes!({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :as => :admin)
+ person.reload
+
+ assert_equal 'Josh', person.first_name
+ assert_equal 'm', person.gender
+ assert_equal 'from NZ', person.comments
+ end
+
+ def test_update_attributestes_with_bang_without_protection
+ person = TightPerson.create({ "first_name" => 'Joshua' })
+ person.update_attributes!({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :without_protection => true)
+ person.reload
+
+ assert_equal 'Josh', person.first_name
+ assert_equal 'm', person.gender
+ assert_equal 'from NZ', person.comments
+ end
+
def test_destroyed_returns_boolean
developer = Developer.first
assert_equal false, developer.destroyed?
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 63d8c7d1c1..05a41d8a0a 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -136,4 +136,13 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_nil ActiveRecord::Base.connection.primary_key('developers_projects')
end
end
+
+ def test_quoted_primary_key_after_set_primary_key
+ k = Class.new( ActiveRecord::Base )
+ assert_equal k.connection.quote_column_name("id"), k.quoted_primary_key
+ k.primary_key = "foo"
+ assert_equal k.connection.quote_column_name("foo"), k.quoted_primary_key
+ k.set_primary_key "bar"
+ assert_equal k.connection.quote_column_name("bar"), k.quoted_primary_key
+ end
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 53aefc7b58..287f7e255b 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -13,7 +13,7 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_find_queries
- assert_queries(2) { Task.find(1); Task.find(1) }
+ assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Task.find(1); Task.find(1) }
end
def test_find_queries_with_cache
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index b87fb51d97..80ee74e41e 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -154,15 +154,16 @@ module ActiveRecord
end
def test_crazy_object
- crazy = Class.new { def to_yaml; 'lol' end }.new
- assert_equal "'lol'", @quoter.quote(crazy, nil)
- assert_equal "'lol'", @quoter.quote(crazy, Object.new)
+ crazy = Class.new.new
+ expected = "'#{YAML.dump(crazy)}'"
+ assert_equal expected, @quoter.quote(crazy, nil)
+ assert_equal expected, @quoter.quote(crazy, Object.new)
end
def test_crazy_object_calls_quote_string
- crazy = Class.new { def to_yaml; 'lo\l' end }.new
- assert_equal "'lo\\\\l'", @quoter.quote(crazy, nil)
- assert_equal "'lo\\\\l'", @quoter.quote(crazy, Object.new)
+ crazy = Class.new { def initialize; @lol = 'lo\l' end }.new
+ assert_match "lo\\\\l", @quoter.quote(crazy, nil)
+ assert_match "lo\\\\l", @quoter.quote(crazy, Object.new)
end
def test_quote_string_no_column
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index eb580928ba..97d9669483 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -7,9 +7,16 @@ require 'models/subscriber'
require 'models/ship'
require 'models/pirate'
require 'models/price_estimate'
-require 'models/tagging'
+require 'models/essay'
require 'models/author'
+require 'models/organization'
require 'models/post'
+require 'models/tagging'
+require 'models/category'
+require 'models/book'
+require 'models/subscriber'
+require 'models/subscription'
+require 'models/tag'
require 'models/sponsor'
class ReflectionTest < ActiveRecord::TestCase
@@ -195,10 +202,54 @@ class ReflectionTest < ActiveRecord::TestCase
assert_kind_of ThroughReflection, Subscriber.reflect_on_association(:books)
end
+ def test_chain
+ expected = [
+ Organization.reflect_on_association(:author_essay_categories),
+ Author.reflect_on_association(:essays),
+ Organization.reflect_on_association(:authors)
+ ]
+ actual = Organization.reflect_on_association(:author_essay_categories).chain
+
+ assert_equal expected, actual
+ end
+
+ def test_conditions
+ expected = [
+ [{ :tags => { :name => 'Blue' } }],
+ [{ :taggings => { :comment => 'first' } }, { "taggable_type" => "Post" }],
+ [{ :posts => { :title => ['misc post by bob', 'misc post by mary'] } }]
+ ]
+ actual = Author.reflect_on_association(:misc_post_first_blue_tags).conditions
+ assert_equal expected, actual
+
+ expected = [
+ [{ :tags => { :name => 'Blue' } }, { :taggings => { :comment => 'first' } }, { :posts => { :title => ['misc post by bob', 'misc post by mary'] } }],
+ [{ "taggable_type" => "Post" }],
+ []
+ ]
+ actual = Author.reflect_on_association(:misc_post_first_blue_tags_2).conditions
+ assert_equal expected, actual
+ end
+
+ def test_nested?
+ assert !Author.reflect_on_association(:comments).nested?
+ assert Author.reflect_on_association(:tags).nested?
+
+ # Only goes :through once, but the through_reflection is a has_and_belongs_to_many, so this is
+ # a nested through association
+ assert Category.reflect_on_association(:post_comments).nested?
+ end
+
def test_association_primary_key
- assert_equal "id", Author.reflect_on_association(:posts).association_primary_key.to_s
+ # Normal association
+ assert_equal "id", Author.reflect_on_association(:posts).association_primary_key.to_s
assert_equal "name", Author.reflect_on_association(:essay).association_primary_key.to_s
- assert_equal "id", Tagging.reflect_on_association(:taggable).association_primary_key.to_s
+ assert_equal "id", Tagging.reflect_on_association(:taggable).association_primary_key.to_s
+
+ # Through association (uses the :primary_key option from the source reflection)
+ assert_equal "nick", Author.reflect_on_association(:subscribers).association_primary_key.to_s
+ assert_equal "name", Author.reflect_on_association(:essay_category).association_primary_key.to_s
+ assert_equal "custom_primary_key", Author.reflect_on_association(:tags_with_primary_key).association_primary_key.to_s # nested
end
def test_active_record_primary_key
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index cda2850b02..2ed676fe69 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -132,8 +132,6 @@ class RelationScopingTest < ActiveRecord::TestCase
end
def test_ensure_that_method_scoping_is_correctly_restored
- scoped_methods = Developer.send(:current_scoped_methods)
-
begin
Developer.where("name = 'Jamis'").scoping do
raise "an exception"
@@ -141,7 +139,7 @@ class RelationScopingTest < ActiveRecord::TestCase
rescue
end
- assert_equal scoped_methods, Developer.send(:current_scoped_methods)
+ assert !Developer.scoped.where_values.include?("name = 'Jamis'")
end
end
@@ -310,33 +308,20 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
- def test_default_scope_with_lambda
- expected = Post.find_all_by_author_id(2)
- PostForAuthor.selected_author = 2
- received = PostForAuthor.all
- assert_equal expected, received
- expected = Post.find_all_by_author_id(1)
- PostForAuthor.selected_author = 1
- received = PostForAuthor.all
- assert_equal expected, received
+ def test_default_scope_as_class_method
+ assert_equal [developers(:david).becomes(ClassMethodDeveloperCalledDavid)], ClassMethodDeveloperCalledDavid.all
end
- def test_default_scope_with_thing_that_responds_to_call
- klass = Class.new(ActiveRecord::Base) do
- self.table_name = 'posts'
- end
+ def test_default_scope_with_lambda
+ assert_equal [developers(:david).becomes(LazyLambdaDeveloperCalledDavid)], LazyLambdaDeveloperCalledDavid.all
+ end
- klass.class_eval do
- default_scope Class.new(Struct.new(:klass)) {
- def call
- klass.where(:author_id => 2)
- end
- }.new(self)
- end
+ def test_default_scope_with_block
+ assert_equal [developers(:david).becomes(LazyBlockDeveloperCalledDavid)], LazyBlockDeveloperCalledDavid.all
+ end
- records = klass.all
- assert_equal 1, records.length
- assert_equal 2, records.first.author_id
+ def test_default_scope_with_callable
+ assert_equal [developers(:david).becomes(CallableDeveloperCalledDavid)], CallableDeveloperCalledDavid.all
end
def test_default_scope_is_unscoped_on_find
@@ -360,53 +345,20 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_default_scoping_with_threads
2.times do
- Thread.new { assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values }.join
+ Thread.new { assert DeveloperOrderedBySalary.scoped.to_sql.include?('salary DESC') }.join
end
end
- def test_default_scoping_with_inheritance
- # Inherit a class having a default scope and define a new default scope
- klass = Class.new(DeveloperOrderedBySalary)
- klass.send :default_scope, :limit => 1
-
- # Scopes added on children should append to parent scope
- assert_equal 1, klass.scoped.limit_value
- assert_equal ['salary DESC'], klass.scoped.order_values
-
- # Parent should still have the original scope
- assert_nil DeveloperOrderedBySalary.scoped.limit_value
- assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values
- end
-
- def test_default_scope_called_twice_merges_conditions
- Developer.destroy_all
- Developer.create!(:name => "David", :salary => 80000)
- Developer.create!(:name => "David", :salary => 100000)
- Developer.create!(:name => "Brian", :salary => 100000)
-
- klass = Class.new(Developer)
- klass.__send__ :default_scope, :conditions => { :name => "David" }
- klass.__send__ :default_scope, :conditions => { :salary => 100000 }
- assert_equal 1, klass.count
- assert_equal "David", klass.first.name
- assert_equal 100000, klass.first.salary
+ def test_default_scope_with_inheritance
+ wheres = InheritedPoorDeveloperCalledJamis.scoped.where_values_hash
+ assert_equal "Jamis", wheres[:name]
+ assert_equal 50000, wheres[:salary]
end
- def test_default_scope_called_twice_in_different_place_merges_where_clause
- Developer.destroy_all
- Developer.create!(:name => "David", :salary => 80000)
- Developer.create!(:name => "David", :salary => 100000)
- Developer.create!(:name => "Brian", :salary => 100000)
-
- klass = Class.new(Developer)
- klass.class_eval do
- default_scope where("name = 'David'")
- default_scope where("salary = 100000")
- end
-
- assert_equal 1, klass.count
- assert_equal "David", klass.first.name
- assert_equal 100000, klass.first.salary
+ def test_default_scope_with_multiple_calls
+ wheres = MultiplePoorDeveloperCalledJamis.scoped.where_values_hash
+ assert_equal "Jamis", wheres[:name]
+ assert_equal 50000, wheres[:salary]
end
def test_method_scope
@@ -429,9 +381,9 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
- def test_except_and_order_overrides_default_scope_order
+ def test_reorder_overrides_default_scope_order
expected = Developer.order('name DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.except(:order).order('name DESC').collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.reorder('name DESC').collect { |dev| dev.name }
assert_equal expected, received
end
@@ -449,12 +401,6 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
- def test_default_scope_using_relation
- posts = PostWithComment.scoped
- assert_equal 2, posts.count
- assert_equal posts(:thinking), posts.first
- end
-
def test_create_attribute_overwrites_default_scoping
assert_equal 'David', PoorDeveloperCalledJamis.create!(:name => 'David').name
assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary
@@ -503,4 +449,25 @@ class DefaultScopingTest < ActiveRecord::TestCase
jamis = PoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with(nil).new
assert_equal 'Jamis', jamis.name
end
+
+ def test_unscoped_with_named_scope_should_not_have_default_scope
+ assert_equal [DeveloperCalledJamis.find(developers(:poor_jamis).id)], DeveloperCalledJamis.poor
+
+ assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis))
+ assert_equal 10, DeveloperCalledJamis.unscoped.poor.length
+ end
+
+ def test_multiple_default_scope_calls_are_deprecated
+ klass = Class.new(ActiveRecord::Base)
+
+ assert_not_deprecated do
+ klass.send(:default_scope, :foo => :bar)
+ end
+
+ assert_deprecated do
+ klass.send(:default_scope, :foo => :bar)
+ end
+
+ assert_equal 2, klass.default_scopes.length
+ end
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 7bdbd773b6..6874bd18f8 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -20,7 +20,7 @@ module ActiveRecord
end
def test_single_values
- assert_equal [:limit, :offset, :lock, :readonly, :create_with, :from].map(&:to_s).sort,
+ assert_equal [:limit, :offset, :lock, :readonly, :create_with, :from, :reorder].map(&:to_s).sort,
Relation::SINGLE_VALUE_METHODS.map(&:to_s).sort
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 948fcf7012..fc9df8c7a3 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -151,6 +151,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal topics(:fourth).title, topics.first.title
end
+ def test_finding_with_reorder
+ topics = Topic.order('author_name').order('title').reorder('id').all
+ topics_titles = topics.map{ |t| t.title }
+ assert_equal ['The First Topic', 'The Second Topic of the day', 'The Third Topic of the day', 'The Fourth Topic of the day'], topics_titles
+ end
+
def test_finding_with_order_and_take
entrants = Entrant.order("id ASC").limit(2).to_a
@@ -165,7 +171,7 @@ class RelationTest < ActiveRecord::TestCase
def test_finding_with_complex_order
tags = Tag.includes(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a
- assert_equal 2, tags.length
+ assert_equal 3, tags.length
end
def test_finding_with_order_limit_and_offset
@@ -281,27 +287,27 @@ class RelationTest < ActiveRecord::TestCase
def test_find_with_preloaded_associations
assert_queries(2) do
- posts = Post.preload(:comments)
+ posts = Post.preload(:comments).order('posts.id')
assert posts.first.comments.first
end
assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
- posts = Post.preload(:comments).to_a
+ posts = Post.preload(:comments).order('posts.id')
assert posts.first.comments.first
end
assert_queries(2) do
- posts = Post.preload(:author)
+ posts = Post.preload(:author).order('posts.id')
assert posts.first.author
end
assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
- posts = Post.preload(:author).to_a
+ posts = Post.preload(:author).order('posts.id')
assert posts.first.author
end
assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 3) do
- posts = Post.preload(:author, :comments).to_a
+ posts = Post.preload(:author, :comments).order('posts.id')
assert posts.first.author
assert posts.first.comments.first
end
@@ -309,22 +315,22 @@ class RelationTest < ActiveRecord::TestCase
def test_find_with_included_associations
assert_queries(2) do
- posts = Post.includes(:comments)
+ posts = Post.includes(:comments).order('posts.id')
assert posts.first.comments.first
end
assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
- posts = Post.scoped.includes(:comments)
+ posts = Post.scoped.includes(:comments).order('posts.id')
assert posts.first.comments.first
end
assert_queries(2) do
- posts = Post.includes(:author)
+ posts = Post.includes(:author).order('posts.id')
assert posts.first.author
end
assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 3) do
- posts = Post.includes(:author, :comments).to_a
+ posts = Post.includes(:author, :comments).order('posts.id')
assert posts.first.author
assert posts.first.comments.first
end
@@ -538,7 +544,7 @@ class RelationTest < ActiveRecord::TestCase
def test_last
authors = Author.scoped
- assert_equal authors(:mary), authors.last
+ assert_equal authors(:bob), authors.last
end
def test_destroy_all
@@ -618,22 +624,22 @@ class RelationTest < ActiveRecord::TestCase
def test_count
posts = Post.scoped
- assert_equal 7, posts.count
- assert_equal 7, posts.count(:all)
- assert_equal 7, posts.count(:id)
+ assert_equal 11, posts.count
+ assert_equal 11, posts.count(:all)
+ assert_equal 11, posts.count(:id)
assert_equal 1, posts.where('comments_count > 1').count
- assert_equal 5, posts.where(:comments_count => 0).count
+ assert_equal 9, posts.where(:comments_count => 0).count
end
def test_count_with_distinct
posts = Post.scoped
assert_equal 3, posts.count(:comments_count, :distinct => true)
- assert_equal 7, posts.count(:comments_count, :distinct => false)
+ assert_equal 11, posts.count(:comments_count, :distinct => false)
assert_equal 3, posts.select(:comments_count).count(:distinct => true)
- assert_equal 7, posts.select(:comments_count).count(:distinct => false)
+ assert_equal 11, posts.select(:comments_count).count(:distinct => false)
end
def test_count_explicit_columns
@@ -643,7 +649,7 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [0], posts.select('comments_count').where('id is not null').group('id').order('id').count.values.uniq
assert_equal 0, posts.where('id is not null').select('comments_count').count
- assert_equal 7, posts.select('comments_count').count('id')
+ assert_equal 11, posts.select('comments_count').count('id')
assert_equal 0, posts.select('comments_count').count
assert_equal 0, posts.count(:comments_count)
assert_equal 0, posts.count('comments_count')
@@ -658,12 +664,40 @@ class RelationTest < ActiveRecord::TestCase
def test_size
posts = Post.scoped
- assert_queries(1) { assert_equal 7, posts.size }
+ assert_queries(1) { assert_equal 11, posts.size }
+ assert ! posts.loaded?
+
+ best_posts = posts.where(:comments_count => 0)
+ best_posts.to_a # force load
+ assert_no_queries { assert_equal 9, best_posts.size }
+ end
+
+ def test_size_with_limit
+ posts = Post.limit(10)
+
+ assert_queries(1) { assert_equal 10, posts.size }
assert ! posts.loaded?
best_posts = posts.where(:comments_count => 0)
best_posts.to_a # force load
- assert_no_queries { assert_equal 5, best_posts.size }
+ assert_no_queries { assert_equal 9, best_posts.size }
+ end
+
+ def test_size_with_zero_limit
+ posts = Post.limit(0)
+
+ assert_no_queries { assert_equal 0, posts.size }
+ assert ! posts.loaded?
+
+ posts.to_a # force load
+ assert_no_queries { assert_equal 0, posts.size }
+ end
+
+ def test_empty_with_zero_limit
+ posts = Post.limit(0)
+
+ assert_no_queries { assert_equal true, posts.empty? }
+ assert ! posts.loaded?
end
def test_count_complex_chained_relations
@@ -673,6 +707,32 @@ class RelationTest < ActiveRecord::TestCase
assert_equal expected, posts.count
end
+ def test_empty
+ posts = Post.scoped
+
+ assert_queries(1) { assert_equal false, posts.empty? }
+ assert ! posts.loaded?
+
+ no_posts = posts.where(:title => "")
+ assert_queries(1) { assert_equal true, no_posts.empty? }
+ assert ! no_posts.loaded?
+
+ best_posts = posts.where(:comments_count => 0)
+ best_posts.to_a # force load
+ assert_no_queries { assert_equal false, best_posts.empty? }
+ end
+
+ def test_empty_complex_chained_relations
+ posts = Post.select("comments_count").where("id is not null").group("author_id").where("comments_count > 0")
+
+ assert_queries(1) { assert_equal false, posts.empty? }
+ assert ! posts.loaded?
+
+ no_posts = posts.where(:title => "")
+ assert_queries(1) { assert_equal true, no_posts.empty? }
+ assert ! no_posts.loaded?
+ end
+
def test_any
posts = Post.scoped
@@ -771,6 +831,10 @@ class RelationTest < ActiveRecord::TestCase
assert_equal Post.all, all_posts.all
end
+ def test_extensions_with_except
+ assert_equal 2, Topic.named_extension.order(:author_name).except(:order).two
+ end
+
def test_only
relation = Post.where(:author_id => 1).order('id ASC').limit(1)
assert_equal [posts(:welcome)], relation.all
@@ -782,6 +846,10 @@ class RelationTest < ActiveRecord::TestCase
assert_equal Post.limit(1).all.first, all_posts.first
end
+ def test_extensions_with_only
+ assert_equal 2, Topic.named_extension.order(:author_name).only(:order).two
+ end
+
def test_anonymous_extension
relation = Post.where(:author_id => 1).order('id ASC').extending do
def author
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 9b2c7c00df..e8f2f44189 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -203,6 +203,13 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t.xml "data"}, output
end
end
+
+ def test_schema_dump_includes_tsvector_shorthand_definition
+ output = standard_dump
+ if %r{create_table "postgresql_tsvectors"} =~ output
+ assert_match %r{t.tsvector "text_vector"}, output
+ end
+ end
end
def test_schema_dump_keeps_large_precision_integer_columns_as_decimal
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 1c21f0f3b6..ceb1452afd 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -131,8 +131,9 @@ class TimestampTest < ActiveRecord::TestCase
toy = Toy.first
pet = toy.pet
owner = pet.owner
+ time = 3.days.ago
- owner.update_attribute(:updated_at, (time = 3.days.ago))
+ owner.update_column(:updated_at, time)
toy.touch
owner.reload
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index b4f3dd034c..0f1b3667cc 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -162,6 +162,32 @@ class UniquenessValidationTest < ActiveRecord::TestCase
end
end
+ def test_validate_case_sensitive_uniqueness_with_special_sql_like_chars
+ Topic.validates_uniqueness_of(:title, :case_sensitive => true)
+
+ t = Topic.new("title" => "I'm unique!")
+ assert t.save, "Should save t as unique"
+
+ t2 = Topic.new("title" => "I'm %")
+ assert t2.save, "Should save t2 as unique"
+
+ t3 = Topic.new("title" => "I'm uniqu_!")
+ assert t3.save, "Should save t3 as unique"
+ end
+
+ def test_validate_case_insensitive_uniqueness_with_special_sql_like_chars
+ Topic.validates_uniqueness_of(:title, :case_sensitive => false)
+
+ t = Topic.new("title" => "I'm unique!")
+ assert t.save, "Should save t as unique"
+
+ t2 = Topic.new("title" => "I'm %")
+ assert t2.save, "Should save t2 as unique"
+
+ t3 = Topic.new("title" => "I'm uniqu_!")
+ assert t3.save, "Should save t3 as unique"
+ end
+
def test_validate_case_sensitive_uniqueness
Topic.validates_uniqueness_of(:title, :case_sensitive => true, :allow_nil => true)
diff --git a/activerecord/test/fixtures/authors.yml b/activerecord/test/fixtures/authors.yml
index de2ec7d38b..832236a486 100644
--- a/activerecord/test/fixtures/authors.yml
+++ b/activerecord/test/fixtures/authors.yml
@@ -3,7 +3,13 @@ david:
name: David
author_address_id: 1
author_address_extra_id: 2
+ organization_id: No Such Agency
+ owned_essay_id: A Modest Proposal
mary:
id: 2
name: Mary
+
+bob:
+ id: 3
+ name: Bob
diff --git a/activerecord/test/fixtures/books.yml b/activerecord/test/fixtures/books.yml
index 473663ff5b..fb48645456 100644
--- a/activerecord/test/fixtures/books.yml
+++ b/activerecord/test/fixtures/books.yml
@@ -1,7 +1,9 @@
awdr:
+ author_id: 1
id: 1
name: "Agile Web Development with Rails"
rfr:
+ author_id: 1
id: 2
name: "Ruby for Rails"
diff --git a/activerecord/test/fixtures/categories.yml b/activerecord/test/fixtures/categories.yml
index b0770a093d..3e75e733a6 100644
--- a/activerecord/test/fixtures/categories.yml
+++ b/activerecord/test/fixtures/categories.yml
@@ -12,3 +12,8 @@ sti_test:
id: 3
name: Special category
type: SpecialCategory
+
+cooking:
+ id: 4
+ name: Cooking
+ type: Category
diff --git a/activerecord/test/fixtures/categories_posts.yml b/activerecord/test/fixtures/categories_posts.yml
index 9b67ab4fa4..c6f0d885f5 100644
--- a/activerecord/test/fixtures/categories_posts.yml
+++ b/activerecord/test/fixtures/categories_posts.yml
@@ -21,3 +21,11 @@ sti_test_sti_habtm:
general_hello:
category_id: 1
post_id: 4
+
+general_misc_by_bob:
+ category_id: 1
+ post_id: 8
+
+cooking_misc_by_bob:
+ category_id: 4
+ post_id: 8
diff --git a/activerecord/test/fixtures/categorizations.yml b/activerecord/test/fixtures/categorizations.yml
index c5b6fc9a51..62e5bd111a 100644
--- a/activerecord/test/fixtures/categorizations.yml
+++ b/activerecord/test/fixtures/categorizations.yml
@@ -15,3 +15,9 @@ mary_thinking_general:
author_id: 2
post_id: 2
category_id: 1
+
+bob_misc_by_bob_technology:
+ id: 4
+ author_id: 3
+ post_id: 8
+ category_id: 2
diff --git a/activerecord/test/fixtures/clubs.yml b/activerecord/test/fixtures/clubs.yml
index 1986d28229..82e439e8e5 100644
--- a/activerecord/test/fixtures/clubs.yml
+++ b/activerecord/test/fixtures/clubs.yml
@@ -1,6 +1,8 @@
boring_club:
name: Banana appreciation society
+ category_id: 1
moustache_club:
name: Moustache and Eyebrow Fancier Club
crazy_club:
- name: Skull and bones \ No newline at end of file
+ name: Skull and bones
+ category_id: 2
diff --git a/activerecord/test/fixtures/essays.yml b/activerecord/test/fixtures/essays.yml
new file mode 100644
index 0000000000..9d15d82359
--- /dev/null
+++ b/activerecord/test/fixtures/essays.yml
@@ -0,0 +1,6 @@
+david_modest_proposal:
+ name: A Modest Proposal
+ writer_type: Author
+ writer_id: David
+ category_id: General
+ author_id: David
diff --git a/activerecord/test/fixtures/member_details.yml b/activerecord/test/fixtures/member_details.yml
new file mode 100644
index 0000000000..e1fe695a9b
--- /dev/null
+++ b/activerecord/test/fixtures/member_details.yml
@@ -0,0 +1,8 @@
+groucho:
+ id: 1
+ member_id: 1
+ organization: nsa
+some_other_guy:
+ id: 2
+ member_id: 2
+ organization: nsa
diff --git a/activerecord/test/fixtures/members.yml b/activerecord/test/fixtures/members.yml
index 824840b7e5..f3bbf0dac6 100644
--- a/activerecord/test/fixtures/members.yml
+++ b/activerecord/test/fixtures/members.yml
@@ -6,3 +6,6 @@ some_other_guy:
id: 2
name: Englebert Humperdink
member_type_id: 2
+blarpy_winkup:
+ id: 3
+ name: Blarpy Winkup
diff --git a/activerecord/test/fixtures/memberships.yml b/activerecord/test/fixtures/memberships.yml
index eed8b22af8..60eb641054 100644
--- a/activerecord/test/fixtures/memberships.yml
+++ b/activerecord/test/fixtures/memberships.yml
@@ -18,3 +18,10 @@ other_guys_membership:
member_id: 2
favourite: false
type: CurrentMembership
+
+blarpy_winkup_crazy_club:
+ joined_on: <%= 4.weeks.ago.to_s(:db) %>
+ club: crazy_club
+ member_id: 3
+ favourite: false
+ type: CurrentMembership
diff --git a/activerecord/test/fixtures/owners.yml b/activerecord/test/fixtures/owners.yml
index d5493a84b7..2d21ce433c 100644
--- a/activerecord/test/fixtures/owners.yml
+++ b/activerecord/test/fixtures/owners.yml
@@ -1,6 +1,7 @@
blackbeard:
owner_id: 1
name: blackbeard
+ essay_id: A Modest Proposal
ashley:
owner_id: 2
diff --git a/activerecord/test/fixtures/posts.yml b/activerecord/test/fixtures/posts.yml
index 07069a064f..7298096025 100644
--- a/activerecord/test/fixtures/posts.yml
+++ b/activerecord/test/fixtures/posts.yml
@@ -52,3 +52,31 @@ eager_other:
title: eager loading with OR'd conditions
body: hello
type: Post
+
+misc_by_bob:
+ id: 8
+ author_id: 3
+ title: misc post by bob
+ body: hello
+ type: Post
+
+misc_by_mary:
+ id: 9
+ author_id: 2
+ title: misc post by mary
+ body: hello
+ type: Post
+
+other_by_bob:
+ id: 10
+ author_id: 3
+ title: other post by bob
+ body: hello
+ type: Post
+
+other_by_mary:
+ id: 11
+ author_id: 2
+ title: other post by mary
+ body: hello
+ type: Post
diff --git a/activerecord/test/fixtures/ratings.yml b/activerecord/test/fixtures/ratings.yml
new file mode 100644
index 0000000000..34e208efa3
--- /dev/null
+++ b/activerecord/test/fixtures/ratings.yml
@@ -0,0 +1,14 @@
+normal_comment_rating:
+ id: 1
+ comment_id: 8
+ value: 1
+
+special_comment_rating:
+ id: 2
+ comment_id: 6
+ value: 1
+
+sub_special_comment_rating:
+ id: 3
+ comment_id: 12
+ value: 1
diff --git a/activerecord/test/fixtures/taggings.yml b/activerecord/test/fixtures/taggings.yml
index 3db6a4c079..d339c12b25 100644
--- a/activerecord/test/fixtures/taggings.yml
+++ b/activerecord/test/fixtures/taggings.yml
@@ -26,3 +26,53 @@ godfather:
orphaned:
id: 5
tag_id: 1
+
+misc_post_by_bob:
+ id: 6
+ tag_id: 2
+ taggable_id: 8
+ taggable_type: Post
+
+misc_post_by_mary:
+ id: 7
+ tag_id: 2
+ taggable_id: 9
+ taggable_type: Post
+
+misc_by_bob_blue_first:
+ id: 8
+ tag_id: 3
+ taggable_id: 8
+ taggable_type: Post
+ comment: first
+
+misc_by_bob_blue_second:
+ id: 9
+ tag_id: 3
+ taggable_id: 8
+ taggable_type: Post
+ comment: second
+
+other_by_bob_blue:
+ id: 10
+ tag_id: 3
+ taggable_id: 10
+ taggable_type: Post
+ comment: first
+
+other_by_mary_blue:
+ id: 11
+ tag_id: 3
+ taggable_id: 11
+ taggable_type: Post
+ comment: first
+
+special_comment_rating:
+ id: 12
+ taggable_id: 2
+ taggable_type: Rating
+
+normal_comment_rating:
+ id: 13
+ taggable_id: 1
+ taggable_type: Rating
diff --git a/activerecord/test/fixtures/tags.yml b/activerecord/test/fixtures/tags.yml
index 7610fd38b9..d4b7c9a4d5 100644
--- a/activerecord/test/fixtures/tags.yml
+++ b/activerecord/test/fixtures/tags.yml
@@ -4,4 +4,8 @@ general:
misc:
id: 2
- name: Misc \ No newline at end of file
+ name: Misc
+
+blue:
+ id: 3
+ name: Blue
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 83a6f5d926..e0cbc44265 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -94,16 +94,50 @@ class Author < ActiveRecord::Base
has_many :author_favorites
has_many :favorite_authors, :through => :author_favorites, :order => 'name'
- has_many :tagging, :through => :posts # through polymorphic has_one
- has_many :taggings, :through => :posts, :source => :taggings # through polymorphic has_many
- has_many :tags, :through => :posts # through has_many :through
+ has_many :tagging, :through => :posts
+ has_many :taggings, :through => :posts
+ has_many :tags, :through => :posts
+ has_many :similar_posts, :through => :tags, :source => :tagged_posts, :uniq => true
+ has_many :distinct_tags, :through => :posts, :source => :tags, :select => "DISTINCT tags.*", :order => "tags.name"
has_many :post_categories, :through => :posts, :source => :categories
+ has_many :tagging_tags, :through => :taggings, :source => :tag
+ has_many :tags_with_primary_key, :through => :posts
+
+ has_many :books
+ has_many :subscriptions, :through => :books
+ has_many :subscribers, :through => :subscriptions, :order => "subscribers.nick" # through has_many :through (on through reflection)
+ has_many :distinct_subscribers, :through => :subscriptions, :source => :subscriber, :select => "DISTINCT subscribers.*", :order => "subscribers.nick"
has_one :essay, :primary_key => :name, :as => :writer
+ has_one :essay_category, :through => :essay, :source => :category
+ has_one :essay_owner, :through => :essay, :source => :owner
+
+ has_one :essay_2, :primary_key => :name, :class_name => 'Essay', :foreign_key => :author_id
+ has_one :essay_category_2, :through => :essay_2, :source => :category
+
+ has_many :essays, :primary_key => :name, :as => :writer
+ has_many :essay_categories, :through => :essays, :source => :category
+ has_many :essay_owners, :through => :essays, :source => :owner
+
+ has_many :essays_2, :primary_key => :name, :class_name => 'Essay', :foreign_key => :author_id
+ has_many :essay_categories_2, :through => :essays_2, :source => :category
+
+ belongs_to :owned_essay, :primary_key => :name, :class_name => 'Essay'
+ has_one :owned_essay_category, :through => :owned_essay, :source => :category
- belongs_to :author_address, :dependent => :destroy
+ belongs_to :author_address, :dependent => :destroy
belongs_to :author_address_extra, :dependent => :delete, :class_name => "AuthorAddress"
+ has_many :post_categories, :through => :posts, :source => :categories
+ has_many :category_post_comments, :through => :categories, :source => :post_comments
+
+ has_many :misc_posts, :class_name => 'Post',
+ :conditions => { :posts => { :title => ['misc post by bob', 'misc post by mary'] } }
+ has_many :misc_post_first_blue_tags, :through => :misc_posts, :source => :first_blue_tags
+
+ has_many :misc_post_first_blue_tags_2, :through => :posts, :source => :first_blue_tags_2,
+ :conditions => { :posts => { :title => ['misc post by bob', 'misc post by mary'] } }
+
scope :relation_include_posts, includes(:posts)
scope :relation_include_tags, includes(:tags)
diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb
index 1e030b4f59..d27d0af77c 100644
--- a/activerecord/test/models/book.rb
+++ b/activerecord/test/models/book.rb
@@ -1,4 +1,6 @@
class Book < ActiveRecord::Base
+ has_many :authors
+
has_many :citations, :foreign_key => 'book1_id'
has_many :references, :through => :citations, :source => :reference_of, :uniq => true
diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb
index 7178bb0d00..c68d008c26 100644
--- a/activerecord/test/models/bulb.rb
+++ b/activerecord/test/models/bulb.rb
@@ -1,14 +1,12 @@
class Bulb < ActiveRecord::Base
-
- default_scope :conditions => {:name => 'defaulty' }
-
+ default_scope where(:name => 'defaulty')
belongs_to :car
- attr_reader :scoped_methods_after_initialize
+ attr_reader :scope_after_initialize
- after_initialize :record_scoped_methods_after_initialize
- def record_scoped_methods_after_initialize
- @scoped_methods_after_initialize = self.class.scoped_methods.dup
+ after_initialize :record_scope_after_initialize
+ def record_scope_after_initialize
+ @scope_after_initialize = self.class.scoped
end
end
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index e7db3d3423..b036f0f5c9 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -1,6 +1,7 @@
class Car < ActiveRecord::Base
has_many :bulbs
+ has_many :foo_bulbs, :class_name => "Bulb", :conditions => { :name => 'foo' }
has_many :tyres
has_many :engines
has_many :wheels, :as => :wheelable
@@ -18,5 +19,5 @@ class CoolCar < Car
end
class FastCar < Car
- default_scope order('name desc')
+ default_scope :order => 'name desc'
end
diff --git a/activerecord/test/models/categorization.rb b/activerecord/test/models/categorization.rb
index 45f50e4af3..4bd980e606 100644
--- a/activerecord/test/models/categorization.rb
+++ b/activerecord/test/models/categorization.rb
@@ -4,13 +4,14 @@ class Categorization < ActiveRecord::Base
belongs_to :named_category, :class_name => 'Category', :foreign_key => :named_category_name, :primary_key => :name
belongs_to :author
+ has_many :post_taggings, :through => :author, :source => :taggings
+
belongs_to :author_using_custom_pk, :class_name => 'Author', :foreign_key => :author_id, :primary_key => :author_address_extra_id
has_many :authors_using_custom_pk, :class_name => 'Author', :foreign_key => :id, :primary_key => :category_id
end
class SpecialCategorization < ActiveRecord::Base
self.table_name = 'categorizations'
-
default_scope where(:special => true)
belongs_to :author
diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb
index 8f37433ec6..02b85fd38a 100644
--- a/activerecord/test/models/category.rb
+++ b/activerecord/test/models/category.rb
@@ -22,6 +22,8 @@ class Category < ActiveRecord::Base
end
has_many :categorizations
+ has_many :post_comments, :through => :posts, :source => :comments
+
has_many :authors, :through => :categorizations
has_many :authors_with_select, :through => :categorizations, :source => :author, :select => 'authors.*, categorizations.post_id'
diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb
index c432a6ace8..24a65b0f2f 100644
--- a/activerecord/test/models/club.rb
+++ b/activerecord/test/models/club.rb
@@ -5,6 +5,7 @@ class Club < ActiveRecord::Base
has_many :current_memberships
has_one :sponsor
has_one :sponsored_member, :through => :sponsor, :source => :sponsorable, :source_type => "Member"
+ belongs_to :category
private
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index ff533717cc..2a4c37089a 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -8,6 +8,7 @@ class Comment < ActiveRecord::Base
:conditions => { "posts.author_id" => 1 }
belongs_to :post, :counter_cache => true
+ has_many :ratings
def self.what_are_you
'a comment...'
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 32d060cf09..10701dd6fd 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -1,3 +1,5 @@
+require 'ostruct'
+
module DeveloperProjectsAssociationExtension
def find_most_recent
find(:first, :order => "id DESC")
@@ -87,6 +89,7 @@ end
class DeveloperOrderedBySalary < ActiveRecord::Base
self.table_name = 'developers'
default_scope :order => 'salary DESC'
+
scope :by_name, order('name DESC')
def self.all_ordered_by_name
@@ -98,15 +101,56 @@ end
class DeveloperCalledDavid < ActiveRecord::Base
self.table_name = 'developers'
- default_scope :conditions => "name = 'David'"
+ default_scope where("name = 'David'")
+end
+
+class LazyLambdaDeveloperCalledDavid < ActiveRecord::Base
+ self.table_name = 'developers'
+ default_scope lambda { where(:name => 'David') }
+end
+
+class LazyBlockDeveloperCalledDavid < ActiveRecord::Base
+ self.table_name = 'developers'
+ default_scope { where(:name => 'David') }
+end
+
+class CallableDeveloperCalledDavid < ActiveRecord::Base
+ self.table_name = 'developers'
+ default_scope OpenStruct.new(:call => where(:name => 'David'))
+end
+
+class ClassMethodDeveloperCalledDavid < ActiveRecord::Base
+ self.table_name = 'developers'
+
+ def self.default_scope
+ where(:name => 'David')
+ end
end
class DeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
- default_scope :conditions => { :name => 'Jamis' }
+ default_scope where(:name => 'Jamis')
+ scope :poor, where('salary < 150000')
end
class PoorDeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
- default_scope :conditions => { :name => 'Jamis', :salary => 50000 }
+ default_scope where(:name => 'Jamis', :salary => 50000)
+end
+
+class InheritedPoorDeveloperCalledJamis < DeveloperCalledJamis
+ self.table_name = 'developers'
+
+ ActiveSupport::Deprecation.silence do
+ default_scope where(:salary => 50000)
+ end
+end
+
+class MultiplePoorDeveloperCalledJamis < ActiveRecord::Base
+ self.table_name = 'developers'
+ default_scope where(:name => 'Jamis')
+
+ ActiveSupport::Deprecation.silence do
+ default_scope where(:salary => 50000)
+ end
end
diff --git a/activerecord/test/models/essay.rb b/activerecord/test/models/essay.rb
index 6c28f5e49b..ec4b982b5b 100644
--- a/activerecord/test/models/essay.rb
+++ b/activerecord/test/models/essay.rb
@@ -1,3 +1,5 @@
class Essay < ActiveRecord::Base
belongs_to :writer, :primary_key => :name, :polymorphic => true
+ belongs_to :category, :primary_key => :name
+ has_one :owner, :primary_key => :name
end
diff --git a/activerecord/test/models/job.rb b/activerecord/test/models/job.rb
index 3333a02e27..f7b0e787b1 100644
--- a/activerecord/test/models/job.rb
+++ b/activerecord/test/models/job.rb
@@ -2,4 +2,6 @@ class Job < ActiveRecord::Base
has_many :references
has_many :people, :through => :references
belongs_to :ideal_reference, :class_name => 'Reference'
+
+ has_many :agents, :through => :people
end
diff --git a/activerecord/test/models/loose_person.rb b/activerecord/test/models/loose_person.rb
deleted file mode 100644
index 256c281d0d..0000000000
--- a/activerecord/test/models/loose_person.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-class LoosePerson < ActiveRecord::Base
- self.table_name = 'people'
- self.abstract_class = true
-
- attr_protected :credit_rating, :administrator
-end
-
-class LooseDescendant < LoosePerson
- attr_protected :phone_number
-end
-
-class LooseDescendantSecond< LoosePerson
- attr_protected :phone_number
- attr_protected :name
-end
-
-class TightPerson < ActiveRecord::Base
- self.table_name = 'people'
- attr_accessible :name, :address
-end
-
-class TightDescendant < TightPerson
- attr_accessible :phone_number
-end \ No newline at end of file
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index e6e78f9e45..991e0e051f 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -11,6 +11,17 @@ class Member < ActiveRecord::Base
has_one :organization, :through => :member_detail
belongs_to :member_type
+ has_many :nested_member_types, :through => :member_detail, :source => :member_type
+ has_one :nested_member_type, :through => :member_detail, :source => :member_type
+
+ has_many :nested_sponsors, :through => :sponsor_club, :source => :sponsor
+ has_one :nested_sponsor, :through => :sponsor_club, :source => :sponsor
+
+ has_many :organization_member_details, :through => :member_detail
+ has_many :organization_member_details_2, :through => :organization, :source => :member_details
+
+ has_one :club_category, :through => :club, :source => :category
+
has_many :current_memberships
has_one :club_through_many, :through => :current_memberships, :source => :club
diff --git a/activerecord/test/models/member_detail.rb b/activerecord/test/models/member_detail.rb
index 94f59e5794..fe619f8732 100644
--- a/activerecord/test/models/member_detail.rb
+++ b/activerecord/test/models/member_detail.rb
@@ -2,4 +2,6 @@ class MemberDetail < ActiveRecord::Base
belongs_to :member
belongs_to :organization
has_one :member_type, :through => :member
+
+ has_many :organization_member_details, :through => :organization, :source => :member_details
end
diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb
index 1da342a0bd..4a4111833f 100644
--- a/activerecord/test/models/organization.rb
+++ b/activerecord/test/models/organization.rb
@@ -2,5 +2,11 @@ class Organization < ActiveRecord::Base
has_many :member_details
has_many :members, :through => :member_details
+ has_many :authors, :primary_key => :name
+ has_many :author_essay_categories, :through => :authors, :source => :essay_categories
+
+ has_one :author, :primary_key => :name
+ has_one :author_owned_essay_category, :through => :author, :source => :owned_essay_category
+
scope :clubs, { :from => 'clubs' }
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index cc3a4f5f9d..9c4794902d 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -21,6 +21,9 @@ class Person < ActiveRecord::Base
has_many :agents_of_agents, :through => :agents, :source => :agents
belongs_to :number1_fan, :class_name => 'Person'
+ has_many :agents_posts, :through => :agents, :source => :posts
+ has_many :agents_posts_authors, :through => :agents_posts, :source => :author
+
scope :males, :conditions => { :gender => 'M' }
scope :females, :conditions => { :gender => 'F' }
end
@@ -45,3 +48,22 @@ class PersonWithDependentNullifyJobs < ActiveRecord::Base
has_many :references, :foreign_key => :person_id
has_many :jobs, :source => :job, :through => :references, :dependent => :nullify
end
+
+
+class LoosePerson < ActiveRecord::Base
+ self.table_name = 'people'
+ self.abstract_class = true
+
+ attr_protected :comments
+ attr_protected :as => :admin
+end
+
+class LooseDescendant < LoosePerson; end
+
+class TightPerson < ActiveRecord::Base
+ self.table_name = 'people'
+ attr_accessible :first_name, :gender
+ attr_accessible :first_name, :gender, :comments, :as => :admin
+end
+
+class TightDescendant < TightPerson; end \ No newline at end of file
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index 0d3f62bb33..5e0f5323e6 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -34,7 +34,7 @@ class Pirate < ActiveRecord::Base
:after_remove => proc {|p,b| p.ship_log << "after_removing_proc_bird_#{b.id}"}
has_many :birds_with_reject_all_blank, :class_name => "Bird"
- has_one :bulb, :foreign_key => :car_id
+ has_one :foo_bulb, :foreign_key => :car_id, :class_name => "Bulb", :conditions => { :name => 'foo' }
accepts_nested_attributes_for :parrots, :birds, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index a342aaf60b..80296032bb 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -7,6 +7,7 @@ class Post < ActiveRecord::Base
scope :containing_the_letter_a, where("body LIKE '%a%'")
scope :ranked_by_comments, order("comments_count DESC")
+
scope :limit_by, lambda {|l| limit(l) }
scope :with_authors_at_address, lambda { |address| {
:conditions => [ 'authors.author_address_id = ?', address.id ],
@@ -49,6 +50,9 @@ class Post < ActiveRecord::Base
has_many :special_comments
has_many :nonexistant_comments, :class_name => 'Comment', :conditions => 'comments.id < 0'
+ has_many :special_comments_ratings, :through => :special_comments, :source => :ratings
+ has_many :special_comments_ratings_taggings, :through => :special_comments_ratings, :source => :taggings
+
has_and_belongs_to_many :categories
has_and_belongs_to_many :special_categories, :join_table => "categories_posts", :association_foreign_key => 'category_id'
@@ -70,11 +74,17 @@ class Post < ActiveRecord::Base
has_many :tags_with_destroy, :through => :taggings, :source => :tag, :dependent => :destroy
has_many :tags_with_nullify, :through => :taggings, :source => :tag, :dependent => :nullify
- has_many :misc_tags, :through => :taggings, :source => :tag, :conditions => "tags.name = 'Misc'"
+ has_many :misc_tags, :through => :taggings, :source => :tag, :conditions => { :tags => { :name => 'Misc' } }
has_many :funky_tags, :through => :taggings, :source => :tag
has_many :super_tags, :through => :taggings
+ has_many :tags_with_primary_key, :through => :taggings, :source => :tag_with_primary_key
has_one :tagging, :as => :taggable
+ has_many :first_taggings, :as => :taggable, :class_name => 'Tagging', :conditions => { :taggings => { :comment => 'first' } }
+ has_many :first_blue_tags, :through => :first_taggings, :source => :tag, :conditions => { :tags => { :name => 'Blue' } }
+
+ has_many :first_blue_tags_2, :through => :taggings, :source => :blue_tag, :conditions => { :taggings => { :comment => 'first' } }
+
has_many :invalid_taggings, :as => :taggable, :class_name => "Tagging", :conditions => 'taggings.id < 0'
has_many :invalid_tags, :through => :invalid_taggings, :source => :tag
@@ -133,20 +143,22 @@ class SubStiPost < StiPost
self.table_name = Post.table_name
end
-class PostWithComment < ActiveRecord::Base
- self.table_name = 'posts'
- default_scope where("posts.comments_count > 0").order("posts.comments_count ASC")
+ActiveSupport::Deprecation.silence do
+ class DeprecatedPostWithComment < ActiveRecord::Base
+ self.table_name = 'posts'
+ default_scope where("posts.comments_count > 0").order("posts.comments_count ASC")
+ end
end
class PostForAuthor < ActiveRecord::Base
self.table_name = 'posts'
cattr_accessor :selected_author
- default_scope lambda { where(:author_id => PostForAuthor.selected_author) }
end
class FirstPost < ActiveRecord::Base
self.table_name = 'posts'
default_scope where(:id => 1)
+
has_many :comments, :foreign_key => :post_id
has_one :comment, :foreign_key => :post_id
end
diff --git a/activerecord/test/models/rating.rb b/activerecord/test/models/rating.rb
new file mode 100644
index 0000000000..25a52c4ad7
--- /dev/null
+++ b/activerecord/test/models/rating.rb
@@ -0,0 +1,4 @@
+class Rating < ActiveRecord::Base
+ belongs_to :comment
+ has_many :taggings, :as => :taggable
+end
diff --git a/activerecord/test/models/reference.rb b/activerecord/test/models/reference.rb
index 06c4f79ef3..c5af0b5d5f 100644
--- a/activerecord/test/models/reference.rb
+++ b/activerecord/test/models/reference.rb
@@ -2,6 +2,8 @@ class Reference < ActiveRecord::Base
belongs_to :person
belongs_to :job
+ has_many :agents_posts_authors, :through => :person
+
class << self
attr_accessor :make_comments
end
@@ -16,6 +18,6 @@ class Reference < ActiveRecord::Base
end
class BadReference < ActiveRecord::Base
- self.table_name ='references'
- default_scope :conditions => {:favourite => false }
+ self.table_name = 'references'
+ default_scope where(:favourite => false)
end
diff --git a/activerecord/test/models/tagging.rb b/activerecord/test/models/tagging.rb
index 231d2b5890..ef323df158 100644
--- a/activerecord/test/models/tagging.rb
+++ b/activerecord/test/models/tagging.rb
@@ -6,6 +6,8 @@ class Tagging < ActiveRecord::Base
belongs_to :tag, :include => :tagging
belongs_to :super_tag, :class_name => 'Tag', :foreign_key => 'super_tag_id'
belongs_to :invalid_tag, :class_name => 'Tag', :foreign_key => 'tag_id'
+ belongs_to :blue_tag, :class_name => 'Tag', :foreign_key => :tag_id, :conditions => { :tags => { :name => 'Blue' } }
+ belongs_to :tag_with_primary_key, :class_name => 'Tag', :foreign_key => :tag_id, :primary_key => :custom_primary_key
belongs_to :interpolated_tag, :class_name => 'Tag', :foreign_key => :tag_id, :conditions => proc { "1 = #{1}" }
belongs_to :taggable, :polymorphic => true, :counter_cache => true
has_many :things, :through => :taggable
diff --git a/activerecord/test/models/without_table.rb b/activerecord/test/models/without_table.rb
index 87f80911e1..184ab1649e 100644
--- a/activerecord/test/models/without_table.rb
+++ b/activerecord/test/models/without_table.rb
@@ -1,3 +1,3 @@
class WithoutTable < ActiveRecord::Base
default_scope where(:published => true)
-end \ No newline at end of file
+end
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index f38f4f3b44..5cf9a207f3 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_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings
+ %w(postgresql_tsvectors 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
@@ -55,6 +55,14 @@ _SQL
nicknames TEXT[]
);
_SQL
+
+ execute <<_SQL
+ CREATE TABLE postgresql_tsvectors (
+ id SERIAL PRIMARY KEY,
+ text_vector tsvector
+ );
+_SQL
+
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 0b3865fc78..ceadb05644 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -49,6 +49,8 @@ ActiveRecord::Schema.define do
t.string :name, :null => false
t.integer :author_address_id
t.integer :author_address_extra_id
+ t.string :organization_id
+ t.string :owned_essay_id
end
create_table :author_addresses, :force => true do |t|
@@ -65,6 +67,7 @@ ActiveRecord::Schema.define do
end
create_table :binaries, :force => true do |t|
+ t.string :name
t.binary :data
end
@@ -75,6 +78,7 @@ ActiveRecord::Schema.define do
end
create_table :books, :force => true do |t|
+ t.integer :author_id
t.column :name, :string
end
@@ -123,6 +127,7 @@ ActiveRecord::Schema.define do
create_table :clubs, :force => true do |t|
t.string :name
+ t.integer :category_id
end
create_table :collections, :force => true do |t|
@@ -216,6 +221,8 @@ ActiveRecord::Schema.define do
t.string :name
t.string :writer_id
t.string :writer_type
+ t.string :category_id
+ t.string :author_id
end
create_table :events, :force => true do |t|
@@ -393,6 +400,7 @@ ActiveRecord::Schema.define do
t.string :name
t.column :updated_at, :datetime
t.column :happy_at, :datetime
+ t.string :essay_id
end
create_table :paint_colors, :force => true do |t|
@@ -482,6 +490,11 @@ ActiveRecord::Schema.define do
t.string :type
end
+ create_table :ratings, :force => true do |t|
+ t.integer :comment_id
+ t.integer :value
+ end
+
create_table :readers, :force => true do |t|
t.integer :post_id, :null => false
t.integer :person_id, :null => false
@@ -553,6 +566,7 @@ ActiveRecord::Schema.define do
t.column :super_tag_id, :integer
t.column :taggable_type, :string
t.column :taggable_id, :integer
+ t.string :comment
end
create_table :tasks, :force => true do |t|
@@ -677,6 +691,9 @@ ActiveRecord::Schema.define do
t.integer :molecule_id
t.string :name
end
+ create_table :weirds, :force => true do |t|
+ t.string 'a$b'
+ end
except 'SQLite' do
# fk_test_has_fk should be before fk_test_has_pk
diff --git a/activeresource/activeresource.gemspec b/activeresource/activeresource.gemspec
index a71168722b..c2fd707e9b 100644
--- a/activeresource/activeresource.gemspec
+++ b/activeresource/activeresource.gemspec
@@ -17,7 +17,6 @@ Gem::Specification.new do |s|
s.files = Dir['CHANGELOG', 'README.rdoc', 'examples/**/*', 'lib/**/*']
s.require_path = 'lib'
- s.has_rdoc = true
s.extra_rdoc_files = %w( README.rdoc )
s.rdoc_options.concat ['--main', 'README.rdoc']
diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb
index 480f2fbecb..765575d866 100644
--- a/activeresource/lib/active_resource/connection.rb
+++ b/activeresource/lib/active_resource/connection.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/benchmark'
require 'active_support/core_ext/uri'
+require 'active_support/core_ext/object/inclusion'
require 'net/https'
require 'date'
require 'time'
@@ -277,7 +278,7 @@ module ActiveResource
def legitimize_auth_type(auth_type)
return :basic if auth_type.nil?
auth_type = auth_type.to_sym
- [:basic, :digest].include?(auth_type) ? auth_type : :basic
+ auth_type.in?([:basic, :digest]) ? auth_type : :basic
end
end
end
diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb
index 75649053d0..e085a05f6d 100644
--- a/activeresource/lib/active_resource/http_mock.rb
+++ b/activeresource/lib/active_resource/http_mock.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/kernel/reporting'
+require 'active_support/core_ext/object/inclusion'
module ActiveResource
class InvalidRequestError < StandardError; end #:nodoc:
@@ -299,7 +300,7 @@ module ActiveResource
end
def success?
- (200..299).include?(code)
+ code.in?(200..299)
end
def [](key)
diff --git a/activeresource/test/cases/http_mock_test.rb b/activeresource/test/cases/http_mock_test.rb
index 43cf5f5ef0..cd5155924a 100644
--- a/activeresource/test/cases/http_mock_test.rb
+++ b/activeresource/test/cases/http_mock_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/core_ext/object/inclusion'
class HttpMockTest < ActiveSupport::TestCase
setup do
@@ -192,7 +193,7 @@ class HttpMockTest < ActiveSupport::TestCase
end
def request(method, path, headers = {}, body = nil)
- if [:put, :post].include? method
+ if method.in?([:put, :post])
@http.send(method, path, body, headers)
else
@http.send(method, path, headers)
diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb
index 6e79845aa0..7c36393cf2 100644
--- a/activeresource/test/connection_test.rb
+++ b/activeresource/test/connection_test.rb
@@ -50,7 +50,7 @@ class ConnectionTest < Test::Unit::TestCase
# 404 is a missing resource.
assert_response_raises ActiveResource::ResourceNotFound, 404
- # 405 is a missing not allowed error
+ # 405 is a method not allowed error
assert_response_raises ActiveResource::MethodNotAllowed, 405
# 409 is an optimistic locking error
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 373236ce9a..69e9cbfd42 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,9 @@
*Rails 3.1.0 (unreleased)*
+* Add String#inquiry as a convenience method for turning a string into a StringInquirer object [DHH]
+
+* Add Object#in? to test if an object is included in another object [Prem Sichanugrist, Brian Morearty, John Reitano]
+
* LocalCache strategy is now a real middleware class, not an anonymous class
posing for pictures.
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index df7f68fecf..37a74a9e62 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -17,5 +17,5 @@ Gem::Specification.new do |s|
s.files = Dir['CHANGELOG', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
- s.has_rdoc = true
+ s.add_dependency('multi_json', '~> 1.0.0')
end
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 6b662ac660..6b87774978 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -42,7 +42,6 @@ module ActiveSupport
autoload :DescendantsTracker
autoload :FileUpdateChecker
- autoload :FileWatcher
autoload :LogSubscriber
autoload :Notifications
diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb
index e41731f3e7..88b50fc506 100644
--- a/activesupport/lib/active_support/buffered_logger.rb
+++ b/activesupport/lib/active_support/buffered_logger.rb
@@ -41,7 +41,7 @@ module ActiveSupport
def initialize(log, level = DEBUG)
@level = level
- @buffer = {}
+ @buffer = Hash.new { |h,k| h[k] = [] }
@auto_flushing = 1
@guard = Mutex.new
@@ -100,13 +100,8 @@ module ActiveSupport
def flush
@guard.synchronize do
- unless buffer.empty?
- old_buffer = buffer
- all_content = StringIO.new
- old_buffer.each do |content|
- all_content << content
- end
- @log.write(all_content.string)
+ buffer.each do |content|
+ @log.write(content)
end
# Important to do this even if buffer was empty or else @buffer will
@@ -127,7 +122,7 @@ module ActiveSupport
end
def buffer
- @buffer[Thread.current] ||= []
+ @buffer[Thread.current]
end
def clear_buffer
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 18182bbb40..a1376ae52a 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/file/atomic'
require 'active_support/core_ext/string/conversions'
+require 'active_support/core_ext/object/inclusion'
require 'rack/utils'
module ActiveSupport
@@ -20,7 +21,7 @@ module ActiveSupport
end
def clear(options = nil)
- root_dirs = Dir.entries(cache_path).reject{|f| ['.', '..'].include?(f)}
+ root_dirs = Dir.entries(cache_path).reject{|f| f.in?(['.', '..'])}
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
end
@@ -120,7 +121,7 @@ module ActiveSupport
# Lock a file for a block so only one process can modify it at a time.
def lock_file(file_name, &block) # :nodoc:
if File.exist?(file_name)
- File.open(file_name, 'r') do |f|
+ File.open(file_name, 'r+') do |f|
begin
f.flock File::LOCK_EX
yield
@@ -161,7 +162,7 @@ module ActiveSupport
# Delete empty directories in the cache.
def delete_empty_directories(dir)
return if dir == cache_path
- if Dir.entries(dir).reject{|f| ['.', '..'].include?(f)}.empty?
+ if Dir.entries(dir).reject{|f| f.in?(['.', '..'])}.empty?
File.delete(dir) rescue nil
delete_empty_directories(File.dirname(dir))
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index b531a094cf..656cba625c 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -1,8 +1,10 @@
+require 'active_support/concern'
require 'active_support/descendants_tracker'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
+require 'active_support/core_ext/object/inclusion'
module ActiveSupport
# \Callbacks are code hooks that are run at key points in an object's lifecycle.
@@ -411,11 +413,11 @@ module ActiveSupport
# CallbackChain.
#
def __update_callbacks(name, filters = [], block = nil) #:nodoc:
- type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
+ type = filters.first.in?([:before, :after, :around]) ? filters.shift : :before
options = filters.last.is_a?(Hash) ? filters.pop : {}
filters.unshift(block) if block
- ([self] + ActiveSupport::DescendantsTracker.descendants(self)).each do |target|
+ ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target|
chain = target.send("_#{name}_callbacks")
yield target, chain.dup, type, filters, options
target.__define_runner(name)
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index c69a015f12..2df4fd1da1 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -3,10 +3,10 @@ class Array
#
# %w( a b c d ).from(0) # => %w( a b c d )
# %w( a b c d ).from(2) # => %w( c d )
- # %w( a b c d ).from(10) # => nil
+ # %w( a b c d ).from(10) # => %w()
# %w().from(0) # => %w()
def from(position)
- self[position..-1]
+ self[position, length] || []
end
# Returns the beginning of the array up to +position+.
diff --git a/activesupport/lib/active_support/core_ext/array/random_access.rb b/activesupport/lib/active_support/core_ext/array/random_access.rb
index ab1fa7cd5b..9eba4642b8 100644
--- a/activesupport/lib/active_support/core_ext/array/random_access.rb
+++ b/activesupport/lib/active_support/core_ext/array/random_access.rb
@@ -1,12 +1,15 @@
class Array
# Backport of Array#sample based on Marc-Andre Lafortune's https://github.com/marcandre/backports/
# Returns a random element or +n+ random elements from the array.
- # If the array is empty and +n+ is nil, returns <tt>nil</tt>. if +n+ is passed, returns <tt>[]</tt>.
+ # If the array is empty and +n+ is nil, returns <tt>nil</tt>.
+ # If +n+ is passed and its value is less than 0, it raises an +ArgumentError+ exception.
+ # If the value of +n+ is equal or greater than 0 it returns <tt>[]</tt>.
#
- # [1,2,3,4,5,6].sample # => 4
- # [1,2,3,4,5,6].sample(3) # => [2, 4, 5]
- # [].sample # => nil
- # [].sample(3) # => []
+ # [1,2,3,4,5,6].sample # => 4
+ # [1,2,3,4,5,6].sample(3) # => [2, 4, 5]
+ # [1,2,3,4,5,6].sample(-3) # => ArgumentError: negative sample number
+ # [].sample # => nil
+ # [].sample(3) # => []
def sample(n=nil)
return self[Kernel.rand(size)] if n.nil?
n = n.to_int
diff --git a/activesupport/lib/active_support/core_ext/date_time/zones.rb b/activesupport/lib/active_support/core_ext/date_time/zones.rb
index 82a4f7ac5a..6fa55a9255 100644
--- a/activesupport/lib/active_support/core_ext/date_time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/zones.rb
@@ -16,6 +16,6 @@ class DateTime
def in_time_zone(zone = ::Time.zone)
return self unless zone
- ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.__send__(:get_zone, zone))
+ ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 3005fef44c..61a1d88b0e 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -108,7 +108,7 @@ class Hash
raise "can't typecast #{entries.inspect}"
end
end
- elsif value.has_key?("__content__")
+ elsif value['type'] == 'file' || value["__content__"].present?
content = value["__content__"]
if parser = ActiveSupport::XmlMini::PARSING[value["type"]]
parser.arity == 1 ? parser.call(content) : parser.call(content, value)
diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb
index 5a5b4e3f80..9c169a2598 100644
--- a/activesupport/lib/active_support/core_ext/module/deprecation.rb
+++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb
@@ -1,3 +1,5 @@
+require 'active_support/deprecation'
+
class Module
# Declare that a method has been deprecated.
# deprecate :foo
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index 790a26f5c1..9ad1e12699 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -2,6 +2,7 @@ require 'active_support/core_ext/object/acts_like'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/try'
+require 'active_support/core_ext/object/inclusion'
require 'active_support/core_ext/object/conversions'
require 'active_support/core_ext/object/instance_variables'
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index d0c1ea8326..20085c4fb3 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -18,8 +18,8 @@ class Object
!blank?
end
- # Returns object if it's #present? otherwise returns nil.
- # object.presence is equivalent to object.present? ? object : nil.
+ # Returns object if it's <tt>present?</tt> otherwise returns +nil+.
+ # <tt>object.presence</tt> is equivalent to <tt>object.present? ? object : nil</tt>.
#
# This is handy for any representation of objects where blank is the same
# as not present at all. For example, this simplifies a common check for
@@ -37,39 +37,72 @@ class Object
end
end
-class NilClass #:nodoc:
+class NilClass
+ # +nil+ is blank:
+ #
+ # nil.blank? # => true
+ #
def blank?
true
end
end
-class FalseClass #:nodoc:
+class FalseClass
+ # +false+ is blank:
+ #
+ # false.blank? # => true
+ #
def blank?
true
end
end
-class TrueClass #:nodoc:
+class TrueClass
+ # +true+ is not blank:
+ #
+ # true.blank? # => false
+ #
def blank?
false
end
end
-class Array #:nodoc:
+class Array
+ # An array is blank if it's empty:
+ #
+ # [].blank? # => true
+ # [1,2,3].blank? # => false
+ #
alias_method :blank?, :empty?
end
-class Hash #:nodoc:
+class Hash
+ # A hash is blank if it's empty:
+ #
+ # {}.blank? # => true
+ # {:key => 'value'}.blank? # => false
+ #
alias_method :blank?, :empty?
end
-class String #:nodoc:
+class String
+ # A string is blank if it's empty or contains whitespaces only:
+ #
+ # "".blank? # => true
+ # " ".blank? # => true
+ # " something here ".blank? # => false
+ #
def blank?
self !~ /\S/
end
end
class Numeric #:nodoc:
+ # No number is blank:
+ #
+ # 1.blank? # => false
+ # 0.blank? # => false
+ #
def blank?
false
end
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index b05325790c..02cb5dfee7 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -15,50 +15,89 @@
# That's why we hardcode the following cases and check duplicable? instead of
# using that rescue idiom.
class Object
- # Can you safely .dup this object?
- # False for nil, false, true, symbols, numbers, class and module objects; true otherwise.
+ # Can you safely dup this object?
+ #
+ # False for +nil+, +false+, +true+, symbols, numbers, class and module objects;
+ # true otherwise.
def duplicable?
true
end
end
-class NilClass #:nodoc:
+class NilClass
+ # +nil+ is not duplicable:
+ #
+ # nil.duplicable? # => false
+ # nil.dup # => TypeError: can't dup NilClass
+ #
def duplicable?
false
end
end
-class FalseClass #:nodoc:
+class FalseClass
+ # +false+ is not duplicable:
+ #
+ # false.duplicable? # => false
+ # false.dup # => TypeError: can't dup FalseClass
+ #
def duplicable?
false
end
end
-class TrueClass #:nodoc:
+class TrueClass
+ # +true+ is not duplicable:
+ #
+ # true.duplicable? # => false
+ # true.dup # => TypeError: can't dup TrueClass
+ #
def duplicable?
false
end
end
-class Symbol #:nodoc:
+class Symbol
+ # Symbols are not duplicable:
+ #
+ # :my_symbol.duplicable? # => false
+ # :my_symbol.dup # => TypeError: can't dup Symbol
+ #
def duplicable?
false
end
end
-class Numeric #:nodoc:
+class Numeric
+ # Numbers are not duplicable:
+ #
+ # 3.duplicable? # => false
+ # 3.dup # => TypeError: can't dup Fixnum
+ #
def duplicable?
false
end
end
-class Class #:nodoc:
+class Class
+ # Classes are not duplicable:
+ #
+ # c = Class.new # => #<Class:0x10328fd80>
+ # c.dup # => #<Class:0x10328fd80>
+ #
+ # Note +dup+ returned the same class object.
def duplicable?
false
end
end
-class Module #:nodoc:
+class Module
+ # Modules are not duplicable:
+ #
+ # m = Module.new # => #<Module:0x10328b6e0>
+ # m.dup # => #<Module:0x10328b6e0>
+ #
+ # Note +dup+ returned the same module object.
def duplicable?
false
end
diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb
new file mode 100644
index 0000000000..b5671f66d0
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb
@@ -0,0 +1,15 @@
+class Object
+ # Returns true if this object is included in the argument. Argument must be
+ # any object which responds to +#include?+. Usage:
+ #
+ # characters = ["Konata", "Kagami", "Tsukasa"]
+ # "Konata".in?(characters) # => true
+ #
+ # This will throw an ArgumentError if the argument doesn't respond
+ # to +#include?+.
+ def in?(another_object)
+ another_object.include?(self)
+ rescue NoMethodError
+ raise ArgumentError.new("The parameter passed to #in? must respond to #include?")
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index 04619124a1..e77a9da0ec 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -9,12 +9,12 @@ class Object
#
# ==== Examples
#
- # Without try
+ # Without +try+
# @person && @person.name
# or
# @person ? @person.name : nil
#
- # With try
+ # With +try+
# @person.try(:name)
#
# +try+ also accepts arguments and/or a block, for the method it is trying
@@ -34,7 +34,19 @@ class Object
end
end
-class NilClass #:nodoc:
+class NilClass
+ # Calling +try+ on +nil+ always returns +nil+.
+ # It becomes specially helpful when navigating through associations that may return +nil+.
+ #
+ # === Examples
+ #
+ # nil.try(:name) # => nil
+ #
+ # Without +try+
+ # @person && !@person.children.blank? && @person.children.first.name
+ #
+ # With +try+
+ # @person.try(:children).try(:first).try(:name)
def try(*args)
nil
end
diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb
index 8fb8c31ade..72522d395c 100644
--- a/activesupport/lib/active_support/core_ext/string.rb
+++ b/activesupport/lib/active_support/core_ext/string.rb
@@ -11,3 +11,4 @@ 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/inquiry.rb b/activesupport/lib/active_support/core_ext/string/inquiry.rb
new file mode 100644
index 0000000000..604f3bf4dc
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/string/inquiry.rb
@@ -0,0 +1,13 @@
+require 'active_support/string_inquirer'
+
+class String
+ # Wraps the current string in the ActiveSupport::StringInquirer class,
+ # which gives you a prettier way to test for equality. Example:
+ #
+ # env = "production".inquiry
+ # env.production? # => true
+ # env.development? # => false
+ def inquiry
+ ActiveSupport::StringInquirer.new(self)
+ end
+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 addd4dab95..c27cbc37c5 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -9,7 +9,7 @@ class ERB
# A utility method for escaping HTML tag characters.
# This method is also aliased as <tt>h</tt>.
#
- # In your ERb templates, use this method to escape any unsafe content. For example:
+ # In your ERB templates, use this method to escape any unsafe content. For example:
# <%=h @person.name %>
#
# ==== Example:
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index ff90d7ca58..0c5962858e 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -34,29 +34,36 @@ class Time
# end
# end
def zone=(time_zone)
- Thread.current[:time_zone] = get_zone(time_zone)
+ Thread.current[:time_zone] = find_zone!(time_zone)
end
# Allows override of <tt>Time.zone</tt> locally inside supplied block; resets <tt>Time.zone</tt> to existing value when done.
def use_zone(time_zone)
- old_zone, ::Time.zone = ::Time.zone, get_zone(time_zone)
- yield
- ensure
- ::Time.zone = old_zone
+ new_zone = find_zone!(time_zone)
+ begin
+ old_zone, ::Time.zone = ::Time.zone, new_zone
+ yield
+ ensure
+ ::Time.zone = old_zone
+ end
end
- private
- def get_zone(time_zone)
- return time_zone if time_zone.nil? || time_zone.is_a?(ActiveSupport::TimeZone)
- # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone)
- unless time_zone.respond_to?(:period_for_local)
- time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone) rescue nil
- end
- # Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone
- if time_zone
- time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone)
- end
+ # Returns a TimeZone instance or nil, or raises an ArgumentError for invalid timezones.
+ def find_zone!(time_zone)
+ return time_zone if time_zone.nil? || time_zone.is_a?(ActiveSupport::TimeZone)
+ # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone)
+ unless time_zone.respond_to?(:period_for_local)
+ time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone)
end
+ # Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone
+ time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone)
+ rescue TZInfo::InvalidTimezoneIdentifier
+ raise ArgumentError, "Invalid Timezone: #{time_zone}"
+ end
+
+ def find_zone(time_zone)
+ find_zone!(time_zone) rescue nil
+ end
end
# Returns the simultaneous time in <tt>Time.zone</tt>.
@@ -74,6 +81,6 @@ class Time
def in_time_zone(zone = ::Time.zone)
return self unless zone
- ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.__send__(:get_zone, zone))
+ ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
end
end
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index ce0775a690..45b9dda5ca 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -9,7 +9,7 @@ module ActiveSupport
# The version the deprecated behavior will be removed, by default.
attr_accessor :deprecation_horizon
end
- self.deprecation_horizon = '3.1'
+ self.deprecation_horizon = '3.2'
# By default, warnings are not silenced and debugging is off.
self.silenced = false
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index e136e4c5b3..d5d55b7207 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -96,7 +96,7 @@ module ActiveSupport
def clear(scope = :all)
case scope
when :all
- @plurals, @singulars, @uncountables = [], [], []
+ @plurals, @singulars, @uncountables, @humans = [], [], [], []
else
instance_variable_set "@#{scope}", []
end
diff --git a/activesupport/lib/active_support/json/backends/jsongem.rb b/activesupport/lib/active_support/json/backends/jsongem.rb
deleted file mode 100644
index 533ba25da3..0000000000
--- a/activesupport/lib/active_support/json/backends/jsongem.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-require 'json' unless defined?(JSON)
-
-module ActiveSupport
- module JSON
- module Backends
- module JSONGem
- ParseError = ::JSON::ParserError
- extend self
-
- # Parses a JSON string or IO and convert it into an object
- def decode(json)
- if json.respond_to?(:read)
- json = json.read
- end
- data = ::JSON.parse(json)
- if ActiveSupport.parse_json_times
- convert_dates_from(data)
- else
- data
- end
- end
-
- private
- def convert_dates_from(data)
- case data
- when nil
- nil
- when DATE_REGEX
- begin
- DateTime.parse(data)
- rescue ArgumentError
- data
- end
- when Array
- data.map! { |d| convert_dates_from(d) }
- when Hash
- data.each do |key, value|
- data[key] = convert_dates_from(value)
- end
- else
- data
- end
- end
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/json/backends/yajl.rb b/activesupport/lib/active_support/json/backends/yajl.rb
deleted file mode 100644
index 58818658c7..0000000000
--- a/activesupport/lib/active_support/json/backends/yajl.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-require 'yajl' unless defined?(Yajl)
-
-module ActiveSupport
- module JSON
- module Backends
- module Yajl
- ParseError = ::Yajl::ParseError
- extend self
-
- # Parses a JSON string or IO and convert it into an object
- def decode(json)
- data = ::Yajl::Parser.new.parse(json)
- if ActiveSupport.parse_json_times
- convert_dates_from(data)
- else
- data
- end
- end
-
- private
- def convert_dates_from(data)
- case data
- when nil
- nil
- when DATE_REGEX
- begin
- DateTime.parse(data)
- rescue ArgumentError
- data
- end
- when Array
- data.map! { |d| convert_dates_from(d) }
- when Hash
- data.each do |key, value|
- data[key] = convert_dates_from(value)
- end
- else
- data
- end
- end
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/json/backends/yaml.rb b/activesupport/lib/active_support/json/backends/yaml.rb
deleted file mode 100644
index 077eda548a..0000000000
--- a/activesupport/lib/active_support/json/backends/yaml.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-require 'active_support/core_ext/string/starts_ends_with'
-
-module ActiveSupport
- module JSON
- module Backends
- module Yaml
- ParseError = ::StandardError
- extend self
-
- EXCEPTIONS = [::ArgumentError] # :nodoc:
- begin
- require 'psych'
- EXCEPTIONS << Psych::SyntaxError
- rescue LoadError
- end
-
- # Parses a JSON string or IO and converts it into an object
- def decode(json)
- if json.respond_to?(:read)
- json = json.read
- end
- YAML.load(convert_json_to_yaml(json))
- rescue *EXCEPTIONS => e
- raise ParseError, "Invalid JSON string: '%s'" % json
- end
-
- protected
- # Ensure that ":" and "," are always followed by a space
- def convert_json_to_yaml(json) #:nodoc:
- require 'strscan' unless defined? ::StringScanner
- scanner, quoting, marks, pos, times = ::StringScanner.new(json), false, [], nil, []
- while scanner.scan_until(/(\\['"]|['":,\\]|\\.)/)
- case char = scanner[1]
- when '"', "'"
- if !quoting
- quoting = char
- pos = scanner.pos
- elsif quoting == char
- if valid_date?(json[pos..scanner.pos-2])
- # found a date, track the exact positions of the quotes so we can
- # overwrite them with spaces later.
- times << pos
- end
- quoting = false
- end
- when ":",","
- marks << scanner.pos - 1 unless quoting
- when "\\"
- scanner.skip(/\\/)
- end
- end
-
- if marks.empty?
- json.gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do
- ustr = $1
- if ustr.start_with?('u')
- char = [ustr[1..-1].to_i(16)].pack("U")
- # "\n" needs extra escaping due to yaml formatting
- char == "\n" ? "\\n" : char
- elsif ustr == '\\'
- '\\\\'
- else
- ustr
- end
- end
- else
- left_pos = [-1].push(*marks)
- right_pos = marks << scanner.pos + scanner.rest_size
- output = []
- left_pos.each_with_index do |left, i|
- scanner.pos = left.succ
- chunk = scanner.peek(right_pos[i] - scanner.pos + 1)
- # overwrite the quotes found around the dates with spaces
- while times.size > 0 && times[0] <= right_pos[i]
- chunk.insert(times.shift - scanner.pos - 1, '! ')
- end
- chunk.gsub!(/\\([\\\/]|u[[:xdigit:]]{4})/) do
- ustr = $1
- if ustr.start_with?('u')
- char = [ustr[1..-1].to_i(16)].pack("U")
- # "\n" needs extra escaping due to yaml formatting
- char == "\n" ? "\\n" : char
- elsif ustr == '\\'
- '\\\\'
- else
- ustr
- end
- end
- output << chunk
- end
- output = output * " "
-
- output.gsub!(/\\\//, '/')
- output
- end
- end
-
- private
- def valid_date?(date_string)
- begin
- date_string =~ DATE_REGEX && DateTime.parse(date_string)
- rescue ArgumentError
- false
- end
- end
-
- end
- end
- end
-end
-
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index c1f6330c6c..cbeb6c0a28 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -1,32 +1,31 @@
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/module/delegation'
+require 'multi_json'
module ActiveSupport
# Look for and parse json strings that look like ISO 8601 times.
mattr_accessor :parse_json_times
module JSON
- # Listed in order of preference.
- DECODERS = %w(Yajl Yaml)
-
class << self
- attr_reader :parse_error
- delegate :decode, :to => :backend
+ def decode(json, options ={})
+ data = MultiJson.decode(json, options)
+ if ActiveSupport.parse_json_times
+ convert_dates_from(data)
+ else
+ data
+ end
+ end
- def backend
- set_default_backend unless defined?(@backend)
- @backend
+ def engine
+ MultiJson.engine
end
+ alias :backend :engine
- def backend=(name)
- if name.is_a?(Module)
- @backend = name
- else
- require "active_support/json/backends/#{name.to_s.downcase}"
- @backend = ActiveSupport::JSON::Backends::const_get(name)
- end
- @parse_error = @backend::ParseError
+ def engine=(name)
+ MultiJson.engine = name
end
+ alias :backend= :engine=
def with_backend(name)
old_backend, self.backend = backend, name
@@ -35,15 +34,30 @@ module ActiveSupport
self.backend = old_backend
end
- def set_default_backend
- DECODERS.find do |name|
+ def parse_error
+ MultiJson::DecodeError
+ end
+
+ private
+
+ def convert_dates_from(data)
+ case data
+ when nil
+ nil
+ when DATE_REGEX
begin
- self.backend = name
- true
- rescue LoadError
- # Try next decoder.
- false
+ DateTime.parse(data)
+ rescue ArgumentError
+ data
end
+ when Array
+ data.map! { |d| convert_dates_from(d) }
+ when Hash
+ data.each do |key, value|
+ data[key] = convert_dates_from(value)
+ end
+ else
+ data
end
end
end
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index 10675edac5..1c4dd24227 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -81,7 +81,7 @@ module ActiveSupport
# Flush all log_subscribers' logger.
def flush_all!
- flushable_loggers.each(&:flush)
+ flushable_loggers.each { |log| log.flush }
end
end
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index c2deba3b1b..04df2ea562 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -46,7 +46,7 @@ module ActiveSupport
# If assigned value cannot be matched to a TimeZone, an exception will be raised.
initializer "active_support.initialize_time_zone" do |app|
require 'active_support/core_ext/time/zones'
- zone_default = Time.__send__(:get_zone, app.config.time_zone)
+ zone_default = Time.find_zone!(app.config.time_zone)
unless zone_default
raise \
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index fb52fc7083..8d6c27e381 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -5,16 +5,9 @@ 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'
-begin
- silence_warnings { require 'mocha' }
-rescue LoadError
- # Fake Mocha::ExpectationError so we can rescue it in #run. Bleh.
- Object.const_set :Mocha, Module.new
- Mocha.const_set :ExpectationError, Class.new(StandardError)
-end
-
module ActiveSupport
class TestCase < ::Test::Unit::TestCase
if defined? MiniTest
diff --git a/activesupport/lib/active_support/testing/mochaing.rb b/activesupport/lib/active_support/testing/mochaing.rb
new file mode 100644
index 0000000000..4ad75a6681
--- /dev/null
+++ b/activesupport/lib/active_support/testing/mochaing.rb
@@ -0,0 +1,7 @@
+begin
+ silence_warnings { require 'mocha' }
+rescue LoadError
+ # Fake Mocha::ExpectationError so we can rescue it in #run. Bleh.
+ Object.const_set :Mocha, Module.new
+ Mocha.const_set :ExpectationError, Class.new(StandardError)
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/testing/pending.rb b/activesupport/lib/active_support/testing/pending.rb
index 3d119e2fba..feac7bc347 100644
--- a/activesupport/lib/active_support/testing/pending.rb
+++ b/activesupport/lib/active_support/testing/pending.rb
@@ -11,32 +11,36 @@ module ActiveSupport
@@at_exit = false
def pending(description = "", &block)
- if description.is_a?(Symbol)
- is_pending = $tags[description]
- return block.call unless is_pending
- end
+ if defined?(::MiniTest)
+ skip(description.blank? ? nil : description)
+ else
+ if description.is_a?(Symbol)
+ is_pending = $tags[description]
+ return block.call unless is_pending
+ end
- if block_given?
- failed = false
+ if block_given?
+ failed = false
- begin
- block.call
- rescue Exception
- failed = true
- end
+ begin
+ block.call
+ rescue Exception
+ failed = true
+ end
- flunk("<#{description}> did not fail.") unless failed
- end
+ flunk("<#{description}> did not fail.") unless failed
+ end
- caller[0] =~ (/(.*):(.*):in `(.*)'/)
- @@pending_cases << "#{$3} at #{$1}, line #{$2}"
- print "P"
+ caller[0] =~ (/(.*):(.*):in `(.*)'/)
+ @@pending_cases << "#{$3} at #{$1}, line #{$2}"
+ print "P"
- @@at_exit ||= begin
- at_exit do
- puts "\nPending Cases:"
- @@pending_cases.each do |test_case|
- puts test_case
+ @@at_exit ||= begin
+ at_exit do
+ puts "\nPending Cases:"
+ @@pending_cases.each do |test_case|
+ puts test_case
+ end
end
end
end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index c66aa78ce8..3d092529d6 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,5 +1,6 @@
require "active_support/values/time_zone"
require 'active_support/core_ext/object/acts_like'
+require 'active_support/core_ext/object/inclusion'
module ActiveSupport
# A Time-like class that can represent a time in any time zone. Necessary because standard Ruby Time instances are
@@ -309,7 +310,7 @@ module ActiveSupport
end
def marshal_load(variables)
- initialize(variables[0].utc, ::Time.__send__(:get_zone, variables[1]), variables[2].utc)
+ initialize(variables[0].utc, ::Time.find_zone(variables[1]), variables[2].utc)
end
# Ensure proxy class responds to all methods that underlying time instance responds to.
@@ -344,7 +345,7 @@ module ActiveSupport
end
def duration_of_variable_length?(obj)
- ActiveSupport::Duration === obj && obj.parts.any? {|p| [:years, :months, :days].include? p[0] }
+ ActiveSupport::Duration === obj && obj.parts.any? {|p| p[0].in?([:years, :months, :days]) }
end
end
end
diff --git a/activesupport/test/callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb
index 71249050fc..d569cbb4fb 100644
--- a/activesupport/test/callback_inheritance_test.rb
+++ b/activesupport/test/callback_inheritance_test.rb
@@ -82,6 +82,30 @@ class EmptyChild < EmptyParent
end
end
+class CountingParent
+ include ActiveSupport::Callbacks
+
+ attr_reader :count
+
+ define_callbacks :dispatch
+
+ def initialize
+ @count = 0
+ end
+
+ def count!
+ @count += 1
+ end
+
+ def dispatch
+ run_callbacks(:dispatch)
+ self
+ end
+end
+
+class CountingChild < CountingParent
+end
+
class BasicCallbacksTest < Test::Unit::TestCase
def setup
@index = GrandParent.new("index").dispatch
@@ -147,4 +171,10 @@ class DynamicInheritedCallbacks < Test::Unit::TestCase
child = EmptyChild.new.dispatch
assert child.performed?
end
+
+ def test_callbacks_should_be_performed_once_in_child_class
+ CountingParent.set_callback(:dispatch, :before) { count! }
+ child = CountingChild.new.dispatch
+ assert_equal 1, child.count
+ end
end
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index d7ab3ce605..0e5407bc35 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -10,7 +10,7 @@ class ArrayExtAccessTests < Test::Unit::TestCase
def test_from
assert_equal %w( a b c d ), %w( a b c d ).from(0)
assert_equal %w( c d ), %w( a b c d ).from(2)
- assert_nil %w( a b c d ).from(10)
+ assert_equal %w(), %w( a b c d ).from(10)
end
def test_to
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index a0479d45ac..012b956d7f 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -889,6 +889,15 @@ class HashToXmlTest < Test::Unit::TestCase
assert_equal 'application/octet-stream', file.content_type
end
+ def test_tag_with_attrs_and_whitespace
+ xml = <<-XML
+ <blog name="bacon is the best">
+ </blog>
+ XML
+ hash = Hash.from_xml(xml)
+ assert_equal "bacon is the best", hash['blog']['name']
+ end
+
def test_xsd_like_types_from_xml
bacon_xml = <<-EOT
<bacon>
diff --git a/activesupport/test/core_ext/object/inclusion_test.rb b/activesupport/test/core_ext/object/inclusion_test.rb
new file mode 100644
index 0000000000..1de857d678
--- /dev/null
+++ b/activesupport/test/core_ext/object/inclusion_test.rb
@@ -0,0 +1,50 @@
+require 'abstract_unit'
+require 'active_support/core_ext/object/inclusion'
+
+class InTest < Test::Unit::TestCase
+ def test_in_array
+ assert 1.in?([1,2])
+ assert !3.in?([1,2])
+ end
+
+ def test_in_hash
+ h = { "a" => 100, "b" => 200 }
+ assert "a".in?(h)
+ assert !"z".in?(h)
+ end
+
+ def test_in_string
+ assert "lo".in?("hello")
+ assert !"ol".in?("hello")
+ assert ?h.in?("hello")
+ end
+
+ def test_in_range
+ assert 25.in?(1..50)
+ assert !75.in?(1..50)
+ end
+
+ def test_in_set
+ s = Set.new([1,2])
+ assert 1.in?(s)
+ assert !3.in?(s)
+ end
+
+ module A
+ end
+ class B
+ include A
+ end
+ class C < B
+ end
+
+ def test_in_module
+ assert A.in?(B)
+ assert A.in?(C)
+ assert !A.in?(A)
+ end
+
+ def test_no_method_catching
+ assert_raise(ArgumentError) { 1.in?(1) }
+ end
+end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index bb865cae91..f0c289a418 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -250,6 +250,11 @@ class StringInflectionsTest < Test::Unit::TestCase
# And changes the original string:
assert_equal original, expected
end
+
+ def test_string_inquiry
+ assert "production".inquiry.production?
+ assert !"production".inquiry.development?
+ end
def test_truncate
assert_equal "Hello World!", "Hello World!".truncate(12)
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index bafa335a09..72b55183ba 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -36,6 +36,12 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal @twz.object_id, @twz.in_time_zone(ActiveSupport::TimeZone['Eastern Time (US & Canada)']).object_id
end
+ def test_in_time_zone_with_bad_argument
+ assert_raise(ArgumentError) { @twz.in_time_zone('No such timezone exists') }
+ assert_raise(ArgumentError) { @twz.in_time_zone(-15.hours) }
+ assert_raise(ArgumentError) { @twz.in_time_zone(Object.new) }
+ end
+
def test_localtime
assert_equal @twz.localtime, @twz.utc.getlocal
end
@@ -760,6 +766,15 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase
end
end
+ def test_in_time_zone_with_invalid_argument
+ assert_raise(ArgumentError) { @t.in_time_zone("No such timezone exists") }
+ assert_raise(ArgumentError) { @dt.in_time_zone("No such timezone exists") }
+ assert_raise(ArgumentError) { @t.in_time_zone(-15.hours) }
+ assert_raise(ArgumentError) { @dt.in_time_zone(-15.hours) }
+ assert_raise(ArgumentError) { @t.in_time_zone(Object.new) }
+ assert_raise(ArgumentError) { @dt.in_time_zone(Object.new) }
+ end
+
def test_in_time_zone_with_time_local_instance
with_env_tz 'US/Eastern' do
time = Time.local(1999, 12, 31, 19) # == Time.utc(2000)
@@ -790,6 +805,14 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase
assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone
end
+ def test_use_zone_raises_on_invalid_timezone
+ Time.zone = 'Alaska'
+ assert_raise ArgumentError do
+ Time.use_zone("No such timezone exists") { }
+ end
+ assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone
+ end
+
def test_time_zone_getter_and_setter
Time.zone = ActiveSupport::TimeZone['Alaska']
assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone
@@ -843,11 +866,27 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase
end
def test_time_zone_setter_with_invalid_zone
- Time.zone = 'foo'
- assert_nil Time.zone
+ assert_raise(ArgumentError){ Time.zone = "No such timezone exists" }
+ assert_raise(ArgumentError){ Time.zone = -15.hours }
+ assert_raise(ArgumentError){ Time.zone = Object.new }
+ end
+
+ def test_find_zone_without_bang_returns_nil_if_time_zone_can_not_be_found
+ assert_nil Time.find_zone('No such timezone exists')
+ assert_nil Time.find_zone(-15.hours)
+ assert_nil Time.find_zone(Object.new)
+ end
+
+ def test_find_zone_with_bang_raises_if_time_zone_can_not_be_found
+ assert_raise(ArgumentError) { Time.find_zone!('No such timezone exists') }
+ assert_raise(ArgumentError) { Time.find_zone!(-15.hours) }
+ assert_raise(ArgumentError) { Time.find_zone!(Object.new) }
+ end
- Time.zone = -15.hours
- assert_nil Time.zone
+ def test_time_zone_setter_with_find_zone_without_bang
+ assert_nil Time.zone = Time.find_zone('No such timezone exists')
+ assert_nil Time.zone = Time.find_zone(-15.hours)
+ assert_nil Time.zone = Time.find_zone(Object.new)
end
def test_current_returns_time_now_when_zone_not_set
diff --git a/activesupport/test/file_watcher_test.rb b/activesupport/test/file_watcher_test.rb
deleted file mode 100644
index 7b4d4be24f..0000000000
--- a/activesupport/test/file_watcher_test.rb
+++ /dev/null
@@ -1,233 +0,0 @@
-require 'abstract_unit'
-require 'fssm'
-require "fileutils"
-require "timeout"
-
-
-class FileWatcherTest < ActiveSupport::TestCase
- class DumbBackend < ActiveSupport::FileWatcher::Backend
- end
-
- def setup
- @watcher = ActiveSupport::FileWatcher.new
-
- # In real life, the backend would take the path and use it to observe the file
- # system. In our case, we will manually trigger the events for unit testing,
- # so we can pass any path.
- @backend = DumbBackend.new("RAILS_WOOT", @watcher)
-
- @payload = []
- @watcher.watch %r{^app/assets/.*\.scss$} do |pay|
- pay.each do |status, files|
- files.sort!
- end
- @payload << pay
- end
- end
-
- def test_use_triple_equals
- fw = ActiveSupport::FileWatcher.new
- called = []
- fw.watch("some_arbitrary_file.rb") do |file|
- called << "omg"
- end
- fw.trigger(%w{ some_arbitrary_file.rb })
- assert_equal ['omg'], called
- end
-
- def test_one_change
- @backend.trigger("app/assets/main.scss" => :changed)
- assert_equal({:changed => ["app/assets/main.scss"]}, @payload.first)
- end
-
- def test_multiple_changes
- @backend.trigger("app/assets/main.scss" => :changed, "app/assets/javascripts/foo.coffee" => :changed)
- assert_equal([{:changed => ["app/assets/main.scss"]}], @payload)
- end
-
- def test_multiple_changes_match
- @backend.trigger("app/assets/main.scss" => :changed, "app/assets/print.scss" => :changed, "app/assets/javascripts/foo.coffee" => :changed)
- assert_equal([{:changed => ["app/assets/main.scss", "app/assets/print.scss"]}], @payload)
- end
-
- def test_multiple_state_changes
- @backend.trigger("app/assets/main.scss" => :created, "app/assets/print.scss" => :changed)
- assert_equal([{:changed => ["app/assets/print.scss"], :created => ["app/assets/main.scss"]}], @payload)
- end
-
- def test_more_blocks
- payload = []
- @watcher.watch %r{^config/routes\.rb$} do |pay|
- payload << pay
- end
-
- @backend.trigger "config/routes.rb" => :changed
- assert_equal [:changed => ["config/routes.rb"]], payload
- assert_equal [], @payload
- end
-
- def test_overlapping_watchers
- payload = []
- @watcher.watch %r{^app/assets/main\.scss$} do |pay|
- payload << pay
- end
-
- @backend.trigger "app/assets/print.scss" => :changed, "app/assets/main.scss" => :changed
- assert_equal [:changed => ["app/assets/main.scss"]], payload
- assert_equal [:changed => ["app/assets/main.scss", "app/assets/print.scss"]], @payload
- end
-end
-
-module FSSM::Backends
- class Polling
- def initialize_with_low_latency(options={})
- initialize_without_low_latency(options.merge(:latency => 0.1))
- end
- alias_method_chain :initialize, :low_latency
- end
-end
-
-class FSSMFileWatcherTest < ActiveSupport::TestCase
- class FSSMBackend < ActiveSupport::FileWatcher::Backend
- def initialize(path, watcher)
- super
-
- monitor = FSSM::Monitor.new
- monitor.path(path, '**/*') do |p|
- p.update { |base, relative| trigger relative => :changed }
- p.delete { |base, relative| trigger relative => :deleted }
- p.create { |base, relative| trigger relative => :created }
- end
-
- @thread = Thread.new do
- monitor.run
- end
- end
-
- def stop
- @thread.kill
- end
- end
-
- def setup
- Thread.abort_on_exception = true
-
- @payload = []
- @triggered = false
-
- @watcher = ActiveSupport::FileWatcher.new
-
- @path = path = File.expand_path("../tmp", __FILE__)
- FileUtils.rm_rf path
-
- create "app/assets/main.scss", true
- create "app/assets/javascripts/foo.coffee", true
- create "app/assets/print.scss", true
- create "app/assets/videos.scss", true
-
- @backend = FSSMBackend.new(path, @watcher)
-
- @watcher.watch %r{^app/assets/.*\.scss$} do |pay|
- pay.each do |status, files|
- files.sort!
- end
- @payload << pay
- trigger
- end
- end
-
- def teardown
- @backend.stop
- Thread.abort_on_exception = false
- end
-
- def create(path, past = false)
- wait(past) do
- path = File.join(@path, path)
- FileUtils.mkdir_p(File.dirname(path))
-
- FileUtils.touch(path)
- File.utime(Time.now - 100, Time.now - 100, path) if past
- end
- end
-
- def change(path)
- wait do
- FileUtils.touch(File.join(@path, path))
- end
- end
-
- def delete(path)
- wait do
- FileUtils.rm(File.join(@path, path))
- end
- end
-
- def wait(past = false)
- yield
- return if past
-
- begin
- Timeout.timeout(1) do
- sleep 0.05 until @triggered
- end
- rescue Timeout::Error
- end
-
- @triggered = false
- end
-
- def trigger
- @triggered = true
- end
-
- def test_one_change
- change "app/assets/main.scss"
- assert_equal({:changed => ["app/assets/main.scss"]}, @payload.first)
- end
-
- def test_multiple_changes
- change "app/assets/main.scss"
- change "app/assets/javascripts/foo.coffee"
- assert_equal([{:changed => ["app/assets/main.scss"]}], @payload)
- end
-
- def test_multiple_changes_match
- change "app/assets/main.scss"
- change "app/assets/print.scss"
- change "app/assets/javascripts/foo.coffee"
- assert_equal([{:changed => ["app/assets/main.scss"]}, {:changed => ["app/assets/print.scss"]}], @payload)
- end
-
- def test_multiple_state_changes
- create "app/assets/new.scss"
- change "app/assets/print.scss"
- delete "app/assets/videos.scss"
- assert_equal([{:created => ["app/assets/new.scss"]}, {:changed => ["app/assets/print.scss"]}, {:deleted => ["app/assets/videos.scss"]}], @payload)
- end
-
- def test_more_blocks
- payload = []
- @watcher.watch %r{^config/routes\.rb$} do |pay|
- payload << pay
- trigger
- end
-
- create "config/routes.rb"
- assert_equal [{:created => ["config/routes.rb"]}], payload
- assert_equal [], @payload
- end
-
- def test_overlapping_watchers
- payload = []
- @watcher.watch %r{^app/assets/main\.scss$} do |pay|
- payload << pay
- trigger
- end
-
- change "app/assets/main.scss"
- change "app/assets/print.scss"
- assert_equal [{:changed => ["app/assets/main.scss"]}], payload
- assert_equal [{:changed => ["app/assets/main.scss"]}, {:changed => ["app/assets/print.scss"]}], @payload
- end
-end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 1670d9ee7d..95f18126d4 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -255,12 +255,21 @@ class InflectorTest < Test::Unit::TestCase
end
def test_clear_all
- cached_values = ActiveSupport::Inflector.inflections.plurals, ActiveSupport::Inflector.inflections.singulars, ActiveSupport::Inflector.inflections.uncountables, ActiveSupport::Inflector.inflections.humans
- ActiveSupport::Inflector.inflections.clear :all
- assert ActiveSupport::Inflector.inflections.plurals.empty?
- assert ActiveSupport::Inflector.inflections.singulars.empty?
- assert ActiveSupport::Inflector.inflections.uncountables.empty?
- assert ActiveSupport::Inflector.inflections.humans.empty?
+ 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?
+ 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]
@@ -268,12 +277,21 @@ class InflectorTest < Test::Unit::TestCase
end
def test_clear_with_default
- cached_values = ActiveSupport::Inflector.inflections.plurals, ActiveSupport::Inflector.inflections.singulars, ActiveSupport::Inflector.inflections.uncountables, ActiveSupport::Inflector.inflections.humans
- ActiveSupport::Inflector.inflections.clear
- assert ActiveSupport::Inflector.inflections.plurals.empty?
- assert ActiveSupport::Inflector.inflections.singulars.empty?
- assert ActiveSupport::Inflector.inflections.uncountables.empty?
- assert ActiveSupport::Inflector.inflections.humans.empty?
+ 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?
+ 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]
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index 24d9f88c09..6ccffa59b1 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -17,6 +17,8 @@ class TestJSONDecoding < ActiveSupport::TestCase
%({"matzue": "松江", "asakusa": "浅草"}) => {"matzue" => "松江", "asakusa" => "浅草"},
%({"a": "2007-01-01"}) => {'a' => Date.new(2007, 1, 1)},
%({"a": "2007-01-01 01:12:34 Z"}) => {'a' => Time.utc(2007, 1, 1, 1, 12, 34)},
+ %(["2007-01-01 01:12:34 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34)],
+ %(["2007-01-01 01:12:34 Z", "2007-01-01 01:12:35 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34), Time.utc(2007, 1, 1, 1, 12, 35)],
# no time zone
%({"a": "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"},
# invalid date
@@ -54,12 +56,9 @@ class TestJSONDecoding < ActiveSupport::TestCase
%q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"}
}
- # load the default JSON backend
- ActiveSupport::JSON.backend = 'Yaml'
-
- backends = %w(Yaml)
- backends << "JSONGem" if defined?(::JSON)
- backends << "Yajl" if defined?(::Yajl)
+ backends = [:ok_json]
+ backends << :json_gem if defined?(::JSON)
+ backends << :yajl if defined?(::Yajl)
backends.each do |backend|
TESTS.each do |json, expected|
@@ -72,13 +71,11 @@ class TestJSONDecoding < ActiveSupport::TestCase
end
end
end
- end
- if backends.include?("JSONGem")
- test "json decodes time json with time parsing disabled" do
+ test "json decodes time json with time parsing disabled with the #{backend} backend" do
ActiveSupport.parse_json_times = false
expected = {"a" => "2007-01-01 01:12:34 Z"}
- ActiveSupport::JSON.with_backend "JSONGem" do
+ ActiveSupport::JSON.with_backend backend do
assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"}))
end
end
diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb
index e48425ca25..b215b60df3 100644
--- a/activesupport/test/ordered_options_test.rb
+++ b/activesupport/test/ordered_options_test.rb
@@ -50,4 +50,30 @@ class OrderedOptionsTest < Test::Unit::TestCase
assert_equal 2, a.size
assert_equal 56, a.else_where
end
+
+ def test_inheritable_options_continues_lookup_in_parent
+ parent = ActiveSupport::OrderedOptions.new
+ parent[:foo] = true
+
+ child = ActiveSupport::InheritableOptions.new(parent)
+ assert child.foo
+ end
+
+ def test_inheritable_options_can_override_parent
+ parent = ActiveSupport::OrderedOptions.new
+ parent[:foo] = :bar
+
+ child = ActiveSupport::InheritableOptions.new(parent)
+ child[:foo] = :baz
+
+ assert_equal :baz, child.foo
+ end
+
+ def test_inheritable_options_inheritable_copy
+ original = ActiveSupport::InheritableOptions.new
+ copy = original.inheritable_copy
+
+ assert copy.kind_of?(original.class)
+ assert_not_equal copy.object_id, original.object_id
+ end
end
diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb
index b054855d08..08e11d4f38 100644
--- a/activesupport/test/transliterate_test.rb
+++ b/activesupport/test/transliterate_test.rb
@@ -1,6 +1,7 @@
# encoding: utf-8
require 'abstract_unit'
require 'active_support/inflector/transliterate'
+require 'active_support/core_ext/object/inclusion'
class TransliterateTest < Test::Unit::TestCase
@@ -15,7 +16,7 @@ class TransliterateTest < Test::Unit::TestCase
# create string with range of Unicode"s western characters with
# diacritics, excluding the division and multiplication signs which for
# some reason or other are floating in the middle of all the letters.
- string = (0xC0..0x17E).to_a.reject {|c| [0xD7, 0xF7].include? c}.pack("U*")
+ string = (0xC0..0x17E).to_a.reject {|c| c.in?([0xD7, 0xF7])}.pack("U*")
string.each_char do |char|
assert_match %r{^[a-zA-Z']*$}, ActiveSupport::Inflector.transliterate(string)
end
diff --git a/bin/rails b/bin/rails
index a628cc35ca..f9725d78d0 100755
--- a/bin/rails
+++ b/bin/rails
@@ -1,9 +1,7 @@
#!/usr/bin/env ruby
-begin
- require "rails/cli"
-rescue LoadError
+if File.exists?(File.join(File.expand_path('../..', __FILE__), '.git'))
railties_path = File.expand_path('../../railties/lib', __FILE__)
$:.unshift(railties_path)
- require "rails/cli"
end
+require "rails/cli"
diff --git a/load_paths.rb b/load_paths.rb
index 8f37364629..15345e31e3 100644
--- a/load_paths.rb
+++ b/load_paths.rb
@@ -1,32 +1,9 @@
-begin
- require File.expand_path('../.bundle/environment', __FILE__)
-rescue LoadError
- begin
- # bust gem prelude
- if defined? Gem
- Gem.source_index
- gem 'bundler'
- else
- require 'rubygems'
- end
- require 'bundler'
- Bundler.setup
- rescue LoadError
- module Bundler
- def self.require(*args, &block); end
- def self.method_missing(*args, &block); end
- end
-
- %w(
- actionmailer
- actionpack
- activemodel
- activerecord
- activeresource
- activesupport
- railties
- ).each do |framework|
- $:.unshift File.expand_path("../#{framework}/lib", __FILE__)
- end
- end
+# bust gem prelude
+if defined? Gem
+ Gem.source_index
+ gem 'bundler'
+else
+ require 'rubygems'
end
+require 'bundler'
+Bundler.setup \ No newline at end of file
diff --git a/rails.gemspec b/rails.gemspec
index 98b5f46554..4ee814c507 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -17,7 +17,6 @@ Gem::Specification.new do |s|
s.bindir = 'bin'
s.executables = ['rails']
- s.default_executable = 'rails'
s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version)
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 75f1df44e7..a4f0d31971 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,31 @@
*Rails 3.1.0 (unreleased)*
+* The scaffold controller will now produce SCSS file if Sass is available [Prem Sichanugrist]
+
+* The controller and resource generators will now automatically produce asset stubs (this can be turned off with --skip-assets). These stubs will use Coffee and Sass, if those libraries are available. [DHH]
+
+* jQuery is the new default JavaScript library. [fxn]
+
+* Changed scaffold and app generator to create Ruby 1.9 style hash when running on Ruby 1.9 [Prem Sichanugrist]
+
+ So instead of creating something like:
+
+ redirect_to users_path, :notice => "User has been created"
+
+ it will now be like this:
+
+ redirect_to users_path, notice: "User has been created"
+
+ You can also passing `--old-style-hash` to make Rails generate old style hash even you're on Ruby 1.9
+
+* Changed scaffold_controller generator to create format block for JSON instead of XML [Prem Sichanugrist]
+
+* Add using Turn with natural language test case names for test_help.rb when running with minitest (Ruby 1.9.2+) [DHH]
+
+* Direct logging of Active Record to STDOUT so it's shown inline with the results in the console [DHH]
+
+* Added `config.force_ssl` configuration which loads Rack::SSL middleware and force all requests to be under HTTPS protocol [DHH, Prem Sichanugrist, and Josh Peek]
+
* Added `rails plugin new` command which generates rails plugin with gemspec, tests and dummy application for testing [Piotr Sarnacki]
* Added -j parameter with jquery/prototype as options. Now you can create your apps with jQuery using `rails new myapp -j jquery`. The default is still Prototype. [siong1987]
diff --git a/railties/guides/rails_guides/textile_extensions.rb b/railties/guides/rails_guides/textile_extensions.rb
index affef1ccb0..352c5e91dd 100644
--- a/railties/guides/rails_guides/textile_extensions.rb
+++ b/railties/guides/rails_guides/textile_extensions.rb
@@ -1,9 +1,11 @@
+require 'active_support/core_ext/object/inclusion'
+
module RailsGuides
module TextileExtensions
def notestuff(body)
body.gsub!(/^(IMPORTANT|CAUTION|WARNING|NOTE|INFO)[.:](.*)$/) do |m|
css_class = $1.downcase
- css_class = 'warning' if ['caution', 'important'].include?(css_class)
+ css_class = 'warning' if css_class.in?(['caution', 'important'])
result = "<div class='#{css_class}'><p>"
result << $2.strip
@@ -33,7 +35,7 @@ module RailsGuides
def code(body)
body.gsub!(%r{<(yaml|shell|ruby|erb|html|sql|plain)>(.*?)</\1>}m) do |m|
es = ERB::Util.h($2)
- css_class = ['erb', 'shell'].include?($1) ? 'html' : $1
+ css_class = $1.in?(['erb', 'shell']) ? 'html' : $1
%{<notextile><div class="code_container"><code class="#{css_class}">#{es}</code></div></notextile>}
end
end
diff --git a/railties/guides/source/2_3_release_notes.textile b/railties/guides/source/2_3_release_notes.textile
index 67743a4797..15abba66ab 100644
--- a/railties/guides/source/2_3_release_notes.textile
+++ b/railties/guides/source/2_3_release_notes.textile
@@ -410,7 +410,7 @@ You're likely familiar with Rails' practice of adding timestamps to static asset
h4. Asset Hosts as Objects
-Asset hosts get more flexible in edge Rails with the ability to declare an asset host as a specific object that responds to a call. This allows you to to implement any complex logic you need in your asset hosting.
+Asset hosts get more flexible in edge Rails with the ability to declare an asset host as a specific object that responds to a call. This allows you to implement any complex logic you need in your asset hosting.
* More Information: "asset-hosting-with-minimum-ssl":http://github.com/dhh/asset-hosting-with-minimum-ssl/tree/master
diff --git a/railties/guides/source/3_0_release_notes.textile b/railties/guides/source/3_0_release_notes.textile
index 001f458fd9..f75b245ed8 100644
--- a/railties/guides/source/3_0_release_notes.textile
+++ b/railties/guides/source/3_0_release_notes.textile
@@ -59,12 +59,12 @@ The +config.gem+ method is gone and has been replaced by using +bundler+ and a +
h4. Upgrade Process
-To help with the upgrade process, a plugin named "Rails Upgrade":http://github.com/rails/rails_upgrade has been created to automate part of it.
+To help with the upgrade process, a plugin named "Rails Upgrade":http://github.com/jm/rails_upgrade has been created to automate part of it.
Simply install the plugin, then run +rake rails:upgrade:check+ to check your app for pieces that need to be updated (with links to information on how to update them). It also offers a task to generate a +Gemfile+ based on your current +config.gem+ calls and a task to generate a new routes file from your current one. To get the plugin, simply run the following:
<shell>
-$ ruby script/plugin install git://github.com/rails/rails_upgrade.git
+$ ruby script/plugin install git://github.com/jm/rails_upgrade.git
</shell>
You can see an example of how that works at "Rails Upgrade is now an Official Plugin":http://omgbloglol.com/post/364624593/rails-upgrade-is-now-an-official-plugin
diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile
index ecb03a48e4..f8b586c151 100644
--- a/railties/guides/source/action_controller_overview.textile
+++ b/railties/guides/source/action_controller_overview.textile
@@ -368,7 +368,7 @@ class UsersController < ApplicationController
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @users}
- format.json { render :json => @users}
+ format.json { render :json => @users}
end
end
end
@@ -615,26 +615,15 @@ Rails comes with two built-in HTTP authentication mechanisms:
h4. HTTP Basic Authentication
-HTTP basic authentication is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, consider an administration section which will only be available by entering a username and a password into the browser's HTTP basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, +authenticate_or_request_with_http_basic+.
+HTTP basic authentication is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, consider an administration section which will only be available by entering a username and a password into the browser's HTTP basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, +http_basic_authenticate_with+.
<ruby>
class AdminController < ApplicationController
- USERNAME, PASSWORD = "humbaba", "5baa61e4"
-
- before_filter :authenticate
-
- private
-
- def authenticate
- authenticate_or_request_with_http_basic do |username, password|
- username == USERNAME &&
- Digest::SHA1.hexdigest(password) == PASSWORD
- end
- end
+ http_basic_authenticate_with :name => "humbaba", :password => "5baa61e4"
end
</ruby>
-With this in place, you can create namespaced controllers that inherit from +AdminController+. The before filter will thus be run for all actions in those controllers, protecting them with HTTP basic authentication.
+With this in place, you can create namespaced controllers that inherit from +AdminController+. The filter will thus be run for all actions in those controllers, protecting them with HTTP basic authentication.
h4. HTTP Digest Authentication
@@ -816,6 +805,28 @@ end
NOTE: Certain exceptions are only rescuable from the +ApplicationController+ class, as they are raised before the controller gets initialized and the action gets executed. See Pratik Naik's "article":http://m.onkey.org/2008/7/20/rescue-from-dispatching on the subject for more information.
+h3. Force HTTPS protocol
+
+Sometime you might want to force a particular controller to only be accessible via an HTTPS protocol for security reason. Since Rails 3.1 you can now use +force_ssl+ method in your controller to enforce that:
+
+<ruby>
+class DinnerController
+ force_ssl
+end
+</ruby>
+
+Just like the filter, you could also passing +:only+ and +:except+ to enforce the secure connection only to specific actions
+
+<ruby>
+class DinnerController
+ force_ssl :only => :cheeseburger
+ # or
+ force_ssl :except => :cheeseburger
+end
+</ruby>
+
+Please note that if you found yourself adding +force_ssl+ to many controllers, you may found yourself wanting to force the whole application to use HTTPS instead. In that case, you can set the +config.force_ssl+ in your environment file.
+
h3. Changelog
* February 17, 2009: Yet another proofread by Xavier Noria.
diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile
index cfd71ad287..172932fdab 100644
--- a/railties/guides/source/action_view_overview.textile
+++ b/railties/guides/source/action_view_overview.textile
@@ -20,7 +20,28 @@ Note: Some features of Action View are tied to Active Record, but that doesn't m
h3. Using Action View with Rails
-TODO...
+For each controller there is an associated directory in the <tt>app/views</tt> directory which holds the template files that make up the views associated with that controller. These files are used to display the view that results from each controller action.
+
+Let's take a look at what Rails does by default when creating a new resource using the scaffold generator:
+
+<shell>
+$ rails generate scaffold post
+ [...]
+ invoke scaffold_controller
+ create app/controllers/posts_controller.rb
+ invoke erb
+ create app/views/posts
+ create app/views/posts/index.html.erb
+ create app/views/posts/edit.html.erb
+ create app/views/posts/show.html.erb
+ create app/views/posts/new.html.erb
+ create app/views/posts/_form.html.erb
+ [...]
+</shell>
+
+There is a naming convention for views in Rails. Typically, the views share their name with the associated controller action, as you can see above.
+For example, the index controller action of the <tt>posts_controller.rb</tt> will use the <tt>index.html.erb</tt> view file in the <tt>app/views/posts</tt> directory.
+The complete HTML returned to the client is composed of a combination of this ERB file, a layout template that wraps it, and all the partials that the view may reference. Later on this guide you can find a more detailed documentation of each one of this three components.
h3. Using Action View outside of Rails
@@ -94,9 +115,213 @@ TODO needs a screenshot? I have one - not sure where to put it.
h3. Templates, Partials and Layouts
-TODO...
+As mentioned before, the final HTML output is a composition of three Rails elements: +Templates+, +Partials+ and +Layouts+.
+Find below a brief overview of each one of them.
+
+h4. Templates
+
+Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> extension then it uses a mixture of ERB (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> extension then a fresh instance of <tt>Builder::XmlMarkup</tt> library is used.
+
+Rails supports multiple template systems and uses a file extension to distinguish amongst them. For example, an HTML file using the ERB template system will have <tt>.html.erb</tt> as a file extension.
+
+h5. ERB
+
+Within an ERB template Ruby code can be included using both +<% %>+ and +<%= %>+ tags. The +<% %>+ are used to execute Ruby code that does not return anything, such as conditions, loops or blocks, and the +<%= %>+ tags are used when you want output.
+
+Consider the following loop for names:
+
+<erb>
+<b>Names of all the people</b>
+<% @people.each do |person| %>
+ Name: <%= person.name %><br/>
+<% end %>
+</erb>
+
+The loop is setup in regular embedding tags +<% %>+ and the name is written using the output embedding tag +<%= %>+. Note that this is not just a usage suggestion, for Regular output functions like print or puts won't work with ERB templates. So this would be wrong:
+
+<erb>
+<%# WRONG %>
+Hi, Mr. <% puts "Frodo" %>
+</erb>
+
+To suppress leading and trailing whitespaces, you can use +<%-+ +-%>+ interchangeably with +<%+ and +%>+.
+
+h5. Builder
+
+Builder templates are a more programmatic alternative to ERB. They are especially useful for generating XML content. An XmlMarkup object named +xml+ is automatically made available to templates with a <tt>.builder</tt> extension.
+
+Here are some basic examples:
+
+<ruby>
+xml.em("emphasized")
+xml.em { xml.b("emph & bold") }
+xml.a("A Link", "href"=>"http://rubyonrails.org")
+xml.target("name"=>"compile", "option"=>"fast")
+</ruby>
+
+will produce
+
+<html>
+<em>emphasized</em>
+<em><b>emph &amp; bold</b></em>
+<a href="http://rubyonrails.org">A link</a>
+<target option="fast" name="compile" \>
+</html>
+
+Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following:
+
+<ruby>
+xml.div {
+ xml.h1(@person.name)
+ xml.p(@person.bio)
+}
+</ruby>
+
+would produce something like:
+
+<html>
+<div>
+ <h1>David Heinemeier Hansson</h1>
+ <p>A product of Danish Design during the Winter of '79...</p>
+</div>
+</html>
+
+A full-length RSS example actually used on Basecamp:
+
+<ruby>
+xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
+ xml.channel do
+ xml.title(@feed_title)
+ xml.link(@url)
+ xml.description "Basecamp: Recent items"
+ xml.language "en-us"
+ xml.ttl "40"
+
+ for item in @recent_items
+ xml.item do
+ xml.title(item_title(item))
+ xml.description(item_description(item)) if item_description(item)
+ xml.pubDate(item_pubDate(item))
+ xml.guid(@person.firm.account.url + @recent_items.url(item))
+ xml.link(@person.firm.account.url + @recent_items.url(item))
+ xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
+ end
+ end
+ end
+end
+</ruby>
+
+h5. Template caching
+
+By default, Rails will compile each template to a method in order to render it. When you alter a template, Rails will check the file's modification time and recompile it in development mode.
+
+h4. Partials
-TODO see http://guides.rubyonrails.org/layouts_and_rendering.html
+Partial templates – usually just called "partials" – are another device for breaking the rendering process into more manageable chunks. With a partial, you can move the code for rendering a particular piece of a response to its own file.
+
+h5. Naming Partials
+
+To render a partial as part of a view, you use the +render+ method within the view:
+
+<ruby>
+<%= render "menu" %>
+</ruby>
+
+This will render a file named +_menu.html.erb+ at that point within the view is being rendered. Note the leading underscore character: partials are named with a leading underscore to distinguish them from regular views, even though they are referred to without the underscore. This holds true even when you're pulling in a partial from another folder:
+
+<ruby>
+<%= render "shared/menu" %>
+</ruby>
+
+That code will pull in the partial from +app/views/shared/_menu.html.erb+.
+
+h5. Using Partials to Simplify Views
+
+One way to use partials is to treat them as the equivalent of subroutines: as a way to move details out of a view so that you can grasp what's going on more easily. For example, you might have a view that looked like this:
+
+<erb>
+<%= render "shared/ad_banner" %>
+
+<h1>Products</h1>
+
+<p>Here are a few of our fine products:</p>
+<% @products.each do |product| %>
+ <%= render :partial => "product", :locals => { :product => product } %>
+<% end %>
+
+<%= render "shared/footer" %>
+</erb>
+
+Here, the +_ad_banner.html.erb+ and +_footer.html.erb+ partials could contain content that is shared among many pages in your application. You don't need to see the details of these sections when you're concentrating on a particular page.
+
+h5. The :as and :object options
+
+By default <tt>ActionView::Partials::PartialRenderer</tt> has its object in a local variable with the same name as the template. So, given
+
+<erb>
+<%= render :partial => "product" %>
+</erb>
+
+within product we'll get <tt>@product</tt> in the local variable +product+, as if we had written:
+
+<erb>
+<%= render :partial => "product", :locals => { :product => @product } %>
+</erb>
+
+With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we wanted it to be +item+ instead of product+ we'd do:
+
+<erb>
+<%= render :partial => "product", :as => 'item' %>
+</erb>
+
+The <tt>:object</tt> option can be used to directly specify which object is rendered into the partial; useful when the template's object is elsewhere, in a different ivar or in a local variable for instance.
+
+For example, instead of:
+
+<erb>
+<%= render :partial => "product", :locals => { :product => @item } %>
+</erb>
+
+you'd do:
+
+<erb>
+<%= render :partial => "product", :object => @item %>
+</erb>
+
+The <tt>:object</tt> and <tt>:as</tt> options can be used together.
+
+h5. Rendering Collections
+
+The example of partial use describes a familiar pattern where a template needs to iterate over an array and render a sub template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders a partial by the same name as the elements contained within.
+So the three-lined example for rendering all the products can be rewritten with a single line:
+
+<erb>
+<%= render :partial => "product", :collection => @products %>
+</erb>
+
+When a partial is called with a pluralized collection, then the individual instances of the partial have access to the member of the collection being rendered via a variable named after the partial. In this case, the partial is +_product+ , and within the +_product+ partial, you can refer to +product+ to get the instance that is being rendered.
+
+You can use a shorthand syntax for rendering collections. Assuming @products is a collection of +Product+ instances, you can simply write the following to produce the same result:
+
+<erb>
+<%= render @products %>
+</erb>
+
+Rails determines the name of the partial to use by looking at the model name in the collection. In fact, you can even create a heterogeneous collection and render it this way, and Rails will choose the proper partial for each member of the collection.
+
+h5. Spacer Templates
+
+You can also specify a second partial to be rendered between instances of the main partial by using the +:spacer_template+ option:
+
+<erb>
+<%= render @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.
+
+h4. Layouts
+
+TODO...
h3. Using Templates, Partials and Layouts in "The Rails Way"
@@ -262,9 +487,9 @@ Register one or more stylesheet files to be included when symbol is passed to +s
ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"]
stylesheet_link_tag :monkey # =>
- <link href="/stylesheets/head.css" media="screen" rel="stylesheet" type="text/css" />
- <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" />
- <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" />
+ <link href="/stylesheets/head.css" media="screen" rel="stylesheet" type="text/css" />
+ <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" />
+ <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" />
</ruby>
h5. auto_discovery_link_tag
@@ -1295,7 +1520,7 @@ end
h5. update_page_tag
-Works like update_page but wraps the generated JavaScript in a +script+ tag. Use this to include generated JavaScript in an ERb template.
+Works like update_page but wraps the generated JavaScript in a +script+ tag. Use this to include generated JavaScript in an ERB template.
h4. PrototypeHelper::JavaScriptGenerator::GeneratorMethods
@@ -1472,5 +1697,6 @@ You can read more about the Rails Internationalization (I18n) API "here":i18n.ht
h3. Changelog
+* April 16, 2011: Added 'Using Action View with Rails', 'Templates' and 'Partials' sections. "Sebastian Martinez":http://wyeworks.com
* September 3, 2009: Continuing work by Trevor Turk, leveraging the Action Pack docs and "What's new in Edge Rails":http://ryandaigle.com/articles/2007/8/3/what-s-new-in-edge-rails-partials-get-layouts
* April 5, 2009: Starting work by Trevor Turk, leveraging Mike Gunderloy's docs
diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile
index ed3968e226..2f0a51e868 100644
--- a/railties/guides/source/active_record_querying.textile
+++ b/railties/guides/source/active_record_querying.textile
@@ -76,7 +76,7 @@ Primary operation of <tt>Model.find(options)</tt> can be summarized as:
h4. Retrieving a Single Object
-Active Record lets you retrieve a single object using three different ways.
+Active Record lets you retrieve a single object using five different ways.
h5. Using a Primary Key
@@ -130,6 +130,40 @@ SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
<tt>Model.last</tt> returns +nil+ if no matching record is found. No exception will be raised.
+h5. +first!+
+
+<tt>Model.first!</tt> finds the first record. For example:
+
+<ruby>
+client = Client.first!
+=> #<Client id: 1, first_name: "Lifo">
+</ruby>
+
+SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients LIMIT 1
+</sql>
+
+<tt>Model.first!</tt> raises +RecordNotFound+ if no matching record is found.
+
+h5. +last!+
+
+<tt>Model.last!</tt> finds the last record. For example:
+
+<ruby>
+client = Client.last!
+=> #<Client id: 221, first_name: "Russel">
+</ruby>
+
+SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
+</sql>
+
+<tt>Model.last!</tt> raises +RecordNotFound+ if no matching record is found.
+
h4. Retrieving Multiple Objects
h5. Using Multiple Primary Keys
@@ -384,7 +418,7 @@ SELECT viewable_by, locked FROM clients
Be careful because this also means you're initializing a model object with only the fields that you've selected. If you attempt to access a field that is not in the initialized record you'll receive:
<shell>
-ActiveRecord::MissingAttributeError: missing attribute: <attribute>
+ActiveModel::MissingAttributeError: missing attribute: <attribute>
</shell>
Where +&lt;attribute&gt;+ is the attribute you asked for. The +id+ method will not raise the +ActiveRecord::MissingAttributeError+, so just be careful when working with associations because they need the +id+ method to function properly.
@@ -512,7 +546,7 @@ Active Record provides two locking mechanisms:
h4. Optimistic Locking
-Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of conflicts with the data. It does this by checking whether another process has made changes to a record since it was opened. An +ActiveRecord::StaleObjectError+ exception is thrown if that has occurred and the update is ignored.
+Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of conflicts with the data. It does this by checking whether another process has made changes to a record since it was opened. An +ActiveRecord::StaleObjectError+ exception is thrown if that has occurred and the update is ignored.
<strong>Optimistic locking column</strong>
@@ -747,6 +781,22 @@ h4. Specifying Conditions on Eager Loaded Associations
Even though Active Record lets you specify conditions on the eager loaded associations just like +joins+, the recommended way is to use "joins":#joining-tables instead.
+However if you must do this, you may use +where+ as you would normally.
+
+<ruby>
+Post.includes(:comments).where("comments.visible", true)
+</ruby>
+
+This would generate a query which contains a +LEFT OUTER JOIN+ whereas the +joins+ method would generate one using the +INNER JOIN+ function instead.
+
+<ruby>
+ SELECT "posts"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE (comments.visible = 1)
+</ruby>
+
+If there was no +where+ condition, this would generate the normal set of two queries.
+
+If, in the case of this +includes+ query, there were no comments for any posts, all the posts would still be loaded. By using +joins+ (an INNER JOIN), the join conditions *must* match, otherwise no records will be returned.
+
h3. Scopes
Scoping allows you to specify commonly-used ARel queries which can be referenced as method calls on the association objects or models. With these scopes, you can use every method previously covered such as +where+, +joins+ and +includes+. All scope methods will return an +ActiveRecord::Relation+ object which will allow for further methods (such as other scopes) to be called on it.
@@ -912,6 +962,26 @@ Client.exists?
The above returns +false+ if the +clients+ table is empty and +true+ otherwise.
+You can also use +any?+ and +many?+ to check for existence on a model or relation.
+
+<ruby>
+# via a model
+Post.any?
+Post.many?
+
+# via a named scope
+Post.recent.any?
+Post.recent.many?
+
+# via a relation
+Post.where(:published => true).any?
+Post.where(:published => true).many?
+
+# via an association
+Post.first.categories.any?
+Post.first.categories.many?
+</ruby>
+
h3. Calculations
This section uses count as an example method in this preamble, but the options described apply to all sub-sections.
@@ -923,7 +993,7 @@ Client.count
# SELECT count(*) AS count_all FROM clients
</ruby>
-Or on a relation :
+Or on a relation:
<ruby>
Client.where(:first_name => 'Ryan').count
@@ -990,7 +1060,7 @@ If you want to find the sum of a field for all records in your table you can cal
Client.sum("orders_count")
</ruby>
-For options, please see the parent section, "Calculations":#calculations.
+For options, please see the parent section, "Calculations":#calculations.
h3. Changelog
diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile
index 58a184a48e..19bd4ad0f1 100644
--- a/railties/guides/source/active_record_validations_callbacks.textile
+++ b/railties/guides/source/active_record_validations_callbacks.textile
@@ -84,6 +84,7 @@ The following methods skip validations, and will save the object to the database
* +toggle!+
* +update_all+
* +update_attribute+
+* +update_column+
* +update_counters+
Note that +save+ also has the ability to skip validations if passed +:validate => false+ as argument. This technique should be used with caution.
@@ -461,11 +462,11 @@ The block receives the model, the attribute's name and the attribute's value. Yo
h3. Common Validation Options
-There are some common options that all the validation helpers can use. Here they are, except for the +:if+ and +:unless+ options, which are discussed later in "Conditional Validation":#conditional-validation.
+These are common validation options:
h4. +:allow_nil+
-The +:allow_nil+ option skips the validation when the value being validated is +nil+. Using +:allow_nil+ with +validates_presence_of+ allows for +nil+, but any other +blank?+ value will still be rejected.
+The +:allow_nil+ option skips the validation when the value being validated is +nil+.
<ruby>
class Coffee < ActiveRecord::Base
@@ -474,6 +475,8 @@ class Coffee < ActiveRecord::Base
end
</ruby>
+TIP: +:allow_nil+ is ignored by the presence validator.
+
h4. +:allow_blank+
The +:allow_blank+ option is similar to the +:allow_nil+ option. This option will let validation pass if the attribute's value is +blank?+, like +nil+ or an empty string for example.
@@ -487,6 +490,8 @@ Topic.create("title" => "").valid? # => true
Topic.create("title" => nil).valid? # => true
</ruby>
+TIP: +:allow_blank+ is ignored by the presence validator.
+
h4. +:message+
As you've already seen, the +:message+ option lets you specify the message that will be added to the +errors+ collection when validation fails. When this option is not used, Active Record will use the respective default error message for each validation helper.
@@ -547,6 +552,21 @@ class Account < ActiveRecord::Base
end
</ruby>
+h4. Grouping conditional validations
+
+Sometimes it is useful to have multiple validations use one condition, it can be easily achieved using +with_options+.
+
+<ruby>
+class User < ActiveRecord::Base
+ with_options :if => :is_admin? do |admin|
+ admin.validates_length_of :password, :minimum => 10
+ admin.validates_presence_of :email
+ end
+end
+</ruby>
+
+All validations inside of +with_options+ block will have automatically passed the condition +:if => :is_admin?+
+
h3. Creating Custom Validation Methods
When the built-in validation helpers are not enough for your needs, you can write your own validation methods.
@@ -742,7 +762,7 @@ Rails maintains an official plugin that provides helpers to display the error me
h4. Installing as a plugin
<shell>
-$ rails plugin install git://github.com/rails/dynamic_form.git
+$ rails plugin install git://github.com/joelmoss/dynamic_form.git
</shell>
h4 Installing as a Gem
@@ -817,7 +837,7 @@ The selectors to customize the style of error messages are:
* +#errorExplanation p+ - Style for the paragraph that holds the message that appears right below the header of the +div+ element.
* +#errorExplanation ul li+ - Style for the list items with individual error messages.
-Scaffolding for example generates +public/stylesheets/scaffold.css+, which defines the red-based style you saw above.
+Scaffolding for example generates +app/assets/stylesheets/scaffold.css.scss+, which later compiles to +app/assets/stylesheets/scaffold.css+ and defines the red-based style you saw above.
The name of the class and the id can be changed with the +:class+ and +:id+ options, accepted by both helpers.
@@ -877,8 +897,9 @@ The macro-style class methods can also receive a block. Consider using this styl
class User < ActiveRecord::Base
validates_presence_of :login, :email
- before_create {|user| user.name = user.login.capitalize
- if user.name.blank?}
+ before_create do |user|
+ user.name = user.login.capitalize if user.name.blank?
+ end
end
</ruby>
@@ -989,6 +1010,7 @@ Just as with validations, it's also possible to skip callbacks. These methods sh
* +increment+
* +increment_counter+
* +toggle+
+* +update_column+
* +update_all+
* +update_counters+
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index 788f528654..f89c83e4cd 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -442,6 +442,20 @@ require_library_or_gem('mysql')
NOTE: Defined in +active_support/core_ext/kernel/requires.rb+.
+h4. +in?+
+
+The predicate +in?+ tests if an object is included in another object. An +ArgumentError+ exception will be raised if the argument passed does not respond to +include?+.
+
+Examples of +in?+:
+
+<ruby>
+ 1.in?([1,2]) # => true
+ "lo".in?("hello") # => true
+ 25.in?(30..50) # => false
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/object/inclusion.rb+.
+
h3. Extensions to +Module+
h4. +alias_method_chain+
@@ -1252,6 +1266,15 @@ WARNING: The option +:separator+ can't be a regexp.
NOTE: Defined in +active_support/core_ext/string/filters.rb+.
+h4. +inquiry+
+
+The <tt>inquiry</tt> method converts a string into a +StringInquirer+ object making equality checks prettier.
+
+<ruby>
+"production".inquiry.production? # => true
+"active".inquiry.inactive? # => false
+</ruby>
+
h4. Key-based Interpolation
In Ruby 1.9 the <tt>%</tt> string operator supports key-based interpolation, both formatted and unformatted:
@@ -1983,6 +2006,11 @@ Similarly, +from+ returns the tail from the element at the passed index on:
The methods +second+, +third+, +fourth+, and +fifth+ return the corresponding element (+first+ is built-in). Thanks to social wisdom and positive constructiveness all around, +forty_two+ is also available.
+<ruby>
+%w(a b c d).third # => c
+%w(a b c d).fifth # => nil
+</ruby>
+
NOTE: Defined in +active_support/core_ext/array/access.rb+.
h4. Random Access
@@ -2078,7 +2106,7 @@ h5. +to_xml+
The method +to_xml+ returns a string containing an XML representation of its receiver:
<ruby>
-Contributor.all(:limit => 2, :order => 'rank ASC').to_xml
+Contributor.limit(2).order(:rank).to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <contributors type="array">
@@ -2153,7 +2181,7 @@ The name of children nodes is by default the name of the root node singularized.
The default XML builder is a fresh instance of <tt>Builder::XmlMarkup</tt>. You can configure your own builder via the <tt>:builder</tt> option. The method also accepts options like <tt>:dasherize</tt> and friends, they are forwarded to the builder:
<ruby>
-Contributor.all(:limit => 2, :order => 'rank ASC').to_xml(:skip_types => true)
+Contributor.limit(2).order(:rank).to_xml(:skip_types => true)
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <contributors>
@@ -3393,11 +3421,11 @@ h4. +silence+
Silences every log level lesser to the specified one for the duration of the given block. Log level orders are: debug, info, error and fatal.
<ruby>
- logger = Logger.new("log/development.log")
- logger.silence(Logger::INFO) do
- logger.debug("In space, no one can hear you scream.")
- logger.info("Scream all you want, small mailman!")
- end
+logger = Logger.new("log/development.log")
+logger.silence(Logger::INFO) do
+ logger.debug("In space, no one can hear you scream.")
+ logger.info("Scream all you want, small mailman!")
+end
</ruby>
h4. +datetime_format=+
@@ -3405,17 +3433,17 @@ h4. +datetime_format=+
Modifies the datetime format output by the formatter class associated with this logger. If the formatter class does not have a +datetime_format+ method then this is ignored.
<ruby>
- class Logger::FormatWithTime < Logger::Formatter
- cattr_accessor(:datetime_format) { "%Y%m%d%H%m%S" }
+class Logger::FormatWithTime < Logger::Formatter
+ cattr_accessor(:datetime_format) { "%Y%m%d%H%m%S" }
- def self.call(severity, timestamp, progname, msg)
- "#{timestamp.strftime(datetime_format)} -- #{String === msg ? msg : msg.inspect}\n"
- end
+ def self.call(severity, timestamp, progname, msg)
+ "#{timestamp.strftime(datetime_format)} -- #{String === msg ? msg : msg.inspect}\n"
end
+end
- logger = Logger.new("log/development.log")
- logger.formatter = Logger::FormatWithTime
- logger.info("<- is the current time")
+logger = Logger.new("log/development.log")
+logger.formatter = Logger::FormatWithTime
+logger.info("<- is the current time")
</ruby>
NOTE: Defined in +active_support/core_ext/logger.rb+.
diff --git a/railties/guides/source/ajax_on_rails.textile b/railties/guides/source/ajax_on_rails.textile
index b80df4aa58..1566c23414 100644
--- a/railties/guides/source/ajax_on_rails.textile
+++ b/railties/guides/source/ajax_on_rails.textile
@@ -3,14 +3,14 @@ h2. AJAX on Rails
This guide covers the built-in Ajax/JavaScript functionality of Rails (and more); it will enable you to create rich and dynamic AJAX applications with ease! We will cover the following topics:
* Quick introduction to AJAX and related technologies
-* Handling JavaScript the Rails way: Rails helpers, RJS, Prototype and script.aculo.us
+* Handling JavaScript the Rails way: Rails helpers, Prototype and script.aculo.us
* Testing JavaScript functionality
endprologue.
h3. Hello AJAX - a Quick Intro
-If you are a 'show me the code' type of person, you might want to skip this part and jump to the RJS section right away. However, I would really recommend to read it - you'll need the basics of DOM, http requests and other topics discussed here to really understand Ajax on Rails.
+You'll need the basics of DOM, HTTP requests and other topics discussed here to really understand Ajax on Rails.
h4. Asynchronous JavaScript + XML
@@ -42,7 +42,7 @@ You are ready to add some AJAX love to your Rails app!
h4. The Quintessential AJAX Rails Helper: link_to_remote
-Let's start with what is probably the most often used helper: +link_to_remote+. It has an interesting feature from the documentation point of view: the options supplied to +link_to_remote+ are shared by all other AJAX helpers, so learning the mechanics and options of +link_to_remote+ is a great help when using other helpers.
+Let's start with what is probably the most often used helper: +link_to_remote+. It has an interesting feature from the documentation point of view: the options supplied to +link_to_remote+ are shared by all other AJAX helpers, so learning the mechanics and options of +link_to_remote+ is a great help when using other helpers.
The signature of +link_to_remote+ function is the same as that of the standard +link_to+ helper:
@@ -62,7 +62,7 @@ link_to_remote "Add to cart",
* The second parameter, the +options+ hash is the most interesting part as it has the AJAX specific stuff:
** *:url* This is the only parameter that is always required to generate the simplest remote link (technically speaking, it is not required, you can pass an empty +options+ hash to +link_to_remote+ - but in this case the URL used for the POST request will be equal to your current URL which is probably not your intention). This URL points to your AJAX action handler. The URL is typically specified by Rails REST view helpers, but you can use the +url_for+ format too.
-** *:update* There are basically two ways of injecting the server response into the page: One is involving RJS and we will discuss it in the next chapter, and the other is specifying a DOM id of the element we would like to update. The above example demonstrates the simplest way of accomplishing this - however, we are in trouble if the server responds with an error message because that will be injected into the page too! However, Rails has a solution for this situation:
+** *:update* Specifying a DOM id of the element we would like to update. The above example demonstrates the simplest way of accomplishing this - however, we are in trouble if the server responds with an error message because that will be injected into the page too! However, Rails has a solution for this situation:
<ruby>
link_to_remote "Add to cart",
@@ -72,7 +72,7 @@ link_to_remote "Add to cart",
If the server returns 200, the output of the above example is equivalent to our first, simple one. However, in case of error, the element with the DOM id +error+ is updated rather than the +cart+ element.
-** *position* By default (i.e. when not specifying this option, like in the examples before) the repsonse is injected into the element with the specified DOM id, replacing the original content of the element (if there was any). You might want to alter this behavior by keeping the original content - the only question is where to place the new content? This can specified by the +position+ parameter, with four possibilities:
+** *position* By default (i.e. when not specifying this option, like in the examples before) the repsonse is injected into the element with the specified DOM id, replacing the original content of the element (if there was any). You might want to alter this behavior by keeping the original content - the only question is where to place the new content? This can specified by the +position+ parameter, with four possibilities:
*** +:before+ Inserts the response text just before the target element. More precisely, it creates a text node from the response and inserts it as the left sibling of the target element.
*** +:after+ Similar behavior to +:before+, but in this case the response is inserted after the target element.
*** +:top+ Inserts the text into the target element, before it's original content. If the target element was empty, this is equivalent with not specifying +:position+ at all.
@@ -178,12 +178,7 @@ h5. +remote_function+
h5. +update_page+
-
-h3. JavaScript the Rails way: RJS
-
-In the last section we sent some AJAX requests to the server, and inserted the HTML response into the page (with the +:update+ option). However, sometimes a more complicated interaction with the page is needed, which you can either achieve with JavaScript... or with RJS! You are sending JavaScript instructions to the server in both cases, but while in the former case you have to write vanilla JavaScript, in the second you can code Rails, and sit back while Rails generates the JavaScript for you - so basically RJS is a Ruby DSL to write JavaScript in your Rails code.
-
-h4. JavaScript without RJS
+h4. Serving JavaScript
First we'll check out how to send JavaScript to the server manually. You are practically never going to need this, but it's interesting to understand what's going on under the hood.
@@ -198,136 +193,6 @@ end
What happens here is that by specifying the Content-Type header variable, we instruct the browser to evaluate the text we are sending over (rather than displaying it as plain text, which is the default behavior).
-h4. Inline RJS
-
-As we said, the purpose of RJS is to write Ruby which is then auto-magically turned into JavaScript by Rails. The above example didn't look too Ruby-esque so let's see how to do it the Rails way:
-
-<ruby>
-def javascript_test
- render :update do |page|
- page.alert "Hello from inline RJS"
- end
-end
-</ruby>
-
-The above code snippet does exactly the same as the one in the previous section - going about it much more elegantly though. You don't need to worry about headers,write ugly JavaScript code into a string etc. When the first parameter to +render+ is +:update+, Rails expects a block with a single parameter (+page+ in our case, which is the traditional naming convention) which is an instance of the JavaScriptGenerator:"http://api.rubyonrails.org/classes/ActionView/Helpers/PrototypeHelper/JavaScriptGenerator/GeneratorMethods.html" object. As it's name suggests, JavaScriptGenerator is responsible for generating JavaScript from your Ruby code. You can execute multiple method calls on the +page+ instance - it's all turned into JavaScript code and sent to the server with the appropriate Content Type, "text/javascript".
-
-h4. RJS Templates
-
-If you don't want to clutter your controllers with view code (especially when your inline RJS is more than a few lines), you can move your RJS code to a template file. RJS templates should go to the +/app/views/+ directory, just as +.html.erb+ or any other view files of the appropriate controller, conventionally named +js.rjs+.
-
-To rewrite the above example, you can leave the body of the action empty, and create a RJS template named +javascript_test.js.rjs+, containing the following line:
-
-<ruby>
-page.alert "Hello from inline RJS"
-</ruby>
-
-h4. RJS Reference
-
-In this section we'll go through the methods RJS offers.
-
-h5. JavaScriptGenerator Methods
-
-h6. DOM Element Manipulation
-
-It is possible to manipulate multiple elements at once through the +page+ JavaScriptGenerator instance. Let's see this in action:
-
-<ruby>
-page.show :div_one, :div_two
-page.hide :div_one
-page.remove :div_one, :div_two, :div_three
-page.toggle :other_div
-</ruby>
-
-The above methods (+show+, +hide+, +remove+, +toggle+) have the same semantics as the Prototype methods of the same name. You can pass an arbitrary number (but at least one) of DOM ids to these calls.
-
-
-h6. Inserting and Replacing Content
-
-You can insert content into an element on the page with the +insert_html+ method:
-
-<ruby>
-page.insert_html :top, :result, '42'
-</ruby>
-
-The first parameter is the position of the new content relative to the element specified by the second parameter, a DOM id.
-
-Position can be one of these four values:
-
-*** +:before+ Inserts the response text just before the target element.
-*** +:after+ The response is inserted after the target element.
-*** +:top+ Inserts the text into the target element, before it's original content.
-*** +:bottom+ The counterpart of +:top+: the response is inserted after the target element's original content.
-
-The third parameter can either be a string, or a hash of options to be passed to ActionView::Base#render - for example:
-
-<ruby>
-page.insert_html :top, :result, :partial => "the_answer"
-</ruby>
-
-You can replace the contents (innerHTML) of an element with the +replace_html+ method. The only difference is that since it's clear where should the new content go, there is no need for a position parameter - so +replace_html+ takes only two arguments,
-the DOM id of the element you wish to modify and a string or a hash of options to be passed to ActionView::Base#render.
-
-h6. Delay
-
-You can delay the execution of a block of code with +delay+:
-
-<ruby>
-page.delay(10) { page.alert('Hey! Just waited 10 seconds') }
-</ruby>
-
-+delay+ takes one parameter (time to wait in seconds) and a block which will be executed after the specified time has passed - whatever else follows a +page.delay+ line is executed immediately, the delay affects only the code in the block.
-
-h6. Reloading and Redirecting
-
-You can reload the page with the +reload+ method:
-
-<ruby>
-page.reload
-</ruby>
-
-When using AJAX, you can't rely on the standard +redirect_to+ controller method - you have to use the +page+'s instance method, also called +redirect_to+:
-
-<ruby>
-page.redirect_to some_url
-</ruby>
-
-h6. Generating Arbitrary JavaScript
-
-Sometimes even the full power of RJS is not enough to accomplish everything, but you still don't want to drop to pure JavaScript. A nice golden mean is offered by the combination of +<<+, +assign+ and +call+ methods:
-
-<ruby>
- page << "alert('1+1 equals 3')"
-</ruby>
-
-So +<<+ is used to execute an arbitrary JavaScript statement, passed as string to the method. The above code is equivalent to:
-
-<ruby>
- page.assign :result, 3
- page.call :alert, '1+1 equals ' + result
-</ruby>
-
-+assign+ simply assigns a value to a variable. +call+ is similar to +<<+ with a slightly different syntax: the first parameter is the name of the function to call, followed by the list of parameters passed to the function.
-
-h6. Class Proxies
-
-h5. Element Proxies
-
-h5. Collection Proxies
-
-h5. RJS Helpers
-
-
-
-h3. I Want my Yellow Thingy: Quick overview of Script.aculo.us
-
-h4. Introduction
-
-h4. Visual Effects
-
-h4. Drag and Drop
-
-
h3. Testing JavaScript
diff --git a/railties/guides/source/api_documentation_guidelines.textile b/railties/guides/source/api_documentation_guidelines.textile
index 7433507866..e22ffa4c04 100644
--- a/railties/guides/source/api_documentation_guidelines.textile
+++ b/railties/guides/source/api_documentation_guidelines.textile
@@ -29,7 +29,7 @@ Documentation has to be concise but comprehensive. Explore and document edge cas
The proper names of Rails components have a space in between the words, like "Active Support". +ActiveRecord+ is a Ruby module, whereas Active Record is an ORM. All Rails documentation should consistently refer to Rails components by their proper name, and if in your next blog post or presentation you remember this tidbit and take it into account that'd be phenomenal.
-Spell names correctly: Arel, Test::Unit, RSpec, HTML, MySQL, JavaScript, ERb. When in doubt, please have a look at some authoritative source like their official documentation.
+Spell names correctly: Arel, Test::Unit, RSpec, HTML, MySQL, JavaScript, ERB. When in doubt, please have a look at some authoritative source like their official documentation.
Use the article "an" for "SQL", as in "an SQL statement". Also "an SQLite database".
diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile
index 1b5ec40d16..799339e674 100644
--- a/railties/guides/source/caching_with_rails.textile
+++ b/railties/guides/source/caching_with_rails.textile
@@ -25,7 +25,7 @@ h4. Page Caching
Page caching is a Rails mechanism which allows the request for a generated page to be fulfilled by the webserver (i.e. apache or nginx), without ever having to go through the Rails stack at all. Obviously, this is super-fast. Unfortunately, it can't be applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with.
-So, how do you enable this super-fast cache behavior? Simple, let's say you have a controller called +ProductsController+ and an +index+ action that lists all the products
+So, how do you enable this super-fast cache behavior? Simple, let's say you have a controller called +ProductsController+ and an +index+ action that lists all the products
<ruby>
class ProductsController < ActionController
@@ -65,7 +65,7 @@ end
If you want a more complicated expiration scheme, you can use cache sweepers to expire cached objects when things change. This is covered in the section on Sweepers.
-Note: Page caching ignores all parameters. For example +/products?page=1+ will be written out to the filesystem as +products.html+ with no reference to the +page+ parameter. Thus, if someone requests +/products?page=2+ later, they will get the cached first page. Be careful when page caching GET parameters in the URL!
+NOTE: Page caching ignores all parameters. For example +/products?page=1+ will be written out to the filesystem as +products.html+ with no reference to the +page+ parameter. Thus, if someone requests +/products?page=2+ later, they will get the cached first page. Be careful when page caching GET parameters in the URL!
INFO: Page caching runs in an after filter. Thus, invalid requests won't generate spurious cache entries as long as you halt them. Typically, a redirection in some before filter that checks request preconditions does the job.
@@ -152,7 +152,7 @@ expire_fragment('all_available_products')
h4. Sweepers
-Cache sweeping is a mechanism which allows you to get around having a ton of +expire_{page,action,fragment}+ calls in your code. It does this by moving all the work required to expire cached content into an +ActionController::Caching::Sweeper+ subclass. This class is an observer and looks for changes to an object via callbacks, and when a change occurs it expires the caches associated with that object in an around or after filter.
+Cache sweeping is a mechanism which allows you to get around having a ton of +expire_{page,action,fragment}+ calls in your code. It does this by moving all the work required to expire cached content into an +ActionController::Caching::Sweeper+ subclass. This class is an observer and looks for changes to an object via callbacks, and when a change occurs it expires the caches associated with that object in an around or after filter.
Continuing with our Product controller example, we could rewrite it with a sweeper like this:
@@ -230,9 +230,9 @@ class ProductsController < ActionController
end
</ruby>
-The second time the same query is run against the database, it's not actually going to hit the database. The first time the result is returned from the query it is stored in the query cache (in memory) and the second time it's pulled from memory.
+The second time the same query is run against the database, it's not actually going to hit the database. The first time the result is returned from the query it is stored in the query cache (in memory) and the second time it's pulled from memory.
-However, it's important to note that query caches are created at the start of an action and destroyed at the end of that action and thus persist only for the duration of the action. If you'd like to store query results in a more persistent fashion, you can in Rails by using low level caching.
+However, it's important to note that query caches are created at the start of an action and destroyed at the end of that action and thus persist only for the duration of the action. If you'd like to store query results in a more persistent fashion, you can in Rails by using low level caching.
h3. Cache Stores
@@ -322,7 +322,7 @@ You can use Hashes and Arrays of values as cache keys.
<ruby>
# This is a legal cache key
-Rails.cache.read(:site => "mysite", :owners => [owner_1, owner2])
+Rails.cache.read(:site => "mysite", :owners => [owner_1, owner_2])
</ruby>
The keys you use on +Rails.cache+ will not be the same as those actually used with the storage engine. They may be modified with a namespace or altered to fit technology backend constraints. This means, for instance, that you can't save values with +Rails.cache+ and then try to pull them out with the +memcache-client+ gem. However, you also don't need to worry about exceeding the memcached size limit or violating syntax rules.
@@ -352,7 +352,7 @@ class ProductsController < ApplicationController
# If the request is fresh (i.e. it's not modified) then you don't need to do
# anything. The default render checks for this using the parameters
# used in the previous call to stale? and will automatically send a
- # :not_modified. So that's it, you're done.
+ # :not_modified. So that's it, you're done.
end
</ruby>
diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile
index 581fece1ab..ad36c6532e 100644
--- a/railties/guides/source/command_line.textile
+++ b/railties/guides/source/command_line.textile
@@ -8,10 +8,10 @@ Rails comes with every command line tool you'll need to
* Mess with objects through an interactive shell
* Profile and benchmark your new creation
-NOTE: This tutorial assumes you have basic Rails knowledge from reading the "Getting Started with Rails Guide":getting_started.html.
-
endprologue.
+NOTE: This tutorial assumes you have basic Rails knowledge from reading the "Getting Started with Rails Guide":getting_started.html.
+
WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails.
h3. Command Line Basics
@@ -144,10 +144,13 @@ $ rails generate controller Greetings hello
create app/helpers/greetings_helper.rb
invoke test_unit
create test/unit/helpers/greetings_helper_test.rb
+ invoke assets
+ create app/assets/javascripts/greetings.js
+ create app/assets/stylesheets/greetings.css
</shell>
-What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a functional test file, a helper for the view, and a view file.
+What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a view file, a functional test file, a helper for the view, a javascript file and a stylesheet file.
Check out the controller and modify it a little (in +app/controllers/greetings_controller.rb+):
@@ -215,13 +218,13 @@ $ rails generate scaffold HighScore game:string score:integer
create app/views/layouts/
exists test/functional/
create test/unit/
- create public/stylesheets/
+ create app/assets/stylesheets/
create app/views/high_scores/index.html.erb
create app/views/high_scores/show.html.erb
create app/views/high_scores/new.html.erb
create app/views/high_scores/edit.html.erb
create app/views/layouts/high_scores.html.erb
- create public/stylesheets/scaffold.css
+ create app/assets/stylesheets/scaffold.css.scss
create app/controllers/high_scores_controller.rb
create test/functional/high_scores_controller_test.rb
create app/helpers/high_scores_helper.rb
@@ -484,7 +487,7 @@ end
We take whatever args are supplied, save them to an instance variable, and literally copying from the Rails source, implement a +manifest+ method, which calls +record+ with a block, and we:
* Check there's a *public* directory. You bet there is.
-* Run the ERb template called "tutorial.erb".
+* Run the ERB template called "tutorial.erb".
* Save it into "Rails.root/public/tutorial.txt".
* Pass in the arguments we saved through the +:assigns+ parameter.
diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile
index 62b846e871..d7069b31fc 100644
--- a/railties/guides/source/configuring.textile
+++ b/railties/guides/source/configuring.textile
@@ -81,6 +81,8 @@ end
* +config.filter_parameters+ used for filtering out the parameters that you don't want shown in the logs, such as passwords or credit card numbers.
+* +config.force_ssl+ forcing all requests to be under HTTPS protocol by using +Rack::SSL+ middleware. This will secure your application from a session hijack attempt.
+
* +config.helper_paths+ configures where Rails can find helpers for this application.
* +config.log_level+ defines the verbosity of the Rails logger. In production mode, this defaults to +:info+. In development mode, it defaults to +:debug+.
@@ -147,6 +149,7 @@ h4. Configuring Middleware
Every Rails application comes with a standard set of middleware which it uses in this order in the development environment:
+* +Rack::SSL+ Will force every request to be under HTTPS protocol. Will be available if +config.force_ssl+ is set to _true_.
* +ActionDispatch::Static+ is used to serve static assets. Disabled if +config.serve_static_assets+ is _true_.
* +Rack::Lock+ Will wrap the app in mutex so it can only be called by a single thread at a time. Only enabled if +config.action_controller.allow_concurrency+ is set to _false_, which it is by default.
* +ActiveSupport::Cache::Strategy::LocalCache+ Serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread.
@@ -226,6 +229,8 @@ h4. Configuring Active Record
* +config.active_record.lock_optimistically+ controls whether ActiveRecord will use optimistic locking. By default this is +true+.
+* +config.active_record.whitelist_attributes+ will create an empty whitelist of attributes available for mass-assignment security for all models in your app.
+
The MySQL adapter adds one additional configuration option:
* +ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans+ controls whether ActiveRecord will consider all +tinyint(1)+ columns in a MySQL database to be booleans. By default this is +true+.
@@ -288,8 +293,6 @@ h4. Configuring Action View
There are only a few configuration options for Action View, starting with four on +ActionView::Base+:
-* +config.action_view.debug_rjs+ specifies whether RJS responses should be wrapped in a try/catch block that alerts the caught exception (and then re-raises it). The default is +false+.
-
* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is <tt>Proc.new{ |html_tag, instance| %Q(%&lt;div class=&quot;field_with_errors&quot;&gt;#{html_tag}&lt;/div&gt;).html_safe }</tt>
* +config.action_view.default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+.
diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile
index 4ba01a2a8f..cbc4acfeca 100644
--- a/railties/guides/source/contributing_to_ruby_on_rails.textile
+++ b/railties/guides/source/contributing_to_ruby_on_rails.textile
@@ -314,14 +314,20 @@ You should not be the only person who looks at the code before you submit it. Yo
You might also want to check out the "RailsBridge BugMash":http://wiki.railsbridge.org/projects/railsbridge/wiki/BugMash as a way to get involved in a group effort to improve Rails. This can help you get started and help check your code when you're writing your first patches.
+h4. Create a Lighthouse Ticket
+
+Now create a ticket for your patch. Go to the "new ticket":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/new page at Lighthouse. Fill in a reasonable title and description, as well as tag the ticket with the ‘patch’ tag and whatever other subject area tags make sense. Write down your ticket number, for you will need it in the following step.
+
h4. Commit Your Changes
When you're happy with the code on your computer, you need to commit the changes to git:
<shell>
-$ git commit -a -m "Here is a commit message"
+$ git commit -a -m "Here is a commit message [#ticket_number state:committed]"
</shell>
+NOTE: By adding '[#ticket_number state:committed]' at the end of your commit message, the ticket will automatically change its status to commited once your patch is pushed to the repository.
+
h4. Update master
It’s pretty likely that other changes to master have happened while you were working. Go get them:
@@ -361,30 +367,16 @@ $ git apply --check my_new_patch.diff
Please make sure the patch does not introduce whitespace errors:
<shell>
-$ git apply --whitespace=error-all mynew_patch.diff
-</shell>
-
-You can check your patches by applying your patch to an different dedicated branch:
-
-<shell>
-$ git checkout -b testing_branch
-$ git apply --check my_new_patch.diff
-</shell>
-
-You can make sure your patches don't add any whitespace by applying it yourself using the --whitespace=error-all option. Make sure you are on your dedicated test branche and:
-
-<shell>
-$ git apply --whitespace=error-all mynew_patch.diff
+$ git apply --whitespace=error-all my_new_patch.diff
</shell>
+h4. Attach your Patch to the Lighthouse Ticket
-h4. Create a Lighthouse Ticket
-
-Now create a ticket with your patch. Go to the "new ticket":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/new page at Lighthouse. Fill in a reasonable title and description, remember to attach your patch file, and tag the ticket with the ‘patch’ tag and whatever other subject area tags make sense.
+Now you need to update the ticket by attaching the patch file you just created.
h4. Get Some Feedback
-Now you need to get other people to look at your patch, just as you've looked at other people's patches. You can use the rubyonrails-core mailing list or the #rails-contrib channel on IRC freenode for this. You might also try just talking to Rails developers that you know.
+Now you need to get other people to look at your patch, just as you've looked at other people's patches. You can use the "rubyonrails-core mailing list":http://groups.google.com/group/rubyonrails-core/ or the #rails-contrib channel on IRC freenode for this. You might also try just talking to Rails developers that you know.
h4. Iterate as Necessary
@@ -398,6 +390,7 @@ All contributions, either via master or docrails, get credit in "Rails Contribut
h3. Changelog
+* April 14, 2001: Modified Contributing to the Rails Code section to add '[#ticket_number state:commited]' on patches commit messages by "Sebastian Martinez":http://wyeworks.com
* December 28, 2010: Complete revision by "Xavier Noria":credits.html#fxn
* April 6, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com
* August 1, 2009: Updates/amplifications by "Mike Gunderloy":credits.html#mgunderloy
diff --git a/railties/guides/source/debugging_rails_applications.textile b/railties/guides/source/debugging_rails_applications.textile
index 67e17056e5..477ff29dbd 100644
--- a/railties/guides/source/debugging_rails_applications.textile
+++ b/railties/guides/source/debugging_rails_applications.textile
@@ -96,26 +96,6 @@ Will be rendered as follows:
Title: Rails debugging guide
</pre>
-h4. Debugging RJS
-
-Rails has optional built-in support to debug RJS. When enabled, responses are wrapped in a try/catch block that displays the caught exception using +alert()+, and then re-raises it.
-
-The flag to enable RJS debugging in your configuration files is +config.action_view.debug_rjs+:
-
-<ruby>
-config.action_view.debug_rjs = true
-</ruby>
-
-or at any time setting +ActionView::Base.debug_rjs+:
-
-<ruby>
-ActionView::Base.debug_rjs = true
-</ruby>
-
-It is enabled by default in development mode, and disabled in the rest.
-
-TIP: For more information on debugging JavaScript, refer to "Firebug":http://getfirebug.com/, the popular debugger for Firefox.
-
h3. The Logger
It can also be useful to save information to log files at runtime. Rails maintains a separate log file for each runtime environment.
@@ -328,6 +308,41 @@ If you repeat the +list+ command, this time using just +l+, the next ten lines o
And so on until the end of the current file. When the end of file is reached, the +list+ command will start again from the beginning of the file and continue again up to the end, treating the file as a circular buffer.
+On the other hand, to see the previous ten lines you should type +list-+ (or +l-+)
+
+<shell>
+(rdb:7) l-
+[1, 10] in /PathToProject/posts_controller.rb
+ 1 class PostsController < ApplicationController
+ 2 # GET /posts
+ 3 # GET /posts.xml
+ 4 def index
+ 5 debugger
+ 6 @posts = Post.all
+ 7
+ 8 respond_to do |format|
+ 9 format.html # index.html.erb
+ 10 format.xml { render :xml => @posts }
+</shell>
+
+This way you can move inside the file, being able to see the code above and over the line you added the +debugger+.
+Finally, to see where you are in the code again you can type +list=+
+
+<shell>
+(rdb:7) list=
+[1, 10] in /PathToProject/posts_controller.rb
+ 1 class PostsController < ApplicationController
+ 2 # GET /posts
+ 3 # GET /posts.xml
+ 4 def index
+ 5 debugger
+=> 6 @posts = Post.all
+ 7
+ 8 respond_to do |format|
+ 9 format.html # index.html.erb
+ 10 format.xml { render :xml => @posts }
+</shell>
+
h4. The Context
When you start debugging your application, you will be placed in different contexts as you go through the different parts of the stack.
@@ -686,7 +701,7 @@ There are some Rails plugins to help you to find errors and debug your applicati
* "Query Trace":https://github.com/ntalbott/query_trace/tree/master: Adds query origin tracing to your logs.
* "Query Stats":https://github.com/dan-manges/query_stats/tree/master: A Rails plugin to track database queries.
* "Query Reviewer":http://code.google.com/p/query-reviewer/: This rails plugin not only runs "EXPLAIN" before each of your select queries in development, but provides a small DIV in the rendered output of each page with the summary of warnings for each query that it analyzed.
-* "Exception Notifier":https://github.com/rails/exception_notification/tree/master: Provides a mailer object and a default set of templates for sending email notifications when errors occur in a Rails application.
+* "Exception Notifier":https://github.com/smartinez87/exception_notification/tree/master: Provides a mailer object and a default set of templates for sending email notifications when errors occur in a Rails application.
* "Exception Logger":https://github.com/defunkt/exception_logger/tree/master: Logs your Rails exceptions in the database and provides a funky web interface to manage them.
h3. References
diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile
index 0cfc005f80..a63245acec 100644
--- a/railties/guides/source/form_helpers.textile
+++ b/railties/guides/source/form_helpers.textile
@@ -258,7 +258,7 @@ The name passed to +form_for+ controls the key used in +params+ to access the fo
The helper methods called on the form builder are identical to the model object helpers except that it is not necessary to specify which object is being edited since this is already managed by the form builder.
-You can create a similar binding without actually creating +&lt;form&gt;+ tags with the +fields_for+ helper. This is useful for editing additional model objects with the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for creating both like so:
+You can create a similar binding without actually creating +&lt;form&gt;+ tags with the +fields_for+ helper. This is useful for editing additional model objects with the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for creating both like so:
<erb>
<%= form_for @person, :url => { :action => "create" } do |person_form| %>
@@ -438,7 +438,7 @@ As with other helpers, if you were to use the +select+ helper on a form builder
<%= f.select(:city_id, ...) %>
</erb>
-WARNING: If you are using +select+ (or similar helpers such as +collection_select+, +select_tag+) to set a +belongs_to+ association you must pass the name of the foreign key (in the example above +city_id+), not the name of association itself. If you specify +city+ instead of +city_id+ Active Record will raise an error along the lines of <tt> ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) </tt> when you pass the +params+ hash to +Person.new+ or +update_attributes+. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. You may wish to consider the use of +attr_protected+ and +attr_accessible+. For further details on this, see the "Ruby On Rails Security Guide":security.html#_mass_assignment.
+WARNING: If you are using +select+ (or similar helpers such as +collection_select+, +select_tag+) to set a +belongs_to+ association you must pass the name of the foreign key (in the example above +city_id+), not the name of association itself. If you specify +city+ instead of +city_id+ Active Record will raise an error along the lines of <tt> ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) </tt> when you pass the +params+ hash to +Person.new+ or +update_attributes+. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. You may wish to consider the use of +attr_protected+ and +attr_accessible+. For further details on this, see the "Ruby On Rails Security Guide":security.html#_mass_assignment.
h4. Option Tags from a Collection of Arbitrary Objects
@@ -475,7 +475,7 @@ To leverage time zone support in Rails, you have to ask your users what time zon
There is also +time_zone_options_for_select+ helper for a more manual (therefore more customizable) way of doing this. Read the API documentation to learn about the possible arguments for these two methods.
-Rails _used_ to have a +country_select+ helper for choosing countries, but this has been extracted to the "country_select plugin":https://github.com/rails/country_select/tree/master. When using this, be aware that the exclusion or inclusion of certain names from the list can be somewhat controversial (and was the reason this functionality was extracted from Rails).
+Rails _used_ to have a +country_select+ helper for choosing countries, but this has been extracted to the "country_select plugin":https://github.com/chrislerum/country_select. When using this, be aware that the exclusion or inclusion of certain names from the list can be somewhat controversial (and was the reason this functionality was extracted from Rails).
h3. Using Date and Time Form Helpers
@@ -513,7 +513,7 @@ The +:prefix+ option is the key used to retrieve the hash of date components fro
h4(#select-model-object-helpers). Model Object Helpers
+select_date+ does not work well with forms that update or create Active Record objects as Active Record expects each element of the +params+ hash to correspond to one attribute.
-The model object helpers for dates and times submit parameters with special names, when Active Record sees parameters with such names it knows they must be combined with the other parameters and given to a constructor appropriate to the column type. For example:
+The model object helpers for dates and times submit parameters with special names, when Active Record sees parameters with such names it knows they must be combined with the other parameters and given to a constructor appropriate to the column type. For example:
<erb>
<%= date_select :person, :birth_date %>
diff --git a/railties/guides/source/generators.textile b/railties/guides/source/generators.textile
index d32ba48003..ac709968d9 100644
--- a/railties/guides/source/generators.textile
+++ b/railties/guides/source/generators.textile
@@ -190,7 +190,7 @@ $ rails generate scaffold User name:string
invoke test_unit
create test/unit/helpers/users_helper_test.rb
invoke stylesheets
- create public/stylesheets/scaffold.css
+ create app/assets/stylesheets/scaffold.css
</shell>
Looking at this output, it's easy to understand how generators work in Rails 3.0 and above. The scaffold generator doesn't actually generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication.
@@ -383,7 +383,7 @@ if yes?("Would you like to install Devise?")
end
</ruby>
-In the above template we specify that the application relies on the +rspec-rails+ and +cucumber-rails+ gem so these two will be added to the +test+ group in the +Gemfile+. Then we pose a question to the user about whether or not they would like to install Devise. If the user replies "y" or "yes" to this question, then the template will add Devise to the +Gemfile+ outside of any group and then runs the +devise:install+ generator. This template then takes the users input and runs the +devise+ generator, with the user's answer from the last question being passed to this generator.
+In the above template we specify that the application relies on the +rspec-rails+ and +cucumber-rails+ gem so these two will be added to the +test+ group in the +Gemfile+. Then we pose a question to the user about whether or not they would like to install Devise. If the user replies "y" or "yes" to this question, then the template will add Devise to the +Gemfile+ outside of any group and then runs the +devise:install+ generator. This template then takes the users input and runs the +devise+ generator, with the user's answer from the last question being passed to this generator.
Imagine that this template was in a file called +template.rb+. We can use it to modify the outcome of the +rails new+ command by using the +-m+ option and passing in the filename:
diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile
index e94bdc97b0..366df9cd84 100644
--- a/railties/guides/source/getting_started.textile
+++ b/railties/guides/source/getting_started.textile
@@ -22,7 +22,7 @@ TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails 3.
* The "RubyGems":http://rubyforge.org/frs/?group_id=126 packaging system
* A working installation of the "SQLite3 Database":http://www.sqlite.org
-Rails is a web application framework running on the Ruby programming language. If you have no prior experience with Ruby, you will find a very steep learning curve diving straight into Rails. There are some good free resources on the internet for learning Ruby, including:
+Rails is a web application framework running on the Ruby programming language. If you have no prior experience with Ruby, you will find a very steep learning curve diving straight into Rails. There are some good free resources on the internet for learning Ruby, including:
* "Mr. Neighborly's Humble Little Ruby Book":http://www.humblelittlerubybook.com
* "Programming Ruby":http://www.ruby-doc.org/docs/ProgrammingRuby/
@@ -78,7 +78,7 @@ Rails ships as many individual components.
h5. Action Pack
-Action Pack is a single gem that contains Action Controller, Action View and Action Dispatch. The "VC" part of "MVC".
+Action Pack is a single gem that contains Action Controller, Action View and Action Dispatch. The "VC" part of "MVC".
h5. Action Controller
@@ -98,7 +98,7 @@ Action Mailer is a framework for building e-mail services. You can use Action Ma
h5. Active Model
-Active Model provides a defined interface between the Action Pack gem services and Object Relationship Mapping gems such as Active Record. Active Model allows Rails to utilize other ORM frameworks in place of Active Record if your application needs this.
+Active Model provides a defined interface between the Action Pack gem services and Object Relationship Mapping gems such as Active Record. Active Model allows Rails to utilize other ORM frameworks in place of Active Record if your application needs this.
h5. Active Record
@@ -149,11 +149,11 @@ Usually run this as the root user:
# gem install rails
</shell>
-TIP. If you're working on Windows, you should be aware that the vast majority of Rails development is done in Unix environments. While Ruby and Rails themselves install easily using for example "Ruby Installer":http://rubyinstaller.org/, the supporting ecosystem often assumes you are able to build C-based rubygems and work in a command window. If at all possible, we suggest that you install a Linux virtual machine and use that for Rails development, instead of using Windows.
+TIP. If you're working on Windows, you can quickly install Ruby and Rails with "Rails Installer":http://railsinstaller.org.
h4. Creating the Blog Application
-The best way to use this guide is to follow each step as it happens, no code or step needed to make this example application has been left out, so you can literally follow along step by step. If you need to see the completed code, you can download it from "Getting Started Code":https://github.com/mikel/getting-started-code.
+The best way to use this guide is to follow each step as it happens, no code or step needed to make this example application has been left out, so you can literally follow along step by step. If you need to see the completed code, you can download it from "Getting Started Code":https://github.com/mikel/getting-started-code.
To begin, open a terminal, navigate to a folder where you have rights to create files, and type:
@@ -184,7 +184,7 @@ In any case, Rails will create a folder in your working directory called <tt>blo
|doc/|In-depth documentation for your application.|
|lib/|Extended modules for your application (not covered in this guide).|
|log/|Application log files.|
-|public/|The only folder seen to the world as-is. This is where your images, JavaScript files, stylesheets (CSS), and other static files go.|
+|public/|The only folder seen to the world as-is. This is where your images, JavaScript files, stylesheets (CSS), and other static files go.|
|script/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.|
|test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing.html|
|tmp/|Temporary files|
@@ -290,7 +290,7 @@ This will fire up an instance of the WEBrick web server by default (Rails can al
TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. In development mode, Rails does not generally require you to stop the server; changes you make in files will be automatically picked up by the server.
-The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. You can also click on the _About your application’s environment_ link to see a summary of your Application's environment.
+The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. You can also click on the _About your application’s environment_ link to see a summary of your Application's environment.
h4. Say "Hello", Rails
@@ -310,7 +310,7 @@ Rails will create several files for you, including +app/views/home/index.html.er
h4. Setting the Application Home Page
-Now that we have made the controller and view, we need to tell Rails when we want "Hello Rails" to show up. In our case, we want it to show up when we navigate to the root URL of our site, "http://localhost:3000":http://localhost:3000, instead of the "Welcome Aboard" smoke test.
+Now that we have made the controller and view, we need to tell Rails when we want "Hello Rails" to show up. In our case, we want it to show up when we navigate to the root URL of our site, "http://localhost:3000":http://localhost:3000, instead of the "Welcome Aboard" smoke test.
The first step to doing this is to delete the default page from your application:
@@ -368,7 +368,7 @@ The scaffold generator will build 15 files in your application, along with some
|test/functional/posts_controller_test.rb |Functional testing harness for the posts controller|
|test/unit/helpers/posts_helper_test.rb |Unit testing harness for the posts helper|
|config/routes.rb |Edited to include routing information for posts|
-|public/stylesheets/scaffold.css |Cascading style sheet to make the scaffolded views look better|
+|app/assets/stylesheets/scaffold.css.scss |Cascading style sheet to make the scaffolded views look better|
h4. Running a Migration
@@ -394,7 +394,7 @@ class CreatePosts < ActiveRecord::Migration
end
</ruby>
-The above migration creates two methods, +up+, called when you run this migration into the database, and +down+ in case you need to reverse the changes made by this migration at a later date. The +up+ command in this case creates a +posts+ table with two string columns and a text column. It also creates two timestamp fields to track record creation and updating. More information about Rails migrations can be found in the "Rails Database Migrations":migrations.html guide.
+The above migration creates two methods, +up+, called when you run this migration into the database, and +down+ in case you need to reverse the changes made by this migration at a later date. The +up+ command in this case creates a +posts+ table with two string columns and a text column. It also creates two timestamp fields to track record creation and updating. More information about Rails migrations can be found in the "Rails Database Migrations":migrations.html guide.
At this point, you can use a rake command to run the migration:
@@ -501,8 +501,8 @@ def index
@posts = Post.all
respond_to do |format|
- format.html # index.html.erb
- format.xml { render :xml => @posts }
+ format.html # index.html.erb
+ format.json { render :json => @posts }
end
end
</ruby>
@@ -511,7 +511,7 @@ end
TIP: For more information on finding records with Active Record, see "Active Record Query Interface":active_record_querying.html.
-The +respond_to+ block handles both HTML and XML calls to this action. If you browse to "http://localhost:3000/posts.xml":http://localhost:3000/posts.xml, you'll see all of the posts in XML format. The HTML format looks for a view in +app/views/posts/+ with a name that corresponds to the action name. Rails makes all of the instance variables from the action available to the view. Here's +app/views/posts/index.html.erb+:
+The +respond_to+ block handles both HTML and JSON calls to this action. If you browse to "http://localhost:3000/posts.json":http://localhost:3000/posts.json, you'll see a JSON containing all of the posts. The HTML format looks for a view in +app/views/posts/+ with a name that corresponds to the action name. Rails makes all of the instance variables from the action available to the view. Here's +app/views/posts/index.html.erb+:
<erb>
<h1>Listing posts</h1>
@@ -548,7 +548,7 @@ This view iterates over the contents of the +@posts+ array to display content an
* +link_to+ builds a hyperlink to a particular destination
* +edit_post_path+ and +new_post_path+ are helpers that Rails provides as part of RESTful routing. You'll see a variety of these helpers for the different actions that the controller includes.
-NOTE. In previous versions of Rails, you had to use +&lt;%=h post.name %&gt;+ so that any HTML would be escaped before being inserted into the page. In Rails 3.0, this is now the default. To get unescaped HTML, you now use +&lt;%= raw post.name %&gt;+.
+NOTE. In previous versions of Rails, you had to use +&lt;%=h post.name %&gt;+ so that any HTML would be escaped before being inserted into the page. In Rails 3.0, this is now the default. To get unescaped HTML, you now use +&lt;%= raw post.name %&gt;+.
TIP: For more details on the rendering process, see "Layouts and Rendering in Rails":layouts_and_rendering.html.
@@ -584,8 +584,8 @@ def new
@post = Post.new
respond_to do |format|
- format.html # new.html.erb
- format.xml { render :xml => @post }
+ format.html # new.html.erb
+ format.json { render :json => @post }
end
end
</ruby>
@@ -600,7 +600,7 @@ The +new.html.erb+ view displays this empty Post to the user:
<%= link_to 'Back', posts_path %>
</erb>
-The +&lt;%= render 'form' %&gt;+ line is our first introduction to _partials_ in Rails. A partial is a snippet of HTML and Ruby code that can be reused in multiple locations. In this case, the form used to make a new post, is basically identical to a form used to edit a post, both have text fields for the name and title and a text area for the content with a button to make a new post or update the existing post.
+The +&lt;%= render 'form' %&gt;+ line is our first introduction to _partials_ in Rails. A partial is a snippet of HTML and Ruby code that can be reused in multiple locations. In this case, the form used to make a new post, is basically identical to a form used to edit a post, both have text fields for the name and title and a text area for the content with a button to make a new post or update the existing post.
If you take a look at +views/posts/_form.html.erb+ file, you will see the following:
@@ -653,13 +653,13 @@ def create
respond_to do |format|
if @post.save
- format.html { redirect_to(@post,
+ format.html { redirect_to(@post,
:notice => 'Post was successfully created.') }
- format.xml { render :xml => @post,
+ format.json { render :json => @post,
:status => :created, :location => @post }
else
- format.html { render :action => "new" }
- format.xml { render :xml => @post.errors,
+ format.html { render :action => "new" }
+ format.json { render :json => @post.errors,
:status => :unprocessable_entity }
end
end
@@ -681,8 +681,8 @@ def show
@post = Post.find(params[:id])
respond_to do |format|
- format.html # show.html.erb
- format.xml { render :xml => @post }
+ format.html # show.html.erb
+ format.json { render :json => @post }
end
end
</ruby>
@@ -743,12 +743,12 @@ def update
respond_to do |format|
if @post.update_attributes(params[:post])
- format.html { redirect_to(@post,
+ format.html { redirect_to(@post,
:notice => 'Post was successfully updated.') }
- format.xml { head :ok }
+ format.json { render :json => {}, :status => :ok }
else
- format.html { render :action => "edit" }
- format.xml { render :xml => @post.errors,
+ format.html { render :action => "edit" }
+ format.json { render :json => @post.errors,
:status => :unprocessable_entity }
end
end
@@ -767,8 +767,8 @@ def destroy
@post.destroy
respond_to do |format|
- format.html { redirect_to(posts_url) }
- format.xml { head :ok }
+ format.html { redirect_to(posts_url) }
+ format.json { render :json => {}, :status => :ok }
end
end
</ruby>
@@ -873,7 +873,7 @@ TIP: For more information on Active Record associations, see the "Active Record
h4. Adding a Route for Comments
-As with the +home+ controller, we will need to add a route so that Rails knows where we would like to navigate to see +comments+. Open up the +config/routes.rb+ file again, you will see an entry that was added automatically for +posts+ near the top by the scaffold generator, +resources :posts+, edit it as follows:
+As with the +home+ controller, we will need to add a route so that Rails knows where we would like to navigate to see +comments+. Open up the +config/routes.rb+ file again, you will see an entry that was added automatically for +posts+ near the top by the scaffold generator, +resources :posts+, edit it as follows:
<ruby>
resources :posts do
@@ -901,7 +901,7 @@ This creates four files and one empty directory:
* +test/unit/helpers/comments_helper_test.rb+ - The unit tests for the helper
* +app/views/comments/+ - Views of the controller are stored here
-Like with any blog, our readers will create their comments directly after reading the post, and once they have added their comment, will be sent back to the post show page to see their comment now listed. Due to this, our +CommentsController+ is there to provide a method to create comments and delete SPAM comments when they arrive.
+Like with any blog, our readers will create their comments directly after reading the post, and once they have added their comment, will be sent back to the post show page to see their comment now listed. Due to this, our +CommentsController+ is there to provide a method to create comments and delete SPAM comments when they arrive.
So first, we'll wire up the Post show template (+/app/views/posts/show.html.erb+) to let us make a new comment:
@@ -1078,7 +1078,7 @@ Then in the +app/views/posts/show.html.erb+ you can change it to look like the f
<%= link_to 'Back to Posts', posts_path %> |
</erb>
-This will now render the partial in +app/views/comments/_comment.html.erb+ once for each comment that is in the +@post.comments+ collection. As the +render+ method iterates over the <tt>@post.comments</tt> collection, it assigns each comment to a local variable named the same as the partial, in this case +comment+ which is then available in the partial for us to show.
+This will now render the partial in +app/views/comments/_comment.html.erb+ once for each comment that is in the +@post.comments+ collection. As the +render+ method iterates over the <tt>@post.comments</tt> collection, it assigns each comment to a local variable named the same as the partial, in this case +comment+ which is then available in the partial for us to show.
h4. Rendering a Partial Form
@@ -1138,7 +1138,7 @@ The +@post+ object is available to any partials rendered in the view because we
h3. Deleting Comments
-Another important feature on a blog is being able to delete SPAM comments. To do this, we need to implement a link of some sort in the view and a +DELETE+ action in the +CommentsController+.
+Another important feature on a blog is being able to delete SPAM comments. To do this, we need to implement a link of some sort in the view and a +DELETE+ action in the +CommentsController+.
So first, let's add the delete link in the +app/views/comments/_comment.html.erb+ partial:
@@ -1201,36 +1201,19 @@ h3. Security
If you were to publish your blog online, anybody would be able to add, edit and delete posts or delete comments.
-Rails provides a very simple HTTP authentication system that will work nicely in this situation. First, we enable simple HTTP based authentication in our <tt>app/controllers/application_controller.rb</tt>:
+Rails provides a very simple HTTP authentication system that will work nicely in this situation.
-<ruby>
-class ApplicationController < ActionController::Base
- protect_from_forgery
-
- private
-
- def authenticate
- authenticate_or_request_with_http_basic do |user_name, password|
- user_name == 'admin' && password == 'password'
- end
- end
-
-end
-</ruby>
-
-You can obviously change the username and password to whatever you want. We put this method inside of +ApplicationController+ so that it is available to all of our controllers.
-
-Then in the +PostsController+ we need to have a way to block access to the various actions if the person is not authenticated, here we can use the Rails <tt>before_filter</tt> method, which allows us to specify that Rails must run a method and only then allow access to the requested action if that method allows it.
+In the +PostsController+ we need to have a way to block access to the various actions if the person is not authenticated, here we can use the Rails <tt>http_basic_authenticate_with</tt> method, allowing access to the requested action if that method allows it.
-To use the before filter, we specify it at the top of our +PostsController+, in this case, we want the user to be authenticated on every action, except for +index+ and +show+, so we write that:
+To use the authentication system, we specify it at the top of our +PostsController+, in this case, we want the user to be authenticated on every action, except for +index+ and +show+, so we write that:
<ruby>
class PostsController < ApplicationController
- before_filter :authenticate, :except => [:index, :show]
+ http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index
# GET /posts
- # GET /posts.xml
+ # GET /posts.json
def index
@posts = Post.all
respond_to do |format|
@@ -1242,7 +1225,7 @@ We also only want to allow authenticated users to delete comments, so in the +Co
<ruby>
class CommentsController < ApplicationController
- before_filter :authenticate, :only => :destroy
+ http_basic_authenticate_with :name => "dhh", :password => "secret", :only => :destroy
def create
@post = Post.find(params[:post_id])
@@ -1329,7 +1312,7 @@ Note that we have changed the +f+ in +form_for(@post) do |f|+ to +post_form+ to
This example shows another option of the render helper, being able to pass in local variables, in this case, we want the local variable +form+ in the partial to refer to the +post_form+ object.
-We also add a <tt>@post.tags.build</tt> at the top of this form, this is to make sure there is a new tag ready to have it's name filled in by the user. If you do not build the new tag, then the form will not appear as there is no new Tag object ready to create.
+We also add a <tt>@post.tags.build</tt> at the top of this form, this is to make sure there is a new tag ready to have it's name filled in by the user. If you do not build the new tag, then the form will not appear as there is no new Tag object ready to create.
Now create the folder <tt>app/views/tags</tt> and make a file in there called <tt>_form.html.erb</tt> which contains the form for the tag:
@@ -1390,7 +1373,7 @@ However, that method call <tt>@post.tags.map { |t| t.name }.join(", ")</tt> is a
h3. View Helpers
-View Helpers live in <tt>app/helpers</tt> and provide small snippets of reusable code for views. In our case, we want a method that strings a bunch of objects together using their name attribute and joining them with a comma. As this is for the Post show template, we put it in the PostsHelper.
+View Helpers live in <tt>app/helpers</tt> and provide small snippets of reusable code for views. In our case, we want a method that strings a bunch of objects together using their name attribute and joining them with a comma. As this is for the Post show template, we put it in the PostsHelper.
Open up <tt>app/helpers/posts_helper.rb</tt> and add the following:
@@ -1475,6 +1458,7 @@ Two very common sources of data that are not UTF-8:
h3. Changelog
+* April 11, 2011: Changed scaffold_controller generator to create format block for JSON instead of XML "Sebastian Martinez":http://www.wyeworks.com
* August 30, 2010: Minor editing after Rails 3 release by "Joost Baaij":http://www.spacebabies.nl
* July 12, 2010: Fixes, editing and updating of code samples by "Jaime Iniesta":http://jaimeiniesta.com
* May 16, 2010: Added a section on configuration gotchas to address common encoding problems that people might have by "Yehuda Katz":http://www.yehudakatz.com
diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile
index 3e7e396e8d..608643b3d3 100644
--- a/railties/guides/source/i18n.textile
+++ b/railties/guides/source/i18n.textile
@@ -91,7 +91,7 @@ This means, that in the +:en+ locale, the key _hello_ will map to the _Hello wor
The I18n library will use *English* as a *default locale*, i.e. if you don't set a different locale, +:en+ will be used for looking up translations.
-NOTE: The i18n library takes a *pragmatic approach* to locale keys (after "some discussion":http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), including only the _locale_ ("language") part, like +:en+, +:pl+, not the _region_ part, like +:en-US+ or +:en-UK+, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as +:cz+, +:th+ or +:es+ (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the +:en-US+ locale you would have $ as a currency symbol, while in +:en-UK+, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a +:en-UK+ dictionary. Various "Rails I18n plugins":http://rails-i18n.org/wiki such as "Globalize2":https://github.com/joshmh/globalize2/tree/master may help you implement it.
+NOTE: The i18n library takes a *pragmatic approach* to locale keys (after "some discussion":http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), including only the _locale_ ("language") part, like +:en+, +:pl+, not the _region_ part, like +:en-US+ or +:en-UK+, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as +:cz+, +:th+ or +:es+ (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the +:en-US+ locale you would have $ as a currency symbol, while in +:en-UK+, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a +:en-UK+ dictionary. Various "Rails I18n plugins":http://rails-i18n.org/wiki such as "Globalize2":https://github.com/joshmh/globalize2/tree/master may help you implement it.
The *translations load path* (+I18n.load_path+) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you.
@@ -133,9 +133,9 @@ The _setting part_ is easy. You can set the locale in a +before_filter+ in the +
<ruby>
before_filter :set_locale
+
def set_locale
- # if params[:locale] is nil then I18n.default_locale will be used
- I18n.locale = params[:locale]
+ I18n.locale = params[:locale] || I18n.default_locale
end
</ruby>
@@ -158,7 +158,7 @@ You can implement it like this in your +ApplicationController+:
before_filter :set_locale
def set_locale
- I18n.locale = extract_locale_from_tld
+ I18n.locale = extract_locale_from_tld || I18n.default_locale
end
# Get locale from top-level domain or return nil if such locale is not available
@@ -182,7 +182,7 @@ We can also set the locale from the _subdomain_ in a very similar way:
# in your /etc/hosts file to try this out locally
def extract_locale_from_subdomain
parsed_locale = request.subdomains.first
- I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil
+ I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil
end
</ruby>
@@ -441,7 +441,7 @@ Do check the "Rails i18n Wiki":http://rails-i18n.org/wiki for list of tools avai
h3. Overview of the I18n API Features
-You should have good understanding of using the i18n library now, knowing all necessary aspects of internationalizing a basic Rails application. In the following chapters, we'll cover it's features in more depth.
+You should have good understanding of using the i18n library now, knowing all necessary aspects of internationalizing a basic Rails application. In the following chapters, we'll cover it's features in more depth.
Covered are features like these:
@@ -520,7 +520,7 @@ I18n.t 'activerecord.errors.messages'
h5. "Lazy" Lookup
-Rails 2.3 implements a convenient way to look up the locale inside _views_. When you have the following dictionary:
+Rails implements a convenient way to look up the locale inside _views_. When you have the following dictionary:
<yaml>
es:
@@ -863,7 +863,7 @@ If you find anything missing or wrong in this guide, please file a ticket on our
h3. Contributing to Rails I18n
-I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in plugins and real applications first, and only then cherry-picking the best-of-bread of most widely useful features for inclusion in the core.
+I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in plugins and real applications first, and only then cherry-picking the best-of-breed of most widely useful features for inclusion in the core.
Thus we encourage everybody to experiment with new ideas and features in plugins or other libraries and make them available to the community. (Don't forget to announce your work on our "mailing list":http://groups.google.com/group/rails-i18n!)
diff --git a/railties/guides/source/index.html.erb b/railties/guides/source/index.html.erb
index af46beee56..b48488d8a2 100644
--- a/railties/guides/source/index.html.erb
+++ b/railties/guides/source/index.html.erb
@@ -17,7 +17,7 @@ Ruby on Rails Guides
<% else %>
<p>
These are the new guides for Rails 3. The guides for Rails 2.3 are still available
- at <a href="http://guides.rubyonrails.org/v2.3.8/">http://guides.rubyonrails.org/v2.3.8/</a>.
+ at <a href="http://guides.rubyonrails.org/v2.3.11/">http://guides.rubyonrails.org/v2.3.11/</a>.
</p>
<% end %>
<p>
diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile
index 0cbbe1f389..638830cd83 100644
--- a/railties/guides/source/initialization.textile
+++ b/railties/guides/source/initialization.textile
@@ -468,7 +468,7 @@ h4. +config/application.rb+
This file requires +config/boot.rb+, but only if it hasn't been required before, which would be the case in +rails server+ but *wouldn't* be the case with Passenger.
-Then the fun begins!
+Then the fun begins!
h3. Loading Rails
@@ -478,8 +478,7 @@ The next line in +config/application.rb+ is:
require 'rails/all'
</ruby>
-h4 +railties/lib/rails/all.rb+
-
+h4. +railties/lib/rails/all.rb+
This file is responsible for requiring all the individual parts of Rails like so:
@@ -591,9 +590,13 @@ h4. +activesupport/lib/active_support/deprecation/behaviors.rb+
This file defines the behavior of the +ActiveSupport::Deprecation+ module, setting up the +DEFAULT_BEHAVIORS+ hash constant which contains the three defaults to outputting deprecation warnings: +:stderr+, +:log+ and +:notify+. This file begins by requiring +activesupport/notifications+ and +activesupport/core_ext/array/wrap+.
-h4 +activesupport/lib/active_support/notifications.rb+
+h4. +activesupport/lib/active_support/notifications.rb+
+
+This file defines the +ActiveSupport::Notifications+ module. Notifications provides an instrumentation API for Ruby, shipping with a queue implementation that consumes and publish events to log subscribers in a thread.
+
+The "API documentation":http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html for +ActiveSupport::Notifications+ explains the usage of this module, including the methods that it defines.
-TODO: document +ActiveSupport::Notifications+.
+The file required in +active_support/notifications.rb+ is +active_support/core_ext/module/delegation+ which is documented in the "Active Support Core Extensions Guide":http://guides.rubyonrails.org/active_support_core_extensions.html#method-delegation.
h4. +activesupport/core_ext/array/wrap+
@@ -661,7 +664,7 @@ The +active_support/inflector/methods+ file has already been required by +active
h4. +activesupport/lib/active_support/inflector/inflections.rb+
-This file references the +ActiveSupport::Inflector+ constant which isn't loaded by this point. But there were autoloads set up in +activesupport/lib/active_support.rb+ which will load the file which loads this constant and so then it will be defined. Then this file defines pluralization and singularization rules for words in Rails. This is how Rails knows how to pluralize "tomato" to "tomatoes".
+This file references the +ActiveSupport::Inflector+ constant which isn't loaded by this point. But there were autoloads set up in +activesupport/lib/active_support.rb+ which will load the file which loads this constant and so then it will be defined. Then this file defines pluralization and singularization rules for words in Rails. This is how Rails knows how to pluralize "tomato" to "tomatoes".
h4. +activesupport/lib/active_support/inflector/transliterate.rb+
@@ -695,7 +698,7 @@ h4. Back to +railties/lib/rails/plugin.rb+
The next file required in this is a core extension from Active Support called +array/conversions+ which is covered in "this section":http://guides.rubyonrails.org/active_support_core_extensions.html#array-conversions of the Active Support Core Extensions Guide.
-Once that file has finished loading, the +Rails::Plugin+ class is defined.
+Once that file has finished loading, the +Rails::Plugin+ class is defined.
h4. Back to +railties/lib/rails/application.rb+
@@ -705,7 +708,7 @@ Once this file's done then we go back to the +railties/lib/rails.rb+ file, which
h4. +railties/lib/rails/version.rb+
-Much like +active_support/version+, this file defines the +VERSION+ constant which has a +STRING+ constant on it which returns the current version of Rails.
+Much like +active_support/version+, this file defines the +VERSION+ constant which has a +STRING+ constant on it which returns the current version of Rails.
Once this file has finished loading we go back to +railties/lib/rails.rb+ which then requires +active_support/railtie.rb+.
@@ -923,7 +926,7 @@ This file defines the +ActionDispatch::Railtie+ class, but not before requiring
h4. +activesupport/lib/action_dispatch.rb+
-This file attempts to locate the +active_support+ and +active_model+ libraries by looking a couple of directories back from the current file and then adds the +active_support+ and +active_model+ +lib+ directories to the load path, but only if they aren't already, which they are.
+This file attempts to locate the +active_support+ and +active_model+ libraries by looking a couple of directories back from the current file and then adds the +active_support+ and +active_model+ +lib+ directories to the load path, but only if they aren't already, which they are.
<ruby>
activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
@@ -1006,7 +1009,7 @@ The loading of this file finishes the loading of +active_model+ and so we go bac
h4. Back to +activesupport/lib/action_dispatch.rb+
-The remainder of this file requires the +rack+ file from the Rack gem which defines the +Rack+ module. After +rack+, there's autoloads defined for the +Rack+, +ActionDispatch+, +ActionDispatch::Http+, +ActionDispatch::Session+. A new method called +autoload_under+ is used here, and this simply prefixes the files where the modules are autoloaded from with the path specified. For example here:
+The remainder of this file requires the +rack+ file from the Rack gem which defines the +Rack+ module. After +rack+, there's autoloads defined for the +Rack+, +ActionDispatch+, +ActionDispatch::Http+, +ActionDispatch::Session+. A new method called +autoload_under+ is used here, and this simply prefixes the files where the modules are autoloaded from with the path specified. For example here:
<ruby>
autoload_under 'testing' do
@@ -1109,4 +1112,4 @@ However the require after these is to a file that hasn't yet been loaded, +actio
h4. +actionpack/lib/action_view.rb+
-+action_view.rb+
++action_view.rb+
diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb
index f2681c6461..911655e0f4 100644
--- a/railties/guides/source/layout.html.erb
+++ b/railties/guides/source/layout.html.erb
@@ -111,7 +111,7 @@
<h3>Feedback</h3>
<p>
- You're encouraged to help in keeping the quality of this guide.
+ You're encouraged to help improve the quality of this guide.
</p>
<p>
If you see any typos or factual errors you are confident to
diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile
index 1548da0eb5..620df970dc 100644
--- a/railties/guides/source/layouts_and_rendering.textile
+++ b/railties/guides/source/layouts_and_rendering.textile
@@ -90,7 +90,7 @@ If we want to display the properties of all the books in our view, we can do so
<%= link_to 'New book', new_book_path %>
</ruby>
-NOTE: The actual rendering is done by subclasses of +ActionView::TemplateHandlers+. This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. In Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), +.rjs+ for RJS (JavaScript with embedded ruby) and +.builder+ for Builder (XML generator).
+NOTE: The actual rendering is done by subclasses of +ActionView::TemplateHandlers+. This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. In Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), and +.builder+ for Builder (XML generator).
h4. Using +render+
@@ -250,18 +250,6 @@ render :inline =>
"xml.p {'Horrid coding practice!'}", :type => :builder
</ruby>
-h5. Using +render+ with +:update+
-
-You can also render JavaScript-based page updates inline using the +:update+ option to +render+:
-
-<ruby>
-render :update do |page|
- page.replace_html 'warning', "Invalid options supplied"
-end
-</ruby>
-
-WARNING: Placing JavaScript updates in your controller may seem to streamline small updates, but it defeats the MVC orientation of Rails and will make it harder for other developers to follow the logic of your project. We recommend using a separate RJS template instead, no matter how small the update.
-
h5. Rendering Text
You can send plain text - with no markup at all - back to the browser by using the +:text+ option to +render+:
@@ -296,7 +284,7 @@ TIP: You don't need to call +to_xml+ on the object that you want to render. If y
h5. Rendering Vanilla JavaScript
-Rails can render vanilla JavaScript (as an alternative to using +update+ with an +.rjs+ file):
+Rails can render vanilla JavaScript:
<ruby>
render :js => "alert('Hello Rails');"
@@ -406,7 +394,7 @@ end
Now, if the current user is a special user, they'll get a special layout when viewing a product. You can even use an inline method to determine the layout:
-You can also decide the layout by passing a Proc object, the block you give the Proc will be given the +controller+ instance, so you can make decisions based on the current request. For example:
+You can also decide the layout by passing a Proc object, the block you give the Proc will be given the +controller+ instance, so you can make decisions based on the current request. For example:
<ruby>
class ProductsController < ApplicationController
@@ -584,7 +572,7 @@ With this code, the browser will make a fresh request for the index page, the co
The only downside to this code, is that it requires a round trip to the browser, the browser requested the show action with +/books/1+ and the controller finds that there are no books, so the controller sends out a 302 redirect response to the browser telling it to go to +/books/+, the browser complies and sends a new request back to the controller asking now for the +index+ action, the controller then gets all the books in the database and renders the index template, sending it back down to the browser which then shows it on your screen.
-While in a small app, this added latency might not be a problem, it is something to think about when speed of response is of the essence. One way to handle this double request (though a contrived example) could be:
+While in a small app, this added latency might not be a problem, it is something to think about when speed of response is of the essence. One way to handle this double request (though a contrived example) could be:
<ruby>
def index
@@ -707,18 +695,28 @@ To include +http://example.com/main.js+:
<%= javascript_include_tag "http://example.com/main.js" %>
</erb>
-The +defaults+ option loads the Prototype and Scriptaculous libraries:
+The +:defaults+ option loads jQuery by default:
<erb>
<%= javascript_include_tag :defaults %>
</erb>
-The +all+ option loads every JavaScript file in +public/javascripts+, starting with the Prototype and Scriptaculous libraries:
+If the application was generated with "-j prototype" <tt>:defaults</tt> loads Prototype and Scriptaculous. And you can in any case override the expansion in <tt>config/application.rb</tt>:
+
+<ruby>
+config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js)
+</ruby>
+
+When using <tt>:defaults</tt>, if an <tt>application.js</tt> file exists in <tt>public/javascripts</tt> it will be included as well at then end.
+
+The +:all+ option loads every JavaScript file in +public/javascripts+:
<erb>
<%= javascript_include_tag :all %>
</erb>
+Note that your defaults of choice will be included first, so they will be available to all subsequently included files.
+
You can supply the +:recursive+ option to load files in subfolders of +public/javascripts+ as well:
<erb>
@@ -862,7 +860,7 @@ Produces
<video src="/videos/movie.ogg" />
</erb>
-Like an +image_tag+ you can supply a path, either absolute, or relative to the +public/videos+ directory. Additionally you can specify the +:size => "#{width}x#{height}"+ option just like an +image_tag+. Video tags can also have any of the HTML options specified at the end (+id+, +class+ et al).
+Like an +image_tag+ you can supply a path, either absolute, or relative to the +public/videos+ directory. Additionally you can specify the +:size => "#{width}x#{height}"+ option just like an +image_tag+. Video tags can also have any of the HTML options specified at the end (+id+, +class+ et al).
The video tag also supports all of the +&lt;video&gt;+ HTML options through the HTML options hash, including:
@@ -1135,7 +1133,7 @@ You can also pass in arbitrary local variables to any partial you are rendering
Would render a partial +_products.html.erb+ once for each instance of +product+ in the +@products+ instance variable passing the instance to the partial as a local variable called +item+ and to each partial, make the local variable +title+ available with the value +Products Page+.
-TIP: Rails also makes a counter variable available within a partial called by the collection, named after the member of the collection followed by +_counter+. For example, if you're rendering +@products+, within the partial you can refer to +product_counter+ to tell you how many times the partial has been rendered. This does not work in conjunction with the +:as => :value+ option.
+TIP: Rails also makes a counter variable available within a partial called by the collection, named after the member of the collection followed by +_counter+. For example, if you're rendering +@products+, within the partial you can refer to +product_counter+ to tell you how many times the partial has been rendered. This does not work in conjunction with the +:as => :value+ option.
You can also specify a second partial to be rendered between instances of the main partial by using the +:spacer_template+ option:
diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile
index 57e6bcd123..bf5d4d3e4d 100644
--- a/railties/guides/source/migrations.textile
+++ b/railties/guides/source/migrations.textile
@@ -284,7 +284,7 @@ change_table :products do |t|
t.rename :upccode, :upc_code
end
</ruby>
-removes the +description+ and +name+ columns, creates a +part_number+ column and adds an index on it. Finally it renames the +upccode+ column. This is the same as doing
+removes the +description+ and +name+ columns, creates a +part_number+ column and adds an index on it. Finally it renames the +upccode+ column. This is the same as doing
<ruby>
remove_column :products, :description
@@ -335,7 +335,7 @@ NOTE: The +references+ helper does not actually create foreign key constraints f
If the helpers provided by Active Record aren't enough you can use the +execute+ function to execute arbitrary SQL.
-For more details and examples of individual methods check the API documentation, in particular the documentation for "<tt>ActiveRecord::ConnectionAdapters::SchemaStatements</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html (which provides the methods available in the +up+ and +down+ methods), "<tt>ActiveRecord::ConnectionAdapters::TableDefinition</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html (which provides the methods available on the object yielded by +create_table+) and "<tt>ActiveRecord::ConnectionAdapters::Table</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html (which provides the methods available on the object yielded by +change_table+).
+For more details and examples of individual methods check the API documentation, in particular the documentation for "<tt>ActiveRecord::ConnectionAdapters::SchemaStatements</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html (which provides the methods available in the +up+ and +down+ methods), "<tt>ActiveRecord::ConnectionAdapters::TableDefinition</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html (which provides the methods available on the object yielded by +create_table+) and "<tt>ActiveRecord::ConnectionAdapters::Table</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html (which provides the methods available on the object yielded by +change_table+).
h4. Writing Your +down+ Method
diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile
index daca50ee9e..d486e8ade3 100644
--- a/railties/guides/source/plugins.textile
+++ b/railties/guides/source/plugins.textile
@@ -17,8 +17,8 @@ This guide describes how to build a test-driven plugin that will:
* Add methods to ActiveRecord::Base in the tradition of the 'acts_as' plugins
* Give you information about where to put generators in your plugin.
-For the purpose of this guide pretend for a moment that you are an avid bird watcher.
-Your favorite bird is the Yaffle, and you want to create a plugin that allows other developers to share in the Yaffle
+For the purpose of this guide pretend for a moment that you are an avid bird watcher.
+Your favorite bird is the Yaffle, and you want to create a plugin that allows other developers to share in the Yaffle
goodness.
endprologue.
@@ -27,27 +27,27 @@ h3. Setup
h4. Generating the Plugin Skeleton
-Rails currently ships with a generator to generate a plugin within a Rails application. Help text is available that will explain
+Rails currently ships with a generator to generate a plugin within a Rails application. Help text is available that will explain
how this generator works.
<shell>
$ rails generate plugin --help
</shell>
-This generator places the plugin into the vendor/plugins directory.
+This generator places the plugin into the vendor/plugins directory.
-Vendored plugins are useful for quickly prototyping your plugin but current thinking in the Rails community is shifting towards
+Vendored plugins are useful for quickly prototyping your plugin but current thinking in the Rails community is shifting towards
packaging plugins as gems, especially with the inclusion of Bundler as the Rails dependency manager.
Packaging a plugin as a gem may be overkill for any plugins that will not be shared across projects but doing so from the start makes it easier to share the plugin going forward without adding too much additional overhead during development.
Rails 3.1 will ship with a plugin generator that will default to setting up a plugin
-as a gem. This tutorial will begin to bridge that gap by demonstrating how to create a gem based plugin using the
+as a gem. This tutorial will begin to bridge that gap by demonstrating how to create a gem based plugin using the
"Enginex gem":http://www.github.com/josevalim/enginex.
<shell>
- gem install enginex
- enginex --help
- enginex yaffle
+$ gem install enginex
+$ enginex --help
+$ enginex yaffle
</shell>
This command will create a new directory named "yaffle" within the current directory.
@@ -69,7 +69,7 @@ h3. Extending Core Classes
This section will explain how to add a method to String that will be available anywhere in your rails application.
-In this example you will add a method to String named +to_squawk+. To begin, create a new test file with a few assertions:
+In this example you will add a method to String named +to_squawk+. To begin, create a new test file with a few assertions:
<ruby>
# yaffle/test/core_ext_test.rb
@@ -83,7 +83,7 @@ class CoreExtTest < Test::Unit::TestCase
end
</ruby>
-Run +rake+ to run the test. This test should fail because we haven't implemented the +to_squak+ method:
+Run +rake+ to run the test. This test should fail because we haven't implemented the +to_squak+ method:
<shell>
1) Error:
@@ -133,7 +133,7 @@ $ rails console
h3. Add an "acts_as" Method to Active Record
-A common pattern in plugins is to add a method called 'acts_as_something' to models. In this case, you
+A common pattern in plugins is to add a method called 'acts_as_something' to models. In this case, you
want to write a method called 'acts_as_yaffle' that adds a 'squawk' method to your Active Record models.
To begin, set up your files so that you have:
@@ -169,9 +169,9 @@ end
h4. Add a Class Method
-This plugin will expect that you've added a method to your model named 'last_squawk'. However, the
-plugin users might have already defined a method on their model named 'last_squawk' that they use
-for something else. This plugin will allow the name to be changed by adding a class method called 'yaffle_text_field'.
+This plugin will expect that you've added a method to your model named 'last_squawk'. However, the
+plugin users might have already defined a method on their model named 'last_squawk' that they use
+for something else. This plugin will allow the name to be changed by adding a class method called 'yaffle_text_field'.
To start out, write a failing test that shows the behavior you'd like:
@@ -210,7 +210,7 @@ When you run +rake+, you should see the following:
</shell>
This tells us that we don't have the necessary models (Hickwall and Wickwall) that we are trying to test.
-We can easily generate these models in our "dummy" Rails application by running the following commands from the
+We can easily generate these models in our "dummy" Rails application by running the following commands from the
test/dummy directory:
<shell>
@@ -220,7 +220,7 @@ $ rails generate model Wickwall last_squak:string last_tweet:string
</shell>
Now you can create the necessary database tables in your testing database by navigating to your dummy app
-and migrating the database. First
+and migrating the database. First
<shell>
$ cd test/dummy
@@ -319,7 +319,7 @@ When you run +rake+ you should see the tests all pass:
h4. Add an Instance Method
-This plugin will add a method named 'squawk' to any Active Record objects that call 'acts_as_yaffle'. The 'squawk'
+This plugin will add a method named 'squawk' to any Active Record objects that call 'acts_as_yaffle'. The 'squawk'
method will simply set the value of one of the fields in the database.
To start out, write a failing test that shows the behavior you'd like:
@@ -352,7 +352,7 @@ class ActsAsYaffleTest < Test::Unit::TestCase
end
</ruby>
-Run the test to make sure the last two tests fail the an error that contains "NoMethodError: undefined method `squawk'",
+Run the test to make sure the last two tests fail the an error that contains "NoMethodError: undefined method `squawk'",
then update 'acts_as_yaffle.rb' to look like this:
<ruby>
@@ -387,21 +387,23 @@ Run +rake+ one final time and you should see:
7 tests, 7 assertions, 0 failures, 0 errors, 0 skips
</shell>
-NOTE: The use of +write_attribute+ to write to the field in model is just one example of how a plugin can
-interact with the model, and will not always be the right method to use. For example, you could also
+NOTE: The use of +write_attribute+ to write to the field in model is just one example of how a plugin can
+interact with the model, and will not always be the right method to use. For example, you could also
use +send("#{self.class.yaffle_text_field}=", string.to_squawk)+.
h3. Generators
-Generators can be included in your gem simply by creating them in a lib/generators directory of your plugin. More information about
+Generators can be included in your gem simply by creating them in a lib/generators directory of your plugin. More information about
the creation of generators can be found in the "Generators Guide":generators.html
h3. Publishing your Gem
-Gem plugins in progress can be easily be shared from any Git repository. To share the Yaffle gem with others, simply
+Gem plugins in progress can be easily be shared from any Git repository. To share the Yaffle gem with others, simply
commit the code to a Git repository (like Github) and add a line to the Gemfile of the any application:
-gem 'yaffle', :git => 'git://github.com/yaffle_watcher/yaffle.git'
+<ruby>
+gem 'yaffle', :git => 'git://github.com/yaffle_watcher/yaffle.git'
+</ruby>
After running +bundle install+, your gem functionality will be available to the application.
@@ -410,7 +412,7 @@ For more information about publishing gems to RubyGems, see: "http://blog.thepet
h3. Non-Gem Plugins
-Non-gem plugins are useful for functionality that won't be shared with another project. Keeping your custom functionality in the
+Non-gem plugins are useful for functionality that won't be shared with another project. Keeping your custom functionality in the
vendor/plugins directory un-clutters the rest of the application.
Move the directory that you created for the gem based plugin into the vendor/plugins directory of a generated Rails application, create a vendor/plugins/yaffle/init.rb file that contains "require 'yaffle'" and everything will still work.
@@ -421,7 +423,7 @@ Move the directory that you created for the gem based plugin into the vendor/plu
require 'yaffle'
</ruby>
-You can test this by changing to the Rails application that you added the plugin to and starting a rails console. Once in the
+You can test this by changing to the Rails application that you added the plugin to and starting a rails console. Once in the
console we can check to see if the String has an instance method of to_squawk.
<shell>
$ cd my_app
@@ -433,7 +435,7 @@ You can also remove the .gemspec, Gemfile and Gemfile.lock files as they will no
h3. RDoc Documentation
-Once your plugin is stable and you are ready to deploy do everyone else a favor and document it! Luckily, writing documentation for your plugin is easy.
+Once your plugin is stable and you are ready to deploy do everyone else a favor and document it! Luckily, writing documentation for your plugin is easy.
The first step is to update the README file with detailed information about how to use your plugin. A few key things to include are:
@@ -450,8 +452,6 @@ Once your comments are good to go, navigate to your plugin directory and run:
$ rake rdoc
</shell>
-!!!!!!!!!!!!!! Make sure these still make sense. Add any references that you see fit. !!!!!!!!!!!!!
-
h4. References
* "Developing a RubyGem using Bundler":https://github.com/radar/guides/blob/master/gem-development.md
@@ -462,6 +462,7 @@ h4. References
h3. Changelog
+* March 10, 2011: Minor formatting tweaks.
* February 13, 2011: Get guide in synch with Rails 3.0.3. Remove information not compatible with Rails 3. Send reader elsewhere
for information that is covered elsewhere.
* April 4, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com
diff --git a/railties/guides/source/rails_application_templates.textile b/railties/guides/source/rails_application_templates.textile
index 8e51f9e23b..388d8eea3e 100644
--- a/railties/guides/source/rails_application_templates.textile
+++ b/railties/guides/source/rails_application_templates.textile
@@ -11,19 +11,19 @@ endprologue.
h3. Usage
-To apply a template, you need to provide the Rails generator with the location of the template you wish to apply, using -m option :
+To apply a template, you need to provide the Rails generator with the location of the template you wish to apply, using -m option:
<shell>
$ rails new blog -m ~/template.rb
</shell>
-It's also possible to apply a template using a URL :
+It's also possible to apply a template using a URL:
<shell>
$ rails new blog -m https://gist.github.com/755496.txt
</shell>
-Alternatively, you can use the rake task +rails:template+ to apply a template to an existing Rails application :
+Alternatively, you can use the rake task +rails:template+ to apply a template to an existing Rails application:
<shell>
$ rake rails:template LOCATION=~/template.rb
@@ -31,7 +31,7 @@ $ rake rails:template LOCATION=~/template.rb
h3. Template API
-Rails templates API is very self explanatory and easy to understand. Here's an example of a typical Rails template :
+Rails templates API is very self explanatory and easy to understand. Here's an example of a typical Rails template:
<ruby>
# template.rb
@@ -45,20 +45,20 @@ git :add => "."
git :commit => "-a -m 'Initial commit'"
</ruby>
-The following sections outlines the primary methods provided by the API :
+The following sections outlines the primary methods provided by the API:
h4. gem(name, options = {})
Adds a +gem+ entry for the supplied gem to the generated application’s +Gemfile+.
-For example, if your application depends on the gems +bj+ and +nokogiri+ :
+For example, if your application depends on the gems +bj+ and +nokogiri+:
<ruby>
gem "bj"
gem "nokogiri"
</ruby>
-Please note that this will NOT install the gems for you. So you may want to run the +rake gems:install+ task too :
+Please note that this will NOT install the gems for you. So you may want to run the +rake gems:install+ task too:
<ruby>
rake "gems:install"
@@ -80,13 +80,13 @@ h4. plugin(name, options = {})
Installs a plugin to the generated application.
-Plugin can be installed from Git :
+Plugin can be installed from Git:
<ruby>
plugin 'authentication', :git => 'git://github.com/foor/bar.git'
</ruby>
-You can even install plugins as git submodules :
+You can even install plugins as git submodules:
<ruby>
plugin 'authentication', :git => 'git://github.com/foor/bar.git',
@@ -95,7 +95,7 @@ plugin 'authentication', :git => 'git://github.com/foor/bar.git',
Please note that you need to +git :init+ before you can install a plugin as a submodule.
-Or use plain old SVN :
+Or use plain old SVN:
<ruby>
plugin 'usingsvn', :svn => 'svn://example.com/usingsvn/trunk'
@@ -105,7 +105,7 @@ h4. vendor/lib/file/initializer(filename, data = nil, &block)
Adds an initializer to the generated application’s +config/initializers+ directory.
-Lets say you like using +Object#not_nil?+ and +Object#not_blank?+ :
+Lets say you like using +Object#not_nil?+ and +Object#not_blank?+:
<ruby>
initializer 'bloatlol.rb', <<-CODE
@@ -123,7 +123,7 @@ CODE
Similarly +lib()+ creates a file in the +lib/+ directory and +vendor()+ creates a file in the +vendor/+ directory.
-There is even +file()+, which accepts a relative path from +Rails.root+ and creates all the directories/file needed :
+There is even +file()+, which accepts a relative path from +Rails.root+ and creates all the directories/file needed:
<ruby>
file 'app/components/foo.rb', <<-CODE
@@ -136,7 +136,7 @@ That’ll create +app/components+ directory and put +foo.rb+ in there.
h4. rakefile(filename, data = nil, &block)
-Creates a new rake file under +lib/tasks+ with the supplied tasks :
+Creates a new rake file under +lib/tasks+ with the supplied tasks:
<ruby>
rakefile("bootstrap.rake") do
@@ -154,7 +154,7 @@ The above creates +lib/tasks/bootstrap.rake+ with a +boot:strap+ rake task.
h4. generate(what, args)
-Runs the supplied rails generator with given arguments. For example, I love to scaffold some whenever I’m playing with Rails :
+Runs the supplied rails generator with given arguments. For example, I love to scaffold some whenever I’m playing with Rails:
<ruby>
generate(:scaffold, "person", "name:string", "address:text", "age:number")
@@ -162,7 +162,7 @@ generate(:scaffold, "person", "name:string", "address:text", "age:number")
h4. run(command)
-Executes an arbitrary command. Just like the backticks. Let's say you want to remove the +public/index.html+ file :
+Executes an arbitrary command. Just like the backticks. Let's say you want to remove the +public/index.html+ file:
<ruby>
run "rm public/index.html"
@@ -170,19 +170,19 @@ run "rm public/index.html"
h4. rake(command, options = {})
-Runs the supplied rake tasks in the Rails application. Let's say you want to migrate the database :
+Runs the supplied rake tasks in the Rails application. Let's say you want to migrate the database:
<ruby>
rake "db:migrate"
</ruby>
-You can also run rake tasks with a different Rails environment :
+You can also run rake tasks with a different Rails environment:
<ruby>
rake "db:migrate", :env => 'production'
</ruby>
-Or even use sudo :
+Or even use sudo:
<ruby>
rake "gems:install", :sudo => true
@@ -190,7 +190,7 @@ rake "gems:install", :sudo => true
h4. route(routing_code)
-This adds a routing entry to the +config/routes.rb+ file. In above steps, we generated a person scaffold and also removed +public/index.html+. Now to make +PeopleController#index+ as the default page for the application :
+This adds a routing entry to the +config/routes.rb+ file. In above steps, we generated a person scaffold and also removed +public/index.html+. Now to make +PeopleController#index+ as the default page for the application:
<ruby>
route "root :to => 'person#index'"
@@ -208,7 +208,7 @@ end
h4. ask(question)
-+ask()+ gives you a chance to get some feedback from the user and use it in your templates. Lets say you want your user to name the new shiny library you’re adding :
++ask()+ gives you a chance to get some feedback from the user and use it in your templates. Lets say you want your user to name the new shiny library you’re adding:
<ruby>
lib_name = ask("What do you want to call the shiny library ?")
@@ -222,7 +222,7 @@ CODE
h4. yes?(question) or no?(question)
-These methods let you ask questions from templates and decide the flow based on the user’s answer. Lets say you want to freeze rails only if the user want to :
+These methods let you ask questions from templates and decide the flow based on the user’s answer. Lets say you want to freeze rails only if the user want to:
<ruby>
rake("rails:freeze:gems") if yes?("Freeze rails gems ?")
@@ -231,7 +231,7 @@ no?(question) acts just the opposite.
h4. git(:must => "-a love")
-Rails templates let you run any git command :
+Rails templates let you run any git command:
<ruby>
git :init
diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile
index c447fd911a..43c08165dc 100644
--- a/railties/guides/source/routing.textile
+++ b/railties/guides/source/routing.textile
@@ -321,7 +321,7 @@ resources :photos do
end
</ruby>
-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.
+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:
@@ -421,7 +421,7 @@ You do not need to explicitly use the +:controller+ and +:action+ symbols within
match 'photos/:id' => 'photos#show'
</ruby>
-With this route, Rails will match an incoming path of +/photos/12+ to the +show+ action of +PhotosController+.
+With this route, Rails will match an incoming path of +/photos/12+ to the +show+ action of +PhotosController+.
You can also define other defaults in a route by supplying a hash for the +:defaults+ option. This even applies to parameters that you do not specify as dynamic segments. For example:
@@ -557,6 +557,18 @@ match '*a/foo/*b' => 'test#index'
would match +zoo/woo/foo/bar/baz+ with +params[:a]+ equals +"zoo/woo"+, and +params[:b]+ equals +"bar/baz"+.
+NOTE: Starting from Rails 3.1, wildcard routes will always match the optional format segment by default. For example if you have this route:
+
+<ruby>
+map '*pages' => 'pages#show'
+</ruby>
+
+NOTE: By requesting +"/foo/bar.json"+, your +params[:pages]+ will be equals to +"foo/bar"+ with the request format of JSON. If you want the old 3.0.x behavior back, you could supply +:format => false+ like this:
+
+<ruby>
+map '*pages' => 'pages#show', :format => false
+</ruby>
+
h4. Redirection
You can redirect any path to another path using the +redirect+ helper in your router:
diff --git a/railties/guides/source/ruby_on_rails_guides_guidelines.textile b/railties/guides/source/ruby_on_rails_guides_guidelines.textile
index 6576758856..26a5a4c3c9 100644
--- a/railties/guides/source/ruby_on_rails_guides_guidelines.textile
+++ b/railties/guides/source/ruby_on_rails_guides_guidelines.textile
@@ -10,7 +10,7 @@ Guides are written in "Textile":http://www.textism.com/tools/textile/. There's c
h3. Prologue
-Each guide should start with motivational text at the top. That's the little introduction in the blue area. The prologue should tell the readers what's the guide about, and what will they learn. See for example the "Routing Guide":routing.html.
+Each guide should start with motivational text at the top (that's the little introduction in the blue area.) The prologue should tell the reader what the guide is about, and what they will learn. See for example the "Routing Guide":routing.html.
h3. Titles
@@ -23,7 +23,7 @@ h5. Middleware Stack is an Array
h5. When are Objects Saved?
</plain>
-Use same typography as in regular text:
+Use the same typography as in regular text:
<plain>
h6. The +:content_type+ Option
@@ -42,13 +42,13 @@ Those guidelines apply also to guides.
h3. HTML Generation
-To generate all the guides just cd into the +railties+ directory and execute
+To generate all the guides, just +cd+ into the +railties+ directory and execute:
<plain>
bundle exec rake generate_guides
</plain>
-You'll need the gems erubis, i18n, and RedCloth.
+(You may need to run +bundle install+ first to install the required gems.)
To process +my_guide.textile+ and nothing else use the +ONLY+ environment variable:
@@ -56,13 +56,13 @@ To process +my_guide.textile+ and nothing else use the +ONLY+ environment variab
bundle exec rake generate_guides ONLY=my_guide
</plain>
-Although by default guides that have not been modified are not processed, so +ONLY+ is rarely needed in practice.
+By default, guides that have not been modified are not processed, so +ONLY+ is rarely needed in practice.
To force process of all the guides, pass +ALL=1+.
-It is also recommended that you work with +WARNINGS=1+, this detects duplicate IDs and warns about broken internal links.
+It is also recommended that you work with +WARNINGS=1+. This detects duplicate IDs and warns about broken internal links.
-If you want to generate guides in languages other than English, you can keep them in a separate directory under +source+ (eg. <tt>source/es</tt>) and use the +LANGUAGE+ environment variable.
+If you want to generate guides in languages other than English, you can keep them in a separate directory under +source+ (eg. <tt>source/es</tt>) and use the +LANGUAGE+ environment variable:
<plain>
rake generate_guides LANGUAGE=es
@@ -70,7 +70,7 @@ rake generate_guides LANGUAGE=es
h3. HTML Validation
-Please do validate the generated HTML with
+Please validate the generated HTML with:
<plain>
rake validate_guides
@@ -80,4 +80,5 @@ Particularly, titles get an ID generated from their content and this often leads
h3. Changelog
+* March 31, 2011: grammar tweaks by "Josiah Ivey":http://twitter.com/josiahivey
* October 5, 2010: ported from the docrails wiki and revised by "Xavier Noria":credits.html#fxn
diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile
index 182f3631ef..f87ffdb20d 100644
--- a/railties/guides/source/security.textile
+++ b/railties/guides/source/security.textile
@@ -57,7 +57,11 @@ Many web applications have an authentication system: a user provides a user name
Hence, the cookie serves as temporary authentication for the web application. Everyone who seizes a cookie from someone else, may use the web application as this user – with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures:
-* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. This is one more reason not to work from a coffee shop. For the web application builder this means to _(highlight)provide a secure connection over SSL_.
+* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. This is one more reason not to work from a coffee shop. For the web application builder this means to _(highlight)provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file:
+
+<ruby>
+config.force_ssl = true
+</ruby>
* Most people don't clear out the cookies after working at a public terminal. So if the last user didn't log out of a web application, you would be able to use it as this user. Provide the user with a _(highlight)log-out button_ in the web application, and _(highlight)make it prominent_.
@@ -143,7 +147,7 @@ reset_session
If you use the popular RestfulAuthentication plugin for user management, add reset_session to the SessionsController#create action. Note that this removes any value from the session, _(highlight)you have to transfer them to the new session_.
-Another countermeasure is to _(highlight)save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _(highlight)These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way.
+Another countermeasure is to _(highlight)save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _(highlight)These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way.
h4. Session Expiry
@@ -234,7 +238,7 @@ Or the attacker places the code into the onmouseover event handler of an image:
<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." />
</html>
-There are many other possibilities, including Ajax to attack the victim in the background.
The _(highlight)solution to this is including a security token in non-GET requests_ which check on the server-side. In Rails 2 or higher, this is a one-liner in the application controller:
+There are many other possibilities, including Ajax to attack the victim in the background.
The _(highlight)solution to this is including a security token in non-GET requests_ which check on the server-side. In Rails 2 or higher, this is a one-liner in the application controller:
<ruby>
protect_from_forgery :secret => "123456789012345678901234567890..."
@@ -390,7 +394,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. 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
@@ -414,10 +418,17 @@ To avoid this, Rails provides two class methods in your Active Record class to c
attr_protected :admin
</ruby>
++attr_protected+ also optionally takes a scope option using :as which allows you to define multiple mass-assignment groupings. If no scope is defined then attributes will be added to the default group.
+
+<ruby>
+attr_protected :last_login, :as => :admin
+</ruby>
+
A much better way, because it follows the whitelist-principle, is the +attr_accessible+ method. It is the exact opposite of +attr_protected+, because _(highlight)it takes a list of attributes that will be accessible_. All other attributes will be protected. This way you won't forget to protect attributes when adding new ones in the course of development. Here is an example:
<ruby>
attr_accessible :name
+attr_accessible :name, :is_admin, :as => :admin
</ruby>
If you want to set a protected attribute, you will to have to assign it individually:
@@ -430,13 +441,31 @@ params[:user] # => {:name => "ow3ned", :admin => true}
@user.admin # => true
</ruby>
-A more paranoid technique to protect your whole project would be to enforce that all models whitelist their accessible attributes. This can be easily achieved with a very simple initializer:
+When assigning attributes in Active Record using +new+, +attributes=+, or +update_attributes+ the :default scope will be used. To assign attributes using different scopes you should use +assign_attributes+ which accepts an optional :as options parameter. If no :as option is provided then the :default scope will be used. You can also bypass mass-assignment security by using the +:without_protection+ option. Here is an example:
<ruby>
-ActiveRecord::Base.send(:attr_accessible, nil)
+@user = User.new
+
+@user.assign_attributes({ :name => 'Josh', :is_admin => true })
+@user.name # => Josh
+@user.is_admin # => false
+
+@user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
+@user.name # => Josh
+@user.is_admin # => true
+
+@user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
+@user.name # => Josh
+@user.is_admin # => true
+</ruby>
+
+A more paranoid technique to protect your whole project would be to enforce that all models define their accessible attributes. This can be easily achieved with a very simple application config option of:
+
+<ruby>
+config.active_record.whitelist_attributes = true
</ruby>
-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 accessible parameters by using an +attr_accessible+ declaration. This technique is best applied at the start of a new project. However, for an existing project with a thorough set of functional tests, it should be straightforward and relatively quick to insert this initializer, run your tests, and expose each attribute (via +attr_accessible+) as dictated by your failing tests.
+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. This technique is best applied at the start of a new project. However, for an existing project with a thorough set of functional tests, it should be straightforward and relatively quick to use this application config option; run your tests, and expose each attribute (via +attr_accessible+ or +attr_protected+) as dictated by your failing tests.
h3. User Management
@@ -889,12 +918,6 @@ h4. Ajax Injection
If you use the "in_place_editor plugin":http://dev.rubyonrails.org/browser/plugins/in_place_editing, or actions that return a string, rather than rendering a view, _(highlight)you have to escape the return value in the action_. Otherwise, if the return value contains a XSS string, the malicious code will be executed upon return to the browser. Escape any input value using the h() method.
-h4. RJS Injection
-
--- _Don't forget to escape in JavaScript (RJS) templates, too._
-
-The RJS API generates blocks of JavaScript code based on Ruby code, thus allowing you to manipulate a view or parts of a view from the server side. <em class="highlight">If you allow user input in RJS templates, do escape it using +escape_javascript()+ within JavaScript functions, and in HTML parts using +h()+</em>. Otherwise an attacker could execute arbitrary JavaScript.
-
h4. Command Line Injection
-- _Use user-supplied command line parameters with caution._
diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile
index a75cedbdc1..9b1ef22b97 100644
--- a/railties/guides/source/testing.textile
+++ b/railties/guides/source/testing.textile
@@ -79,9 +79,9 @@ steve:
Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are separated by a blank space. You can place comments in a fixture file by using the # character in the first column.
-h5. ERb'in It Up
+h5. ERB'in It Up
-ERb allows you embed ruby code within templates. Both the YAML and CSV fixture formats are pre-processed with ERb when you load fixtures. This allows you to use Ruby to help you generate some sample data.
+ERB allows you to embed ruby code within templates. Both the YAML and CSV fixture formats are pre-processed with ERB when you load fixtures. This allows you to use Ruby to help you generate some sample data.
<erb>
<% earth_size = 20 %>
@@ -227,7 +227,7 @@ $ rake db:migrate
$ rake db:test:load
</shell>
-Above +rake db:migrate+ runs any pending migrations on the _development_ environment and updates +db/schema.rb+. +rake db:test:load+ recreates the test database from the current +db/schema.rb+. On subsequent attempts, it is a good idea to first run +db:test:prepare+, as it first checks for pending migrations and warns you appropriately.
+The +rake db:migrate+ above runs any pending migrations on the _development_ environment and updates +db/schema.rb+. The +rake db:test:load+ recreates the test database from the current +db/schema.rb+. On subsequent attempts, it is a good idea to first run +db:test:prepare+, as it first checks for pending migrations and warns you appropriately.
NOTE: +db:test:prepare+ will fail with an error if +db/schema.rb+ doesn't exist.
@@ -391,7 +391,7 @@ There are a bunch of different types of assertions you can use. Here's the compl
|+assert_nil( obj, [msg] )+ |Ensures that +obj.nil?+ is true.|
|+assert_not_nil( obj, [msg] )+ |Ensures that +obj.nil?+ is false.|
|+assert_match( regexp, string, [msg] )+ |Ensures that a string matches the regular expression.|
-|+assert_no_match( regexp, string, [msg] )+ |Ensures that a string doesn't matches the regular expression.|
+|+assert_no_match( regexp, string, [msg] )+ |Ensures that a string doesn't match the regular expression.|
|+assert_in_delta( expecting, actual, delta, [msg] )+ |Ensures that the numbers +expecting+ and +actual+ are within +delta+ of each other.|
|+assert_throws( symbol, [msg] ) { block }+ |Ensures that the given block throws the symbol.|
|+assert_raise( exception1, exception2, ... ) { block }+ |Ensures that the given block raises one of the given exceptions.|
@@ -415,8 +415,8 @@ NOTE: +assert_valid(record)+ has been deprecated. Please use +assert(record.vali
|_.Assertion |_.Purpose|
|+assert_valid(record)+ |Ensures that the passed record is valid by Active Record standards and returns any error messages if it is not.|
-|+assert_difference(expressions, difference = 1, message = nil) {...}+ |Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.|
-|+assert_no_difference(expressions, message = nil, &amp;block)+ |Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.|
+|+assert_difference(expressions, difference = 1, message = nil) {...}+ |Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.|
+|+assert_no_difference(expressions, message = nil, &amp;block)+ |Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.|
|+assert_recognizes(expected_options, path, extras={}, message=nil)+ |Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.|
|+assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)+ |Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.|
|+assert_response(type, message = nil)+ |Asserts that the response comes with a specific status code. You can specify +:success+ to indicate 200, +:redirect+ to indicate 300-399, +:missing+ to indicate 404, or +:error+ to match the 500-599 range|
@@ -500,6 +500,8 @@ If you're familiar with the HTTP protocol, you'll know that +get+ is a type of r
All of request types are methods that you can use, however, you'll probably end up using the first two more often than the others.
+NOTE: Functional tests do not verify whether the specified request type should be accepted by the action. Request types in this context exist to make your tests more descriptive.
+
h4. The Four Hashes of the Apocalypse
After a request has been made by using one of the 5 methods (+get+, +post+, etc.) and processed, you will have 4 Hash objects ready for use:
@@ -590,7 +592,6 @@ There are more assertions that are primarily used in testing views:
|_.Assertion |_.Purpose|
|+assert_select_email+ |Allows you to make assertions on the body of an e-mail. |
-|+assert_select_rjs+ |Allows you to make assertions on an RJS response. +assert_select_rjs+ has variants which allow you to narrow down on the updated element or even a particular operation on an element.|
|+assert_select_encoded+ |Allows you to make assertions on encoded HTML. It does this by un-encoding the contents of each element and then calling the block with all the un-encoded elements.|
|+css_select(selector)+ or +css_select(element, selector)+ |Returns an array of all the elements selected by the _selector_. In the second variant it first matches the base _element_ and tries to match the _selector_ expression on any of its children. If there are no matches both variants return an empty array.|
@@ -746,7 +747,8 @@ You don't need to set up and run your tests by hand on a test-by-test basis. Rai
h3. Brief Note About +Test::Unit+
-Ruby ships with a boat load of libraries. One little gem of a library is +Test::Unit+, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in +Test::Unit::Assertions+. The class +ActiveSupport::TestCase+ which we have been using in our unit and functional tests extends +Test::Unit::TestCase+ that it is how we can use all the basic assertions in our tests.
+Ruby ships with a boat load of libraries. One little gem of a library is +Test::Unit+, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in +Test::Unit::Assertions+. The class +ActiveSupport::TestCase+ which we have been using in our unit and functional tests extends +Test::Unit::TestCase+, allowing
+us to use all of the basic assertions in our tests.
NOTE: For more information on +Test::Unit+, refer to "test/unit Documentation":http://ruby-doc.org/stdlib/libdoc/test/unit/rdoc/
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 94819820bc..0c3c7737ea 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -50,6 +50,7 @@ module Rails
end
end
+ attr_accessor :assets
delegate :default_url_options, :default_url_options=, :to => :routes
# This method is called just after an application inherits from Rails::Application,
@@ -116,13 +117,10 @@ module Rails
self
end
- alias :build_middleware_stack :app
-
def env_config
@env_config ||= super.merge({
"action_dispatch.parameter_filter" => config.filter_parameters,
"action_dispatch.secret_token" => config.secret_token,
- "action_dispatch.asset_path" => nil,
"action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions
})
end
@@ -139,21 +137,24 @@ module Rails
protected
- def default_asset_path
- nil
- end
+ alias :build_middleware_stack :app
def default_middleware_stack
ActionDispatch::MiddlewareStack.new.tap do |middleware|
- rack_cache = config.action_controller.perform_caching && config.action_dispatch.rack_cache
+ if rack_cache = config.action_controller.perform_caching && config.action_dispatch.rack_cache
+ require "action_dispatch/http/rack_cache"
+ middleware.use ::Rack::Cache, rack_cache
+ end
- require "action_dispatch/http/rack_cache" if rack_cache
- middleware.use ::Rack::Cache, rack_cache if rack_cache
+ if config.force_ssl
+ require "rack/ssl"
+ middleware.use ::Rack::SSL
+ end
if config.serve_static_assets
- asset_paths = ActiveSupport::OrderedHash[config.static_asset_paths.to_a.reverse]
- middleware.use ::ActionDispatch::Static, asset_paths
+ middleware.use ::ActionDispatch::Static, paths["public"].first
end
+
middleware.use ::Rack::Lock unless config.allow_concurrency
middleware.use ::Rack::Runtime
middleware.use ::Rails::Rack::Logger
@@ -174,7 +175,10 @@ module Rails
middleware.use ::ActionDispatch::Head
middleware.use ::Rack::ConditionalGet
middleware.use ::Rack::ETag, "no-cache"
- middleware.use ::ActionDispatch::BestStandardsSupport, config.action_dispatch.best_standards_support if config.action_dispatch.best_standards_support
+
+ if config.action_dispatch.best_standards_support
+ middleware.use ::ActionDispatch::BestStandardsSupport, config.action_dispatch.best_standards_support
+ end
end
end
@@ -196,4 +200,4 @@ module Rails
require "rails/console/helpers"
end
end
-end
+end \ No newline at end of file
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index c74bcbedf2..f818313955 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -4,12 +4,12 @@ require 'rails/engine/configuration'
module Rails
class Application
class Configuration < ::Rails::Engine::Configuration
- attr_accessor :allow_concurrency, :asset_host, :cache_classes, :cache_store,
- :encoding, :consider_all_requests_local, :dependency_loading,
- :filter_parameters, :helpers_paths, :logger,
- :preload_frameworks, :reload_plugins,
- :secret_token, :serve_static_assets, :session_options,
- :time_zone, :whiny_nils
+ attr_accessor :allow_concurrency, :asset_host, :asset_path, :assets,
+ :cache_classes, :cache_store, :consider_all_requests_local,
+ :dependency_loading, :encoding, :filter_parameters,
+ :force_ssl, :helpers_paths, :logger, :preload_frameworks,
+ :reload_plugins, :secret_token, :serve_static_assets,
+ :session_options, :time_zone, :whiny_nils
attr_writer :log_level
@@ -22,12 +22,22 @@ module Rails
@helpers_paths = []
@dependency_loading = true
@serve_static_assets = true
+ @force_ssl = false
@session_store = :cookie_store
@session_options = {}
@time_zone = "UTC"
@log_level = nil
@middleware = app_middleware
@generators = app_generators
+
+ @assets = ActiveSupport::OrderedOptions.new
+ @assets.enabled = false
+ @assets.paths = []
+ @assets.precompile = [ /\w+\.(?!js|css)$/, "application.js", "application.css" ]
+ @assets.prefix = "/assets"
+
+ @assets.js_compressor = nil
+ @assets.css_compressor = nil
end
def compiled_asset_path
@@ -55,6 +65,9 @@ module Rails
paths.add "config/environment", :with => "config/environment.rb"
paths.add "lib/templates"
paths.add "log", :with => "log/#{Rails.env}.log"
+ paths.add "public"
+ paths.add "public/javascripts"
+ paths.add "public/stylesheets"
paths.add "tmp"
paths.add "tmp/cache"
paths
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index a45b61c99c..028c8814c4 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -41,6 +41,10 @@ module Rails
ActionDispatch::Reloader.prepare!
end
+ initializer :define_main_app_helper do |app|
+ app.routes.define_mounted_helper(:main_app)
+ end
+
initializer :eager_load! do
if config.cache_classes && !$rails_rake_task
ActiveSupport.run_load_hooks(:before_eager_load, self)
@@ -53,6 +57,8 @@ module Rails
end
# Force routes to be loaded just at the end and add it to to_prepare callbacks
+ # This needs to be after the finisher hook to ensure routes added in the hook
+ # are still loaded.
initializer :set_routes_reloader do |app|
reloader = lambda { app.routes_reloader.execute_if_updated }
reloader.call
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index 02ccdf8060..4182757346 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/inclusion'
+
ARGV << '--help' if ARGV.empty?
aliases = {
@@ -69,7 +71,7 @@ when '--version', '-v'
require 'rails/commands/application'
else
- puts "Error: Command not recognized" unless %w(-h --help).include?(command)
+ puts "Error: Command not recognized" unless command.in?(['-h', '--help'])
puts <<-EOT
Usage: rails COMMAND [ARGS]
diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb
index 3b57b925ba..f3fa1fd54d 100644
--- a/railties/lib/rails/commands/application.rb
+++ b/railties/lib/rails/commands/application.rb
@@ -1,5 +1,6 @@
require 'rails/version'
-if %w(--version -v).include? ARGV.first
+
+if ['--version', '-v'].include?(ARGV.first)
puts "Rails #{Rails::VERSION::STRING}"
exit(0)
end
diff --git a/railties/lib/rails/commands/benchmarker.rb b/railties/lib/rails/commands/benchmarker.rb
index 0432261802..f230f405c0 100644
--- a/railties/lib/rails/commands/benchmarker.rb
+++ b/railties/lib/rails/commands/benchmarker.rb
@@ -1,4 +1,6 @@
-if [nil, "-h", "--help"].include?(ARGV.first)
+require 'active_support/core_ext/object/inclusion'
+
+if ARGV.first.in?([nil, "-h", "--help"])
puts "Usage: rails benchmarker [times] 'Person.expensive_way' 'Person.another_expensive_way' ..."
exit 1
end
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index de2f190ad5..dfd3c654ff 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -34,6 +34,10 @@ module Rails
exit
end
end
+
+ if defined?(ActiveRecord)
+ ActiveRecord::Base.logger = Logger.new(STDERR)
+ end
if options[:sandbox]
puts "Loading #{Rails.env} environment in sandbox (Rails #{Rails.version})"
@@ -47,6 +51,6 @@ module Rails
end
# Has to set the RAILS_ENV before config/application is required
-if ARGV.first && !ARGV.first.index("-") && env = ARGV.pop # has to pop the env ARGV so IRB doesn't freak
+if ARGV.first && !ARGV.first.index("-") && env = ARGV.shift # has to shift the env ARGV so IRB doesn't freak
ENV['RAILS_ENV'] = %w(production development test).detect {|e| e =~ /^#{env}/} || env
end
diff --git a/railties/lib/rails/commands/destroy.rb b/railties/lib/rails/commands/destroy.rb
index db59cd8ad9..2a84e2a6df 100644
--- a/railties/lib/rails/commands/destroy.rb
+++ b/railties/lib/rails/commands/destroy.rb
@@ -1,7 +1,9 @@
require 'rails/generators'
+require 'active_support/core_ext/object/inclusion'
+
Rails::Generators.configure!
-if [nil, "-h", "--help"].include?(ARGV.first)
+if ARGV.first.in?([nil, "-h", "--help"])
Rails::Generators.help 'destroy'
exit
end
diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb
index 1b3eef504a..28c1c56352 100644
--- a/railties/lib/rails/commands/generate.rb
+++ b/railties/lib/rails/commands/generate.rb
@@ -1,7 +1,9 @@
require 'rails/generators'
+require 'active_support/core_ext/object/inclusion'
+
Rails::Generators.configure!
-if [nil, "-h", "--help"].include?(ARGV.first)
+if ARGV.first.in?([nil, "-h", "--help"])
Rails::Generators.help 'generate'
exit
end
diff --git a/railties/lib/rails/commands/profiler.rb b/railties/lib/rails/commands/profiler.rb
index 6d9717b5cd..7959d8a981 100644
--- a/railties/lib/rails/commands/profiler.rb
+++ b/railties/lib/rails/commands/profiler.rb
@@ -1,4 +1,6 @@
-if [nil, "-h", "--help"].include?(ARGV.first)
+require 'active_support/core_ext/object/inclusion'
+
+if ARGV.first.in?([nil, "-h", "--help"])
$stderr.puts "Usage: rails profiler 'Person.expensive_method(10)' [times] [flat|graph|graph_html]"
exit(1)
end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 4fc23fe277..6c1064c609 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -171,32 +171,6 @@ module Rails
#
# Now, +Engine+ will get only requests that were not handled by +Application+.
#
- # == Asset path
- #
- # When you use +Engine+ with its own public directory, you will probably want to copy or symlink it
- # to application's public directory. To simplify generating paths for assets, you can set <tt>asset_path</tt>
- # for an engine:
- #
- # module MyEngine
- # class Engine < Rails::Engine
- # config.asset_path = "/my_engine/%s"
- # end
- # end
- #
- # With such a config, asset paths will be automatically modified inside +Engine+:
- #
- # image_path("foo.jpg") #=> "/my_engine/images/foo.jpg"
- #
- # == Serving static files
- #
- # By default, Rails uses <tt>ActionDispatch::Static</tt> to serve static files in development mode. This is ok
- # while you develop your application, but when you want to deploy it, assets from an engine will not be
- # served by default. You should choose one of the two following strategies:
- #
- # * enable serving static files by setting config.serve_static_assets to true
- # * copy engine's public files to application's public folder with <tt>rake ENGINE_NAME:install:assets</tt>, for example
- # <tt>rake my_engine:install:assets</tt>
- #
# == Engine name
#
# There are some places where an Engine's name is used:
@@ -312,6 +286,27 @@ module Rails
#
# This code will use <tt>my_engine.user_path(@user)</tt> to generate the proper route.
#
+ # == Isolated engine's helpers
+ #
+ # Sometimes you may want to isolate engine, but use helpers that are defined for it.
+ # If you want to share just a few specific helpers you can add them to application's
+ # helpers in ApplicationController:
+ #
+ # class ApplicationController < ActionController::Base
+ # helper MyEngine::SharedEngineHelper
+ # end
+ #
+ # If you want to include all of the engine's helpers, you can use #helpers method on egine's
+ # instance:
+ #
+ # class ApplicationController < ActionController::Base
+ # helper MyEngine::Engine.helpers
+ # end
+ #
+ # It will include all of the helpers from engine's directory. Take into account that this does
+ # not include helpers defined in controllers with helper_method or other similar solutions,
+ # only helpers defined in helpers directory will be included.
+ #
# == Migrations & seed data
#
# Engines can have their own migrations. The default path for migrations is exactly the same
@@ -360,11 +355,11 @@ module Rails
def isolate_namespace(mod)
engine_name(generate_railtie_name(mod))
- name = engine_name
- self.routes.default_scope = {:module => name}
+ self.routes.default_scope = { :module => ActiveSupport::Inflector.underscore(mod.name) }
self.isolated = true
unless mod.respond_to?(:_railtie)
+ name = engine_name
_railtie = self
mod.singleton_class.instance_eval do
define_method(:_railtie) do
@@ -410,6 +405,24 @@ module Rails
@railties ||= self.class::Railties.new(config)
end
+ def helpers
+ @helpers ||= begin
+ helpers = Module.new
+
+ helpers_paths = if config.respond_to?(:helpers_paths)
+ config.helpers_paths
+ else
+ paths["app/helpers"].existent
+ end
+
+ all = ActionController::Base.all_helpers_from_path(helpers_paths)
+ ActionController::Base.modules_for_helpers(all).each do |mod|
+ helpers.send(:include, mod)
+ end
+ helpers
+ end
+ end
+
def app
@app ||= begin
config.middleware = config.middleware.merge_into(default_middleware_stack)
@@ -427,8 +440,7 @@ module Rails
def env_config
@env_config ||= {
- 'action_dispatch.routes' => routes,
- 'action_dispatch.asset_path' => config.asset_path
+ 'action_dispatch.routes' => routes
}
end
@@ -509,13 +521,9 @@ module Rails
require environment if environment
end
- initializer :append_asset_paths do
- config.asset_path ||= default_asset_path
-
- public_path = paths["public"].first
- if config.compiled_asset_path && File.exist?(public_path)
- config.static_asset_paths[config.compiled_asset_path] = public_path
- end
+ initializer :append_assets_path do |app|
+ app.config.assets.paths.unshift *paths["vendor/assets"].existent
+ app.config.assets.paths.unshift *paths["app/assets"].existent
end
initializer :prepend_helpers_path do |app|
@@ -537,42 +545,29 @@ module Rails
rake_tasks do
next if self.is_a?(Rails::Application)
+ next unless has_migrations?
namespace railtie_name do
- desc "Shortcut for running both rake #{railtie_name}:install:migrations and #{railtie_name}:install:assets"
- task :install do
- Rake::Task["#{railtie_name}:install:migrations"].invoke
- Rake::Task["#{railtie_name}:install:assets"].invoke
- end
-
namespace :install do
- # TODO Add assets copying to this list
- # TODO Skip this if there is no paths["db/migrate"] for the engine
desc "Copy migrations from #{railtie_name} to application"
task :migrations do
ENV["FROM"] = railtie_name
Rake::Task["railties:install:migrations"].invoke
end
-
- desc "Copy assets from #{railtie_name} to application"
- task :assets do
- ENV["FROM"] = railtie_name
- Rake::Task["railties:install:assets"].invoke
- end
end
end
end
protected
- def default_asset_path
- "/#{railtie_name}%s"
- end
-
def routes?
defined?(@routes)
end
+ def has_migrations?
+ paths["db/migrate"].first.present?
+ end
+
def find_root_with_flag(flag, default=nil)
root_path = self.class.called_from
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 4f458b0aee..241db4b0a9 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -5,7 +5,7 @@ module Rails
class Configuration < ::Rails::Railtie::Configuration
attr_reader :root
attr_writer :middleware, :eager_load_paths, :autoload_once_paths, :autoload_paths
- attr_accessor :plugins, :asset_path
+ attr_accessor :plugins
def initialize(root=nil)
super()
@@ -40,6 +40,7 @@ module Rails
@paths ||= begin
paths = Rails::Paths::Root.new(@root)
paths.add "app", :eager_load => true, :glob => "*"
+ paths.add "app/assets", :glob => "*"
paths.add "app/controllers", :eager_load => true
paths.add "app/helpers", :eager_load => true
paths.add "app/models", :eager_load => true
@@ -55,10 +56,8 @@ module Rails
paths.add "db"
paths.add "db/migrate"
paths.add "db/seeds", :with => "db/seeds.rb"
- paths.add "public"
- paths.add "public/javascripts"
- paths.add "public/stylesheets"
paths.add "vendor", :load_path => true
+ paths.add "vendor/assets", :glob => "*"
paths.add "vendor/plugins"
paths
end
@@ -79,10 +78,6 @@ module Rails
def autoload_paths
@autoload_paths ||= paths.autoload_paths
end
-
- def compiled_asset_path
- asset_path % "" if asset_path
- end
end
end
end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 29e693dfb0..9be395e989 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -24,9 +24,12 @@ module Rails
:rails => {
:actions => '-a',
:orm => '-o',
+ :javascripts => '-j',
+ :javascript_engine => '-je',
:resource_controller => '-c',
:scaffold_controller => '-c',
:stylesheets => '-y',
+ :stylesheet_engine => '-se',
:template_engine => '-e',
:test_framework => '-t'
},
@@ -43,14 +46,18 @@ module Rails
DEFAULT_OPTIONS = {
:rails => {
+ :assets => true,
:force_plural => false,
:helper => true,
- :orm => nil,
:integration_tool => nil,
+ :javascripts => true,
+ :javascript_engine => nil,
+ :orm => nil,
:performance_tool => nil,
:resource_controller => :controller,
:scaffold_controller => :scaffold_controller,
:stylesheets => true,
+ :stylesheet_engine => nil,
:test_framework => nil,
:template_engine => :erb
},
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index ab7ed4eb9e..520d2c6a3a 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -1,5 +1,6 @@
require 'digest/md5'
require 'active_support/secure_random'
+require 'active_support/core_ext/string/strip'
require 'rails/version' unless defined?(Rails::VERSION)
require 'rbconfig'
require 'open-uri'
@@ -9,7 +10,9 @@ module Rails
module Generators
class AppBase < Base
DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db )
- JAVASCRIPTS = %w( prototype jquery )
+ JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql )
+ DATABASES.concat(JDBC_DATABASES)
+ JAVASCRIPTS = %w( jquery prototype )
attr_accessor :rails_template
add_shebang_option!
@@ -35,11 +38,11 @@ module Rails
class_option :database, :type => :string, :aliases => "-d", :default => "sqlite3",
:desc => "Preconfigure for selected database (options: #{DATABASES.join('/')})"
- class_option :javascript, :type => :string, :aliases => "-j", :default => "prototype",
- :desc => "Preconfigure for selected javascript library (options: #{JAVASCRIPTS.join('/')})"
+ class_option :javascript, :type => :string, :aliases => "-j", :default => "jquery",
+ :desc => "Preconfigure for selected JavaScript library (options: #{JAVASCRIPTS.join('/')})"
class_option :skip_javascript, :type => :boolean, :aliases => "-J", :default => false,
- :desc => "Skip javascript files"
+ :desc => "Skip JavaScript files"
class_option :dev, :type => :boolean, :default => false,
:desc => "Setup the #{name} with Gemfile pointing to your Rails checkout"
@@ -52,6 +55,9 @@ module Rails
class_option :help, :type => :boolean, :aliases => "-h", :group => :rails,
:desc => "Show this help message and quit"
+
+ class_option :old_style_hash, :type => :boolean, :default => false,
+ :desc => "Force using old style hash (:foo => 'bar') on Ruby >= 1.9"
end
def initialize(*args)
@@ -112,44 +118,77 @@ module Rails
end
def database_gemfile_entry
- options[:skip_active_record] ? "" : "gem '#{gem_for_database}'"
+ entry = options[:skip_active_record] ? "" : "gem '#{gem_for_database}'"
+ if options[:database] == 'mysql'
+ if options.dev? || options.edge?
+ entry += ", :git => 'git://github.com/brianmario/mysql2.git'"
+ else
+ entry += "\n# gem 'mysql2', :git => 'git://github.com/brianmario/mysql2.git'"
+ end
+ end
+ entry + "\n"
end
def rails_gemfile_entry
if options.dev?
- <<-GEMFILE
-gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}'
-gem 'arel', :git => 'git://github.com/rails/arel.git'
-gem "rack", :git => "git://github.com/rack/rack.git"
+ <<-GEMFILE.strip_heredoc
+ gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}'
+ gem 'arel', :git => 'git://github.com/rails/arel.git'
+ gem 'rack', :git => 'git://github.com/rack/rack.git'
+ gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git'
GEMFILE
elsif options.edge?
- <<-GEMFILE
-gem 'rails', :git => 'git://github.com/rails/rails.git'
-gem 'arel', :git => 'git://github.com/rails/arel.git'
-gem "rack", :git => "git://github.com/rack/rack.git"
+ <<-GEMFILE.strip_heredoc
+ gem 'rails', :git => 'git://github.com/rails/rails.git'
+ gem 'arel', :git => 'git://github.com/rails/arel.git'
+ gem 'rack', :git => 'git://github.com/rack/rack.git'
+ gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git'
GEMFILE
else
- <<-GEMFILE
-gem 'rails', '#{Rails::VERSION::STRING}'
-
-# Bundle edge Rails instead:
-# gem 'rails', :git => 'git://github.com/rails/rails.git'
-# gem 'arel', :git => 'git://github.com/rails/arel.git'
-# gem "rack", :git => "git://github.com/rack/rack.git"
+ <<-GEMFILE.strip_heredoc
+ gem 'rails', '#{Rails::VERSION::STRING}'
+
+ # Bundle edge Rails instead:
+ # gem 'rails', :git => 'git://github.com/rails/rails.git'
+ # gem 'arel', :git => 'git://github.com/rails/arel.git'
+ # gem 'rack', :git => 'git://github.com/rack/rack.git'
+ # gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git'
GEMFILE
end
end
def gem_for_database
- # %w( mysql oracle postgresql sqlite3 frontbase ibm_db )
+ # %w( mysql oracle postgresql sqlite3 frontbase ibm_db jdbcmysql jdbcsqlite3 jdbcpostgresql )
case options[:database]
when "oracle" then "ruby-oci8"
when "postgresql" then "pg"
when "frontbase" then "ruby-frontbase"
when "mysql" then "mysql2"
+ when "jdbcmysql" then "activerecord-jdbcmysql-adapter"
+ when "jdbcsqlite3" then "activerecord-jdbcsqlite3-adapter"
+ when "jdbcpostgresql" then "activerecord-jdbcpostgresql-adapter"
else options[:database]
end
end
+
+ def gem_for_ruby_debugger
+ if RUBY_VERSION < "1.9.2"
+ "gem 'ruby-debug'"
+ else
+ "gem 'ruby-debug19', :require => 'ruby-debug'"
+ end
+ end
+
+ def gem_for_turn
+ unless RUBY_VERSION < "1.9.2"
+ <<-GEMFILE.strip_heredoc
+ group :test do
+ # Pretty printed test output
+ gem 'turn', :require => false
+ end
+ GEMFILE
+ end
+ end
def bundle_if_dev_or_edge
bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle')
@@ -162,9 +201,22 @@ gem 'rails', '#{Rails::VERSION::STRING}'
def empty_directory_with_gitkeep(destination, config = {})
empty_directory(destination, config)
+ git_keep(destination)
+ end
+
+ def git_keep(destination)
create_file("#{destination}/.gitkeep") unless options[:skip_git]
end
+ # Returns Ruby 1.9 style key-value pair if current code is running on
+ # Ruby 1.9.x. Returns the old-style (with hash rocket) otherwise.
+ def key_value(key, value)
+ if options[:old_style_hash] || RUBY_VERSION < '1.9'
+ ":#{key} => #{value}"
+ else
+ "#{key}: #{value}"
+ end
+ end
end
end
-end
+end \ No newline at end of file
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index dfecd2a6e4..8d03cb911b 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -8,6 +8,7 @@ rescue LoadError
end
require 'rails/generators/actions'
+require 'active_support/core_ext/object/inclusion'
module Rails
module Generators
@@ -164,7 +165,7 @@ module Rails
names.each do |name|
defaults = if options[:type] == :boolean
{ }
- elsif [true, false].include?(default_value_for_option(name, options))
+ elsif default_value_for_option(name, options).in?([true, false])
{ :banner => "" }
else
{ :desc => "#{name.to_s.humanize} to be invoked", :banner => "NAME" }
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 4c46db4d67..a7393cfe18 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -17,7 +17,7 @@
<% 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 %>, :confirm => 'Are you sure?', :method => :delete %></td>
+ <td><%%= link_to 'Destroy', <%= singular_table_name %>, <%= key_value :confirm, "'Are you sure?'" %>, <%= key_value :method, ":delete" %> %></td>
</tr>
<%% end %>
</table>
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index 64273e4ca4..b26161f1d0 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -1,4 +1,5 @@
require 'active_support/time'
+require 'active_support/core_ext/object/inclusion'
module Rails
module Generators
@@ -44,7 +45,7 @@ module Rails
end
def reference?
- [ :references, :belongs_to ].include?(self.type)
+ self.type.in?([:references, :belongs_to])
end
end
end
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 2af7f85463..36bc9e055c 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -8,6 +8,9 @@ module Rails
class_option :skip_namespace, :type => :boolean, :default => false,
:desc => "Skip namespace (affects only isolated applications)"
+ class_option :old_style_hash, :type => :boolean, :default => false,
+ :desc => "Force using old style hash (:foo => 'bar') on Ruby >= 1.9"
+
def initialize(args, *options) #:nodoc:
# Unfreeze name in case it's given as a frozen string
args[0] = args[0].dup if args[0].is_a?(String) && args[0].frozen?
@@ -181,6 +184,16 @@ module Rails
class_collisions "#{options[:prefix]}#{name}#{options[:suffix]}"
end
end
+
+ # Returns Ruby 1.9 style key-value pair if current code is running on
+ # Ruby 1.9.x. Returns the old-style (with hash rocket) otherwise.
+ def key_value(key, value)
+ if options[:old_style_hash] || RUBY_VERSION < '1.9'
+ ":#{key} => #{value}"
+ else
+ "#{key}: #{value}"
+ end
+ end
end
end
end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 3f6ff35a86..bc55efa261 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -46,6 +46,8 @@ module Rails
def app
directory 'app'
+ git_keep 'app/mailers'
+ git_keep 'app/models'
end
def config
@@ -80,45 +82,13 @@ module Rails
end
def log
- empty_directory "log"
-
- inside "log" do
- %w( server production development test ).each do |file|
- create_file "#{file}.log"
- chmod "#{file}.log", 0666, :verbose => false
- end
- end
+ empty_directory_with_gitkeep "log"
end
def public_directory
directory "public", "public", :recursive => false
end
- def images
- directory "public/images"
- end
-
- def stylesheets
- empty_directory_with_gitkeep "public/stylesheets"
- end
-
- def javascripts
- empty_directory "public/javascripts"
-
- unless options[:skip_javascript]
- copy_file "public/javascripts/#{options[:javascript]}.js"
- copy_file "public/javascripts/#{options[:javascript]}_ujs.js", "public/javascripts/rails.js"
-
- if options[:javascript] == "prototype"
- copy_file "public/javascripts/controls.js"
- copy_file "public/javascripts/dragdrop.js"
- copy_file "public/javascripts/effects.js"
- end
- end
-
- copy_file "public/javascripts/application.js"
- end
-
def script
directory "script" do |content|
"#{shebang}\n" + content
@@ -127,19 +97,44 @@ module Rails
end
def test
- directory "test"
+ empty_directory_with_gitkeep "test/fixtures"
+ empty_directory_with_gitkeep "test/functional"
+ empty_directory_with_gitkeep "test/integration"
+ empty_directory_with_gitkeep "test/unit"
+
+ template "test/performance/browsing_test.rb"
+ template "test/test_helper.rb"
end
def tmp
- empty_directory "tmp"
+ empty_directory_with_gitkeep "tmp/cache"
+ end
+
+ def vendor
+ vendor_javascripts
+ vendor_stylesheets
+ vendor_plugins
+ end
- inside "tmp" do
- %w(sessions sockets cache pids).each do |dir|
- empty_directory(dir)
+ def vendor_javascripts
+ if options[:skip_javascript]
+ empty_directory_with_gitkeep "vendor/assets/javascripts"
+ else
+ copy_file "vendor/assets/javascripts/#{options[:javascript]}.js"
+ copy_file "vendor/assets/javascripts/#{options[:javascript]}_ujs.js"
+
+ if options[:javascript] == "prototype"
+ copy_file "vendor/assets/javascripts/controls.js"
+ copy_file "vendor/assets/javascripts/dragdrop.js"
+ copy_file "vendor/assets/javascripts/effects.js"
end
end
end
+ def vendor_stylesheets
+ empty_directory_with_gitkeep "vendor/assets/stylesheets"
+ end
+
def vendor_plugins
empty_directory_with_gitkeep "vendor/plugins"
end
@@ -150,15 +145,14 @@ module Rails
# can change in Ruby 1.8.7 when we FileUtils.cd.
RAILS_DEV_PATH = File.expand_path("../../../../../..", File.dirname(__FILE__))
- RESERVED_NAMES = %w[application destroy benchmarker profiler
- plugin runner test]
+ RESERVED_NAMES = %w[application destroy benchmarker profiler plugin runner test]
class AppGenerator < AppBase
add_shared_options_for "application"
# Add bin/rails options
- class_option :version, :type => :boolean, :aliases => "-v", :group => :rails,
- :desc => "Show Rails version number and quit"
+ class_option :version, :type => :boolean, :aliases => "-v", :group => :rails,
+ :desc => "Show Rails version number and quit"
def initialize(*args)
raise Error, "Options should be given after the application name. For details run: rails --help" if args[0].blank?
@@ -168,7 +162,7 @@ module Rails
if !options[:skip_active_record] && !DATABASES.include?(options[:database])
raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}."
end
-
+
if !options[:skip_javascript] && !JAVASCRIPTS.include?(options[:javascript])
raise Error, "Invalid value for --javascript option. Supported for preconfiguration are: #{JAVASCRIPTS.join(", ")}."
end
@@ -221,18 +215,6 @@ module Rails
build(:public_directory)
end
- def create_public_image_files
- build(:images)
- end
-
- def create_public_stylesheets_files
- build(:stylesheets)
- end
-
- def create_javascript_files
- build(:javascripts)
- end
-
def create_script_files
build(:script)
end
@@ -246,7 +228,7 @@ module Rails
end
def create_vendor_files
- build(:vendor_plugins)
+ build(:vendor)
end
def finish_template
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index c383d4842f..8ad64e38ed 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -4,28 +4,19 @@ source 'http://rubygems.org'
<%= database_gemfile_entry -%>
+# Asset template engines
+<%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%>
+gem 'sass'
+gem 'coffee-script'
+gem 'uglifier'
+
# Use unicorn as the web server
# gem 'unicorn'
# Deploy with Capistrano
# gem 'capistrano'
-# To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+)
-# gem 'ruby-debug'
-# gem 'ruby-debug19', :require => 'ruby-debug'
-
-# Bundle the extra gems:
-# gem 'bj'
-# gem 'nokogiri'
-# gem 'sqlite3'
-# gem 'rack-bug', :require => 'rack/bug'
-
-# Bundle gems for the local environment. Make sure to
-# put test-only gems in this group so their generators
-# and rake tasks are available in development mode:
-# group :development, :test do
-# gem 'webrat'
-# end
+# To use debugger
+# <%= gem_for_ruby_debugger %>
-# Needed for guides generation
-# gem "RedCloth", "~> 4.2"
+<%= gem_for_turn -%> \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.png b/railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.png
new file mode 100644
index 0000000000..d5edc04e65
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.png
Binary files differ
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
new file mode 100644
index 0000000000..fb5e91caf4
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
@@ -0,0 +1,11 @@
+// FIXME: Tell people that this is a manifest file, real code should go into discrete files
+// FIXME: Tell people how Sprockets and CoffeeScript works
+//
+//= require <%= options[:javascript] %>
+//= require <%= options[:javascript] %>_ujs
+<% if options[:javascript] == "prototype" %>
+//= require controls
+//= require dragdrop
+//= require effects
+<% end -%>
+//= require_tree .
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
new file mode 100644
index 0000000000..ccfff11a5d
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
@@ -0,0 +1,4 @@
+/*
+ * FIXME: Introduce SCSS & Sprockets
+ *= require_tree .
+*/ \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
index 6d56c331c1..c63d1b6ac5 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
@@ -2,8 +2,8 @@
<html>
<head>
<title><%= camelized %></title>
- <%%= stylesheet_link_tag :all %>
- <%%= javascript_include_tag :defaults %>
+ <%%= stylesheet_link_tag "application" %>
+ <%%= javascript_include_tag "application" %>
<%%= csrf_meta_tags %>
</head>
<body>
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 b7f64af339..ff8e6e5f3e 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -42,10 +42,10 @@ module <%= app_const_base %>
# JavaScript files you want as :defaults (application.js is always included).
<% if options[:skip_javascript] -%>
config.action_view.javascript_expansions[:defaults] = %w()
-<% elsif options[:javascript] == 'jquery' -%>
- config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
+<% elsif options[:javascript] == 'prototype' -%>
+ config.action_view.javascript_expansions[:defaults] = %w(prototype effects dragdrop controls rails)
<% else -%>
- # config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
+ # config.action_view.javascript_expansions[:defaults] = %w(prototype effects dragdrop controls rails)
<% end -%>
<% if options[:skip_test_unit] -%>
@@ -62,5 +62,8 @@ module <%= app_const_base %>
# Enable IdentityMap for Active Record, to disable set to false or remove the line below.
config.active_record.identity_map = true
<% end -%>
+
+ # Enable the asset pipeline
+ config.assets.enabled = true
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
new file mode 100644
index 0000000000..ca807c9f3f
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
@@ -0,0 +1,30 @@
+# MySQL. Versions 4.1 and 5.0 are recommended.
+#
+# Install the MySQL driver:
+# gem install activerecord-jdbcmysql-adapter
+#
+# And be sure to use new-style password hashing:
+# http://dev.mysql.com/doc/refman/5.0/en/old-client.html
+development:
+ adapter: jdbcmysql
+ database: <%= app_name %>_development
+ username: root
+ password:
+ host: localhost
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ adapter: jdbcmysql
+ database: <%= app_name %>_test
+ username: root
+ password:
+ host: localhost
+
+production:
+ adapter: jdbcmysql
+ database: <%= app_name %>_production
+ username: root
+ password:
+ host: localhost
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
new file mode 100644
index 0000000000..a228aca5d2
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
@@ -0,0 +1,48 @@
+# PostgreSQL. Versions 7.4 and 8.x are supported.
+#
+# Install the pg driver:
+# gem install pg
+# On Mac OS X with macports:
+# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
+# On Windows:
+# gem install pg
+# Choose the win32 build.
+# Install PostgreSQL and put its /bin directory on your path.
+development:
+ adapter: jdbcpostgresql
+ encoding: unicode
+ database: <%= app_name %>_development
+ username: <%= app_name %>
+ password:
+
+ # Connect on a TCP socket. Omitted by default since the client uses a
+ # domain socket that doesn't need configuration. Windows does not have
+ # domain sockets, so uncomment these lines.
+ #host: localhost
+ #port: 5432
+
+ # Schema search path. The server defaults to $user,public
+ #schema_search_path: myapp,sharedapp,public
+
+ # Minimum log levels, in increasing order:
+ # debug5, debug4, debug3, debug2, debug1,
+ # log, notice, warning, error, fatal, and panic
+ # The server defaults to notice.
+ #min_messages: warning
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ adapter: jdbcpostgresql
+ encoding: unicode
+ database: <%= app_name %>_test
+ username: <%= app_name %>
+ password:
+
+production:
+ adapter: jdbcpostgresql
+ encoding: unicode
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password:
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml
new file mode 100644
index 0000000000..30776b3b4e
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml
@@ -0,0 +1,17 @@
+# SQLite version 3.x
+# gem 'activerecord-jdbcsqlite3-adapter'
+
+development:
+ adapter: jdbcsqlite3
+ database: db/development.sqlite3
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ adapter: jdbcsqlite3
+ database: db/test.sqlite3
+
+production:
+ adapter: jdbcsqlite3
+ database: db/production.sqlite3
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 91d3133ea4..41b2374eda 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
@@ -3,7 +3,7 @@
# 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 webserver when you make code changes.
+ # since you don't have to restart the web server when you make code changes.
config.cache_classes = false
# Log error messages when you accidentally call methods on nil.
@@ -11,7 +11,6 @@
# Show full error reports and disable caching
config.consider_all_requests_local = true
- config.action_view.debug_rjs = true
config.action_controller.perform_caching = false
# Don't care if the mailer can't send
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 89bb891ddd..9553f3bdde 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,7 +1,6 @@
<%= app_const %>.configure do
# Settings specified here will take precedence over those in config/application.rb
- # The production environment is meant for finished, "live" apps.
# Code is not reloaded between requests
config.cache_classes = true
@@ -9,14 +8,19 @@
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
- # Specifies the header that your server uses for sending files
- config.action_dispatch.x_sendfile_header = "X-Sendfile"
+ # Disable Rails's static asset server (Apache or nginx will already do this)
+ config.serve_static_assets = false
- # For nginx:
- # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
+ # Compress both stylesheets and JavaScripts
+ config.assets.js_compressor = :uglifier
+ config.assets.css_compressor = :scss
+
+ # Specifies the header that your server uses for sending files
+ # (comment out if your front-end server doesn't support this)
+ config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx
- # If you have no front-end server that supports something like X-Sendfile,
- # just comment this out and Rails will serve the files
+ # 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)
# config.log_level = :debug
@@ -27,13 +31,12 @@
# Use a different cache store in production
# config.cache_store = :mem_cache_store
- # Disable Rails's static asset server
- # In production, Apache or nginx will already do this
- config.serve_static_assets = false
-
# 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)
+ # config.assets.precompile += %w( search.js )
+
# Disable delivery errors, bad email addresses will be ignored
# config.action_mailer.raise_delivery_errors = false
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
index 62aa06dc3e..ddfe4ba1e1 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
@@ -1,6 +1,6 @@
# Be sure to restart your server when you modify this file.
-<%= app_const %>.config.session_store :cookie_store, :key => '_<%= app_name %>_session'
+<%= app_const %>.config.session_store :cookie_store, <%= key_value :key, "'_#{app_name}_session'" %>
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
diff --git a/railties/lib/rails/generators/rails/app/templates/db/seeds.rb b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt
index 664d8c74c8..9a2efa68a7 100644
--- a/railties/lib/rails/generators/rails/app/templates/db/seeds.rb
+++ b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt
@@ -3,5 +3,5 @@
#
# Examples:
#
-# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
-# Mayor.create(:name => 'Daley', :city => cities.first)
+# cities = City.create([{ <%= key_value :name, "'Chicago'" %> }, { <%= key_value :name, "'Copenhagen'" %> }])
+# Mayor.create(<%= key_value :name, "'Daley'" %>, <%= key_value :city, "cities.first" %>)
diff --git a/railties/lib/rails/generators/rails/app/templates/public/index.html b/railties/lib/rails/generators/rails/app/templates/public/index.html
index 13a203dd08..9d9811a5bf 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/index.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/index.html
@@ -59,7 +59,7 @@
#header {
- background-image: url("images/rails.png");
+ background-image: url("/assets/rails.png");
background-repeat: no-repeat;
background-position: top left;
height: 64px;
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/application.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/application.js
deleted file mode 100644
index fe4577696b..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/application.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// Place your application-specific JavaScript functions and classes here
-// This file is automatically included by javascript_include_tag :defaults
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
index a8f7aeac7d..a8f7aeac7d 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/controls.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/controls.js
index 7392fb664c..7392fb664c 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/controls.js
+++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/controls.js
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/dragdrop.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/dragdrop.js
index 15c6dbca68..15c6dbca68 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/dragdrop.js
+++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/dragdrop.js
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/effects.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/effects.js
index c81e6c7d5f..c81e6c7d5f 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/effects.js
+++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/effects.js
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery.js
index aa3a4f34fd..aa3a4f34fd 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery.js
+++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery.js
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery_ujs.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js
index 4dcb3779a2..4dcb3779a2 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery_ujs.js
+++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype.js
index 474b2231bb..474b2231bb 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js
+++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype.js
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype_ujs.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js
index 2cd1220786..2cd1220786 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype_ujs.js
+++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js
diff --git a/railties/lib/rails/generators/rails/assets/USAGE b/railties/lib/rails/generators/rails/assets/USAGE
new file mode 100644
index 0000000000..adebfd7e6f
--- /dev/null
+++ b/railties/lib/rails/generators/rails/assets/USAGE
@@ -0,0 +1,20 @@
+Description:
+ Stubs out a new asset placeholders. Pass the asset name, either CamelCased
+ or under_scored.
+
+ To create assets within a folder, specify the assets name as a
+ path like 'parent/name'.
+
+ This generates a JavaScript stub in app/assets/javascripts and a stylesheet
+ stub in app/assets/stylesheets.
+
+ If CoffeeScript is available, JavaScripts will be generated with the .coffee extension.
+ If Sass 3 is available, stylesheets will be generated with the .scss extension.
+
+Example:
+ `rails generate assets posts`
+
+ Posts assets.
+ Javascript: app/assets/javascripts/posts.js
+ Stylesheet: app/assets/stylesheets/posts.css
+
diff --git a/railties/lib/rails/generators/rails/assets/assets_generator.rb b/railties/lib/rails/generators/rails/assets/assets_generator.rb
new file mode 100644
index 0000000000..80beb7abfe
--- /dev/null
+++ b/railties/lib/rails/generators/rails/assets/assets_generator.rb
@@ -0,0 +1,39 @@
+module Rails
+ module Generators
+ class AssetsGenerator < NamedBase
+ class_option :javascripts, :type => :boolean, :desc => "Generate javascripts"
+ class_option :stylesheets, :type => :boolean, :desc => "Generate stylesheets"
+
+ class_option :javascript_engine, :desc => "Engine for javascripts"
+ class_option :stylesheet_engine, :desc => "Engine for stylesheets"
+
+ def create_javascript_files
+ return unless options.javascripts?
+ copy_file "javascript.#{javascript_extension}",
+ File.join('app/assets/javascripts', class_path, "#{asset_name}.#{javascript_extension}")
+ end
+
+ def create_stylesheet_files
+ return unless options.stylesheets?
+ copy_file "stylesheet.#{stylesheet_extension}",
+ File.join('app/assets/stylesheets', class_path, "#{asset_name}.#{stylesheet_extension}")
+ end
+
+ protected
+
+ def asset_name
+ file_name
+ end
+
+ def javascript_extension
+ options.javascript_engine.present? ?
+ "js.#{options.javascript_engine}" : "js"
+ end
+
+ def stylesheet_extension
+ options.stylesheet_engine.present? ?
+ "css.#{options.stylesheet_engine}" : "css"
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/rails/assets/templates/javascript.js b/railties/lib/rails/generators/rails/assets/templates/javascript.js
new file mode 100644
index 0000000000..dee720facd
--- /dev/null
+++ b/railties/lib/rails/generators/rails/assets/templates/javascript.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee b/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee
new file mode 100644
index 0000000000..761567942f
--- /dev/null
+++ b/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/railties/lib/rails/generators/rails/assets/templates/stylesheet.css b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css
new file mode 100644
index 0000000000..7594abf268
--- /dev/null
+++ b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css
@@ -0,0 +1,4 @@
+/*
+ Place all the styles related to the matching controller here.
+ They will automatically be included in application.css.
+*/
diff --git a/railties/lib/rails/generators/rails/assets/templates/stylesheet.css.scss b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css.scss
new file mode 100644
index 0000000000..ba95e217cc
--- /dev/null
+++ b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css.scss
@@ -0,0 +1,5 @@
+/*
+ Place all the styles related to the matching controller here.
+ They will automatically be included in application.css.
+ You can use Sass (SCSS) here: http://sass-lang.com/
+*/
diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb
index 9788c0d0bc..74aa0432a8 100644
--- a/railties/lib/rails/generators/rails/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb
@@ -14,7 +14,7 @@ module Rails
end
end
- hook_for :template_engine, :test_framework, :helper
+ hook_for :template_engine, :test_framework, :helper, :assets
end
end
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 5b4687ce64..126aadb88d 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
@@ -9,10 +9,17 @@ module Rails
end
def app
- if options[:mountable]
+ if mountable?
directory "app"
template "#{app_templates_dir}/app/views/layouts/application.html.erb.tt",
"app/views/layouts/#{name}/application.html.erb"
+ empty_directory_with_gitkeep "app/assets/images"
+ elsif full?
+ empty_directory_with_gitkeep "app/models"
+ empty_directory_with_gitkeep "app/controllers"
+ empty_directory_with_gitkeep "app/views"
+ empty_directory_with_gitkeep "app/helpers"
+ empty_directory_with_gitkeep "app/assets/images"
end
end
@@ -61,8 +68,12 @@ task :default => :test
end
end
+ PASSTHROUGH_OPTIONS = [
+ :skip_active_record, :skip_javascript, :database, :javascript, :quiet, :pretend, :force, :skip
+ ]
+
def generate_test_dummy(force = false)
- opts = (options || {}).slice(:skip_active_record, :skip_javascript, :database, :javascript)
+ opts = (options || {}).slice(*PASSTHROUGH_OPTIONS)
opts[:force] = force
invoke Rails::Generators::AppGenerator,
@@ -84,7 +95,7 @@ task :default => :test
remove_file "doc"
remove_file "Gemfile"
remove_file "lib/tasks"
- remove_file "public/images/rails.png"
+ remove_file "app/assets/images/rails.png"
remove_file "public/index.html"
remove_file "public/robots.txt"
remove_file "README"
@@ -94,26 +105,36 @@ task :default => :test
end
def stylesheets
- empty_directory_with_gitkeep "public/stylesheets" if options[:mountable]
+ if mountable?
+ copy_file "#{app_templates_dir}/app/assets/stylesheets/application.css",
+ "app/assets/stylesheets/application.css"
+ elsif full?
+ empty_directory_with_gitkeep "app/assets/stylesheets"
+ end
end
def javascripts
- return unless options[:mountable]
-
- empty_directory "#{app_templates_dir}/public/javascripts"
+ return if options.skip_javascript?
- unless options[:skip_javascript]
- copy_file "#{app_templates_dir}/public/javascripts/#{options[:javascript]}.js", "public/javascripts/#{options[:javascript]}.js"
- copy_file "#{app_templates_dir}/public/javascripts/#{options[:javascript]}_ujs.js", "public/javascripts/rails.js"
+ if mountable?
+ copy_file "#{app_templates_dir}/app/assets/javascripts/application.js.tt",
+ "app/assets/javascripts/application.js"
+ copy_file "#{app_templates_dir}/vendor/assets/javascripts/#{options[:javascript]}.js",
+ "vendor/assets/javascripts/#{options[:javascript]}.js"
+ copy_file "#{app_templates_dir}/vendor/assets/javascripts/#{options[:javascript]}_ujs.js",
+ "vendor/assets/javascripts/#{options[:javascript]}_ujs.js"
if options[:javascript] == "prototype"
- copy_file "#{app_templates_dir}/public/javascripts/controls.js", "public/javascripts/controls.js"
- copy_file "#{app_templates_dir}/public/javascripts/dragdrop.js", "public/javascripts/dragdrop.js"
- copy_file "#{app_templates_dir}/public/javascripts/effects.js", "public/javascripts/effects.js"
+ copy_file "#{app_templates_dir}/vendor/assets/javascripts/controls.js",
+ "vendor/assets/javascripts/controls.js"
+ copy_file "#{app_templates_dir}/vendor/assets/javascripts/dragdrop.js",
+ "vendor/assets/javascripts/dragdrop.js"
+ copy_file "#{app_templates_dir}/vendor/assets/javascripts/effects.js",
+ "vendor/assets/javascripts/effects.js"
end
+ elsif full?
+ empty_directory_with_gitkeep "app/assets/javascripts"
end
-
- copy_file "#{app_templates_dir}/public/javascripts/application.js", "public/javascripts/application.js"
end
def script(force = false)
@@ -130,21 +151,22 @@ task :default => :test
alias_method :plugin_path, :app_path
- class_option :dummy_path, :type => :string, :default => "test/dummy",
- :desc => "Create dummy application at given path"
+ class_option :dummy_path, :type => :string, :default => "test/dummy",
+ :desc => "Create dummy application at given path"
- class_option :full, :type => :boolean, :default => false,
- :desc => "Generate rails engine with integration tests"
+ class_option :full, :type => :boolean, :default => false,
+ :desc => "Generate rails engine with integration tests"
- class_option :mountable, :type => :boolean, :default => false,
- :desc => "Generate mountable isolated application"
+ class_option :mountable, :type => :boolean, :default => false,
+ :desc => "Generate mountable isolated application"
- class_option :skip_gemspec, :type => :boolean, :default => false,
- :desc => "Skip gemspec file"
+ class_option :skip_gemspec, :type => :boolean, :default => false,
+ :desc => "Skip gemspec file"
def initialize(*args)
raise Error, "Options should be given after the plugin name. For details run: rails plugin --help" if args[0].blank?
+ @dummy_path = nil
super
end
@@ -179,6 +201,10 @@ task :default => :test
build(:javascripts)
end
+ def create_images_directory
+ build(:images)
+ end
+
def create_script_files
build(:script)
end
@@ -199,6 +225,7 @@ task :default => :test
public_task :apply_rails_template, :bundle_if_dev_or_edge
protected
+
def app_templates_dir
"../../app/templates"
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/models/.empty_directory b/railties/lib/rails/generators/rails/plugin_new/templates/app/models/.empty_directory
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/app/models/.empty_directory
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb b/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb
index dd4d2da4eb..824caecb24 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb
@@ -5,9 +5,8 @@ class NavigationTest < ActionDispatch::IntegrationTest
fixtures :all
<% end -%>
- # Replace this with your real tests.
- test "the truth" do
- assert true
- end
+ # test "the truth" do
+ # assert true
+ # end
end
diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
index 779f933785..6eef0dbe5b 100644
--- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
@@ -6,8 +6,27 @@ module Rails
remove_hook_for :resource_controller
remove_class_option :actions
+ class_option :stylesheets, :type => :boolean, :desc => "Generate stylesheets"
+ class_option :stylesheet_engine, :desc => "Engine for stylesheets"
+
hook_for :scaffold_controller, :required => true
- hook_for :stylesheets
+
+ def copy_stylesheets_file
+ if behavior == :invoke && options.stylesheets?
+ template "scaffold.#{stylesheet_extension}", "app/assets/stylesheets/scaffold.#{stylesheet_extension}"
+ end
+ end
+
+ hook_for :assets do |assets|
+ invoke assets, [controller_name]
+ end
+
+ private
+
+ def stylesheet_extension
+ options.stylesheet_engine.present? ?
+ "css.#{options.stylesheet_engine}" : "css"
+ end
end
end
end
diff --git a/railties/lib/rails/generators/rails/stylesheets/templates/scaffold.css b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
index 1ae7000299..1ae7000299 100644
--- a/railties/lib/rails/generators/rails/stylesheets/templates/scaffold.css
+++ b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
diff --git a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css.scss b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css.scss
new file mode 100644
index 0000000000..45116b53f6
--- /dev/null
+++ b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css.scss
@@ -0,0 +1,58 @@
+body { background-color: #fff; color: #333; }
+
+body, p, ol, ul, td {
+ font-family: verdana, arial, helvetica, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+}
+
+pre {
+ background-color: #eee;
+ padding: 10px;
+ font-size: 11px;
+}
+
+a {
+ color: #000;
+ &:visited { color: #666; }
+ &:hover { color: #fff; background-color:#000; }
+}
+
+div.field, div.actions {
+ margin-bottom: 10px;
+}
+
+#notice {
+ color: green;
+}
+
+.field_with_errors {
+ padding: 2px;
+ background-color: red;
+ display: table;
+}
+
+#error_explanation {
+ width: 450px;
+ border: 2px solid red;
+ padding: 7px;
+ padding-bottom: 0;
+ margin-bottom: 20px;
+ background-color: #f0f0f0;
+
+ h2 {
+ text-align: left;
+ font-weight: bold;
+ padding: 5px 5px 5px 15px;
+ font-size: 12px;
+ margin: -7px;
+ margin-bottom: 0px;
+ background-color: #c00;
+ color: #fff;
+ }
+
+ ul li {
+ font-size: 12px;
+ list-style: square;
+ }
+} \ No newline at end of file
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 b5317a055b..32b961d9fc 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -1,35 +1,35 @@
<% module_namespacing do -%>
class <%= controller_class_name %>Controller < ApplicationController
# GET <%= route_url %>
- # GET <%= route_url %>.xml
+ # GET <%= route_url %>.json
def index
@<%= plural_table_name %> = <%= orm_class.all(class_name) %>
respond_to do |format|
format.html # index.html.erb
- format.xml { render :xml => @<%= plural_table_name %> }
+ format.json { render <%= key_value :json, "@#{plural_table_name}" %> }
end
end
# GET <%= route_url %>/1
- # GET <%= route_url %>/1.xml
+ # GET <%= route_url %>/1.json
def show
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
respond_to do |format|
format.html # show.html.erb
- format.xml { render :xml => @<%= singular_table_name %> }
+ format.json { render <%= key_value :json, "@#{singular_table_name}" %> }
end
end
# GET <%= route_url %>/new
- # GET <%= route_url %>/new.xml
+ # GET <%= route_url %>/new.json
def new
@<%= singular_table_name %> = <%= orm_class.build(class_name) %>
respond_to do |format|
format.html # new.html.erb
- format.xml { render :xml => @<%= singular_table_name %> }
+ format.json { render <%= key_value :json, "@#{singular_table_name}" %> }
end
end
@@ -39,46 +39,46 @@ class <%= controller_class_name %>Controller < ApplicationController
end
# POST <%= route_url %>
- # POST <%= route_url %>.xml
+ # POST <%= route_url %>.json
def create
@<%= singular_table_name %> = <%= orm_class.build(class_name, "params[:#{singular_table_name}]") %>
respond_to do |format|
if @<%= orm_instance.save %>
- format.html { redirect_to(@<%= singular_table_name %>, :notice => '<%= human_name %> was successfully created.') }
- format.xml { render :xml => @<%= singular_table_name %>, :status => :created, :location => @<%= singular_table_name %> }
+ format.html { redirect_to @<%= singular_table_name %>, <%= key_value :notice, "'#{human_name} was successfully created.'" %> }
+ format.json { render <%= key_value :json, "@#{singular_table_name}" %>, <%= key_value :status, ':created' %>, <%= key_value :location, "@#{singular_table_name}" %> }
else
- format.html { render :action => "new" }
- format.xml { render :xml => @<%= orm_instance.errors %>, :status => :unprocessable_entity }
+ format.html { render <%= key_value :action, '"new"' %> }
+ format.json { render <%= key_value :json, "@#{orm_instance.errors}" %>, <%= key_value :status, ':unprocessable_entity' %> }
end
end
end
# PUT <%= route_url %>/1
- # PUT <%= route_url %>/1.xml
+ # PUT <%= route_url %>/1.json
def update
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
respond_to do |format|
if @<%= orm_instance.update_attributes("params[:#{singular_table_name}]") %>
- format.html { redirect_to(@<%= singular_table_name %>, :notice => '<%= human_name %> was successfully updated.') }
- format.xml { head :ok }
+ format.html { redirect_to @<%= singular_table_name %>, <%= key_value :notice, "'#{human_name} was successfully updated.'" %> }
+ format.json { head :ok }
else
- format.html { render :action => "edit" }
- format.xml { render :xml => @<%= orm_instance.errors %>, :status => :unprocessable_entity }
+ format.html { render <%= key_value :action, '"edit"' %> }
+ format.json { render <%= key_value :json, "@#{orm_instance.errors}" %>, <%= key_value :status, ':unprocessable_entity' %> }
end
end
end
# DELETE <%= route_url %>/1
- # DELETE <%= route_url %>/1.xml
+ # DELETE <%= route_url %>/1.json
def destroy
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
@<%= orm_instance.destroy %>
respond_to do |format|
- format.html { redirect_to(<%= index_helper %>_url) }
- format.xml { head :ok }
+ format.html { redirect_to <%= index_helper %>_url }
+ format.json { head :ok }
end
end
end
diff --git a/railties/lib/rails/generators/rails/stylesheets/USAGE b/railties/lib/rails/generators/rails/stylesheets/USAGE
deleted file mode 100644
index 59e5495d0b..0000000000
--- a/railties/lib/rails/generators/rails/stylesheets/USAGE
+++ /dev/null
@@ -1,5 +0,0 @@
-Description:
- Copies scaffold stylesheets to public/stylesheets/.
-
-Examples:
- `rails generate stylesheets`
diff --git a/railties/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb b/railties/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb
deleted file mode 100644
index ce68443c39..0000000000
--- a/railties/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module Rails
- module Generators
- class StylesheetsGenerator < Base
- def copy_stylesheets_file
- template "scaffold.css", "public/stylesheets/scaffold.css" if behavior == :invoke
- end
- end
- end
-end
diff --git a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
index 11a73ebad7..0bc5fd8ca2 100644
--- a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
@@ -3,10 +3,9 @@ require 'test_helper'
<% module_namespacing do -%>
class <%= class_name %>ControllerTest < ActionController::TestCase
<% if actions.empty? -%>
- # Replace this with your real tests.
- test "the truth" do
- assert true
- end
+ # test "the truth" do
+ # assert true
+ # end
<% else -%>
<% for action in actions -%>
test "should get <%= action %>" do
diff --git a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb
index de0823749c..e7a06e4a73 100644
--- a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb
+++ b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb
@@ -3,8 +3,7 @@ require 'test_helper'
class <%= class_name %>Test < ActionDispatch::IntegrationTest
fixtures :all
- # Replace this with your real tests.
- test "the truth" do
- assert true
- end
+ # test "the truth" do
+ # assert true
+ # end
end
diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb
index b62c7fd279..c05102290c 100644
--- a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb
@@ -13,10 +13,9 @@ class <%= class_name %>Test < ActionMailer::TestCase
<% end -%>
<% if actions.blank? -%>
- # replace this with your real tests
- test "the truth" do
- assert true
- end
+ # test "the truth" do
+ # assert true
+ # end
<% end -%>
end
<% end -%>
diff --git a/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb b/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb
index 6f79879838..c9bc7d5b90 100644
--- a/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb
+++ b/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb
@@ -2,9 +2,8 @@ require 'test_helper'
<% module_namespacing do -%>
class <%= class_name %>Test < ActiveSupport::TestCase
- # Replace this with your real tests.
- test "the truth" do
- assert true
- end
+ # test "the truth" do
+ # assert true
+ # end
end
<% end -%>
diff --git a/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb b/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb
index cd116f5ce9..28aa23626a 100644
--- a/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb
+++ b/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb
@@ -2,9 +2,8 @@ require 'test_helper'
<% module_namespacing do -%>
class <%= class_name %>ObserverTest < ActiveSupport::TestCase
- # Replace this with your real tests.
- test "the truth" do
- assert true
- end
+ # test "the truth" do
+ # assert true
+ # end
end
<% end -%>
diff --git a/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt b/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt
index 3e0bc29d3a..0cbae1120e 100644
--- a/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt
+++ b/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt
@@ -1,8 +1,7 @@
require 'test_helper'
class <%= class_name %>Test < ActiveSupport::TestCase
- # Replace this with your real tests.
- test "the truth" do
- assert true
- end
+ # test "the truth" do
+ # assert true
+ # end
end
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
index 964d59d84c..01fe6dda7a 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
@@ -19,30 +19,30 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
test "should create <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count') do
- post :create, :<%= singular_table_name %> => @<%= singular_table_name %>.attributes
+ post :create, <%= key_value singular_table_name, "@#{singular_table_name}.attributes" %>
end
assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
end
test "should show <%= singular_table_name %>" do
- get :show, :id => @<%= singular_table_name %>.to_param
+ get :show, <%= key_value :id, "@#{singular_table_name}.to_param" %>
assert_response :success
end
test "should get edit" do
- get :edit, :id => @<%= singular_table_name %>.to_param
+ get :edit, <%= key_value :id, "@#{singular_table_name}.to_param" %>
assert_response :success
end
test "should update <%= singular_table_name %>" do
- put :update, :id => @<%= singular_table_name %>.to_param, :<%= singular_table_name %> => @<%= singular_table_name %>.attributes
+ put :update, <%= key_value :id, "@#{singular_table_name}.to_param" %>, <%= key_value singular_table_name, "@#{singular_table_name}.attributes" %>
assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
end
test "should destroy <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count', -1) do
- delete :destroy, :id => @<%= singular_table_name %>.to_param
+ delete :destroy, <%= key_value :id, "@#{singular_table_name}.to_param" %>
end
assert_redirected_to <%= index_helper %>_path
diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb
index e4368866a1..bfd2a73aeb 100644
--- a/railties/lib/rails/railtie/configuration.rb
+++ b/railties/lib/rails/railtie/configuration.rb
@@ -31,26 +31,34 @@ module Rails
app_generators(&block)
end
+ # First configurable block to run. Called before any initializers are run.
def before_configuration(&block)
ActiveSupport.on_load(:before_configuration, :yield => true, &block)
end
+ # Third configurable block to run. Does not run if config.cache_classes
+ # set to false.
def before_eager_load(&block)
ActiveSupport.on_load(:before_eager_load, :yield => true, &block)
end
+ # Second configurable block to run. Called before frameworks initialize.
def before_initialize(&block)
ActiveSupport.on_load(:before_initialize, :yield => true, &block)
end
+ # Last configurable block to run. Called after frameworks initialize.
def after_initialize(&block)
ActiveSupport.on_load(:after_initialize, :yield => true, &block)
end
+ # Array of callbacks defined by #to_prepare.
def to_prepare_blocks
@@to_prepare_blocks ||= []
end
+ # Defines generic callbacks to run before #after_initialize. Useful for
+ # Rails::Railtie subclasses.
def to_prepare(&blk)
to_prepare_blocks << blk if blk
end
@@ -59,13 +67,6 @@ module Rails
super || @@options.key?(name.to_sym)
end
- # static_asset_paths is a Hash containing asset_paths
- # with associated public folders, like:
- # { "/" => "/app/public", "/my_engine" => "app/engines/my_engine/public" }
- def static_asset_paths
- @@static_asset_paths ||= ActiveSupport::OrderedHash.new
- end
-
private
def method_missing(name, *args, &blk)
diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb
index 591fd6f6bd..6d6e7f8b59 100644
--- a/railties/lib/rails/source_annotation_extractor.rb
+++ b/railties/lib/rails/source_annotation_extractor.rb
@@ -11,7 +11,7 @@
#
# Annotations are looked for in comments and modulus whitespace they have to
# start with the tag optionally followed by a colon. Everything up to the end
-# of the line (or closing ERb comment tag) is considered to be their text.
+# of the line (or closing ERB comment tag) is considered to be their text.
class SourceAnnotationExtractor
class Annotation < Struct.new(:line, :tag, :text)
@@ -30,7 +30,7 @@ class SourceAnnotationExtractor
# Prints all annotations with tag +tag+ under the root directories +app+, +lib+,
# and +test+ (recursively). Only filenames with extension +.builder+, +.rb+,
- # +.rxml+, +.rjs+, +.rhtml+, or +.erb+ are taken into account. The +options+
+ # +.rxml+, +.rhtml+, or +.erb+ are taken into account. The +options+
# hash is passed to each annotation's +to_s+.
#
# This class method is the single entry point for the rake tasks.
@@ -47,7 +47,7 @@ class SourceAnnotationExtractor
# Returns a hash that maps filenames under +dirs+ (recursively) to arrays
# with their annotations. Only files with annotations are included, and only
- # those with extension +.builder+, +.rb+, +.rxml+, +.rjs+, +.rhtml+, and +.erb+
+ # those with extension +.builder+, +.rb+, +.rxml+, +.rhtml+, and +.erb+
# are taken into account.
def find(dirs=%w(app lib test))
dirs.inject({}) { |h, dir| h.update(find_in(dir)) }
@@ -55,7 +55,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+, +.rxml+, +.rjs+, +.rhtml+, and +.erb+
+ # those with extension +.builder+, +.rb+, +.rxml+, +.rhtml+, and +.erb+
# are taken into account.
def find_in(dir)
results = {}
@@ -99,4 +99,4 @@ class SourceAnnotationExtractor
puts
end
end
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb
index af52014728..166d518f7c 100644
--- a/railties/lib/rails/tasks.rb
+++ b/railties/lib/rails/tasks.rb
@@ -3,6 +3,7 @@ $VERBOSE = nil
# Load Rails rakefile extensions
%w(
annotations
+ assets
documentation
framework
log
@@ -11,7 +12,6 @@ $VERBOSE = nil
routes
statistics
tmp
- railties
).each do |task|
load "rails/tasks/#{task}.rake"
end
diff --git a/railties/lib/rails/tasks/assets.rake b/railties/lib/rails/tasks/assets.rake
new file mode 100644
index 0000000000..158df31749
--- /dev/null
+++ b/railties/lib/rails/tasks/assets.rake
@@ -0,0 +1,10 @@
+namespace :assets do
+ desc "Compile all the assets named in config.assets.precompile"
+ task :precompile => :environment do
+ # Give assets access to asset_path
+ ActionView::Helpers::SprocketsHelper
+
+ assets = Rails.application.config.assets.precompile
+ Rails.application.assets.precompile(*assets)
+ end
+end
diff --git a/railties/lib/rails/tasks/railties.rake b/railties/lib/rails/tasks/railties.rake
deleted file mode 100644
index 16703879cf..0000000000
--- a/railties/lib/rails/tasks/railties.rake
+++ /dev/null
@@ -1,29 +0,0 @@
-namespace :railties do
- namespace :install do
- # desc "Copies missing assets from Railties (e.g. plugins, engines). You can specify Railties to use with FROM=railtie1,railtie2"
- task :assets => :rails_env do
- require 'rails/generators/base'
- Rails.application.initialize!
-
- to_load = ENV["FROM"].blank? ? :all : ENV["FROM"].split(",").map {|n| n.strip }
- app_public_path = Rails.application.paths["public"].first
-
- Rails.application.railties.all do |railtie|
- next unless to_load == :all || to_load.include?(railtie.railtie_name)
-
- if railtie.respond_to?(:paths) && (path = railtie.paths["public"].first) &&
- (assets_dir = railtie.config.compiled_asset_path) && File.exist?(path)
-
- Rails::Generators::Base.source_root(path)
- copier = Rails::Generators::Base.new
- Dir[File.join(path, "**/*")].each do |file|
- relative = file.gsub(/^#{path}\//, '')
- if File.file?(file)
- copier.copy_file relative, File.join(app_public_path, assets_dir, relative)
- end
- end
- end
- end
- end
- end
-end
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index 00029e627e..41485c8bac 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -13,6 +13,18 @@ if defined?(Test::Unit::Util::BacktraceFilter) && ENV['BACKTRACE'].nil?
Test::Unit::Util::BacktraceFilter.module_eval { include Rails::BacktraceFilterForTestUnit }
end
+if defined?(MiniTest)
+ # Enable turn if it is available
+ begin
+ require 'turn'
+
+ if MiniTest::Unit.respond_to?(:use_natural_language_case_names=)
+ MiniTest::Unit.use_natural_language_case_names = true
+ end
+ rescue LoadError
+ end
+end
+
if defined?(ActiveRecord)
require 'active_record/test_case'
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index c3793d3ac3..cd0646b8ed 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -17,10 +17,10 @@ Gem::Specification.new do |s|
s.require_path = 'lib'
s.rdoc_options << '--exclude' << '.'
- s.has_rdoc = false
s.add_dependency('rake', '>= 0.8.7')
s.add_dependency('thor', '~> 0.14.4')
+ s.add_dependency('rack-ssl', '~> 1.3.2')
s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version)
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 044fd2a278..ab3eb4c9e7 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -1,5 +1,18 @@
require "isolation/abstract_unit"
+class ::MyMailInterceptor
+ def self.delivering_email(email); email; end
+end
+
+class ::MyOtherMailInterceptor < ::MyMailInterceptor; end
+
+class ::MyMailObserver
+ def self.delivered_email(email); email; end
+end
+
+class ::MyOtherMailObserver < ::MyMailObserver; end
+
+
module ApplicationTests
class ConfigurationTest < Test::Unit::TestCase
include ActiveSupport::Testing::Isolation
@@ -245,6 +258,92 @@ module ApplicationTests
assert_equal res, last_response.body # value should be unchanged
end
+ test "sets all Active Record models to whitelist all attributes by default" do
+ add_to_config <<-RUBY
+ config.active_record.whitelist_attributes = true
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert_equal ActiveModel::MassAssignmentSecurity::WhiteList,
+ ActiveRecord::Base.active_authorizers[:default].class
+ assert_equal [""], ActiveRecord::Base.active_authorizers[:default].to_a
+ end
+
+ test "registers interceptors with ActionMailer" do
+ add_to_config <<-RUBY
+ config.action_mailer.interceptors = MyMailInterceptor
+ RUBY
+
+ require "#{app_path}/config/environment"
+ require "mail"
+
+ ActionMailer::Base
+
+ assert_equal [::MyMailInterceptor], ::Mail.send(:class_variable_get, "@@delivery_interceptors")
+ end
+
+ test "registers multiple interceptors with ActionMailer" do
+ add_to_config <<-RUBY
+ config.action_mailer.interceptors = [MyMailInterceptor, "MyOtherMailInterceptor"]
+ RUBY
+
+ require "#{app_path}/config/environment"
+ require "mail"
+
+ ActionMailer::Base
+
+ assert_equal [::MyMailInterceptor, ::MyOtherMailInterceptor], ::Mail.send(:class_variable_get, "@@delivery_interceptors")
+ end
+
+ test "registers observers with ActionMailer" do
+ add_to_config <<-RUBY
+ config.action_mailer.observers = MyMailObserver
+ RUBY
+
+ require "#{app_path}/config/environment"
+ require "mail"
+
+ ActionMailer::Base
+
+ assert_equal [::MyMailObserver], ::Mail.send(:class_variable_get, "@@delivery_notification_observers")
+ end
+
+ test "registers multiple observers with ActionMailer" do
+ add_to_config <<-RUBY
+ config.action_mailer.observers = [MyMailObserver, "MyOtherMailObserver"]
+ RUBY
+
+ require "#{app_path}/config/environment"
+ require "mail"
+
+ ActionMailer::Base
+
+ assert_equal [::MyMailObserver, ::MyOtherMailObserver], ::Mail.send(:class_variable_get, "@@delivery_notification_observers")
+ end
+
+ test "valid timezone is setup correctly" do
+ add_to_config <<-RUBY
+ config.root = "#{app_path}"
+ config.time_zone = "Wellington"
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert_equal "Wellington", Rails.application.config.time_zone
+ end
+
+ test "raises when an invalid timezone is defined in the config" do
+ add_to_config <<-RUBY
+ config.root = "#{app_path}"
+ config.time_zone = "That big hill over yonder hill"
+ RUBY
+
+ assert_raise(ArgumentError) do
+ require "#{app_path}/config/environment"
+ end
+ end
+
test "config.action_controller.perform_caching = false" do
make_basic_app do |app|
app.config.action_controller.perform_caching = false
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index b314832685..01e6c49d9c 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -52,6 +52,12 @@ module ApplicationTests
assert_equal "Rack::Cache", middleware.first
end
+ test "Rack::SSL is present when force_ssl is set" do
+ add_to_config "config.force_ssl = true"
+ boot!
+ assert middleware.include?("Rack::SSL")
+ end
+
test "removing Active Record omits its middleware" do
use_frameworks []
boot!
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 018c2fa6bf..1902484301 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -8,6 +8,8 @@ DEFAULT_APP_FILES = %w(
Gemfile
Rakefile
config.ru
+ app/assets/javascripts
+ app/assets/stylesheets
app/controllers
app/helpers
app/mailers
@@ -21,9 +23,7 @@ DEFAULT_APP_FILES = %w(
lib
lib/tasks
log
- public/images
- public/javascripts
- public/stylesheets
+ app/assets/images
script/rails
test/fixtures
test/functional
@@ -31,11 +31,9 @@ DEFAULT_APP_FILES = %w(
test/performance
test/unit
vendor
+ vendor/assets
vendor/plugins
- tmp/sessions
- tmp/sockets
tmp/cache
- tmp/pids
)
class AppGeneratorTest < Rails::Generators::TestCase
@@ -49,8 +47,9 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_application_controller_and_layout_files
run_generator
- assert_file "app/views/layouts/application.html.erb", /stylesheet_link_tag :all/
- assert_no_file "public/stylesheets/application.css"
+ assert_file "app/views/layouts/application.html.erb", /stylesheet_link_tag\s+"application"/
+ assert_file "app/views/layouts/application.html.erb", /javascript_include_tag\s+"application"/
+ assert_file "app/assets/stylesheets/application.css"
end
def test_invalid_application_name_raises_an_error
@@ -132,6 +131,24 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile", /^gem\s+["']mysql2["']$/
end
+ def test_config_jdbcmysql_database
+ run_generator([destination_root, "-d", "jdbcmysql"])
+ assert_file "config/database.yml", /jdbcmysql/
+ assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/
+ end
+
+ def test_config_jdbcsqlite3_database
+ run_generator([destination_root, "-d", "jdbcsqlite3"])
+ assert_file "config/database.yml", /jdbcsqlite3/
+ assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/
+ end
+
+ def test_config_jdbcpostgresql_database
+ run_generator([destination_root, "-d", "jdbcpostgresql"])
+ assert_file "config/database.yml", /jdbcpostgresql/
+ assert_file "Gemfile", /^gem\s+["']activerecord-jdbcpostgresql-adapter["']$/
+ end
+
def test_generator_if_skip_active_record_is_given
run_generator [destination_root, "--skip-active-record"]
assert_no_file "config/database.yml"
@@ -146,43 +163,40 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
end
- def test_prototype_and_test_unit_are_added_by_default
+ def test_jquery_and_test_unit_are_added_by_default
run_generator
- assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(jquery rails\)/
- assert_file "public/javascripts/application.js"
- assert_file "public/javascripts/prototype.js"
- assert_file "public/javascripts/rails.js"
- assert_file "public/javascripts/controls.js"
- assert_file "public/javascripts/dragdrop.js"
- assert_file "public/javascripts/effects.js"
+ assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(prototype effects dragdrop controls rails\)/
+ assert_file "app/assets/javascripts/application.js"
+ assert_file "vendor/assets/javascripts/jquery.js"
+ assert_file "vendor/assets/javascripts/jquery_ujs.js"
assert_file "test"
end
def test_javascript_is_skipped_if_required
run_generator [destination_root, "--skip-javascript"]
assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(\)/
- assert_file "public/javascripts/application.js"
- assert_no_file "public/javascripts/prototype.js"
- assert_no_file "public/javascripts/rails.js"
+ assert_file "app/assets/javascripts/application.js"
+ assert_no_file "vendor/assets/javascripts/jquery.js"
+ assert_no_file "vendor/assets/javascripts/jquery_ujs.js"
end
def test_config_prototype_javascript_library
run_generator [destination_root, "-j", "prototype"]
- assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(jquery rails\)/
- assert_file "public/javascripts/application.js"
- assert_file "public/javascripts/prototype.js"
- assert_file "public/javascripts/controls.js"
- assert_file "public/javascripts/dragdrop.js"
- assert_file "public/javascripts/effects.js"
- assert_file "public/javascripts/rails.js", /prototype/
+ assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(prototype effects dragdrop controls rails\)/
+ assert_file "app/assets/javascripts/application.js"
+ assert_file "vendor/assets/javascripts/prototype.js"
+ assert_file "vendor/assets/javascripts/effects.js"
+ assert_file "vendor/assets/javascripts/dragdrop.js"
+ assert_file "vendor/assets/javascripts/controls.js"
+ assert_file "vendor/assets/javascripts/prototype_ujs.js", /prototype/
end
def test_config_jquery_javascript_library
run_generator [destination_root, "-j", "jquery"]
- assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(jquery rails\)/
- assert_file "public/javascripts/application.js"
- assert_file "public/javascripts/jquery.js"
- assert_file "public/javascripts/rails.js", /jQuery/
+ assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(prototype effects dragdrop controls rails\)/
+ assert_file "app/assets/javascripts/application.js"
+ assert_file "vendor/assets/javascripts/jquery.js"
+ assert_file "vendor/assets/javascripts/jquery_ujs.js", /jQuery/
end
def test_template_from_dir_pwd
@@ -216,6 +230,24 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_new_hash_style
+ run_generator [destination_root]
+ assert_file "config/initializers/session_store.rb" do |file|
+ if RUBY_VERSION < "1.9"
+ assert_match /config.session_store :cookie_store, :key => '_.+_session'/, file
+ else
+ assert_match /config.session_store :cookie_store, key: '_.+_session'/, file
+ end
+ end
+ end
+
+ def test_force_old_style_hash
+ run_generator [destination_root, "--old-style-hash"]
+ assert_file "config/initializers/session_store.rb" do |file|
+ assert_match /config.session_store :cookie_store, :key => '_.+_session'/, file
+ end
+ end
+
protected
def action(*args, &block)
diff --git a/railties/test/generators/assets_generator_test.rb b/railties/test/generators/assets_generator_test.rb
new file mode 100644
index 0000000000..375632e5bc
--- /dev/null
+++ b/railties/test/generators/assets_generator_test.rb
@@ -0,0 +1,26 @@
+require 'generators/generators_test_helper'
+require 'rails/generators/rails/assets/assets_generator'
+
+# FIXME: Silence the 'Could not find task "using_coffee?"' message in tests due to the public stub
+class AssetsGeneratorTest < Rails::Generators::TestCase
+ include GeneratorsTestHelper
+ arguments %w(posts)
+
+ def test_assets
+ run_generator
+ assert_file "app/assets/javascripts/posts.js.coffee"
+ assert_file "app/assets/stylesheets/posts.css.scss"
+ end
+
+ def test_skipping_assets
+ content = run_generator ["posts", "--no-stylesheets", "--no-javascripts"]
+ assert_no_file "app/assets/javascripts/posts.js.coffee"
+ assert_no_file "app/assets/stylesheets/posts.css.scss"
+ end
+
+ def test_vanilla_assets
+ run_generator ["posts", "--no-javascript-engine", "--no-stylesheet-engine"]
+ assert_file "app/assets/javascripts/posts.js"
+ assert_file "app/assets/stylesheets/posts.css"
+ end
+end
diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb
index be99dc068d..2dfc91c683 100644
--- a/railties/test/generators/controller_generator_test.rb
+++ b/railties/test/generators/controller_generator_test.rb
@@ -37,6 +37,12 @@ class ControllerGeneratorTest < Rails::Generators::TestCase
assert_no_file "test/unit/helpers/account_helper_test.rb"
end
+ def test_invokes_assets
+ run_generator
+ assert_file "app/assets/javascripts/account.js.coffee"
+ assert_file "app/assets/stylesheets/account.css.scss"
+ end
+
def test_invokes_default_test_framework
run_generator
assert_file "test/functional/account_controller_test.rb"
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index d61a02d32f..eb56e8d1a4 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -248,7 +248,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
assert_file "test/unit/helpers/test_app/product_lines_helper_test.rb"
# Stylesheets
- assert_file "public/stylesheets/scaffold.css"
+ assert_file "app/assets/stylesheets/scaffold.css.scss"
end
def test_scaffold_on_revoke
@@ -279,7 +279,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
assert_no_file "test/unit/helpers/test_app/product_lines_helper_test.rb"
# Stylesheets (should not be removed)
- assert_file "public/stylesheets/scaffold.css"
+ assert_file "app/assets/stylesheets/scaffold.css.scss"
end
def test_scaffold_with_namespace_on_invoke
@@ -320,7 +320,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
assert_file "test/unit/helpers/test_app/admin/roles_helper_test.rb"
# Stylesheets
- assert_file "public/stylesheets/scaffold.css"
+ assert_file "app/assets/stylesheets/scaffold.css.scss"
end
def test_scaffold_with_namespace_on_revoke
@@ -352,6 +352,6 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
assert_no_file "test/unit/helpers/test_app/admin/roles_helper_test.rb"
# Stylesheets (should not be removed)
- assert_file "public/stylesheets/scaffold.css"
+ assert_file "app/assets/stylesheets/scaffold.css.scss"
end
end
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 2a8b337144..fb956a8335 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -55,7 +55,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_ensure_that_plugin_options_are_not_passed_to_app_generator
FileUtils.cd(Rails.root)
- assert_no_match /It works from file!.*It works_from_file/, run_generator([destination_root, "-m", "lib/template.rb"])
+ assert_no_match(/It works from file!.*It works_from_file/, run_generator([destination_root, "-m", "lib/template.rb"]))
end
def test_ensure_that_test_dummy_can_be_generated_from_a_template
@@ -85,7 +85,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_ensure_that_skip_active_record_option_is_passed_to_app_generator
run_generator [destination_root, "--skip_active_record"]
assert_no_file "test/dummy/config/database.yml"
- assert_no_match /ActiveRecord/, File.read(File.join(destination_root, "test/test_helper.rb"))
+ assert_no_match(/ActiveRecord/, File.read(File.join(destination_root, "test/test_helper.rb")))
end
def test_ensure_that_database_option_is_passed_to_app_generator
@@ -95,75 +95,77 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_skipping_javascripts_without_mountable_option
run_generator
- assert_no_file "public/javascripts/prototype.js"
- assert_no_file "public/javascripts/rails.js"
- assert_no_file "public/javascripts/controls.js"
- assert_no_file "public/javascripts/dragdrop.js"
- assert_no_file "public/javascripts/dragdrop.js"
- assert_no_file "public/javascripts/application.js"
+ assert_no_file "app/assets/javascripts/application.js"
+ assert_no_file "vendor/assets/javascripts/jquery.js"
+ assert_no_file "vendor/assets/javascripts/jquery_ujs.js"
end
def test_javascripts_generation
run_generator [destination_root, "--mountable"]
- assert_file "public/javascripts/rails.js"
- assert_file "public/javascripts/prototype.js"
- assert_file "public/javascripts/controls.js"
- assert_file "public/javascripts/dragdrop.js"
- assert_file "public/javascripts/dragdrop.js"
- assert_file "public/javascripts/application.js"
+ assert_file "app/assets/javascripts/application.js"
+ assert_file "vendor/assets/javascripts/jquery.js"
+ assert_file "vendor/assets/javascripts/jquery_ujs.js"
end
def test_skip_javascripts
run_generator [destination_root, "--skip-javascript", "--mountable"]
- assert_no_file "public/javascripts/prototype.js"
- assert_no_file "public/javascripts/rails.js"
- assert_no_file "public/javascripts/controls.js"
- assert_no_file "public/javascripts/dragdrop.js"
- assert_no_file "public/javascripts/dragdrop.js"
+ assert_no_file "app/assets/javascripts/application.js"
+ assert_no_file "vendor/assets/javascripts/jquery.js"
+ assert_no_file "vendor/assets/javascripts/jquery_ujs.js"
end
- def test_ensure_that_javascript_option_is_passed_to_app_generator
- run_generator [destination_root, "--javascript", "jquery"]
- assert_file "test/dummy/public/javascripts/jquery.js"
- end
-
- def test_ensure_that_skip_javascript_option_is_passed_to_app_generator
- run_generator [destination_root, "--skip_javascript"]
- assert_no_file "test/dummy/public/javascripts/prototype.js"
+ def test_config_prototype_javascript_library
+ run_generator [destination_root, "-j", "prototype", "--mountable"]
+ assert_file "app/assets/javascripts/application.js"
+ assert_file "vendor/assets/javascripts/prototype.js"
+ assert_file "vendor/assets/javascripts/effects.js"
+ assert_file "vendor/assets/javascripts/dragdrop.js"
+ assert_file "vendor/assets/javascripts/controls.js"
+ assert_file "vendor/assets/javascripts/prototype_ujs.js", /prototype/
end
def test_template_from_dir_pwd
FileUtils.cd(Rails.root)
- assert_match /It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"])
+ assert_match(/It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"]))
end
def test_ensure_that_tests_works
run_generator
FileUtils.cd destination_root
`bundle install`
- assert_match /1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`
+ assert_match(/1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`)
end
def test_ensure_that_tests_works_in_full_mode
run_generator [destination_root, "--full", "--skip_active_record"]
FileUtils.cd destination_root
`bundle install`
- assert_match /2 tests, 2 assertions, 0 failures, 0 errors/, `bundle exec rake test`
+ assert_match(/1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`)
end
def test_creating_engine_in_full_mode
run_generator [destination_root, "--full"]
+ assert_file "app/assets/javascripts"
+ assert_file "app/assets/stylesheets"
+ assert_file "app/assets/images"
+ assert_file "app/models"
+ assert_file "app/controllers"
+ assert_file "app/views"
+ assert_file "app/helpers"
assert_file "config/routes.rb", /Rails.application.routes.draw do/
assert_file "lib/bukkits/engine.rb", /module Bukkits\n class Engine < Rails::Engine\n end\nend/
assert_file "lib/bukkits.rb", /require "bukkits\/engine"/
end
def test_being_quiet_while_creating_dummy_application
- assert_no_match /create\s+config\/application.rb/, run_generator
+ assert_no_match(/create\s+config\/application.rb/, run_generator)
end
def test_create_mountable_application_with_mountable_option
run_generator [destination_root, "--mountable"]
+ assert_file "app/assets/javascripts"
+ assert_file "app/assets/stylesheets"
+ assert_file "app/assets/images"
assert_file "config/routes.rb", /Bukkits::Engine.routes.draw do/
assert_file "lib/bukkits/engine.rb", /isolate_namespace Bukkits/
assert_file "test/dummy/config/routes.rb", /mount Bukkits::Engine => "\/bukkits"/
@@ -227,3 +229,4 @@ protected
silence(:stdout){ generator.send(*args, &block) }
end
end
+
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index d55ed22975..c7f45a807d 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -122,4 +122,22 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
ensure
Unknown::Generators.send :remove_const, :ActiveModel
end
+
+ def test_new_hash_style
+ run_generator
+ assert_file "app/controllers/users_controller.rb" do |content|
+ if RUBY_VERSION < "1.9"
+ assert_match /\{ render :action => "new" \}/, content
+ else
+ assert_match /\{ render action: "new" \}/, content
+ end
+ end
+ end
+
+ def test_force_old_style_hash
+ run_generator ["User", "--old-style-hash"]
+ assert_file "app/controllers/users_controller.rb" do |content|
+ assert_match /\{ render :action => "new" \}/, content
+ end
+ end
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index df787f61ba..4b07f8bcbe 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -79,8 +79,10 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "app/helpers/product_lines_helper.rb"
assert_file "test/unit/helpers/product_lines_helper_test.rb"
- # Stylesheets
- assert_file "public/stylesheets/scaffold.css"
+ # Assets
+ assert_file "app/assets/stylesheets/scaffold.css.scss"
+ assert_file "app/assets/javascripts/product_lines.js.coffee"
+ assert_file "app/assets/stylesheets/product_lines.css.scss"
end
def test_scaffold_on_revoke
@@ -110,8 +112,10 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/helpers/product_lines_helper.rb"
assert_no_file "test/unit/helpers/product_lines_helper_test.rb"
- # Stylesheets (should not be removed)
- assert_file "public/stylesheets/scaffold.css"
+ # Assets
+ assert_file "app/assets/stylesheets/scaffold.css.scss", /&:visited/
+ assert_no_file "app/assets/javascripts/product_lines.js.coffee"
+ assert_no_file "app/assets/stylesheets/product_lines.css.scss"
end
def test_scaffold_with_namespace_on_invoke
@@ -184,8 +188,10 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "app/helpers/admin/roles_helper.rb"
assert_file "test/unit/helpers/admin/roles_helper_test.rb"
- # Stylesheets
- assert_file "public/stylesheets/scaffold.css"
+ # Assets
+ assert_file "app/assets/stylesheets/scaffold.css.scss", /&:visited/
+ assert_file "app/assets/javascripts/admin/roles.js.coffee"
+ assert_file "app/assets/stylesheets/admin/roles.css.scss"
end
def test_scaffold_with_namespace_on_revoke
@@ -216,8 +222,10 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/helpers/admin/roles_helper.rb"
assert_no_file "test/unit/helpers/admin/roles_helper_test.rb"
- # Stylesheets (should not be removed)
- assert_file "public/stylesheets/scaffold.css"
+ # Assets
+ assert_file "app/assets/stylesheets/scaffold.css.scss"
+ assert_no_file "app/assets/javascripts/admin/roles.js.coffee"
+ assert_no_file "app/assets/stylesheets/admin/roles.css.scss"
end
def test_scaffold_generator_on_revoke_does_not_mutilate_legacy_map_parameter
@@ -235,6 +243,34 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "config/routes.rb", /\.routes\.draw do\s*\|map\|\s*$/
end
+ def test_scaffold_generator_no_assets
+ run_generator [ "posts", "--no-assets" ]
+ assert_file "app/assets/stylesheets/scaffold.css.scss"
+ assert_no_file "app/assets/javascripts/posts.js.coffee"
+ assert_no_file "app/assets/stylesheets/posts.css.scss"
+ end
+
+ def test_scaffold_generator_no_stylesheets
+ run_generator [ "posts", "--no-stylesheets" ]
+ assert_no_file "app/assets/stylesheets/scaffold.css.scss"
+ assert_file "app/assets/javascripts/posts.js.coffee"
+ assert_no_file "app/assets/stylesheets/posts.css.scss"
+ end
+
+ def test_scaffold_generator_no_javascripts
+ run_generator [ "posts", "--no-javascripts" ]
+ assert_file "app/assets/stylesheets/scaffold.css.scss"
+ assert_no_file "app/assets/javascripts/posts.js.coffee"
+ assert_file "app/assets/stylesheets/posts.css.scss"
+ end
+
+ def test_scaffold_generator_no_negines
+ run_generator [ "posts", "--no-javascript-engine", "--no-stylesheet-engine" ]
+ assert_file "app/assets/stylesheets/scaffold.css"
+ assert_file "app/assets/javascripts/posts.js"
+ assert_file "app/assets/stylesheets/posts.css"
+ end
+
def test_scaffold_generator_outputs_error_message_on_missing_attribute_type
content = capture(:stderr) { run_generator ["post", "title:string", "body"]}
assert_match(/Missing type for attribute 'body'/, content)
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 9e6169721b..c9c5d2fad2 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -26,13 +26,12 @@ module SharedGeneratorTests
def test_plugin_new_generate_pretend
run_generator ["testapp", "--pretend"]
-
- default_files.each{ |path| assert_no_file path }
+ default_files.each{ |path| assert_no_file File.join("testapp",path) }
end
def test_invalid_database_option_raises_an_error
content = capture(:stderr){ run_generator([destination_root, "-d", "unknown"]) }
- assert_match /Invalid value for \-\-database option/, content
+ assert_match(/Invalid value for \-\-database option/, content)
end
def test_test_unit_is_skipped_if_required
@@ -42,21 +41,21 @@ module SharedGeneratorTests
def test_options_before_application_name_raises_an_error
content = capture(:stderr){ run_generator(["--pretend", destination_root]) }
- assert_match /Options should be given after the \w+ name. For details run: rails( plugin)? --help\n/, content
+ assert_match(/Options should be given after the \w+ name. For details run: rails( plugin)? --help\n/, content)
end
def test_name_collision_raises_an_error
reserved_words = %w[application destroy plugin runner test]
reserved_words.each do |reserved|
content = capture(:stderr){ run_generator [File.join(destination_root, reserved)] }
- assert_match /Invalid \w+ name #{reserved}. Please give a name which does not match one of the reserved rails words.\n/, content
+ assert_match(/Invalid \w+ name #{reserved}. Please give a name which does not match one of the reserved rails words.\n/, content)
end
end
def test_name_raises_an_error_if_name_already_used_constant
%w{ String Hash Class Module Set Symbol }.each do |ruby_class|
content = capture(:stderr){ run_generator [File.join(destination_root, ruby_class)] }
- assert_match /Invalid \w+ name #{ruby_class}, constant #{ruby_class} is already in use. Please choose another \w+ name.\n/, content
+ assert_match(/Invalid \w+ name #{ruby_class}, constant #{ruby_class} is already in use. Please choose another \w+ name.\n/, content)
end
end
@@ -72,8 +71,8 @@ module SharedGeneratorTests
def test_template_raises_an_error_with_invalid_path
content = capture(:stderr){ run_generator([destination_root, "-m", "non/existant/path"]) }
- assert_match /The template \[.*\] could not be loaded/, content
- assert_match /non\/existant\/path/, content
+ assert_match(/The template \[.*\] could not be loaded/, content)
+ assert_match(/non\/existant\/path/, content)
end
def test_template_is_executed_when_supplied
@@ -82,7 +81,7 @@ module SharedGeneratorTests
template.instance_eval "def read; self; end" # Make the string respond to read
generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- assert_match /It works!/, silence(:stdout){ generator.invoke_all }
+ assert_match(/It works!/, silence(:stdout){ generator.invoke_all })
end
def test_dev_option
@@ -100,8 +99,8 @@ module SharedGeneratorTests
def test_template_raises_an_error_with_invalid_path
content = capture(:stderr){ run_generator([destination_root, "-m", "non/existant/path"]) }
- assert_match /The template \[.*\] could not be loaded/, content
- assert_match /non\/existant\/path/, content
+ assert_match(/The template \[.*\] could not be loaded/, content)
+ assert_match(/non\/existant\/path/, content)
end
def test_template_is_executed_when_supplied
@@ -110,7 +109,7 @@ module SharedGeneratorTests
template.instance_eval "def read; self; end" # Make the string respond to read
generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- assert_match /It works!/, silence(:stdout){ generator.invoke_all }
+ assert_match(/It works!/, silence(:stdout){ generator.invoke_all })
end
def test_template_is_executed_when_supplied_an_https_path
@@ -119,7 +118,7 @@ module SharedGeneratorTests
template.instance_eval "def read; self; end" # Make the string respond to read
generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- assert_match /It works!/, silence(:stdout){ generator.invoke_all }
+ assert_match(/It works!/, silence(:stdout){ generator.invoke_all })
end
def test_dev_option
@@ -191,6 +190,6 @@ module SharedCustomGeneratorTests
generator([destination_root], :builder => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
capture(:stdout) { generator.invoke_all }
- default_files.each{ |path| assert_no_file path }
+ default_files.each{ |path| assert_no_file(path) }
end
end
diff --git a/railties/test/generators/stylesheets_generator_test.rb b/railties/test/generators/stylesheets_generator_test.rb
deleted file mode 100644
index aaeb686daa..0000000000
--- a/railties/test/generators/stylesheets_generator_test.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'generators/generators_test_helper'
-require 'rails/generators/rails/stylesheets/stylesheets_generator'
-
-class StylesheetsGeneratorTest < Rails::Generators::TestCase
- include GeneratorsTestHelper
-
- def test_copy_stylesheets
- run_generator
- assert_file "public/stylesheets/scaffold.css"
- end
-
- def test_stylesheets_are_not_deleted_on_revoke
- run_generator
- run_generator [], :behavior => :revoke
- assert_file "public/stylesheets/scaffold.css"
- end
-end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index c5b1cb9a80..f2261540ca 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -90,6 +90,7 @@ module TestHelpers
end
module Generation
+ # Build an application by invoking the generator and going through the whole stack.
def build_app(options = {})
FileUtils.rm_rf(app_path)
FileUtils.cp_r(tmp_path('app_template'), app_path)
@@ -115,6 +116,8 @@ module TestHelpers
add_to_config 'config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"; config.session_store :cookie_store, :key => "_myapp_session"; config.active_support.deprecation = :log'
end
+ # Make a very basic app, without creating the whole directory structure.
+ # This is faster and simpler than the method above.
def make_basic_app
require "rails"
require "action_controller/railtie"
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 0ce00db3c4..0c588ba773 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -1,8 +1,7 @@
require "isolation/abstract_unit"
require "railties/shared_tests"
-require 'stringio'
-require 'rack/test'
-require 'rack/file'
+require "stringio"
+require "rack/test"
module RailtiesTest
class EngineTest < Test::Unit::TestCase
@@ -188,6 +187,7 @@ module RailtiesTest
end
RUBY
+ require "rack/file"
boot_rails
env = Rack::MockRequest.env_for("/")
@@ -199,194 +199,6 @@ module RailtiesTest
assert_equal Rails.application.routes, env['action_dispatch.routes']
end
- test "it allows to set asset_path" do
- @plugin.write "lib/bukkits.rb", <<-RUBY
- class Bukkits
- class Engine < ::Rails::Engine
- end
- end
- RUBY
-
-
- @plugin.write "config/routes.rb", <<-RUBY
- Bukkits::Engine.routes.draw do
- match "/foo" => "foo#index"
- end
- RUBY
-
- @plugin.write "app/controllers/foo_controller.rb", <<-RUBY
- class FooController < ActionController::Base
- def index
- render :index
- end
- end
- RUBY
-
- @plugin.write "app/views/foo/index.html.erb", <<-ERB
- <%= image_path("foo.png") %>
- <%= javascript_include_tag("foo") %>
- <%= stylesheet_link_tag("foo") %>
- ERB
-
- app_file "config/routes.rb", <<-RUBY
- Rails.application.routes.draw do
- mount Bukkits::Engine => "/bukkits"
- end
- RUBY
-
- add_to_config 'config.asset_path = "/omg%s"'
-
- boot_rails
-
- # should set asset_path with engine name by default
- assert_equal "/bukkits_engine%s", ::Bukkits::Engine.config.asset_path
-
- ::Bukkits::Engine.config.asset_path = "/bukkits%s"
-
- get("/bukkits/foo")
- stripped_body = last_response.body.split("\n").map(&:strip).join
-
- expected = "/omg/bukkits/images/foo.png" +
- "<script src=\"/omg/bukkits/javascripts/foo.js\" type=\"text/javascript\"></script>" +
- "<link href=\"/omg/bukkits/stylesheets/foo.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />"
- assert_equal expected, stripped_body
- end
-
- test "default application's asset_path" do
- @plugin.write "config/routes.rb", <<-RUBY
- Bukkits::Engine.routes.draw do
- match "/foo" => "foo#index"
- end
- RUBY
-
- @plugin.write "app/controllers/foo_controller.rb", <<-RUBY
- class FooController < ActionController::Base
- def index
- render :inline => '<%= image_path("foo.png") %>'
- end
- end
- RUBY
-
- app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do
- mount Bukkits::Engine => "/bukkits"
- end
- RUBY
-
- boot_rails
-
- get("/bukkits/foo")
- assert_equal "/bukkits/images/foo.png", last_response.body.strip
- end
-
- test "engine's files are served via ActionDispatch::Static" do
- add_to_config "config.serve_static_assets = true"
-
- @plugin.write "lib/bukkits.rb", <<-RUBY
- class Bukkits
- class Engine < ::Rails::Engine
- engine_name :bukkits
- end
- end
- RUBY
-
- @plugin.write "public/bukkits.html", "/bukkits/bukkits.html"
- app_file "public/app.html", "/app.html"
- app_file "public/bukkits/file_from_app.html", "/bukkits/file_from_app.html"
-
- boot_rails
-
- get("/app.html")
- assert_equal File.read(File.join(app_path, "public/app.html")), last_response.body
-
- get("/bukkits/bukkits.html")
- assert_equal File.read(File.join(@plugin.path, "public/bukkits.html")), last_response.body
-
- get("/bukkits/file_from_app.html")
- assert_equal File.read(File.join(app_path, "public/bukkits/file_from_app.html")), last_response.body
- end
-
- test "an applications files are given priority over an engines files when served via ActionDispatch::Static" do
- add_to_config "config.serve_static_assets = true"
-
- @plugin.write "lib/bukkits.rb", <<-RUBY
- class Bukkits
- class Engine < ::Rails::Engine
- engine_name :bukkits
- end
- end
- RUBY
-
- app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do
- mount Bukkits::Engine => "/bukkits"
- end
- RUBY
-
- @plugin.write "public/bukkits.html", "in engine"
-
- app_file "public/bukkits/bukkits.html", "in app"
-
- boot_rails
-
- get('/bukkits/bukkits.html')
-
- assert_equal 'in app', last_response.body.strip
- end
-
- test "shared engine should include application's helpers and own helpers" do
- app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do
- match "/foo" => "bukkits/foo#index", :as => "foo"
- match "/foo/show" => "bukkits/foo#show"
- match "/foo/bar" => "bukkits/foo#bar"
- end
- RUBY
-
- app_file "app/helpers/some_helper.rb", <<-RUBY
- module SomeHelper
- def something
- "Something... Something... Something..."
- end
- end
- RUBY
-
- @plugin.write "app/helpers/bar_helper.rb", <<-RUBY
- module BarHelper
- def bar
- "It's a bar."
- end
- end
- RUBY
-
- @plugin.write "app/controllers/bukkits/foo_controller.rb", <<-RUBY
- class Bukkits::FooController < ActionController::Base
- def index
- render :inline => "<%= something %>"
- end
-
- def show
- render :text => foo_path
- end
-
- def bar
- render :inline => "<%= bar %>"
- end
- end
- RUBY
-
- boot_rails
-
- get("/foo")
- assert_equal "Something... Something... Something...", last_response.body
-
- get("/foo/show")
- assert_equal "/foo", last_response.body
-
- get("/foo/bar")
- assert_equal "It's a bar.", last_response.body
- end
-
test "isolated engine should include only its own routes and helpers" do
@plugin.write "lib/bukkits.rb", <<-RUBY
module Bukkits
@@ -559,6 +371,45 @@ module RailtiesTest
assert_match /name="post\[title\]"/, last_response.body
end
+ test "isolated engine should set correct route module prefix for nested namespace" do
+ @plugin.write "lib/bukkits.rb", <<-RUBY
+ module Bukkits
+ module Awesome
+ class Engine < ::Rails::Engine
+ isolate_namespace Bukkits::Awesome
+ end
+ end
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ mount Bukkits::Awesome::Engine => "/bukkits", :as => "bukkits"
+ end
+ RUBY
+
+ @plugin.write "config/routes.rb", <<-RUBY
+ Bukkits::Awesome::Engine.routes.draw do
+ match "/foo" => "foo#index"
+ end
+ RUBY
+
+ @plugin.write "app/controllers/bukkits/awesome/foo_controller.rb", <<-RUBY
+ class Bukkits::Awesome::FooController < ActionController::Base
+ def index
+ render :text => "ok"
+ end
+ end
+ RUBY
+
+ add_to_config("config.action_dispatch.show_exceptions = false")
+
+ boot_rails
+
+ get("/bukkits/foo")
+ assert_equal "ok", last_response.body
+ end
+
test "loading seed data" do
@plugin.write "db/seeds.rb", <<-RUBY
Bukkits::Engine.config.bukkits_seeds_loaded = true
@@ -733,10 +584,7 @@ module RailtiesTest
assert_equal Bukkits::Engine.instance, Rails::Engine.find(engine_path)
end
- test "ensure that engine properly sets assets directories" do
- add_to_config("config.action_dispatch.show_exceptions = false")
- add_to_config("config.serve_static_assets = true")
-
+ test "gather isolated engine's helpers in Engine#helpers" do
@plugin.write "lib/bukkits.rb", <<-RUBY
module Bukkits
class Engine < ::Rails::Engine
@@ -745,56 +593,40 @@ module RailtiesTest
end
RUBY
- @plugin.write "public/stylesheets/foo.css", ""
- @plugin.write "public/javascripts/foo.js", ""
-
- @plugin.write "app/views/layouts/bukkits/application.html.erb", <<-RUBY
- <%= stylesheet_link_tag :all %>
- <%= javascript_include_tag :all %>
- <%= yield %>
+ app_file "app/helpers/some_helper.rb", <<-RUBY
+ module SomeHelper
+ def foo
+ 'foo'
+ end
+ end
RUBY
- @plugin.write "app/controllers/bukkits/home_controller.rb", <<-RUBY
+ @plugin.write "app/helpers/bukkits/engine_helper.rb", <<-RUBY
module Bukkits
- class HomeController < ActionController::Base
- def index
- render :text => "Good morning!", :layout => "bukkits/application"
+ module EngineHelper
+ def bar
+ 'bar'
end
end
end
RUBY
- @plugin.write "config/routes.rb", <<-RUBY
- Bukkits::Engine.routes.draw do
- match "/home" => "home#index"
- end
- RUBY
-
- app_file "config/routes.rb", <<-RUBY
- Rails.application.routes.draw do
- mount Bukkits::Engine => "/bukkits"
+ @plugin.write "app/helpers/engine_helper.rb", <<-RUBY
+ module EngineHelper
+ def baz
+ 'baz'
+ end
end
RUBY
- require 'rack/test'
- extend Rack::Test::Methods
+ add_to_config("config.action_dispatch.show_exceptions = false")
boot_rails
-
require "#{rails_root}/config/environment"
- assert_equal File.join(@plugin.path, "public"), Bukkits::HomeController.assets_dir
- assert_equal File.join(@plugin.path, "public/stylesheets"), Bukkits::HomeController.stylesheets_dir
- assert_equal File.join(@plugin.path, "public/javascripts"), Bukkits::HomeController.javascripts_dir
-
- assert_equal File.join(app_path, "public"), ActionController::Base.assets_dir
- assert_equal File.join(app_path, "public/stylesheets"), ActionController::Base.stylesheets_dir
- assert_equal File.join(app_path, "public/javascripts"), ActionController::Base.javascripts_dir
-
- get "/bukkits/home"
-
- assert_match %r{bukkits/stylesheets/foo.css}, last_response.body
- assert_match %r{bukkits/javascripts/foo.js}, last_response.body
+ methods = Bukkits::Engine.helpers.public_instance_methods.collect(&:to_s).sort
+ expected = ["bar", "baz"]
+ assert_equal expected, methods
end
private
diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb
index 3eb79d57c8..e975950b85 100644
--- a/railties/test/railties/shared_tests.rb
+++ b/railties/test/railties/shared_tests.rb
@@ -10,42 +10,16 @@ module RailtiesTest
@app ||= Rails.application
end
- def test_install_migrations_and_assets
- @plugin.write "public/javascripts/foo.js", "doSomething()"
+ def test_serving_sprockets_assets
+ @plugin.write "app/assets/javascripts/engine.js.erb", "<%= :alert %>();"
- @plugin.write "db/migrate/1_create_users.rb", <<-RUBY
- class CreateUsers < ActiveRecord::Migration
- end
- RUBY
-
- app_file "db/migrate/1_create_sessions.rb", <<-RUBY
- class CreateSessions < ActiveRecord::Migration
- end
- RUBY
-
- add_to_config "ActiveRecord::Base.timestamped_migrations = false"
-
- Dir.chdir(app_path) do
- `rake bukkits:install`
- assert File.exists?("#{app_path}/db/migrate/2_create_users.rb")
- assert File.exists?(app_path("public/bukkits/javascripts/foo.js"))
- end
- end
-
- def test_copying_assets
- @plugin.write "public/javascripts/foo.js", "doSomething()"
- @plugin.write "public/stylesheets/foo.css", "h1 { font-size: 10000px }"
- @plugin.write "public/images/img.png", ""
-
- Dir.chdir(app_path) do
- `rake bukkits:install:assets --trace`
+ boot_rails
+ require 'rack/test'
+ require 'coffee_script'
+ extend Rack::Test::Methods
- assert File.exists?(app_path("public/bukkits/javascripts/foo.js"))
- assert_equal "doSomething()\n", File.read(app_path("public/bukkits/javascripts/foo.js"))
- assert File.exists?(app_path("public/bukkits/stylesheets/foo.css"))
- assert_equal "h1 { font-size: 10000px }\n", File.read(app_path("public/bukkits/stylesheets/foo.css"))
- assert File.exists?(app_path("public/bukkits/images/img.png"))
- end
+ get "/assets/engine.js"
+ assert_match "alert();", last_response.body
end
def test_copying_migrations
diff --git a/tasks/release.rb b/tasks/release.rb
index a605fed160..01950b227d 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -61,6 +61,32 @@ directory "dist"
end
end
+namespace :changelog do
+ task :release_date do
+ FRAMEWORKS.each do |fw|
+ require 'date'
+ replace = '\1(' + Date.today.strftime('%B %d, %Y') + ')'
+ fname = File.join fw, 'CHANGELOG'
+
+ contents = File.read(fname).sub(/^([^(]*)\(unreleased\)/, replace)
+ File.open(fname, 'wb') { |f| f.write contents }
+ end
+ end
+
+ task :release_summary do
+ FRAMEWORKS.each do |fw|
+ puts "## #{fw}"
+ fname = File.join fw, 'CHANGELOG'
+ contents = File.readlines fname
+ contents.shift
+ changes = []
+ changes << contents.shift until contents.first =~ /^\*Rails \d+\.\d+\.\d+/
+ puts changes.reject { |change| change.strip.empty? }.join
+ puts
+ end
+ end
+end
+
namespace :all do
task :build => FRAMEWORKS.map { |f| "#{f}:build" } + ['rails:build']
task :install => FRAMEWORKS.map { |f| "#{f}:install" } + ['rails:install']